diff --git a/.changeset/witty-wasps-munch.md b/.changeset/witty-wasps-munch.md new file mode 100644 index 000000000..53f8c8301 --- /dev/null +++ b/.changeset/witty-wasps-munch.md @@ -0,0 +1,5 @@ +--- +type: Added +pr: 1595 +--- +**`/gsd-plan-phase` now flags a stale codebase map before planning** — the `drift` capability runs its codebase-drift check at `plan:pre` (non-blocking, warn-only), so a stale STRUCTURE.md is surfaced before the planner is spawned instead of being discovered mid-execution by the existing `execute:wave:post` gate. Gated on a new `workflow.plan_drift_precheck` toggle (default on), independent of `workflow.schema_drift_gate`, so autonomous/CI runs can silence the plan-time advisory without disabling the execute-time gates. diff --git a/capabilities/drift/capability.json b/capabilities/drift/capability.json index 0e570c0ce..bc3014170 100644 --- a/capabilities/drift/capability.json +++ b/capabilities/drift/capability.json @@ -3,7 +3,7 @@ "role": "feature", "version": "1.6.0-rc.2", "title": "Drift detection gates", - "description": "Post-execution drift detection gates that run after each wave completes. Provides two gates at execute:wave:post: a blocking schema drift gate (detects schema files changed without a database push) and a non-blocking codebase drift gate (detects structural additions not reflected in STRUCTURE.md).", + "description": "Drift detection gates for the planning loop. At execute:wave:post: a blocking schema drift gate (detects schema files changed without a database push) and a non-blocking codebase drift gate (detects structural additions not reflected in STRUCTURE.md). At plan:pre: a non-blocking, warn-only codebase drift gate (gated on workflow.plan_drift_precheck) that flags a stale codebase map before planning, so plans are authored against a fresh STRUCTURE.md instead of discovering drift mid-execution.", "tier": "full", "requires": [], "engines": { @@ -37,6 +37,11 @@ "type": "boolean", "default": true, "description": "Enable the drift gates at execute:wave:post. When enabled, the schema drift gate blocks verification if schema-relevant files changed during execution but no database push command was executed; the codebase drift gate (non-blocking) warns when structural additions exceed the drift_threshold." + }, + "workflow.plan_drift_precheck": { + "type": "boolean", + "default": true, + "description": "Enable the non-blocking codebase drift pre-check at plan:pre, before /gsd:plan-phase spawns the planner. When enabled, a stale STRUCTURE.md (structural additions exceeding drift_threshold) is surfaced up front as a warn-only advisory pointing to /gsd:map-codebase; it never blocks planning and never spawns the mapper agent. Separate from schema_drift_gate so autonomous/CI runs can silence the plan-time advisory while keeping the execute:wave:post gates enabled." } }, "steps": [], @@ -59,6 +64,15 @@ "when": "workflow.schema_drift_gate", "blocking": false, "onError": "skip" + }, + { + "point": "plan:pre", + "check": { + "query": "verify.codebase-drift" + }, + "when": "workflow.plan_drift_precheck", + "blocking": false, + "onError": "skip" } ] } diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 37c523140..6c2c8ad6c 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -273,8 +273,9 @@ All workflow toggles follow the **absent = enabled** pattern. If a key is missin | `executor.stall_detect_interval_minutes` | number | `5` | Minutes between executor stall checks while an executor agent is active. The execute-phase orchestrator uses this cadence to inspect recent commits and avoid waiting forever on a silent agent. | | `executor.stall_threshold_minutes` | number | `10` | Minutes without executor completion or expected-branch commit activity before execute-phase offers recovery choices for a possible stalled executor. | | `workflow.inline_plan_threshold` | number | `3` | Maximum number of tasks in a phase before the planner generates a separate PLAN.md file instead of inlining tasks in the prompt | -| `workflow.drift_threshold` | number | `3` | Minimum number of new structural elements (new directories, barrel exports, migrations, route modules) introduced during a phase before the post-execute codebase-drift gate takes action. See [#2003](https://github.com/open-gsd/gsd-core/issues/2003). Added in v1.39 | -| `workflow.drift_action` | string | `warn` | What to do when `workflow.drift_threshold` is exceeded after `/gsd-execute-phase`. `warn` prints a message suggesting `/gsd-map-codebase --paths …`; `auto-remap` spawns `gsd-codebase-mapper` scoped to the affected paths. Added in v1.39 | +| `workflow.drift_threshold` | number | `3` | Minimum number of new structural elements (new directories, barrel exports, migrations, route modules) before the codebase-drift gate takes action. The gate runs at two points: `plan:pre` (before `/gsd-plan-phase` plans — **non-blocking, warn-only**, so plans are authored against a fresh STRUCTURE.md) and `execute:wave:post` (after `/gsd-execute-phase` — honors `workflow.drift_action`). See [#2003](https://github.com/open-gsd/gsd-core/issues/2003). Added in v1.39 | +| `workflow.drift_action` | string | `warn` | What to do when `workflow.drift_threshold` is exceeded **at `execute:wave:post`** (after `/gsd-execute-phase`). `warn` prints a message suggesting `/gsd-map-codebase --paths …`; `auto-remap` spawns `gsd-codebase-mapper` scoped to the affected paths. The `plan:pre` pre-check is always warn-only regardless of this setting — it never auto-spawns the mapper at plan entry. Added in v1.39 | +| `workflow.plan_drift_precheck` | boolean | `true` | Enable the non-blocking codebase-drift pre-check at `plan:pre`, before `/gsd:plan-phase` spawns the planner. Surfaces a stale STRUCTURE.md (drift over `workflow.drift_threshold`) as a warn-only advisory pointing to `/gsd:map-codebase`; never blocks planning, never spawns the mapper. Separate from the `execute:wave:post` gates so autonomous/CI runs can silence the plan-time advisory while keeping execute-time drift detection on. Added in v1.6.0. See [#1592](https://github.com/open-gsd/gsd-core/issues/1592). | | `workflow.build_command` | string | (none) | Shell command to build the project in the post-merge build gate (Step A of step 5.6 in execute-phase). When unset, the gate auto-detects: Xcode (`.xcodeproj` present) → `xcodebuild build`, `Makefile` with `build:` target → `make build`, Justfile → `just build`, `Cargo.toml` → `cargo build`, `go.mod` → `go build ./...`, Python → `python -m py_compile`, `package.json` with `build` script → `npm run build`. Runs with a 5-minute timeout; failure increments `WAVE_FAILURE_COUNT`. Added in v1.39 | | `workflow.test_command` | string | (none) | Shell command to run the project's test suite in the post-merge test gate (Step B of step 5.6 in execute-phase) and the regression gate. When unset, the gate auto-detects: Xcode (`.xcodeproj` present) → `xcodebuild test`, `Makefile` with `test:` target → `make test`, Justfile → `just test`, `package.json` → `npm test`, `Cargo.toml` → `cargo test`, `go.mod` → `go test ./...`, Python → `python -m pytest`. Runs with a 5-minute timeout; failure increments `WAVE_FAILURE_COUNT`. Added in v1.39 | diff --git a/docs/adr/857-capability-system.md b/docs/adr/857-capability-system.md index ed91a2763..9aeba61ed 100644 --- a/docs/adr/857-capability-system.md +++ b/docs/adr/857-capability-system.md @@ -50,7 +50,7 @@ These were grilled to resolution after the initial eight decisions. ### Loop Extension Points (the 12) -`discuss:pre`, `discuss:post`, `plan:pre`, `plan:post`, `execute:pre`, `execute:wave:pre`, `execute:wave:post`, `execute:post`, `verify:pre`, `verify:post`, `ship:pre`, `ship:post`. The planner/checker loop, the verifier, the verify-work gap-closure loop, **and the verifier↔predicate contract** (the spec-reach substrate — see *Verification substrate vs. plug-in tier* below) remain **core** (not hooks). Today's `§`-point features map on as: research / ui-spec / ai-spec / pattern-mapper (`step`) and security / schema-gate / tdd (`contribution`) at `plan:pre`; nyquist / gap-analysis (`gate`) at `plan:post`; build+test / code-review / drift (`gate`/`step`) at `execute:wave:post`; `verification.status` preflight (`gate`) at `ship:pre`; PR-body sections (`contribution`) at `ship:post`. The names are a stability contract — additive-only across versions. +`discuss:pre`, `discuss:post`, `plan:pre`, `plan:post`, `execute:pre`, `execute:wave:pre`, `execute:wave:post`, `execute:post`, `verify:pre`, `verify:post`, `ship:pre`, `ship:post`. The planner/checker loop, the verifier, the verify-work gap-closure loop, **and the verifier↔predicate contract** (the spec-reach substrate — see *Verification substrate vs. plug-in tier* below) remain **core** (not hooks). Today's `§`-point features map on as: research / ui-spec / ai-spec / pattern-mapper (`step`) and security / schema-gate / tdd (`contribution`) and drift (`gate`) at `plan:pre`; nyquist / gap-analysis (`gate`) at `plan:post`; build+test / code-review / drift (`gate`/`step`) at `execute:wave:post`; `verification.status` preflight (`gate`) at `ship:pre`; PR-body sections (`contribution`) at `ship:post`. The names are a stability contract — additive-only across versions. ### Verification substrate vs. plug-in tier (the predicate boundary) diff --git a/docs/reference/capability-matrix.md b/docs/reference/capability-matrix.md index 9e95dd878..802ed6a2e 100644 --- a/docs/reference/capability-matrix.md +++ b/docs/reference/capability-matrix.md @@ -55,7 +55,7 @@ points. | `ai-integration` | feature | full | `>=1.6.0` | `plan:pre` | step | first-party | | `audit` | feature | full | `>=1.6.0` | — | — | first-party | | `code-review` | feature | full | `>=1.6.0` | `execute:post` | step | first-party | -| `drift` | feature | full | `>=1.6.0` | `execute:wave:post` | gate | first-party | +| `drift` | feature | full | `>=1.6.0` | `plan:pre`, `execute:wave:post` | gate | first-party | | `gap-analysis` | feature | standard | `>=1.6.0` | `plan:post` | gate | first-party | | `graphify` | feature | full | `>=1.6.0` | — | — | first-party | | `intel` | feature | full | `>=1.6.0` | `plan:pre` | step | first-party | diff --git a/gsd-core/bin/lib/capability-registry.cjs b/gsd-core/bin/lib/capability-registry.cjs index 45e52f2d0..c26051ea1 100644 --- a/gsd-core/bin/lib/capability-registry.cjs +++ b/gsd-core/bin/lib/capability-registry.cjs @@ -645,7 +645,7 @@ const capabilities = { "role": "feature", "version": "1.6.0-rc.2", "title": "Drift detection gates", - "description": "Post-execution drift detection gates that run after each wave completes. Provides two gates at execute:wave:post: a blocking schema drift gate (detects schema files changed without a database push) and a non-blocking codebase drift gate (detects structural additions not reflected in STRUCTURE.md).", + "description": "Drift detection gates for the planning loop. At execute:wave:post: a blocking schema drift gate (detects schema files changed without a database push) and a non-blocking codebase drift gate (detects structural additions not reflected in STRUCTURE.md). At plan:pre: a non-blocking, warn-only codebase drift gate (gated on workflow.plan_drift_precheck) that flags a stale codebase map before planning, so plans are authored against a fresh STRUCTURE.md instead of discovering drift mid-execution.", "tier": "full", "requires": [], "engines": { @@ -679,6 +679,11 @@ const capabilities = { "type": "boolean", "default": true, "description": "Enable the drift gates at execute:wave:post. When enabled, the schema drift gate blocks verification if schema-relevant files changed during execution but no database push command was executed; the codebase drift gate (non-blocking) warns when structural additions exceed the drift_threshold." + }, + "workflow.plan_drift_precheck": { + "type": "boolean", + "default": true, + "description": "Enable the non-blocking codebase drift pre-check at plan:pre, before /gsd:plan-phase spawns the planner. When enabled, a stale STRUCTURE.md (structural additions exceeding drift_threshold) is surfaced up front as a warn-only advisory pointing to /gsd:map-codebase; it never blocks planning and never spawns the mapper agent. Separate from schema_drift_gate so autonomous/CI runs can silence the plan-time advisory while keeping the execute:wave:post gates enabled." } }, "steps": [], @@ -701,6 +706,15 @@ const capabilities = { "when": "workflow.schema_drift_gate", "blocking": false, "onError": "skip" + }, + { + "point": "plan:pre", + "check": { + "query": "verify.codebase-drift" + }, + "when": "workflow.plan_drift_precheck", + "blocking": false, + "onError": "skip" } ] }, @@ -2228,6 +2242,16 @@ const byLoopPoint = { } ], "gates": [ + { + "capId": "drift", + "point": "plan:pre", + "check": { + "query": "verify.codebase-drift" + }, + "when": "workflow.plan_drift_precheck", + "blocking": false, + "onError": "skip" + }, { "capId": "ui", "point": "plan:pre", @@ -2480,6 +2504,7 @@ const configKeys = { "workflow.drift_threshold": "drift", "workflow.drift_action": "drift", "workflow.schema_drift_gate": "drift", + "workflow.plan_drift_precheck": "drift", "workflow.post_planning_gaps": "gap-analysis", "graphify.enabled": "graphify", "intel.enabled": "intel", @@ -2553,6 +2578,12 @@ const configSchema = { "default": true, "description": "Enable the drift gates at execute:wave:post. When enabled, the schema drift gate blocks verification if schema-relevant files changed during execution but no database push command was executed; the codebase drift gate (non-blocking) warns when structural additions exceed the drift_threshold." }, + "workflow.plan_drift_precheck": { + "owner": "drift", + "type": "boolean", + "default": true, + "description": "Enable the non-blocking codebase drift pre-check at plan:pre, before /gsd:plan-phase spawns the planner. When enabled, a stale STRUCTURE.md (structural additions exceeding drift_threshold) is surfaced up front as a warn-only advisory pointing to /gsd:map-codebase; it never blocks planning and never spawns the mapper agent. Separate from schema_drift_gate so autonomous/CI runs can silence the plan-time advisory while keeping the execute:wave:post gates enabled." + }, "workflow.post_planning_gaps": { "owner": "gap-analysis", "type": "boolean", diff --git a/gsd-core/workflows/plan-phase.md b/gsd-core/workflows/plan-phase.md index 5574aa38d..6432bfd22 100644 --- a/gsd-core/workflows/plan-phase.md +++ b/gsd-core/workflows/plan-phase.md @@ -693,6 +693,21 @@ Also available: **Exit the plan-phase workflow. Do not continue.** +## 5.65. Codebase Map Freshness Pre-Check (drift plan:pre gate) + +If `activeHooks` (from `PLAN_PRE_HOOKS_JSON`, §5.6) has a `kind == "gate"`, `capId == "drift"`, +`check.query == "verify.codebase-drift"` entry (`workflow.plan_drift_precheck` on), run the same check the +execute gate uses; otherwise skip to step 6: + +```bash +DRIFT=$(gsd_run verify codebase-drift 2>/dev/null || echo '{"skipped":true}') +``` + +This gate is **non-blocking** and **never blocks, never spawns** the mapper at plan time. If `skipped` or +`action_required` is false, continue silently to step 6. If `action_required` is true, print `message` +verbatim (it ends with a `/gsd:map-codebase` pointer) and continue — planning proceeds whether or not the +map is refreshed first. (`drift_action: auto-remap` stays at `execute:wave:post`.) + ## 6. Check Existing Plans ```bash diff --git a/tests/enh-1592-plan-drift-precheck.test.cjs b/tests/enh-1592-plan-drift-precheck.test.cjs new file mode 100644 index 000000000..4555ac2c1 --- /dev/null +++ b/tests/enh-1592-plan-drift-precheck.test.cjs @@ -0,0 +1,177 @@ +'use strict'; +// allow-test-rule: source-text-is-the-product see #1592 +// The plan-phase.md host-dispatch assertions below read the workflow .md file — its text IS the +// deployed contract the runtime loads (CONTRIBUTING.md exemption category). The registry assertions +// are behavioral: they build the registry from the REAL capabilities/drift declaration via the +// generator, so they fail if the plan:pre gate is ever removed or mutated. + +/** + * Enhancement (#1592): plan-time codebase-map freshness pre-check. + * + * The `drift` capability gains a non-blocking `plan:pre` codebase-drift gate so a stale codebase map is + * flagged BEFORE planning, instead of being discovered mid-execution by the existing + * `execute:wave:post` codebase-drift gate. Warn-only at `plan:pre` (no mapper-agent spawn): the + * capability's `drift_action: auto-remap` stays at `execute:wave:post`, so plan time never pays + * speculative mapper-agent cost. + * + * Per maintainer review on #1592 (mod 1a), the plan:pre gate is gated on a DEDICATED + * `workflow.plan_drift_precheck` toggle (default true) rather than reusing `workflow.schema_drift_gate`, + * so autonomous/CI runs can silence the plan-time advisory without disabling the execute-time gates. + * The gate declaration conforms to ADR-857 (`plan:pre` is an enumerated, additive-only loop point). + * + * Issue: #1592 (open-gsd/gsd-core). + */ + +const { describe, test, after } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); + +const { loadAndValidate, buildRegistry } = require('../scripts/gen-capability-registry.cjs'); +const { cleanup } = require('./helpers.cjs'); + +const REPO_ROOT = path.join(__dirname, '..'); +const DRIFT_CAP = JSON.parse( + fs.readFileSync(path.join(REPO_ROOT, 'capabilities', 'drift', 'capability.json'), 'utf8'), +); +const PLAN_PHASE = fs.readFileSync( + path.join(REPO_ROOT, 'gsd-core', 'workflows', 'plan-phase.md'), + 'utf8', +); + +// Track every temp dir created so the suite can remove them on teardown — leaked +// mkdtemp dirs have been a flake source here before (per #1592 review). +const tempCapDirs = []; + +function makeTempCapDir(capabilities) { + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'enh-1592-')); + tempCapDirs.push(tmpDir); + for (const [id, cap] of Object.entries(capabilities)) { + const subDir = path.join(tmpDir, id); + fs.mkdirSync(subDir, { recursive: true }); + fs.writeFileSync(path.join(subDir, 'capability.json'), JSON.stringify(cap), 'utf8'); + } + return tmpDir; +} + +after(() => { + for (const dir of tempCapDirs) { + cleanup(dir); + } +}); + +function planPreDriftGate() { + const capDir = makeTempCapDir({ drift: DRIFT_CAP }); + const { capMap, errors } = loadAndValidate(new Set(), capDir); + assert.deepEqual(errors, [], 'drift capability should validate cleanly: ' + JSON.stringify(errors)); + const registry = buildRegistry(capMap); + const planPreGates = registry.byLoopPoint['plan:pre'].gates; + assert.ok(Array.isArray(planPreGates), 'plan:pre.gates should be an array'); + return planPreGates.find( + (g) => g.capId === 'drift' && g.check && g.check.query === 'verify.codebase-drift', + ); +} + +describe('#1592 — drift plan:pre codebase-drift gate (registry, behavioral)', () => { + test('the real drift capability registers a non-blocking plan:pre codebase-drift gate', () => { + const driftGate = planPreDriftGate(); + assert.ok(driftGate, 'plan:pre.gates must contain the drift codebase-drift gate'); + assert.strictEqual(driftGate.blocking, false, 'plan-time drift gate must be NON-blocking'); + assert.strictEqual(driftGate.onError, 'skip', 'must fail-soft (skip) — never halt planning'); + }); + + test('the plan:pre gate is gated on the dedicated plan_drift_precheck toggle (mod 1a)', () => { + const driftGate = planPreDriftGate(); + assert.strictEqual( + driftGate.when, + 'workflow.plan_drift_precheck', + 'plan:pre drift gate must use the dedicated toggle so CI/autonomous runs can silence it ' + + 'without disabling the execute-time gates', + ); + }); + + test('the execute:wave:post codebase-drift gate is preserved and keeps its OWN toggle (no regression)', () => { + const capDir = makeTempCapDir({ drift: DRIFT_CAP }); + const { capMap } = loadAndValidate(new Set(), capDir); + const registry = buildRegistry(capMap); + + const execGates = registry.byLoopPoint['execute:wave:post'].gates; + const stillThere = execGates.find( + (g) => g.capId === 'drift' && g.check && g.check.query === 'verify.codebase-drift', + ); + assert.ok(stillThere, 'execute:wave:post codebase-drift gate must remain after adding the plan:pre gate'); + assert.strictEqual(stillThere.blocking, false, 'execute codebase-drift gate stays non-blocking'); + assert.strictEqual( + stillThere.when, + 'workflow.schema_drift_gate', + 'the execute-time gate keeps schema_drift_gate — the plan-time toggle is separable from it', + ); + }); + + test('plan_drift_precheck is a separate toggle from schema_drift_gate (silencing is independent)', () => { + const planWhen = planPreDriftGate().when; + assert.notStrictEqual( + planWhen, + 'workflow.schema_drift_gate', + 'silencing the plan-time advisory must not require disabling the execute-time gates', + ); + }); + + test('plan_drift_precheck is declared as a boolean defaulting to true', () => { + const cfg = DRIFT_CAP.config['workflow.plan_drift_precheck']; + assert.ok(cfg, 'workflow.plan_drift_precheck must be declared in the drift capability config'); + assert.strictEqual(cfg.type, 'boolean', 'plan_drift_precheck must be a boolean'); + assert.strictEqual(cfg.default, true, 'plan_drift_precheck must default to true (on by default)'); + }); + + test('exactly one new config key is introduced (the dedicated plan_drift_precheck toggle)', () => { + const keys = Object.keys(DRIFT_CAP.config).sort(); + assert.deepStrictEqual( + keys, + [ + 'workflow.drift_action', + 'workflow.drift_threshold', + 'workflow.plan_drift_precheck', + 'workflow.schema_drift_gate', + ], + 'the plan:pre gate adds exactly the dedicated plan_drift_precheck toggle — no other new keys', + ); + }); +}); + +describe('#1592 — plan-phase host dispatches the drift plan:pre gate before planning', () => { + const SECTION = PLAN_PHASE.slice( + PLAN_PHASE.indexOf('5.65. Codebase Map Freshness Pre-Check'), + PLAN_PHASE.indexOf('## 6. Check Existing Plans'), + ); + + test('§5.65 invokes the verify codebase-drift check', () => { + assert.match(PLAN_PHASE, /5\.65\. Codebase Map Freshness Pre-Check/, 'plan-phase must declare §5.65'); + assert.match(PLAN_PHASE, /gsd_run verify codebase-drift/, '§5.65 must invoke `verify codebase-drift`'); + }); + + test('the drift pre-check runs BEFORE the planner spawn (load-bearing ordering)', () => { + const preCheckIdx = PLAN_PHASE.indexOf('5.65. Codebase Map Freshness Pre-Check'); + const plannerIdx = PLAN_PHASE.indexOf('## 8. Spawn gsd-planner Agent'); + assert.ok(preCheckIdx > 0, '§5.65 must exist'); + assert.ok(plannerIdx > 0, '§8 planner spawn must exist'); + assert.ok( + preCheckIdx < plannerIdx, + 'the drift map-freshness pre-check must run before the planner is spawned — the whole point of #1592', + ); + }); + + test('§5.65 is documented as non-blocking and warn-only (no spawn)', () => { + assert.match(SECTION, /non-blocking/i, '§5.65 must state the gate is non-blocking'); + assert.match(SECTION, /never blocks, never spawns/i, '§5.65 must state it never spawns the mapper at plan time'); + }); + + test('§5.65 gates on the dedicated plan_drift_precheck toggle (mod 1a)', () => { + assert.match( + SECTION, + /workflow\.plan_drift_precheck/, + '§5.65 must dispatch on the dedicated plan_drift_precheck toggle, not schema_drift_gate', + ); + }); +}); diff --git a/tests/plan-pre-hook-e2e.test.cjs b/tests/plan-pre-hook-e2e.test.cjs index f2e7fb937..6f92b8b75 100644 --- a/tests/plan-pre-hook-e2e.test.cjs +++ b/tests/plan-pre-hook-e2e.test.cjs @@ -253,6 +253,7 @@ describe('plan:pre all-off — empty resolution', () => { research: false, pattern_mapper: false, schema_push_detection: false, + plan_drift_precheck: false, }, intel: { enabled: false }, }); diff --git a/tests/workflow-size-baseline.json b/tests/workflow-size-baseline.json index 55bb9f176..d878bca24 100644 --- a/tests/workflow-size-baseline.json +++ b/tests/workflow-size-baseline.json @@ -52,7 +52,7 @@ "note.md": 6563, "pause-work.md": 14397, "plan-milestone-gaps.md": 11765, - "plan-phase.md": 93166, + "plan-phase.md": 93973, "plan-review-convergence.md": 23468, "plant-seed.md": 11741, "pr-branch.md": 15919,