diff --git a/.changeset/noble-ravens-howl.md b/.changeset/noble-ravens-howl.md
new file mode 100644
index 000000000..2cc477536
--- /dev/null
+++ b/.changeset/noble-ravens-howl.md
@@ -0,0 +1,5 @@
+---
+type: Added
+pr: 2670
+---
+**Plans now carry a calibrated effort estimate** — every generated PLAN.md includes an `estimate` block, and `/gsd-plan-phase` flags a phase projected to exceed the smart-zone budget with a concrete split recommendation. Advisory only; it never blocks planning. (#2631)
diff --git a/agents/gsd-plan-checker.md b/agents/gsd-plan-checker.md
index 3ab9bde7d..7be4dcf24 100644
--- a/agents/gsd-plan-checker.md
+++ b/agents/gsd-plan-checker.md
@@ -252,6 +252,19 @@ issue:
1. Count tasks per plan
2. Estimate files modified per plan
3. Check against thresholds
+4. **Smart-zone estimate check (#2631, ADR-2629).** For each plan carrying an `estimate` block, run the
+ `estimate-check --calibrated` verb against its `estimate.tokens` (the `--calibrated` flag is required —
+ the plan's figure already has the factor applied, and omitting it would square the correction) (invoked in Step 1 below, after the launcher
+ preamble). The verb reads `workflow.smart_zone_tokens` and applies the project's calibration. Report
+ one line per plan: plan id, estimated tokens, the budget, and — when `over_budget` is true — the
+ returned `recommendation`, which names how many slices the phase should become.
+
+ **Over budget is a WARNING, never a blocker** (ADR-2629 Decision 5). Recommend re-slicing into a tracer
+ plus expansion slices; never fail the check on it. Report `estimate.confidence` alongside: `low` means
+ fewer than 3 completed phases carry actuals, so the figure is not yet calibrated for this project — say
+ so rather than presenting it as precise, and weigh the task/file thresholds above more heavily.
+
+ A plan with no `estimate` block is not a defect; the field is optional and additive.
**Thresholds:**
| Metric | Target | Warning | Blocker |
@@ -706,6 +719,13 @@ gsd_run query phase.list-plans "$phase_number"
gsd_run query phase.list-artifacts "$phase_number" --type research
gsd_run query roadmap.get-phase "$phase_number"
gsd_run query phase.list-artifacts "$phase_number" --type summary
+
+# Smart-zone estimate check (#2631) — advisory, never fails the check.
+for plan in "${phase_dir:-$PHASE_DIR}"/*-PLAN.md; do
+ [ -f "$plan" ] || continue # unmatched glob leaves the literal pattern — skip it
+ EST=$(sed -n '/^estimate:/,/^[a-z_]*:/p' "$plan" | grep -o 'tokens: *[0-9]*' | head -1 | grep -o '[0-9]*')
+ [ -n "$EST" ] && gsd_run query estimate-check --tokens "$EST" --calibrated 2>/dev/null || true
+done
```
**Extract:** Phase goal, requirements (decompose goal), locked decisions, deferred ideas.
diff --git a/agents/gsd-planner.md b/agents/gsd-planner.md
index 040c2acc2..a6a6b320b 100644
--- a/agents/gsd-planner.md
+++ b/agents/gsd-planner.md
@@ -288,30 +288,15 @@ See @~/.claude/gsd-core/references/planner-guidance.md for dependency graph buil
-## Context Budget Rules
+## Sizing and the Estimate Block
-Plans should complete within ~50% context (not 80%). No context anxiety, quality maintained start to finish, room for unexpected complexity.
+Full rules: @~/.claude/gsd-core/references/context-budget.md (Phase Sizing). Read before sizing.
-**Each plan: 2-3 tasks maximum.**
-
-| Context Weight | Tasks/Plan | Context/Task | Total |
-|----------------|------------|--------------|-------|
-| Light (CRUD, config) | 3 | ~10-15% | ~30-45% |
-| Medium (auth, payments) | 2 | ~20-30% | ~40-50% |
-| Heavy (migrations, multi-subsystem) | 1-2 | ~30-40% | ~30-50% |
-
-## Split Signals
-
-**ALWAYS split if:**
-- More than 3 tasks
-- Multiple subsystems (DB + API + UI = separate plans)
-- Any task with >5 file modifications
-- Checkpoint + implementation in same plan
-- Discovery + implementation in same plan
-
-**CONSIDER splitting:** >5 files total, natural semantic boundaries, context cost estimate exceeds 40% for a single plan. See `` for prohibited split reasons.
-
-See @~/.claude/gsd-core/references/planner-guidance.md for Granularity Calibration table (Coarse/Standard/Fine plans-per-phase).
+- **2-3 tasks per plan.** **ALWAYS split if:** >3 tasks, multiple subsystems, or any task touching >5 files.
+- **Emit `estimate`** in PLAN.md frontmatter: run the `estimate-calibration` query, multiply your raw
+ token projection by its calibration factor, and copy its `confidence` verbatim — derived from the
+ sample count, never self-rated.
+- **Over the smart-zone budget?** Re-slice: tracer + expansion slices. Advisory, never a block.
@@ -331,6 +316,11 @@ autonomous: true # false if plan has checkpoints
requirements: [] # REQUIRED — Requirement IDs from ROADMAP this plan addresses. MUST NOT be empty.
user_setup: [] # Human-required setup (omit if empty)
+estimate: # Projected execution cost (see Estimate Emission)
+ tokens: 60000 # calibrated projection
+ tasks: 3 # task count the projection assumes
+ confidence: low # low | med | high — DERIVED from sample count, never self-rated
+
must_haves:
truths: [] # Observable behaviors
artifacts: [] # Files that must exist
@@ -412,6 +402,7 @@ Create `.planning/phases/XX-name/{padded_phase}-{plan}-SUMMARY.md` when done
| `autonomous` | Yes | `true` if no checkpoints |
| `requirements` | Yes | **MUST** list requirement IDs from ROADMAP. Every roadmap requirement ID MUST appear in at least one plan. |
| `user_setup` | No | Human-required setup items |
+| `estimate` | No | Projected cost `{tokens, tasks, confidence}`. See Estimate Emission. |
| `must_haves` | Yes | Goal-backward verification criteria |
Wave numbers are pre-computed during planning. Execute-phase reads `wave` directly from frontmatter.
diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md
index 54e91e9de..a5798ca05 100644
--- a/docs/COMMANDS.md
+++ b/docs/COMMANDS.md
@@ -216,6 +216,8 @@ Research, plan, and verify a phase.
| `--tdd` | TDD mode — planner applies `type: tdd` to eligible behavior-adding tasks so each begins with a failing test. Composable with `--mvp`: `--mvp --tdd` produces vertical slices where every behavior-adding task starts red-green. The leading `tracer` task also starts red under `--tdd`. |
| `--granularity ` | Override the planning granularity for this invocation, ignoring config. Valid values: `coarse`, `standard`, `fine`. Takes precedence over `granularities.planning`, top-level `granularity`, and `planning.granularity` config. |
+**Smart-zone estimate report (#2631).** Every generated PLAN.md carries an optional `estimate` block (`{tokens, tasks, confidence}`). During the plan-check pass, `gsd-plan-checker` runs each plan's `estimate.tokens` through `estimate-check` against the configurable `workflow.smart_zone_tokens` budget (default `100000`) and reports the result; a plan above budget gets a concrete split recommendation. The report is **advisory and never blocks planning**, and it is skipped with `--skip-verify` since it runs inside the verification pass. `confidence` is derived from how many completed phases carry recorded actuals — `low` means fewer than three, so the figure is not yet calibrated for your project. See [ADR-2629](adr/2629-phase-effort-estimation-calibration.md).
+
**Prerequisites:** `.planning/ROADMAP.md` exists
**Produces:** `{phase}-RESEARCH.md`, `{phase}-{N}-PLAN.md`, `{phase}-VALIDATION.md`; `{phase}/SKELETON.md` when Walking Skeleton mode fires
diff --git a/docs/reference/plan-md.md b/docs/reference/plan-md.md
index 9655d0a5a..3a3da716c 100644
--- a/docs/reference/plan-md.md
+++ b/docs/reference/plan-md.md
@@ -73,6 +73,7 @@ must_haves:
| `requirements` | Yes | array of IDs | Requirement IDs from ROADMAP.md that this plan addresses. Every phase requirement ID must appear in at least one plan's `requirements` field. Empty arrays are a BLOCKER. |
| `user_setup` | No | array of objects | External-service setup steps that Claude cannot automate (account creation, secret retrieval, dashboard configuration). When present, execute-phase generates a `USER-SETUP.md` checklist for the developer. |
| `status` | No | `superseded` | Marks a plan that was deliberately reassigned or abandoned mid-phase and will never be executed. A `status: superseded` plan is excluded from the phase's plan and summary counts, so it never holds the phase below 100%. See [Superseded plans](#superseded-plans). Any other value (or the field's absence) has no effect on counting. |
+| `estimate` | No | object | Projected execution cost: `{tokens, tasks, confidence}` (#2631, [ADR-2629](../adr/2629-phase-effort-estimation-calibration.md)). `tokens` is an `estimateTokens`-scale projection with the project's calibration factor **already applied** (which is why the plan-checker passes `--calibrated` to `estimate-check` — re-applying it would square the correction); `confidence` (`low`/`med`/`high`) is **derived from the calibration sample count, never self-rated**. Additive and optional — a plan without it behaves exactly as before. A plan estimated above `workflow.smart_zone_tokens` is flagged with a split recommendation at plan time; the flag is advisory and never blocks. |
| `must_haves` | Yes | object | Goal-backward verification criteria. See below. |
### Superseded plans
diff --git a/gsd-core/references/context-budget.md b/gsd-core/references/context-budget.md
index 7223976d0..32434bf2e 100644
--- a/gsd-core/references/context-budget.md
+++ b/gsd-core/references/context-budget.md
@@ -83,3 +83,43 @@ Either list works — `enabledMcpjsonServers` is an explicit allow-list, `disabl
### Composition with model_profile
Trimming MCPs and tuning `model_profile` are independent levers that **compound**. Disabling a 25k-token MCP saves 25k per turn whether you're running `quality` (opus everywhere) or `budget` (sonnet/haiku); the savings are additive, not in lieu of model tuning. Don't pick one — do both, and audit MCPs first because the per-turn savings show up immediately and stack across every subagent the orchestrator spawns.
+
+---
+
+# Phase Sizing (gsd-planner)
+
+## Estimate Emission (#2631, ADR-2629)
+
+Every plan carries an `estimate` block. It is the quantitative reason a phase must be sliced — tracer-first says *slice thin*, the estimate says *how thin, for this codebase*.
+
+**Compute it:**
+1. Sum `estimateTokens`-scale cost across the plan: implementation + the files each task reads + verification output. Roughly chars/4 over what the executor will actually touch.
+2. Run `estimate-calibration` and **multiply your raw figure by its `factor`.** It is the measured estimate-vs-actual ratio for THIS project — a factor of 1 means there is not yet enough history to correct.
+3. `confidence` is **derived, not judged**: it is the `confidence` value from the same calibration query, keyed to the sample count (`low` <3, `med` 3–5, `high` ≥6). **Do not rate your own certainty.** Self-rated confidence was measured in this project and found weak (`references/honest-verifier.md:25-29`); every signal here routes on measured history instead.
+
+**Over budget?** The plan-checker flags a plan whose estimate exceeds `workflow.smart_zone_tokens`. This is advisory — it never blocks. When flagged, re-slice: a tracer plus expansion slices, each inside the budget. Prefer more, smaller plans over one that spends the agent's best early-context tokens and finishes degraded.
+
+## Context Budget Rules
+
+Plans should complete within ~50% context (not 80%). No context anxiety, quality maintained start to finish, room for unexpected complexity.
+
+**Each plan: 2-3 tasks maximum.**
+
+| Context Weight | Tasks/Plan | Context/Task | Total |
+|----------------|------------|--------------|-------|
+| Light (CRUD, config) | 3 | ~10-15% | ~30-45% |
+| Medium (auth, payments) | 2 | ~20-30% | ~40-50% |
+| Heavy (migrations, multi-subsystem) | 1-2 | ~30-40% | ~30-50% |
+
+## Split Signals
+
+**ALWAYS split if:**
+- More than 3 tasks
+- Multiple subsystems (DB + API + UI = separate plans)
+- Any task with >5 file modifications
+- Checkpoint + implementation in same plan
+- Discovery + implementation in same plan
+
+**CONSIDER splitting:** >5 files total, natural semantic boundaries, context cost estimate exceeds 40% for a single plan. See `` for prohibited split reasons.
+
+See @~/.claude/gsd-core/references/planner-guidance.md for Granularity Calibration table (Coarse/Standard/Fine plans-per-phase).
diff --git a/src/estimate-cli.cts b/src/estimate-cli.cts
index a1c5e78f1..73406c17a 100644
--- a/src/estimate-cli.cts
+++ b/src/estimate-cli.cts
@@ -89,7 +89,7 @@ export function readCalibrationSamples(cwd: string): ReturnType', ERROR_REASON.USAGE);
+ error('Usage: estimate-check --tokens [--calibrated]', ERROR_REASON.USAGE);
}
const value = args[idx + 1];
@@ -119,15 +119,26 @@ export function parseTokensFlag(args: string[]): number {
* in the payload is the signal.
*/
export function cmdEstimateCheck(cwd: string, args: string[], raw: boolean): void {
- const rawTokens = parseTokensFlag(args);
+ const inputTokens = parseTokensFlag(args);
+ const preCalibrated = args.includes('--calibrated');
const budget = readSmartZoneBudget(cwd);
const calibration = estimation.computeCalibration(readCalibrationSamples(cwd));
- const calibratedTokens = estimation.applyCalibration(rawTokens, calibration.factor);
+ // `--calibrated` says the caller already applied the factor. Without it we
+ // would apply the correction a SECOND time and compare factor^2 against the
+ // budget — with the [0.5, 3.0] clamp that is anywhere from 4x under to 9x
+ // over, and it is invisible until a project reaches 3 samples (below that
+ // factor === 1, and 1^2 === 1). A plan's recorded `estimate.tokens` is
+ // calibrated at emission time per ADR-2629 Decision 1, so the plan-checker
+ // MUST pass this flag.
+ const calibratedTokens = preCalibrated
+ ? inputTokens
+ : estimation.applyCalibration(inputTokens, calibration.factor);
const classification = estimation.classifyAgainstBudget(calibratedTokens, budget);
output({
- raw_tokens: rawTokens,
+ raw_tokens: inputTokens,
calibrated_tokens: calibratedTokens,
+ pre_calibrated: preCalibrated,
budget,
over_budget: classification.overBudget,
budget_valid: classification.budgetValid,
diff --git a/tests/agent-size-baseline.json b/tests/agent-size-baseline.json
index afb6f5fa5..78d09ea4c 100644
--- a/tests/agent-size-baseline.json
+++ b/tests/agent-size-baseline.json
@@ -22,8 +22,8 @@
"gsd-nyquist-auditor.md": 7345,
"gsd-pattern-mapper.md": 12487,
"gsd-phase-researcher.md": 40866,
- "gsd-plan-checker.md": 44780,
- "gsd-planner.md": 49327,
+ "gsd-plan-checker.md": 46363,
+ "gsd-planner.md": 49251,
"gsd-project-researcher.md": 22242,
"gsd-research-synthesizer.md": 13847,
"gsd-roadmapper.md": 22273,
diff --git a/tests/fixtures/golden-install-parity/antigravity.json b/tests/fixtures/golden-install-parity/antigravity.json
index 4253c8bcc..f3827ef42 100644
--- a/tests/fixtures/golden-install-parity/antigravity.json
+++ b/tests/fixtures/golden-install-parity/antigravity.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "805a1404d03629f1",
"agents/gsd-pattern-mapper.md": "e62ee90d39084802",
"agents/gsd-phase-researcher.md": "cff1196c8e8bb4fa",
- "agents/gsd-plan-checker.md": "dd1e7cdc837d8f3e",
- "agents/gsd-planner.md": "c7ba1adfe2b1975d",
+ "agents/gsd-plan-checker.md": "893e88036b4ad419",
+ "agents/gsd-planner.md": "8af42f4321c34bc9",
"agents/gsd-project-researcher.md": "85de7f562872ee9b",
"agents/gsd-research-synthesizer.md": "18a2e1b30ff7ae3a",
"agents/gsd-roadmapper.md": "7a8465ac6d4dd29e",
@@ -58,7 +58,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "efd80aca449032ad",
"gsd-core/references/checkpoints.md": "130bb6ef705fc065",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "177520ead2ae3a23",
+ "gsd-core/references/context-budget.md": "0c8a4b3dee1f6666",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "4c13da220b6a4261",
"gsd-core/references/debugger-fix-acceptance.md": "87d367e4e864910e",
diff --git a/tests/fixtures/golden-install-parity/augment.json b/tests/fixtures/golden-install-parity/augment.json
index cc0f460c2..96f5968f5 100644
--- a/tests/fixtures/golden-install-parity/augment.json
+++ b/tests/fixtures/golden-install-parity/augment.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "230e6667411ccf2f",
"agents/gsd-pattern-mapper.md": "43c6021cf7caabfa",
"agents/gsd-phase-researcher.md": "f1f6fd6a3e67c7a8",
- "agents/gsd-plan-checker.md": "bf1a4e636f2390de",
- "agents/gsd-planner.md": "f0195f37cafb303b",
+ "agents/gsd-plan-checker.md": "d9aca5f75d649fc0",
+ "agents/gsd-planner.md": "5b018b0a2df93e37",
"agents/gsd-project-researcher.md": "4531b7cc8f5e5f7d",
"agents/gsd-research-synthesizer.md": "4a4f68e6c75b133a",
"agents/gsd-roadmapper.md": "bb2f57695dbab32c",
@@ -129,7 +129,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "c2fe89c42ca88349",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "f1ce57bf418824af",
+ "gsd-core/references/context-budget.md": "6749084395889c6a",
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
"gsd-core/references/debugger-bug-taxonomy.md": "78fb8c1711acc971",
"gsd-core/references/debugger-fix-acceptance.md": "5616622f33e00436",
diff --git a/tests/fixtures/golden-install-parity/claude-local.json b/tests/fixtures/golden-install-parity/claude-local.json
index 5021f5d2d..e508c8acf 100644
--- a/tests/fixtures/golden-install-parity/claude-local.json
+++ b/tests/fixtures/golden-install-parity/claude-local.json
@@ -23,8 +23,8 @@
"agents/gsd-nyquist-auditor.md": "f86a28f5c164a0d3",
"agents/gsd-pattern-mapper.md": "b45b5e106775bec1",
"agents/gsd-phase-researcher.md": "4772d9eada32e8bd",
- "agents/gsd-plan-checker.md": "75851b147f35354a",
- "agents/gsd-planner.md": "352243b5b6c87c36",
+ "agents/gsd-plan-checker.md": "b3f510f1257ff383",
+ "agents/gsd-planner.md": "831977f45c361561",
"agents/gsd-project-researcher.md": "d7f355894519f9fe",
"agents/gsd-research-synthesizer.md": "1c738df9932d325a",
"agents/gsd-roadmapper.md": "453e9471ad27c7ea",
@@ -128,7 +128,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "c2fe89c42ca88349",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "6bfac08025ea3106",
+ "gsd-core/references/context-budget.md": "3565d2150c2124e1",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "78fb8c1711acc971",
"gsd-core/references/debugger-fix-acceptance.md": "5616622f33e00436",
diff --git a/tests/fixtures/golden-install-parity/claude.json b/tests/fixtures/golden-install-parity/claude.json
index ac584464f..c5e737b05 100644
--- a/tests/fixtures/golden-install-parity/claude.json
+++ b/tests/fixtures/golden-install-parity/claude.json
@@ -23,8 +23,8 @@
"agents/gsd-nyquist-auditor.md": "0bc0cdad7b3a0013",
"agents/gsd-pattern-mapper.md": "b45b5e106775bec1",
"agents/gsd-phase-researcher.md": "85217c69c1ed2ac6",
- "agents/gsd-plan-checker.md": "c70134c61b969589",
- "agents/gsd-planner.md": "1ded63b1c2b85729",
+ "agents/gsd-plan-checker.md": "670e4ad7132a66ca",
+ "agents/gsd-planner.md": "0f5d0199fbce83f8",
"agents/gsd-project-researcher.md": "f468e96f8339d1e0",
"agents/gsd-research-synthesizer.md": "7be02e47f4fd901b",
"agents/gsd-roadmapper.md": "8a7f1f1256a6aed5",
@@ -57,7 +57,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "c2fe89c42ca88349",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "533eae480bfc4bb8",
+ "gsd-core/references/context-budget.md": "b2c7b1703642a342",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "78fb8c1711acc971",
"gsd-core/references/debugger-fix-acceptance.md": "5616622f33e00436",
diff --git a/tests/fixtures/golden-install-parity/cline.json b/tests/fixtures/golden-install-parity/cline.json
index e1f9c0a7a..b7d9fc472 100644
--- a/tests/fixtures/golden-install-parity/cline.json
+++ b/tests/fixtures/golden-install-parity/cline.json
@@ -27,8 +27,8 @@
"agents/gsd-nyquist-auditor.md": "4f9bba7b9762835d",
"agents/gsd-pattern-mapper.md": "b526065fd2efa19c",
"agents/gsd-phase-researcher.md": "c507db2ba66038f4",
- "agents/gsd-plan-checker.md": "a609245dbdc4ef2b",
- "agents/gsd-planner.md": "5249eb5808e67a6a",
+ "agents/gsd-plan-checker.md": "70ccc072a52cc4ce",
+ "agents/gsd-planner.md": "aab8d4d2db4c3d93",
"agents/gsd-project-researcher.md": "049f816c6caa4316",
"agents/gsd-research-synthesizer.md": "2f7dcbff50371d4c",
"agents/gsd-roadmapper.md": "bbb23d3097911516",
@@ -61,7 +61,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "f66fb7d5b435682d",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "c154ba00dbbf4477",
+ "gsd-core/references/context-budget.md": "20caa988a2bca07a",
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
"gsd-core/references/debugger-bug-taxonomy.md": "63521aea0206d4ac",
"gsd-core/references/debugger-fix-acceptance.md": "1057d70f7f2d654a",
diff --git a/tests/fixtures/golden-install-parity/codebuddy.json b/tests/fixtures/golden-install-parity/codebuddy.json
index d0476ea99..a9df20ed4 100644
--- a/tests/fixtures/golden-install-parity/codebuddy.json
+++ b/tests/fixtures/golden-install-parity/codebuddy.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "42676eea063d2ec5",
"agents/gsd-pattern-mapper.md": "92cfa2e6c2a06bf3",
"agents/gsd-phase-researcher.md": "6338474da1a5d65e",
- "agents/gsd-plan-checker.md": "e704c083b02e8c35",
- "agents/gsd-planner.md": "4b0e9f592f31bc1d",
+ "agents/gsd-plan-checker.md": "3fdccb7353c69340",
+ "agents/gsd-planner.md": "55445462761df7b3",
"agents/gsd-project-researcher.md": "e43c59f7f1f2f37a",
"agents/gsd-research-synthesizer.md": "87955470c3c129b2",
"agents/gsd-roadmapper.md": "20b69eff61a7a9fa",
@@ -129,7 +129,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "c2fe89c42ca88349",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "f1ce57bf418824af",
+ "gsd-core/references/context-budget.md": "6749084395889c6a",
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
"gsd-core/references/debugger-bug-taxonomy.md": "78fb8c1711acc971",
"gsd-core/references/debugger-fix-acceptance.md": "5616622f33e00436",
diff --git a/tests/fixtures/golden-install-parity/codex.json b/tests/fixtures/golden-install-parity/codex.json
index f35e7fc39..1f6199621 100644
--- a/tests/fixtures/golden-install-parity/codex.json
+++ b/tests/fixtures/golden-install-parity/codex.json
@@ -118,10 +118,10 @@
"agents/gsd-pattern-mapper.toml": "48a17baa3d7d142c",
"agents/gsd-phase-researcher.md": "87d74324e7708ea0",
"agents/gsd-phase-researcher.toml": "44a3d510cd0ce3bd",
- "agents/gsd-plan-checker.md": "e7f02c10ea788aee",
- "agents/gsd-plan-checker.toml": "6f8ceb421d0ad721",
- "agents/gsd-planner.md": "ef417468fecb841f",
- "agents/gsd-planner.toml": "4c9feaf9e76d6655",
+ "agents/gsd-plan-checker.md": "9ba8ea7643f0a7f5",
+ "agents/gsd-plan-checker.toml": "03488397d892ea09",
+ "agents/gsd-planner.md": "88283bc1d8da011e",
+ "agents/gsd-planner.toml": "1b24b3f19b8afecc",
"agents/gsd-project-researcher.md": "959f2e57c3d69ed8",
"agents/gsd-project-researcher.toml": "f395e8e8c4baf1ed",
"agents/gsd-research-synthesizer.md": "497f85adf53259ef",
@@ -164,7 +164,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "4156025334411073",
"gsd-core/references/checkpoints.md": "d52116f59e92ca43",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "0b67ea1d5db4bc08",
+ "gsd-core/references/context-budget.md": "8bde1f8516046fa7",
"gsd-core/references/continuation-format.md": "e64c0da2b3d0f2f0",
"gsd-core/references/debugger-bug-taxonomy.md": "49f94fe75cabba52",
"gsd-core/references/debugger-fix-acceptance.md": "b2732420bdbcadcd",
diff --git a/tests/fixtures/golden-install-parity/copilot.json b/tests/fixtures/golden-install-parity/copilot.json
index b1be9f6d9..4bb5d3df9 100644
--- a/tests/fixtures/golden-install-parity/copilot.json
+++ b/tests/fixtures/golden-install-parity/copilot.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.agent.md": "6463a50d90ada125",
"agents/gsd-pattern-mapper.agent.md": "b1f488b0fa6a2395",
"agents/gsd-phase-researcher.agent.md": "03cfb510a766fe93",
- "agents/gsd-plan-checker.agent.md": "c50a5b008ddcbfad",
- "agents/gsd-planner.agent.md": "defa418d75a9b3e9",
+ "agents/gsd-plan-checker.agent.md": "0d68eb258b85e24e",
+ "agents/gsd-planner.agent.md": "0c222d93778eb371",
"agents/gsd-project-researcher.agent.md": "d73bdbe986ffa8a6",
"agents/gsd-research-synthesizer.agent.md": "f03eed4aa89e47c5",
"agents/gsd-roadmapper.agent.md": "322048cf8ddcb4e5",
@@ -59,7 +59,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "efd80aca449032ad",
"gsd-core/references/checkpoints.md": "d26b11ab5ece9e61",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "435474d5e10be65a",
+ "gsd-core/references/context-budget.md": "371da06bb42df63b",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "ddfb2bbc1f3de50a",
"gsd-core/references/debugger-fix-acceptance.md": "812922e2b5db9410",
diff --git a/tests/fixtures/golden-install-parity/cursor.json b/tests/fixtures/golden-install-parity/cursor.json
index d715726f7..76a4cfe71 100644
--- a/tests/fixtures/golden-install-parity/cursor.json
+++ b/tests/fixtures/golden-install-parity/cursor.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "e18b0209cea56a2e",
"agents/gsd-pattern-mapper.md": "1229c215677f740d",
"agents/gsd-phase-researcher.md": "982d59921bed463d",
- "agents/gsd-plan-checker.md": "ba51999876d40cf2",
- "agents/gsd-planner.md": "a39e021ed91064bc",
+ "agents/gsd-plan-checker.md": "30bc89279d6586b7",
+ "agents/gsd-planner.md": "61d89d16ac357320",
"agents/gsd-project-researcher.md": "beeac940d3a10e76",
"agents/gsd-research-synthesizer.md": "6315f016d55176f4",
"agents/gsd-roadmapper.md": "d28e7d4bac46dde2",
@@ -129,7 +129,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "273b371c5751f35a",
"gsd-core/references/checkpoints.md": "a44e66095240c46e",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "304dcdab82a27623",
+ "gsd-core/references/context-budget.md": "1aada627aab75f46",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "63c03b8afbd21b8a",
"gsd-core/references/debugger-fix-acceptance.md": "86b7cbde0171fafd",
diff --git a/tests/fixtures/golden-install-parity/hermes.json b/tests/fixtures/golden-install-parity/hermes.json
index 3b35d6288..94805fff5 100644
--- a/tests/fixtures/golden-install-parity/hermes.json
+++ b/tests/fixtures/golden-install-parity/hermes.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "8bfde4a9a36318aa",
"agents/gsd-pattern-mapper.md": "cea092600aeb3978",
"agents/gsd-phase-researcher.md": "2bd0402f33d757ca",
- "agents/gsd-plan-checker.md": "4b4e2b475bf5b5c3",
- "agents/gsd-planner.md": "07bd1760e8861253",
+ "agents/gsd-plan-checker.md": "c31dc063b5506aaf",
+ "agents/gsd-planner.md": "d1fe1e2653cb12de",
"agents/gsd-project-researcher.md": "425a7df7f37a5c06",
"agents/gsd-research-synthesizer.md": "9d31c87fc2c87ffa",
"agents/gsd-roadmapper.md": "64dce5d5f9fa5654",
@@ -58,7 +58,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "6e4b08a89c485c71",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "b93e9f47aa1b1753",
+ "gsd-core/references/context-budget.md": "6e9dbeb47e90493f",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "fd9b10fffb3bd052",
"gsd-core/references/debugger-fix-acceptance.md": "b04b913d5ea0ac90",
diff --git a/tests/fixtures/golden-install-parity/kilo.json b/tests/fixtures/golden-install-parity/kilo.json
index 36660b43b..d6f947e8b 100644
--- a/tests/fixtures/golden-install-parity/kilo.json
+++ b/tests/fixtures/golden-install-parity/kilo.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "7bc3cc159c7be987",
"agents/gsd-pattern-mapper.md": "6a5408fd11d70391",
"agents/gsd-phase-researcher.md": "94818f28c498bb26",
- "agents/gsd-plan-checker.md": "56164206242c8caf",
- "agents/gsd-planner.md": "739c1b66005be087",
+ "agents/gsd-plan-checker.md": "2546e311f7e8e0e8",
+ "agents/gsd-planner.md": "72996d1f61ad7b0f",
"agents/gsd-project-researcher.md": "60573a38d3dfd9fe",
"agents/gsd-research-synthesizer.md": "1f7cd286c5783c86",
"agents/gsd-roadmapper.md": "277e0a3252553ab7",
@@ -129,7 +129,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "3986d58011bf9006",
"gsd-core/references/checkpoints.md": "d52116f59e92ca43",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "533eae480bfc4bb8",
+ "gsd-core/references/context-budget.md": "b2c7b1703642a342",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "49f94fe75cabba52",
"gsd-core/references/debugger-fix-acceptance.md": "b2732420bdbcadcd",
diff --git a/tests/fixtures/golden-install-parity/kimi-code.json b/tests/fixtures/golden-install-parity/kimi-code.json
index b790b1f49..82d95d75d 100644
--- a/tests/fixtures/golden-install-parity/kimi-code.json
+++ b/tests/fixtures/golden-install-parity/kimi-code.json
@@ -52,8 +52,8 @@
"agents/gsd-nyquist-auditor.md": "f2232a1be0835709",
"agents/gsd-pattern-mapper.md": "68ecefd60811a669",
"agents/gsd-phase-researcher.md": "2235f61764d8e969",
- "agents/gsd-plan-checker.md": "bb38f345d3d41edc",
- "agents/gsd-planner.md": "4b7405292d3866ad",
+ "agents/gsd-plan-checker.md": "f3caf89525709445",
+ "agents/gsd-planner.md": "bf4304ea220f3874",
"agents/gsd-project-researcher.md": "f572892f138734ff",
"agents/gsd-research-synthesizer.md": "29949bf3f049a8f1",
"agents/gsd-roadmapper.md": "840ac933e3b094f9",
@@ -86,7 +86,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "c2fe89c42ca88349",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "f1ce57bf418824af",
+ "gsd-core/references/context-budget.md": "6749084395889c6a",
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
"gsd-core/references/debugger-bug-taxonomy.md": "78fb8c1711acc971",
"gsd-core/references/debugger-fix-acceptance.md": "5616622f33e00436",
diff --git a/tests/fixtures/golden-install-parity/kimi.json b/tests/fixtures/golden-install-parity/kimi.json
index 595012cfe..20edb907e 100644
--- a/tests/fixtures/golden-install-parity/kimi.json
+++ b/tests/fixtures/golden-install-parity/kimi.json
@@ -77,9 +77,9 @@
"agents/subagents/gsd-pattern-mapper.yaml": "d1e537e77f953fd4",
"agents/subagents/gsd-phase-researcher.md": "897080d9383bd176",
"agents/subagents/gsd-phase-researcher.yaml": "7633c8e82617e7cc",
- "agents/subagents/gsd-plan-checker.md": "bd302afc01ed40f0",
+ "agents/subagents/gsd-plan-checker.md": "410639f4c4f16e7a",
"agents/subagents/gsd-plan-checker.yaml": "8295181071121db8",
- "agents/subagents/gsd-planner.md": "28172805a3e78163",
+ "agents/subagents/gsd-planner.md": "0384e53e5b5b50e5",
"agents/subagents/gsd-planner.yaml": "2e83ee194bcd7fbd",
"agents/subagents/gsd-project-researcher.md": "39bc2ec5a8b18283",
"agents/subagents/gsd-project-researcher.yaml": "ce12586b0347e2dc",
@@ -122,7 +122,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "c2fe89c42ca88349",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "f1ce57bf418824af",
+ "gsd-core/references/context-budget.md": "6749084395889c6a",
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
"gsd-core/references/debugger-bug-taxonomy.md": "78fb8c1711acc971",
"gsd-core/references/debugger-fix-acceptance.md": "5616622f33e00436",
diff --git a/tests/fixtures/golden-install-parity/opencode.json b/tests/fixtures/golden-install-parity/opencode.json
index c9101c645..4bf370b70 100644
--- a/tests/fixtures/golden-install-parity/opencode.json
+++ b/tests/fixtures/golden-install-parity/opencode.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "7a7e0d1cbafd6b63",
"agents/gsd-pattern-mapper.md": "7c6d1d9817a9c1e7",
"agents/gsd-phase-researcher.md": "9874110700b41f48",
- "agents/gsd-plan-checker.md": "28ca3dc43669894f",
- "agents/gsd-planner.md": "b2ea0c457376ac3e",
+ "agents/gsd-plan-checker.md": "f39aca1ea261d720",
+ "agents/gsd-planner.md": "15c78c3192e53c9b",
"agents/gsd-project-researcher.md": "dae210ae0b3c6e2b",
"agents/gsd-research-synthesizer.md": "e02c6ad5d1b74171",
"agents/gsd-roadmapper.md": "1658a40b20d8b575",
@@ -129,7 +129,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "3986d58011bf9006",
"gsd-core/references/checkpoints.md": "d52116f59e92ca43",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "355826e667f9ccd1",
+ "gsd-core/references/context-budget.md": "876da77377424383",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "49f94fe75cabba52",
"gsd-core/references/debugger-fix-acceptance.md": "b2732420bdbcadcd",
diff --git a/tests/fixtures/golden-install-parity/pi.json b/tests/fixtures/golden-install-parity/pi.json
index 312e69a47..ec1246631 100644
--- a/tests/fixtures/golden-install-parity/pi.json
+++ b/tests/fixtures/golden-install-parity/pi.json
@@ -25,7 +25,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "c2fe89c42ca88349",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "f1ce57bf418824af",
+ "gsd-core/references/context-budget.md": "6749084395889c6a",
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
"gsd-core/references/debugger-bug-taxonomy.md": "78fb8c1711acc971",
"gsd-core/references/debugger-fix-acceptance.md": "5616622f33e00436",
diff --git a/tests/fixtures/golden-install-parity/qwen.json b/tests/fixtures/golden-install-parity/qwen.json
index d16e80d8e..72e11227e 100644
--- a/tests/fixtures/golden-install-parity/qwen.json
+++ b/tests/fixtures/golden-install-parity/qwen.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "2bf7126001ba3e70",
"agents/gsd-pattern-mapper.md": "83c66c7722e8b165",
"agents/gsd-phase-researcher.md": "284e55a86ae46d7f",
- "agents/gsd-plan-checker.md": "c8a8fcc8ed38eff0",
- "agents/gsd-planner.md": "e85ed2535f6a7624",
+ "agents/gsd-plan-checker.md": "e80e6d51017be405",
+ "agents/gsd-planner.md": "f3c8b934ee07020c",
"agents/gsd-project-researcher.md": "b5baac64a15c85e2",
"agents/gsd-research-synthesizer.md": "6cd9b501dc97bd50",
"agents/gsd-roadmapper.md": "c357a77ab919e9e5",
@@ -58,7 +58,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "5e2d923303f16667",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "a1351dd40ef8691f",
+ "gsd-core/references/context-budget.md": "b862a6cda7a5f216",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "47e7929972c06378",
"gsd-core/references/debugger-fix-acceptance.md": "ab6a1cce253944f4",
diff --git a/tests/fixtures/golden-install-parity/trae.json b/tests/fixtures/golden-install-parity/trae.json
index cb8998036..ac49b0e59 100644
--- a/tests/fixtures/golden-install-parity/trae.json
+++ b/tests/fixtures/golden-install-parity/trae.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "58efbed3b81b5ede",
"agents/gsd-pattern-mapper.md": "b5d7a4abb1baecb9",
"agents/gsd-phase-researcher.md": "2256f1f82212c757",
- "agents/gsd-plan-checker.md": "523119bd5d6599fe",
- "agents/gsd-planner.md": "dbec071e8a52ebd4",
+ "agents/gsd-plan-checker.md": "aaa9e928de1abba9",
+ "agents/gsd-planner.md": "ca48b09c8688e70d",
"agents/gsd-project-researcher.md": "ddf7794e81300032",
"agents/gsd-research-synthesizer.md": "a124b00271748d07",
"agents/gsd-roadmapper.md": "493ef92b42b12cf4",
@@ -58,7 +58,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "4d24ce110ed6c746",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "b7e9640063775c98",
+ "gsd-core/references/context-budget.md": "bdff0159e58a8568",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "d0458c690c4487c9",
"gsd-core/references/debugger-fix-acceptance.md": "134f83886c343733",
diff --git a/tests/fixtures/golden-install-parity/windsurf.json b/tests/fixtures/golden-install-parity/windsurf.json
index 7915d0bbc..695c3f9c8 100644
--- a/tests/fixtures/golden-install-parity/windsurf.json
+++ b/tests/fixtures/golden-install-parity/windsurf.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "0ef8609fd897e399",
"agents/gsd-pattern-mapper.md": "ada0c169daa2f0ec",
"agents/gsd-phase-researcher.md": "2a45ebde829555ec",
- "agents/gsd-plan-checker.md": "33fbf70b7b24eb1e",
- "agents/gsd-planner.md": "8b59834f86f84d31",
+ "agents/gsd-plan-checker.md": "c68b9bd6382a22a7",
+ "agents/gsd-planner.md": "b63b7203e0bc7beb",
"agents/gsd-project-researcher.md": "f6697b316b5995ba",
"agents/gsd-research-synthesizer.md": "04036f38c1d373ea",
"agents/gsd-roadmapper.md": "fb62e1e3de84b5f9",
@@ -58,7 +58,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "273b371c5751f35a",
"gsd-core/references/checkpoints.md": "900530adaf675e38",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "ff843cd6139c8564",
+ "gsd-core/references/context-budget.md": "7a53f9d14e306e3a",
"gsd-core/references/continuation-format.md": "580287399ad3ba68",
"gsd-core/references/debugger-bug-taxonomy.md": "5d8c0a266a6d61f8",
"gsd-core/references/debugger-fix-acceptance.md": "ddd00cbd78dd3fdf",
diff --git a/tests/fixtures/golden-install-parity/zcode.json b/tests/fixtures/golden-install-parity/zcode.json
index 0793eb61b..c9a67bce2 100644
--- a/tests/fixtures/golden-install-parity/zcode.json
+++ b/tests/fixtures/golden-install-parity/zcode.json
@@ -24,8 +24,8 @@
"agents/gsd-nyquist-auditor.md": "f2232a1be0835709",
"agents/gsd-pattern-mapper.md": "68ecefd60811a669",
"agents/gsd-phase-researcher.md": "2235f61764d8e969",
- "agents/gsd-plan-checker.md": "bb38f345d3d41edc",
- "agents/gsd-planner.md": "4b7405292d3866ad",
+ "agents/gsd-plan-checker.md": "f3caf89525709445",
+ "agents/gsd-planner.md": "bf4304ea220f3874",
"agents/gsd-project-researcher.md": "f572892f138734ff",
"agents/gsd-research-synthesizer.md": "29949bf3f049a8f1",
"agents/gsd-roadmapper.md": "840ac933e3b094f9",
@@ -129,7 +129,7 @@
"gsd-core/references/autonomous-smart-discuss.md": "2fc710cde0ec7785",
"gsd-core/references/checkpoints.md": "c2fe89c42ca88349",
"gsd-core/references/common-bug-patterns.md": "a4cfea8954dede29",
- "gsd-core/references/context-budget.md": "f1ce57bf418824af",
+ "gsd-core/references/context-budget.md": "6749084395889c6a",
"gsd-core/references/continuation-format.md": "ce73e57d053ab8a7",
"gsd-core/references/debugger-bug-taxonomy.md": "78fb8c1711acc971",
"gsd-core/references/debugger-fix-acceptance.md": "5616622f33e00436",
diff --git a/tests/phase-estimation.test.cjs b/tests/phase-estimation.test.cjs
index dfd632871..2c43742ad 100644
--- a/tests/phase-estimation.test.cjs
+++ b/tests/phase-estimation.test.cjs
@@ -679,3 +679,57 @@ describe('query estimate-calibration', () => {
assert.equal(calibration.factor, 1);
});
});
+
+// ─── double-calibration guard (#2631) ──────────────────────────────────────
+
+describe('estimate-check --calibrated', () => {
+ // A plan's recorded `estimate.tokens` already has the factor applied at
+ // emission time (ADR-2629 Decision 1). Without --calibrated, estimate-check
+ // applies it a SECOND time and compares factor^2 against the budget. With the
+ // [0.5, 3.0] clamp that ranges from 4x under to 9x over — and it is invisible
+ // until a project reaches 3 samples, because below that factor === 1 and
+ // 1^2 === 1. These tests pin both modes at a factor where they diverge.
+ const withHistory = (t) => {
+ const tmpDir = createTempProject();
+ t.after(() => cleanup(tmpDir));
+ fs.writeFileSync(
+ path.join(tmpDir, '.planning', 'estimation-calibration.json'),
+ est.renderCalibrationDocument([sample(100, 200), sample(100, 200), sample(100, 200)]),
+ );
+ return tmpDir;
+ };
+
+ test('without the flag, a raw projection IS corrected', (t) => {
+ const tmpDir = withHistory(t);
+ const out = JSON.parse(runGsdTools('query estimate-check --tokens 50000', tmpDir).output);
+ assert.equal(out.calibration_factor, 2, 'fixture must produce factor 2');
+ assert.equal(out.calibrated_tokens, 100000, 'a raw projection must be multiplied by the factor');
+ assert.equal(out.pre_calibrated, false);
+ });
+
+ test('with the flag, an already-calibrated figure is NOT corrected again', (t) => {
+ const tmpDir = withHistory(t);
+ const out = JSON.parse(runGsdTools('query estimate-check --tokens 50000 --calibrated', tmpDir).output);
+ assert.equal(out.calibration_factor, 2, 'the factor is still reported');
+ assert.equal(out.calibrated_tokens, 50000,
+ 'a pre-calibrated figure must pass through untouched — re-applying squares the correction');
+ assert.equal(out.pre_calibrated, true);
+ });
+
+ test('the two modes diverge by exactly the factor', (t) => {
+ const tmpDir = withHistory(t);
+ const raw = JSON.parse(runGsdTools('query estimate-check --tokens 40000', tmpDir).output);
+ const pre = JSON.parse(runGsdTools('query estimate-check --tokens 40000 --calibrated', tmpDir).output);
+ assert.equal(raw.calibrated_tokens, pre.calibrated_tokens * raw.calibration_factor);
+ });
+
+ test('the flag changes the over-budget verdict at the boundary', (t) => {
+ const tmpDir = withHistory(t);
+ runGsdTools('config-set workflow.smart_zone_tokens 60000', tmpDir);
+ // 50000 raw -> 100000 calibrated -> over 60000. Same value pre-calibrated -> under.
+ const raw = JSON.parse(runGsdTools('query estimate-check --tokens 50000', tmpDir).output);
+ const pre = JSON.parse(runGsdTools('query estimate-check --tokens 50000 --calibrated', tmpDir).output);
+ assert.equal(raw.over_budget, true, 'double-applied correction reports a false over-budget');
+ assert.equal(pre.over_budget, false, 'the honest figure is under budget');
+ });
+});
diff --git a/tests/planner-estimate-emission.test.cjs b/tests/planner-estimate-emission.test.cjs
new file mode 100644
index 000000000..efc937e71
--- /dev/null
+++ b/tests/planner-estimate-emission.test.cjs
@@ -0,0 +1,162 @@
+// allow-test-rule: source-text-is-the-product see #2631
+// agents/gsd-planner.md, agents/gsd-plan-checker.md and docs/reference/plan-md.md — their text IS what the runtime loads and what
+// the planner emits against. Per CONTRIBUTING.md exception matrix.
+
+/**
+ * Planner estimate emission + over-budget surfacing.
+ *
+ * Epic #1952 Phase 2 (#2631). Design lock: docs/adr/2629-phase-effort-estimation-calibration.md.
+ *
+ * Phase 1 (#2630) landed the estimation module and its CLI verbs deliberately
+ * unconsumed. This phase wires them: the planner emits `estimate` into PLAN.md
+ * frontmatter, and plan-phase surfaces the over-budget warning. These tests pin
+ * the wiring so the module cannot silently go back to being dead code.
+ *
+ * The parity test at the bottom is the load-bearing one: the confidence
+ * vocabulary appears in BOTH agent prose and the module's frozen enum, which is
+ * exactly the "generative fix divergence" shape CLAUDE.md requires a parity
+ * assertion for.
+ */
+
+const { describe, test } = require('node:test');
+const assert = require('node:assert/strict');
+const fs = require('node:fs');
+const path = require('node:path');
+
+const ROOT = path.join(__dirname, '..');
+const read = (p) => fs.readFileSync(path.join(ROOT, p), 'utf8');
+
+const PLANNER = 'agents/gsd-planner.md';
+const PLAN_CHECKER = 'agents/gsd-plan-checker.md';
+const PLAN_MD_REF = 'docs/reference/plan-md.md';
+
+/**
+ * Extract the PLAN.md frontmatter template the planner tells agents to emit.
+ *
+ * The template is a bare `---`-delimited YAML block, NOT a fenced ```yaml
+ * region — an earlier version of this helper looked for a fence and silently
+ * matched nothing, which made every assertion below fail for the wrong reason.
+ */
+function plannerFrontmatterTemplate(src) {
+ for (const m of src.matchAll(/^---\r?\n([\s\S]*?)^---\r?$/gm)) {
+ const body = m[1];
+ if (/^phase:/m.test(body) && /^must_haves:/m.test(body)) return body;
+ }
+ return null;
+}
+
+describe('planner emits an estimate block (AC1)', () => {
+ const src = read(PLANNER);
+
+ test('the PLAN.md frontmatter template carries an estimate block', () => {
+ const tmpl = plannerFrontmatterTemplate(src);
+ assert.ok(tmpl, 'could not locate the PLAN.md frontmatter template in the planner');
+ assert.match(tmpl, /^estimate:/m, 'template must declare an `estimate:` block');
+ for (const field of ['tokens', 'tasks', 'confidence']) {
+ assert.match(tmpl, new RegExp(`^\\s+${field}:`, 'm'), `estimate block must carry \`${field}\``);
+ }
+ });
+
+ test('the frontmatter field table documents estimate', () => {
+ assert.match(src, /\|\s*`estimate`\s*\|/, 'field reference table must have an `estimate` row');
+ });
+
+ test('the planner is told to apply the calibration factor, not invent confidence', () => {
+ assert.match(src, /estimate-calibration|calibration factor/i,
+ 'planner must consume the calibration surface Phase 1 exposed');
+ assert.match(src, /derived|sample count/i,
+ 'planner must be told confidence is derived from sample count, not self-rated');
+ });
+});
+
+describe('the over-budget flag is surfaced (AC2)', () => {
+ // Surfaced by gsd-plan-checker, not plan-phase.md: that workflow sits ~74
+ // bytes under the phase-6 capstone ratchet (94519) and cannot take new
+ // content without an unrelated extraction. Dimension 5 already owns scope
+ // sanity, so the estimate check belongs there.
+ const src = read(PLAN_CHECKER);
+
+ test('the checker resolves the configured smart-zone budget', () => {
+ assert.match(src, /workflow\.smart_zone_tokens/,
+ 'must read the configured budget, not hardcode one');
+ });
+
+ test('the checker invokes the estimate-check verb', () => {
+ assert.match(src, /estimate-check/,
+ 'the flag must be computed by the Phase 1 verb, not re-derived in prose');
+ });
+
+ test('over budget recommends splitting and is never a blocker', () => {
+ assert.match(src, /re-slic|split/i, 'must recommend splitting');
+ assert.match(src, /WARNING, never a blocker|never a blocker/i,
+ 'ADR-2629 Decision 5: the flag is advisory');
+ });
+});
+
+describe('plan-checker validates the estimate (Dimension 5)', () => {
+ const src = read(PLAN_CHECKER);
+
+ test('Dimension 5 checks the emitted estimate against the budget', () => {
+ const idx = src.indexOf('Dimension 5');
+ assert.ok(idx !== -1, 'Dimension 5 section must exist');
+ const section = src.slice(idx, idx + 2500);
+ assert.match(section, /estimate/i,
+ 'Scope Sanity must consult the emitted estimate now that one exists');
+ });
+});
+
+describe('plan-md reference documents the field', () => {
+ const src = read(PLAN_MD_REF);
+
+ test('the frontmatter field reference has an estimate row', () => {
+ assert.match(src, /\|\s*`estimate`\s*\|/, 'plan-md.md must document `estimate`');
+ });
+
+ test('the row records that it is optional and additive', () => {
+ const row = src.split('\n').find((l) => /\|\s*`estimate`\s*\|/.test(l));
+ assert.ok(row, 'estimate row not found');
+ assert.match(row, /\bNo\b/, 'estimate must be documented as NOT required (additive/optional)');
+ });
+});
+
+describe('prose ↔ module parity (generative fix divergence guard)', () => {
+ // The confidence vocabulary now lives in two surfaces: the frozen enum in
+ // phase-estimation.cjs and the prose the planner emits against. If they
+ // diverge, the planner starts writing values the parser rejects — silently,
+ // because the estimate block is optional. Fail loudly instead.
+ const est = require('../gsd-core/bin/lib/phase-estimation.cjs');
+
+ test('every confidence value the planner may emit is accepted by the parser', () => {
+ const tmpl = plannerFrontmatterTemplate(read(PLANNER));
+ assert.ok(tmpl, 'template not found');
+
+ const line = tmpl.split('\n').find((l) => /^\s+confidence:/.test(l));
+ assert.ok(line, 'template must show the confidence field');
+
+ // Pull every bare word on the confidence line that looks like a vocabulary
+ // token (the comment enumerates the allowed values).
+ const words = line.match(/\b(low|med|high)\b/g) || [];
+ assert.ok(words.length > 0, 'confidence line must enumerate the allowed values');
+
+ for (const w of words) {
+ assert.ok(
+ est.CONFIDENCE_VALUES.includes(w),
+ `planner prose offers confidence "${w}" but the module's CONFIDENCE_VALUES does not accept it`,
+ );
+ }
+ });
+
+ test('the documented budget default matches the shipped manifest default', () => {
+ const manifest = require('../gsd-core/bin/shared/config-defaults.manifest.json');
+ const shipped = manifest.workflow.smart_zone_tokens;
+ assert.ok(Number.isSafeInteger(shipped) && shipped > 0, 'manifest must ship a usable default');
+
+ // docs/CONFIGURATION.md states the default in prose; a drifted doc silently
+ // misdescribes the gate to every reader.
+ const docs = read('docs/CONFIGURATION.md');
+ const row = docs.split('\n').find((l) => l.includes('workflow.smart_zone_tokens'));
+ assert.ok(row, 'CONFIGURATION.md must document the key');
+ assert.ok(row.includes(String(shipped)),
+ `CONFIGURATION.md documents a default that is not the shipped ${shipped}`);
+ });
+});