build-estimate
The rule: an effort estimate is a calculation from the ledger, not a number typed into a table.
plan.md's Estimated effort section is produced by this tool and cites the rate it used. A
hand-written "3–4 days" has no basis anyone can check, and on the settlement-verifier build it was
wrong by a factor of two within four days (written Aug 23 for 61 tasks; 21 of them then landed in
15.5 hours).
Tool: estimate.py beside this file. Ledger: calibration.json beside it. Stdlib Python only.
When
| Moment |
Mode |
Who calls it |
build-plan Phase 5, writing Estimated effort |
project |
build-plan — the section is this tool's output pasted in |
| Mid-build, "are we still on track" / a scope or date question |
project |
you, on request; re-run whenever tasks land or the target moves |
build-evaluate PASS, before closing the slug |
calibrate |
build-evaluate — the actuals go into the ledger or the next build estimates blind |
project — from a spec to a calendar
- Write the spec at
.claude/PRPs/<slug>/estimate/estimate-<YYYY-MM-DD>.json (the
estimate/ directory is opaque to the PRP naming hook; the JSON is data, not an artifact).
Fields, all named in estimate.py --help:
process — which profile the build runs under. intensive = build-execute with mandatory
per-iteration adversarial review and a mutation harness (the only MEASURED profile today).
standard and light are ASSUMED multipliers and the output says so.
tasks — the open tasks, each with a kind from the ledger's task_kinds and a status
(not-started / staged / in-review). Read the task text and the staging area before
assigning these — a task whose code is staged and green is 40% of a task, not 100%.
fixed — gate steps with a known clock (build-evaluate, quality-review) in minutes.
optional — a second scenario (e.g. the tasks a real-matter demo adds). Reported separately
so the human sees what fits without them and what it costs to add them.
start, start_hours_left, hours_per_day, holidays, target. Look the holidays up
and compute them — US Labor Day is the first Monday of September, and the first estimate
on the settlement-verifier build put it a week early from memory.
- Run
python3 .claude/skills/build-estimate/estimate.py project <spec>.
- Paste the output into
plan.md § Estimated effort, replacing whatever was there, with
the date and the spec path. Keep the assumptions carried block — it is what a later reader
uses to see why the number was what it was.
- State the verdict in one line to the human: fits / marginal / does not fit, at which
estimate, and which optional items would push it over. The decision to cut or move the date
is theirs; the tool's job is to make the arithmetic un-arguable.
Do not add a "review" or "contingency" line on top: a task unit already contains its review
rounds and repairs, because that is how the ledger measured it. If the build is not running the
measured process, change process, not the task list.
calibrate — closing the loop
After build-evaluate returns PASS (or the build is abandoned with tasks landed — those count too):
- Count from evidence, not memory: iteration-report mtimes or timestamps, tasks flipped in
state.json / the plan checklist, hours actually at the keyboard with overnight and meeting
gaps excluded.
- Write a record JSON (
estimate.py --help shows the shape) with a note saying what made the
run typical or atypical — single operator, task mix, anything that would make the rate not
transfer.
python3 .claude/skills/build-estimate/estimate.py calibrate <record.json>; it appends and
prints the profile's new resolved rate.
- Promote —
~/gits/clybor-claude-tooling/scripts/promote.sh .claude/skills/build-estimate
— and commit there. The ledger's value is cumulative across projects; a record left in one
project's copy calibrates nothing else, and the drift gate will block the next commit anyway.
If a build ran under a profile that has no measured record, calibrating it is what turns that
profile's ASSUMED multiplier into data. Add the profile to calibration.json first if it is a
genuinely different process; do not stretch an existing profile to cover it.
Adding a task kind
A kind is a shape of work with a measured cost, not a topic. Add one only when a finished build
shows a class of task that consistently ran at a different multiple of the median, and write the
observation into its basis. Kinds with no basis are guesses wearing a label.
Anti-patterns
- Typing the estimate. If the section in
plan.md has no rate per task unit line, it was
not produced by this tool.
- Re-using a stale projection. The Aug 23 figure was still governing on Aug 28 after half the
tasks had landed. Re-run on every "will we make it" question; it costs seconds.
- Counting calendar days as working days. Weekends and holidays are in the spec for a reason.
- Estimating optional scope as required. Put it under
optional so the human sees the split.
- Calibrating from memory. "It felt like two days" is not a record.
1---2name: build-estimate3description: Project a build's remaining effort onto the calendar from MEASURED rates, not guesses. Reads a calibration ledger of past builds (tasks landed per hour under a named process profile), scales by task kind and status, walks the calendar past weekends and holidays, and returns low/mid/high finish dates with a fit verdict against a target date. Two modes — `project` (before or during a build) and `calibrate` (after a build, append the actuals so the next estimate is better). Use when a plan needs its "Estimated effort" section, when someone asks "will this fit by <date>", when the calendar needs reassessing mid-build, or when a build finishes and its actuals should be recorded. Triggers — "estimate the build", "how long will this take", "will we make the date", "reassess the calendar", "record the actuals", "calibrate the estimate".4---56# build-estimate78**The rule: an effort estimate is a calculation from the ledger, not a number typed into a table.**9`plan.md`'s *Estimated effort* section is produced by this tool and cites the rate it used. A10hand-written "3–4 days" has no basis anyone can check, and on the settlement-verifier build it was11wrong by a factor of two within four days (written Aug 23 for 61 tasks; 21 of them then landed in1215.5 hours).1314Tool: `estimate.py` beside this file. Ledger: `calibration.json` beside it. Stdlib Python only.1516## When1718| Moment | Mode | Who calls it |19|---|---|---|20| `build-plan` Phase 5, writing *Estimated effort* | `project` | build-plan — the section is this tool's output pasted in |21| Mid-build, "are we still on track" / a scope or date question | `project` | you, on request; re-run whenever tasks land or the target moves |22| `build-evaluate` PASS, before closing the slug | `calibrate` | build-evaluate — the actuals go into the ledger or the next build estimates blind |2324## `project` — from a spec to a calendar25261. **Write the spec** at `.claude/PRPs/<slug>/estimate/estimate-<YYYY-MM-DD>.json` (the27 `estimate/` directory is opaque to the PRP naming hook; the JSON is data, not an artifact).28 Fields, all named in `estimate.py --help`:29 - `process` — which profile the build runs under. `intensive` = build-execute with mandatory30 per-iteration adversarial review and a mutation harness (the only MEASURED profile today).31 `standard` and `light` are ASSUMED multipliers and the output says so.32 - `tasks` — the open tasks, each with a `kind` from the ledger's `task_kinds` and a `status`33 (`not-started` / `staged` / `in-review`). **Read the task text and the staging area before34 assigning these** — a task whose code is staged and green is 40% of a task, not 100%.35 - `fixed` — gate steps with a known clock (build-evaluate, quality-review) in minutes.36 - `optional` — a second scenario (e.g. the tasks a real-matter demo adds). Reported separately37 so the human sees what fits without them and what it costs to add them.38 - `start`, `start_hours_left`, `hours_per_day`, `holidays`, `target`. Look the holidays up39 and compute them — US Labor Day is the *first Monday* of September, and the first estimate40 on the settlement-verifier build put it a week early from memory.412. **Run** `python3 .claude/skills/build-estimate/estimate.py project <spec>`.423. **Paste the output** into `plan.md` § *Estimated effort*, replacing whatever was there, with43 the date and the spec path. Keep the *assumptions carried* block — it is what a later reader44 uses to see why the number was what it was.454. **State the verdict in one line to the human**: fits / marginal / does not fit, at which46 estimate, and which optional items would push it over. The decision to cut or move the date47 is theirs; the tool's job is to make the arithmetic un-arguable.4849Do not add a "review" or "contingency" line on top: a task unit already contains its review50rounds and repairs, because that is how the ledger measured it. If the build is not running the51measured process, change `process`, not the task list.5253## `calibrate` — closing the loop5455After `build-evaluate` returns PASS (or the build is abandoned with tasks landed — those count too):56571. Count from evidence, not memory: iteration-report mtimes or timestamps, tasks flipped in58 `state.json` / the plan checklist, hours actually at the keyboard with overnight and meeting59 gaps excluded.602. Write a record JSON (`estimate.py --help` shows the shape) with a `note` saying what made the61 run typical or atypical — single operator, task mix, anything that would make the rate not62 transfer.633. `python3 .claude/skills/build-estimate/estimate.py calibrate <record.json>`; it appends and64 prints the profile's new resolved rate.654. **Promote** — `~/gits/clybor-claude-tooling/scripts/promote.sh .claude/skills/build-estimate`66 — and commit there. The ledger's value is cumulative across projects; a record left in one67 project's copy calibrates nothing else, and the drift gate will block the next commit anyway.6869If a build ran under a profile that has no measured record, calibrating it is what turns that70profile's ASSUMED multiplier into data. Add the profile to `calibration.json` first if it is a71genuinely different process; do not stretch an existing profile to cover it.7273## Adding a task kind7475A kind is a *shape of work with a measured cost*, not a topic. Add one only when a finished build76shows a class of task that consistently ran at a different multiple of the median, and write the77observation into its `basis`. Kinds with no basis are guesses wearing a label.7879## Anti-patterns8081- **Typing the estimate.** If the section in `plan.md` has no `rate per task unit` line, it was82 not produced by this tool.83- **Re-using a stale projection.** The Aug 23 figure was still governing on Aug 28 after half the84 tasks had landed. Re-run on every "will we make it" question; it costs seconds.85- **Counting calendar days as working days.** Weekends and holidays are in the spec for a reason.86- **Estimating optional scope as required.** Put it under `optional` so the human sees the split.87- **Calibrating from memory.** "It felt like two days" is not a record.