Handle scope creep without losing the client
Log every out-of-scope client ask the same day, name the cost in writing within 24 hours, and turn it into a signed change order or a deliberate freebie.
“Scope creep is an absolute b*tch... it messes with your project timelines, the client's satisfaction and ultimately guarantees burnout.”
The problem
Scope creep rarely arrives as one big ask, it arrives as a dozen small ones you say yes to on the spot. This system logs every scope-adjacent request the same day, names the trade-off to the client in writing within 24 hours, and turns it into a priced change order or a deliberate, tracked freebie. A fixed weekly review keeps the log from piling up unresolved.

Key takeaways
- Write scope as an itemized list in the contract, not a paragraph. Anything not on that list is a change order by definition.
- Log every scope-adjacent request the same day it lands, before it turns into a mental note you forget to bill.
- Reply within 24 hours naming the added hours or cost, and let the client choose to invoice separately or swap it for planned work.
- Turn approved requests into a signed change order within one to two business days using a saved one-page template.
- Track time against the specific change order, not one general project bucket, so drift shows up before the invoice fight.
- Review the whole Scope Log on the same day every week: every entry gets a signed change order or a marked, deliberate freebie.
Frontmatter
- name
- handle-scope-creep
- description
- A repeatable system to catch every out-of-scope client request the moment it lands, price the trade-off in writing, and turn it into a signed change order or a deliberate freebie, so scope creep gets billed instead of quietly eating your margin. Use when a client asks for work beyond the agreed scope, when small extra requests keep slipping into a project or retainer, when you need to push back on scope creep without losing the client, or when you are setting up a change-order process. Triggers include "scope creep", "out of scope request", "change order", "client asking for extra work".
Handle scope creep without losing the client
Use this skill whenever a client asks for anything outside the signed scope, or when the user wants to set up a system that stops unbilled work before it starts. It runs a two-part system: a one-time Phase 1 setup per client or contract, then a Phase 2 loop the user runs on every scope-adjacent request, closing with a weekly review. Draft every reply and every change order for the user to approve before anything is sent.
The bundle carries what each step needs: references/scope-log-template.md (the log layout), references/change-order-template.md (the change order), references/naming-script.md (the canned trade-off replies), examples/scope-log-example.csv (a filled log), and scripts/weekly_scope_review.py (the weekly flag-and-tally pass).
Phase 1: Set this up before the work starts (once per client)
Write the scope as an itemized list in the SOW or proposal, not a paragraph of vague language. List what is included. State explicitly, in the contract, that anything not on that list is a change order. A vague scope invites an argument later.
Decide your default posture now, not in the moment. The standard rule is "no signature, no work" for anything off the list. If your business model means you sometimes do a small fix before the paperwork catches up, that is fine, but decide that exception now too, and pair it with "log it the same day regardless." The point is that the rule already exists before the client asks, so you are not inventing a policy under pressure while they are on the phone.
Pick one place every scope-adjacent request gets written down. A shared doc, a spreadsheet, a tag in your project tool, it does not matter which, it matters that there is exactly one. Call it your Scope Log. Requests arrive by call, email, and chat; if you do not funnel them into one place, half of them evaporate and you absorb them without ever deciding to. Set it up using
references/scope-log-template.mdfor the exact column layout, and checkexamples/scope-log-example.csvfor a filled worked example before you build your own.Save a change order template you can fill in fast. One page, plain language, no legal padding. Fields: sequential number and a reference back to the original contract, a specific description of the new work, why it's needed, the cost impact (original contract amount, prior change orders if any, this change order, revised total), the schedule impact, payment terms, and a signature line. Keep it as a doc you duplicate, not something you build from scratch each time. The ready-to-duplicate version is
references/change-order-template.md.Write your naming script once and save it as a canned reply. You will use a version of this every time (see step 8). Having it saved means you send it in two minutes instead of composing it while annoyed. The base script and its variants are in
references/naming-script.md; copy the one that fits and save it in your email client.Set your absorbed-hours/cost threshold now, in writing. Pick a number, hours or dollars absorbed in a single week, that tells you when to stop quietly eating the pattern and raise it with the client directly. The number depends on your rate and your margin. It has to exist before your first weekly review, not get invented on the spot while you're staring at a log full of unbilled hours. Write it down somewhere you'll see it during that review; you'll pass it to
scripts/weekly_scope_review.pyas--threshold-hoursor--threshold-costin step 11.
Phase 2: The loop, run every time a request lands
The moment the ask happens, log it the same day. Date, who asked, what they asked for, your rough estimate of hours or cost. It does not need to be precise yet. The failure mode this step exists to prevent is the mental note that quietly turns into unpaid work, because most scope creep arrives verbally, mid-call or mid-thread, and if you don't capture it right then it is gone.
Reply within 24 hours, in writing, naming the trade-off. Something close to: "Happy to add that in. It's outside what we scoped originally, and I estimate it adds about [X hours / cost]. I can invoice it separately at the end of the month, or we can swap it in for one of the items already planned, whichever works better for you." This does three things at once: you never say no, the cost becomes visible instead of invisible, and the client makes the billing or scheduling call instead of you having to decide alone whether it's worth pushing back on.
Turn it into a real change order within one to two business days. Fill in the template from step 4, send it for signature. Speed here is not a nicety, it's what keeps the process credible. A change order that takes a week to price teaches the client to stop bothering with the process and just ask you to squeeze it in for free next time. Log the date you send it in the Scope Log's
date_co_sentcolumn: that's the date the weekly review measures against for this entry, not the originaldate_requested, since sending it quickly is your job and waiting on a signature after that isn't.Track time against the specific deliverable or change order, not one general project bucket. If everything lands in one undifferentiated time entry, scope drift stays invisible until it shows up as a billing argument at the end of the engagement, which is exactly the conversation you're trying to avoid.
Every week, on a fixed day, review the whole Scope Log. Run
scripts/weekly_scope_review.pyagainst your Scope Log CSV before you start: it flags anything genuinely unresolved past 7 days and tallies the week's absorbed hours and cost, so you're reviewing a short list instead of scanning every row by eye. Go down every entry logged since the last review. Each one needs one of two outcomes: a signed change order, or an explicit decision that this one gets absorbed as goodwill (and it's marked as such, not left blank). Nothing sits unresolved for more than a week. This weekly pass is the part that makes this a system instead of a one-time fix: the log and the template only work if someone actually looks at them on a schedule.Tally the week's absorbed hours. If the number crosses the threshold you set in step 6, that's your trigger to raise it with the client directly, in a real conversation, before the pattern repeats for another month unaddressed.
Copy-paste checklist: when a scope-adjacent request lands
Run this the same day, every time:
- Logged in the Scope Log: date, requester, what was asked, rough hours/cost
- Replied within 24 hours using the naming script (trade-off named, estimate given, client picks the billing or schedule option)
- Change order drafted from the template within 1-2 business days
- Change order sent for signature, or marked "absorbed as goodwill" on purpose
- Time for this item tracked against its own line, not the general project bucket
Copy-paste checklist: weekly Scope Log review
Run this on the same day every week:
- Every entry since last review has a resolved status: signed change order, pending signature, or deliberately absorbed
- No entry has sat unresolved for more than 7 days
- Total absorbed hours/cost for the week tallied
- If absorbed total crosses your threshold, a conversation with the client is scheduled, not skipped
- Change order template and naming script still saved and easy to find for next week
Guardrails: the two ways this fails
Nearly every scope creep failure is one of these two, and they pull in opposite directions.
Failure mode one: silently absorbing the work. You do the extra hours without naming them. It feels like good client service in the moment. It teaches the client, without either of you deciding this on purpose, that your time past the contract is free. This is the direct driver behind why a majority of agencies report losing real monthly revenue to unbilled extra work, with only a small fraction managing to bill for all of it (Ignition's 2025 Agency Pricing and Cash Flow Report, surveying 273 agency leaders). The fix is step 7: log it the same day, every time, before you've had the chance to talk yourself into "it's fine, I'll just do it."
Failure mode two: refusing outright with no alternative. You say no, flatly, over something that felt small to the client, and it reads as difficult rather than professional. Clients do not resent fee adjustments nearly as much as they resent surprises. The fix is step 8: the script never says no, it names the cost and hands the decision back to them.
Do not let the change order sit unpriced for a week. A slow change order teaches the client to route around your process entirely and just ask for favors instead. Turnaround speed is what keeps people using the system rather than working around it.
Do not rely on memory. The single biggest point of failure across how this actually breaks down is that scope-adjacent asks are almost always spoken, not written, which means the moment passes and the ask disappears unless it's captured on the spot. "I'll write it up later" is not a step in this system; it's the thing this system exists to replace.
Stakes, for when you need to make the case to yourself or a client: roughly half of all projects experience scope creep in a given year, and that share has been rising for years, not falling (PMI's Pulse of the Profession research). Among agencies specifically, most report losing money to unbilled scope work every month, and close to a third lose more than $5,000 a month to it, while almost none manage to bill for all of it (Ignition's 2025 Agency Pricing and Cash Flow Report). A log, a script, and a weekly review fix it, none of them depend on noticing anything in the moment.
Install
- Download the .skill bundle, or the raw SKILL.md above.
- Unzip the bundle into your agent's skills directory. SKILL.md sits at the top level; any reference files sit beside it.
- Point your agent at the folder. It reads SKILL.md and follows it.
What's in the bundle
The .skill download contains SKILL.md plus these files, each at its path.
references/change-order-template.md2,716 chars
# Change order template
One page. Duplicate this file for every new change order, fill in the brackets, send it for signature within one to two business days of the request landing in your Scope Log. Speed is what keeps a client using the process instead of asking you to "just squeeze it in" next time.
Name the file `CO-[number]-[client]-[short-description].md` and keep every change order for a client in one folder next to the original contract, so the running total in the cost table below is easy to check against the file history.
---
## Change order [sequential number]
**Client:** [client or project name]
**Reference:** Change order to the [original SOW / contract name], dated [original contract date]
**Date issued:** [date]
### What this covers
[One or two sentences, specific enough that anyone reading this in six months knows exactly what work it authorizes. Not "extra design work," but "two additional homepage hero concepts and one round of revisions on each, beyond the one concept scoped in the original SOW."]
### Why it's needed
[One sentence. The reason the client asked, or the reason the original scope didn't cover it. Plain, not defensive: "The client added a mobile app screen after the original scope was written for desktop only."]
### Cost impact
| Line | Amount |
|---|---|
| Original contract amount | $[amount] |
| Prior change orders (sum, if any) | $[amount or "none"] |
| This change order | $[amount] |
| **Revised total contract value** | **$[sum]** |
### Schedule impact
[State it in days or weeks, or state "no change to the original delivery date" if that's true. If the answer is "we don't know yet," say that and give a date by which you will know, don't leave it blank.]
### Payment terms
[When this amount is due. Match your standard terms unless you've agreed to something different for this change order specifically: "Due on signature," "Due with next invoice," "50% on signature, 50% on delivery."]
### Signatures
Client: _________________________ Date: __________
[Your name / company]: _________________________ Date: __________
---
**Notes for you, delete before sending:**
- If this is the third or later change order on a contract, check whether the revised total is drifting far enough from the original that the client should see a fresh consolidated contract instead of a stack of add-ons. That's a conversation, not a silent decision.
- The moment you send this for signature, log the date back in the Scope Log's `date_co_sent` column (see `references/scope-log-template.md`), along with this change order's number. The weekly review measures how long a signature has been pending from that date, not from when the client first asked.
references/naming-script.md2,793 chars
# Naming script: reply to a scope-adjacent request
Write this once, save it as a canned reply in your email client (Gmail templates, Outlook Quick Parts, or a text-expander snippet), and use it inside 24 hours of any request that lands outside the original scope. The point of saving it is speed: you send it in two minutes instead of composing something while annoyed on a Friday afternoon.
The script says yes, names the trade-off with a number attached, and hands the billing or scheduling decision back to the client. It keeps the cost visible and leaves the choice with them.
## Base version
> Happy to add that in. It's outside what we scoped originally, and I estimate it adds about [X hours / $X]. I can invoice it separately at the end of the month, or we can swap it in for one of the items already planned, whichever works better for you.
## Variant: small ask, verbal or mid-call
Use this when the ask came up out loud and you want to name the trade-off before the call ends, then follow up in writing the same day.
> Sure, I can do that. Quick flag: that one's outside the original scope, roughly [X hours]. I'll log it and send you a couple of options for handling it by end of day.
Then follow with the base version by email so the trade-off exists in writing, not just in a conversation nobody will remember the details of in three weeks.
## Variant: bigger ask, likely needs its own change order
Use this when the estimate is large enough that "swap it for something else" isn't realistic and a signed change order is clearly the outcome.
> Happy to take this on. It's a bigger addition than a quick swap covers, roughly [X hours / $X], so I'll put together a short change order with the cost and schedule impact and send it over in the next day or two for your sign-off before I start.
## Variant: recurring client who asks often
Use this for a client where small asks are frequent enough that a monthly pattern is worth naming directly, without making any single request feel like an accusation.
> Happy to add that in, same as the others this month, I estimate about [X hours]. I'm tracking these separately from the main project so we can look at the total together at your next check-in and decide how you'd like to handle them going forward.
## What not to do
Don't soften the estimate into "shouldn't take long" or "not a big deal." A vague estimate is what lets scope creep stay invisible. Give a number, even a rough one, every time.
Don't skip the reply because the ask felt too small to bother with. The size of the pattern across a month is the reason this script exists, not the size of any one ask (see the weekly review in the main procedure). Small asks that never get named are exactly the ones that add up to unpaid hours nobody decided on.
references/scope-log-template.md3,265 chars
# Scope Log: setup and columns
The Scope Log is the one place every scope-adjacent request gets written down, the same day it lands. Set it up once, in a spreadsheet or your project tool's tag/list view, and every client shares the same log unless you run large enough that a log per client makes more sense.
A filled worked example with sample rows lives at `examples/scope-log-example.csv`, in this exact column order. Import it into a spreadsheet to see the shape, or copy its header row to start your own.
## Columns
| Column | What goes in it |
|---|---|
| `date_requested` | The day the ask happened, not the day you got around to logging it. Format: YYYY-MM-DD. |
| `client` | Client or project name. |
| `requester` | Who asked: a name, or a role if you don't have the name ("client's marketing lead"). |
| `description` | What they asked for, specific enough to turn into a change order later without needing to remember the conversation. |
| `rough_estimate_hours` | Your gut estimate in hours. Doesn't need to be precise, needs to exist. |
| `rough_estimate_cost` | Hours times your rate, or a flat estimate if the work doesn't map cleanly to hours. |
| `status` | One of: `open` (logged, not yet resolved), `change_order_sent` (sent for signature, waiting on the client), `signed` (change order signed, billable), `absorbed_goodwill` (deliberately given away, on purpose, not forgotten). |
| `change_order_number` | The matching number from `references/change-order-template.md`, once one exists. Blank until then. |
| `date_co_sent` | The day you sent the change order for signature. Fill this in the same day you send it (main procedure, step 9). The weekly review measures a pending signature against this date, not `date_requested`, since sending it quickly is your job and waiting on the client's signature after that isn't. Blank until a change order has been sent. |
| `date_resolved` | The day the status became `signed` or `absorbed_goodwill`. Blank while `open` or `change_order_sent`. |
## Rules for using it
1. Log the same day. A mental note that waits until "later" is the exact failure mode this log exists to prevent, most scope creep arrives spoken, on a call or in a chat thread, and the ask disappears the moment you stop holding it in your head.
2. Every entry needs a `status` within a week. `open` is a holding state, not a resting state.
3. `absorbed_goodwill` is a decision, not a default. If you never fill it in on purpose, entries that sit unresolved are just unbilled work you haven't noticed yet, not goodwill.
4. Record `date_co_sent` the moment you send a change order, not later. It's the anchor the weekly review uses for anything sitting in `change_order_sent`, so a blank cell means that entry can't be timed at all.
5. Run the weekly review against this log on the same day every week (see the main procedure, step 11). The script at `scripts/weekly_scope_review.py` reads this CSV and flags two separate things: entries still `open` with no change order sent more than 7 days after the ask, and change orders sent more than 7 days ago that are still unsigned, plus it tallies the week's absorbed hours and cost so you can see the number, not just a feeling that "we've been doing a lot of extra stuff lately."
examples/scope-log-example.csv1,089 chars
date_requested,client,requester,description,rough_estimate_hours,rough_estimate_cost,status,change_order_number,date_co_sent,date_resolved
2026-08-11,Client A,Priya (client lead),Added a mobile app screen not in the original desktop-only scope,6,900,signed,CO-1-ClientA,2026-08-12,2026-08-13
2026-08-14,Client A,Priya (client lead),Extra round of homepage revisions beyond the one scoped,2,300,absorbed_goodwill,,,2026-08-15
2026-08-18,Client B,marketing lead,New landing page for a product launch not in the SOW,10,1500,change_order_sent,CO-4-ClientB,2026-08-19,
2026-08-20,Client B,ops manager,Added a Spanish translation pass on all pages,3,450,open,,,
2026-08-22,Client C,owner,Rush turnaround two days ahead of the agreed delivery date,1,150,absorbed_goodwill,,,2026-08-22
2026-08-28,Client A,Priya (client lead),Second logo concept beyond the one scoped,4,600,open,,,
2026-08-28,Client C,owner,Additional stock photo licensing and placement,1,150,signed,CO-2-ClientC,2026-08-28,2026-08-29
2026-08-30,Client B,marketing lead,Quick copy edit on an already-approved page,0.5,75,open,,,
scripts/weekly_scope_review.py9,751 chars
#!/usr/bin/env python3
"""Runs the weekly Scope Log review described in step 11 of the procedure.
Reads a Scope Log CSV (see references/scope-log-template.md for the column
layout) and prints three things: every entry still `open` with no change
order sent more than 7 days after the ask, every change order sent more than
7 days ago that's still unsigned, and the total absorbed hours and cost
logged as "absorbed_goodwill" in the file. Run it on the same fixed day
every week against your current log, right before you sit down to clear it.
The two overdue checks use different anchor dates on purpose. `open` entries
are measured from `date_requested`, because drafting and sending a change
order is on you. `change_order_sent` entries are measured from
`date_co_sent`, the day you actually sent it, because waiting on the
client's signature after that sits on their side of the process.
This script only reads the file you point it at. It never writes to the
log, never contacts a network, and never deletes anything.
Usage:
python3 weekly_scope_review.py path/to/scope-log.csv
python3 weekly_scope_review.py path/to/scope-log.csv --threshold-hours 10
python3 weekly_scope_review.py path/to/scope-log.csv --threshold-cost 1000
With no path given, it reads the worked example at
examples/scope-log-example.csv relative to this script, so you can see the
report format before pointing it at your own log.
"""
import argparse
import csv
import sys
from datetime import date, datetime
from pathlib import Path
OPEN_STATUS = "open"
STALE_SIGNATURE_STATUS = "change_order_sent"
ABSORBED_STATUS = "absorbed_goodwill"
OVERDUE_AFTER_DAYS = 7
REQUIRED_COLUMNS = {
"date_requested",
"client",
"requester",
"description",
"rough_estimate_hours",
"rough_estimate_cost",
"status",
}
def parse_args(argv):
"""Parses command-line arguments.
Args:
argv: The argument list, excluding the program name.
Returns:
The parsed argparse.Namespace.
"""
default_csv = Path(__file__).resolve().parent.parent / "examples" / "scope-log-example.csv"
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"csv_path",
nargs="?",
default=str(default_csv),
help="Path to the Scope Log CSV. Defaults to the worked example.",
)
parser.add_argument(
"--threshold-hours",
type=float,
default=None,
help="If the week's absorbed hours meet or exceed this, the report flags it (step 12).",
)
parser.add_argument(
"--threshold-cost",
type=float,
default=None,
help="If the week's absorbed cost meets or exceeds this, the report flags it (step 12).",
)
return parser.parse_args(argv)
def load_rows(csv_path):
"""Loads and validates rows from the Scope Log CSV.
Args:
csv_path: Path to the CSV file.
Returns:
A list of dict rows, one per CSV row.
Raises:
FileNotFoundError: If csv_path does not exist.
ValueError: If the header is missing required columns.
"""
path = Path(csv_path)
if not path.is_file():
raise FileNotFoundError(f"Scope Log not found at {path}")
with path.open(newline="", encoding="utf-8") as handle:
reader = csv.DictReader(handle)
if reader.fieldnames is None:
raise ValueError(f"{path} has no header row")
missing = REQUIRED_COLUMNS - set(reader.fieldnames)
if missing:
raise ValueError(
f"{path} is missing required columns: {sorted(missing)}. "
"See references/scope-log-template.md for the expected layout."
)
return list(reader)
def to_float(value, row_number, column):
"""Converts a CSV cell to a float, with a row-specific error on failure.
Args:
value: The raw cell text.
row_number: 1-indexed row number, for the error message.
column: The column name, for the error message.
Returns:
The parsed float, or 0.0 if the cell is blank.
Raises:
ValueError: If the cell is non-blank and not a valid number.
"""
text = (value or "").strip()
if not text:
return 0.0
try:
return float(text)
except ValueError as exc:
raise ValueError(
f"Row {row_number}: '{column}' value {value!r} is not a number"
) from exc
def days_since(date_text, today, row_number, column):
"""Computes how many days have passed since a date column's value.
Args:
date_text: The date cell text, expected as YYYY-MM-DD.
today: The date to measure against.
row_number: 1-indexed row number, for the error message.
column: The column name this date came from, for the error message.
Returns:
The number of whole days between the date and today.
Raises:
ValueError: If the date cannot be parsed.
"""
text = (date_text or "").strip()
try:
parsed = datetime.strptime(text, "%Y-%m-%d").date()
except ValueError as exc:
raise ValueError(
f"Row {row_number}: '{column}' value {text!r} is not YYYY-MM-DD"
) from exc
return (today - parsed).days
def build_report(rows, today):
"""Builds the two overdue lists and the absorbed-hours/cost tally.
Args:
rows: Parsed CSV rows, as returned by load_rows.
today: The date to measure "days since" against.
Returns:
A tuple of (needs_action, awaiting_signature, absorbed_hours,
absorbed_cost). needs_action and awaiting_signature are each a list
of (row_number, row_dict, days) tuples.
"""
needs_action = []
awaiting_signature = []
absorbed_hours = 0.0
absorbed_cost = 0.0
for index, row in enumerate(rows, start=2): # row 1 is the header
status = (row.get("status") or "").strip()
if status == OPEN_STATUS:
open_days = days_since(row.get("date_requested"), today, index, "date_requested")
if open_days > OVERDUE_AFTER_DAYS:
needs_action.append((index, row, open_days))
elif status == STALE_SIGNATURE_STATUS:
sent_text = (row.get("date_co_sent") or "").strip()
if sent_text:
sent_days = days_since(sent_text, today, index, "date_co_sent")
if sent_days > OVERDUE_AFTER_DAYS:
awaiting_signature.append((index, row, sent_days))
# A change_order_sent row with no date_co_sent yet can't be timed.
# Fill in that column the day you send the change order (see
# references/scope-log-template.md).
if status == ABSORBED_STATUS:
absorbed_hours += to_float(row.get("rough_estimate_hours"), index, "rough_estimate_hours")
absorbed_cost += to_float(row.get("rough_estimate_cost"), index, "rough_estimate_cost")
return needs_action, awaiting_signature, absorbed_hours, absorbed_cost
def print_report(csv_path, needs_action, awaiting_signature, absorbed_hours, absorbed_cost, threshold_hours, threshold_cost):
"""Prints the weekly review report to stdout."""
print(f"Weekly Scope Log review: {csv_path}")
print("-" * 60)
if needs_action:
print(f"Still open, no change order sent, more than {OVERDUE_AFTER_DAYS} days since the ask ({len(needs_action)}):")
for row_number, row, open_days in needs_action:
print(
f" Row {row_number}: {row['client']} / {row['requester']}, "
f"{open_days} days since request, status={row['status']!r} "
f"- {row['description']}"
)
else:
print(f"Nothing is sitting open past {OVERDUE_AFTER_DAYS} days without a change order sent.")
print()
if awaiting_signature:
print(f"Change order sent, still unsigned more than {OVERDUE_AFTER_DAYS} days after sending ({len(awaiting_signature)}):")
for row_number, row, sent_days in awaiting_signature:
print(
f" Row {row_number}: {row['client']} / {row['requester']}, "
f"{sent_days} days since the change order was sent "
f"- {row['description']}"
)
else:
print(f"Nothing is awaiting signature more than {OVERDUE_AFTER_DAYS} days after being sent.")
print()
print(f"Absorbed as goodwill so far: {absorbed_hours:g} hours, ${absorbed_cost:g}")
if threshold_hours is not None and absorbed_hours >= threshold_hours:
print(
f" -> Absorbed hours meet the {threshold_hours:g}-hour threshold. "
"Step 12: raise this with the client directly."
)
if threshold_cost is not None and absorbed_cost >= threshold_cost:
print(
f" -> Absorbed cost meets the ${threshold_cost:g} threshold. "
"Step 12: raise this with the client directly."
)
def main(argv=None):
"""Entry point: loads the Scope Log, builds the report, prints it.
Returns:
0 on success, 1 if the Scope Log could not be read or parsed.
"""
args = parse_args(sys.argv[1:] if argv is None else argv)
try:
rows = load_rows(args.csv_path)
needs_action, awaiting_signature, absorbed_hours, absorbed_cost = build_report(rows, date.today())
except (FileNotFoundError, ValueError) as exc:
print(f"Error: {exc}", file=sys.stderr)
return 1
print_report(
args.csv_path,
needs_action,
awaiting_signature,
absorbed_hours,
absorbed_cost,
args.threshold_hours,
args.threshold_cost,
)
return 0
if __name__ == "__main__":
sys.exit(main())
Sources
Merged from the top existing skills on the topic, with credit to every input.
Frequently asked questions
What do I do when a client asks for something small that's not in the contract?
Say yes if you want to, but log it the same day in your Scope Log with the date, who asked, and a rough hour estimate. Then reply within 24 hours naming the added cost and letting them pick how it gets handled.
How fast should I turn a scope change into a change order?
Within one to two business days. Waiting longer trains the client to stop asking and just expect the extra work for free.
What if I don't want to fight the client over every little request?
You don't have to. The naming script never says no, it just makes the cost visible and hands the client the choice between paying for it or swapping it in for something already planned.
Do I need a lawyer to write my change order template?
No. A one-page document with a sequential number, a description of the new work, the cost and schedule impact, and a signature line covers it. You fill in the same template each time instead of drafting one from scratch.
What happens if I just let some scope creep slide as a favor?
That's fine as long as you mark it that way on purpose in the log instead of leaving it blank, so you can tally absorbed hours at the end of the week and see if the pattern is costing you.
How do I stop scope creep from happening again next month?
The weekly review is what makes it stick. Every entry since the last review needs a resolved status, so nothing sits for more than seven days and the log never becomes something you're afraid to open.