feat: auto-remap codebase after significant phase execution (closes #2003)#2605
feat: auto-remap codebase after significant phase execution (closes #2003)#2605
Conversation
Adds a post-phase structural drift detector that compares the committed tree against `.planning/codebase/STRUCTURE.md` and either warns or auto-remaps the affected subtrees when drift exceeds a configurable threshold. ## Summary - New `bin/lib/drift.cjs` — pure detector covering four drift categories: new directories outside mapped paths, new barrel exports at `(packages|apps)/*/src/index.*`, new migration files, and new route modules. Prioritizes the most-specific category per file. - New `verify codebase-drift` CLI subcommand + SDK handler, registered as `gsd-sdk query verify.codebase-drift`. - New `codebase_drift_gate` step in `execute-phase` between `schema_drift_gate` and `verify_phase_goal`. Non-blocking by contract — any error logs and the phase continues. - Two new config keys: `workflow.drift_threshold` (int, default 3) and `workflow.drift_action` (`warn` | `auto-remap`, default `warn`), with enum/integer validation in `config-set`. - `gsd-codebase-mapper` learns an optional `--paths <p1,p2,...>` scope hint for incremental remapping; agent/workflow docs updated. - `last_mapped_commit` lives in YAML frontmatter on each `.planning/codebase/*.md` file; `readMappedCommit`/`writeMappedCommit` round-trip helpers ship in `drift.cjs`. ## Tests - 55 new tests in `tests/drift-detection.test.cjs` covering: classification, threshold gating at 2/3/4 elements, warn vs. auto-remap routing, affected-path scoping, `--paths` sanitization (traversal, absolute, shell metacharacter rejection), frontmatter round-trip, defensive paths (missing STRUCTURE.md, malformed input, non-git repos), CLI JSON output, and documentation parity. - Full suite: 5044 pass / 0 fail. ## Documentation - `docs/CONFIGURATION.md` — rows for both new keys. - `docs/ARCHITECTURE.md` — section on the post-execute drift gate. - `docs/AGENTS.md` — `--paths` flag on `gsd-codebase-mapper`. - `docs/USER-GUIDE.md` — user-facing behavior note + toggle commands. - `docs/FEATURES.md` — new 27a section with REQ-DRIFT-01..06. - `docs/INVENTORY.md` + `docs/INVENTORY-MANIFEST.json` — drift.cjs listed. - `get-shit-done/workflows/execute-phase.md` — `codebase_drift_gate` step. - `get-shit-done/workflows/map-codebase.md` — `parse_paths_flag` step. - `agents/gsd-codebase-mapper.md` — `--paths` directive under parse_focus. ## Design decisions - **Frontmatter over sidecar JSON** for `last_mapped_commit`: keeps the baseline attached to the file, survives git moves, survives per-doc regeneration, no extra file lifecycle. - **Substring match against STRUCTURE.md** for `isPathMapped`: the map is free-form markdown, not a structured manifest; any mention of a path prefix counts as "mapped territory". Cheap, no parser, zero false negatives on reasonable maps. - **Category priority migration > route > barrel > new_dir** so a file matching multiple rules counts exactly once at the most specific level. - **Empty-tree SHA fallback** (`4b825dc6…`) when `last_mapped_commit` is absent — semantically correct (no baseline means everything is drift) and deterministic across repos. - **Four layers of non-blocking** — detector try/catch, CLI try/catch, SDK handler try/catch, and workflow `|| echo` shell fallback. Any single layer failing still returns a valid skipped result. - **SDK handler delegates to `gsd-tools.cjs`** rather than re-porting the detector to TypeScript, keeping drift logic in one canonical place. Closes #2003 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Caution Review failedPull request was closed or merged during review 📝 WalkthroughWalkthroughAdds a non-blocking post-execution codebase drift detector and an incremental remap flow: a new drift library and verify command detect structural changes vs Changes
Sequence DiagramsequenceDiagram
participant Client
participant ExecutePhase as Execute Phase
participant DriftVerifier as Drift Verifier (verify tool)
participant GitRepo as Git Repo
participant StructureFile as STRUCTURE.md
participant DriftDetector as Drift Detector (drift.cjs)
participant Config as Config
participant Mapper as GSD Mapper (auto-remap)
Client->>ExecutePhase: /gsd:execute-phase completes
ExecutePhase->>DriftVerifier: query verify.codebase-drift
DriftVerifier->>StructureFile: read .planning/codebase/STRUCTURE.md
StructureFile-->>DriftVerifier: content + last_mapped_commit
DriftVerifier->>GitRepo: git diff last_mapped_commit..HEAD
GitRepo-->>DriftVerifier: list of changed file paths
DriftVerifier->>DriftDetector: detectDrift({files, structureMd, threshold})
DriftDetector->>DriftDetector: classify files, sanitize/collapse paths
DriftDetector-->>DriftVerifier: {actionRequired, directive, affectedPaths, message}
DriftVerifier->>Config: read workflow.drift_action
Config-->>DriftVerifier: 'warn' or 'auto-remap'
alt action_required && directive == 'auto-remap'
DriftVerifier->>Mapper: spawn gsd-codebase-mapper --paths [affectedPaths]
Mapper->>StructureFile: update .planning/codebase/*.md
StructureFile->>StructureFile: write last_mapped_commit: HEAD
else action_required && directive == 'warn'
DriftVerifier-->>Client: warning with suggested /gsd-map-codebase --paths
end
DriftVerifier-->>ExecutePhase: result (non-blocking)
ExecutePhase->>Client: continue to verify_phase_goal
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
get-shit-done/bin/lib/drift.cjs (1)
349-355: Potential issue:writeMappedCommitthrows if file doesn't exist.Unlike
readMappedCommitwhich catches the exception and returnsnull,writeMappedCommitwill throw if the file doesn't exist (line 350'sreadFileSync). This is likely intentional since you'd only callwriteMappedCommitafter the mapper has produced the file, but the asymmetry is worth noting.If this is intentional (callers must ensure file exists), consider adding a brief comment. If it should be defensive, wrap in try/catch.
💡 Optional: Add defensive file existence check
function writeMappedCommit(filePath, commitSha, isoDate) { + if (!fs.existsSync(filePath)) { + // Create minimal file with frontmatter only + const data = { last_mapped_commit: commitSha }; + if (isoDate) data.last_mapped_at = isoDate; + fs.writeFileSync(filePath, serializeFrontmatter(data, '')); + return; + } const content = fs.readFileSync(filePath, 'utf8');🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@get-shit-done/bin/lib/drift.cjs` around lines 349 - 355, writeMappedCommit currently calls fs.readFileSync(filePath, 'utf8') and will throw if the file is missing, creating an asymmetry with readMappedCommit which returns null on missing files; either document the intended precondition or make it defensive by checking existence or catching ENOENT: update writeMappedCommit (and its callers if needed) so it first checks fs.existsSync(filePath) or wraps readFileSync in try/catch, return a sensible value or no-op (or rethrow with clearer message) when file is absent; reference the writeMappedCommit function and its use of fs.readFileSync, fs.writeFileSync, parseFrontmatter and serializeFrontmatter when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@agents/gsd-codebase-mapper.md`:
- Around line 101-103: The fenced code block containing the line "--paths
<p1>,<p2>,..." triggers MD040; add a language tag to the opening ``` fence (for
example "text") so the block becomes ```text and resolves the lint rule,
updating the block that contains the exact string "--paths <p1>,<p2>,..." in
agents/gsd-codebase-mapper.md.
In `@docs/CONFIGURATION.md`:
- Line 170: The docs row for workflow.drift_action uses colon-style command
tokens `/gsd:execute-phase` and `/gsd:map-codebase` which is inconsistent with
the rest of the guide; update those tokens to the standard space-separated
command format (e.g., `/gsd execute-phase` and `/gsd map-codebase --paths …`)
and keep them in inline code/backticks so the config key workflow.drift_action
and related workflow.drift_threshold text matches the repo's existing command
formatting.
In `@get-shit-done/workflows/execute-phase.md`:
- Around line 1296-1309: The two new fenced code blocks in
get-shit-done/workflows/execute-phase.md are missing language identifiers and
trigger markdownlint MD040; update both blocks by adding a language tag (use
"text") after the opening backticks—one for the block starting with "Codebase
drift detected: {N} structural element(s)..." and the other for the block
starting with "Task("—repeat the same fix for the second occurrence around lines
1316-1328.
- Line 1326: The auto-remap prompt references the undefined placeholder
AGENT_SKILLS_MAPPER which can leak into runtime; either define
AGENT_SKILLS_MAPPER (e.g., build it from the initialized AGENT_SKILLS
array/mapping) before the prompt or remove the placeholder from the template and
directly use AGENT_SKILLS; update the code that constructs the prompt to replace
"${AGENT_SKILLS_MAPPER}" with a real string (for example by adding a
mapper-building step that iterates AGENT_SKILLS and assigns the result to
AGENT_SKILLS_MAPPER) so the prompt never contains the literal placeholder.
In `@get-shit-done/workflows/map-codebase.md`:
- Around line 30-47: The workflow defines incremental-remap semantics in the
parse_paths_flag step but doesn't create a single normalized variable to
propagate validated paths to mappers; add a normalized shell variable (e.g.,
SCOPED_PATHS) that holds the validated comma-separated prefixes (or empty),
compute a PATH_SCOPE_HINT="--paths $SCOPED_PATHS" only when SCOPED_PATHS is
non-empty, and ensure every place that launches mappers (notably the
spawn_agents and sequential_mapping routines and any Task-mode prompt
construction that invokes gsd-codebase-mapper) appends ${PATH_SCOPE_HINT} into
the agent prompt/command so all spawned mappers receive the same deterministic
scope; keep the existing validation rules (reject .., leading /, shell
metacharacters) and preserve stamping behavior via
bin/lib/drift.cjs:writeMappedCommit.
---
Nitpick comments:
In `@get-shit-done/bin/lib/drift.cjs`:
- Around line 349-355: writeMappedCommit currently calls
fs.readFileSync(filePath, 'utf8') and will throw if the file is missing,
creating an asymmetry with readMappedCommit which returns null on missing files;
either document the intended precondition or make it defensive by checking
existence or catching ENOENT: update writeMappedCommit (and its callers if
needed) so it first checks fs.existsSync(filePath) or wraps readFileSync in
try/catch, return a sensible value or no-op (or rethrow with clearer message)
when file is absent; reference the writeMappedCommit function and its use of
fs.readFileSync, fs.writeFileSync, parseFrontmatter and serializeFrontmatter
when making the change.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8fe5d8c0-c253-422c-ae3e-c12595094a14
📒 Files selected for processing (18)
agents/gsd-codebase-mapper.mddocs/AGENTS.mddocs/ARCHITECTURE.mddocs/CONFIGURATION.mddocs/FEATURES.mddocs/INVENTORY-MANIFEST.jsondocs/INVENTORY.mddocs/USER-GUIDE.mdget-shit-done/bin/gsd-tools.cjsget-shit-done/bin/lib/config-schema.cjsget-shit-done/bin/lib/config.cjsget-shit-done/bin/lib/drift.cjsget-shit-done/bin/lib/verify.cjsget-shit-done/workflows/execute-phase.mdget-shit-done/workflows/map-codebase.mdsdk/src/query/index.tssdk/src/query/verify.tstests/drift-detection.test.cjs
| | `workflow.subagent_timeout` | number | `600` | Timeout in seconds for individual subagent invocations. Increase for long-running research or execution phases | | ||
| | `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/gsd-build/get-shit-done/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 | |
There was a problem hiding this comment.
Fix command name formatting in the new drift-action docs.
The new row uses : command syntax, which is inconsistent with the rest of the docs and likely to mislead copy/paste usage.
✏️ Suggested doc fix
-| `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_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 |📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| | `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_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 | |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@docs/CONFIGURATION.md` at line 170, The docs row for workflow.drift_action
uses colon-style command tokens `/gsd:execute-phase` and `/gsd:map-codebase`
which is inconsistent with the rest of the guide; update those tokens to the
standard space-separated command format (e.g., `/gsd execute-phase` and `/gsd
map-codebase --paths …`) and keep them in inline code/backticks so the config
key workflow.drift_action and related workflow.drift_threshold text matches the
repo's existing command formatting.
Comment 3127255172.
…Rabbit) Comment 3127255180. Matches the convention used by every other command reference in docs/CONFIGURATION.md.
Two CodeRabbit findings on the auto-remap branch of the drift gate:
- 3127255186 (must-fix): the mapper Task prompt referenced
${AGENT_SKILLS_MAPPER} but only AGENT_SKILLS (for gsd-executor) is
loaded at init_context (line 72). Without this fix the literal
placeholder string would leak into the spawned mapper's prompt.
Add an explicit gsd-sdk query agent-skills gsd-codebase-mapper step
right before the Task spawn.
- 3127255183: tag the warn-message and Task() fenced code blocks as
text to satisfy markdownlint MD040.
CodeRabbit (review id 4158286952, comment 3127255190) flagged that the
parse_paths_flag step defined incremental-remap semantics but did not
inject a normalized variable into the spawn_agents and sequential_mapping
mapper prompts, so incremental remap could silently regress to a
whole-repo scan.
- Define SCOPED_PATHS / PATH_SCOPE_HINT in parse_paths_flag.
- Inject ${PATH_SCOPE_HINT} into all four spawn_agents Task prompts.
- Document the same scope contract for sequential_mapping mode.
CodeRabbit (review id 4158286952, drift.cjs:349-355 nitpick) noted that readMappedCommit returns null on ENOENT but writeMappedCommit threw — an asymmetry that breaks first-time stamping of a freshly produced doc that the caller has not yet written. - Catch ENOENT on the read; treat absent file as empty content. - Add a regression test that calls writeMappedCommit on a non-existent path and asserts the file is created with correct frontmatter. Test was authored to fail before the fix (ENOENT) and passes after.
CodeRabbit review response (review id 4158286952)
Verification: |
…4811455435) PR #2604 was rebased onto main before #2605 (drift.cjs) merged. The pull_request CI runs against the merge ref (refs/pull/2604/merge), which now contains 28 .cjs files in get-shit-done/bin/lib/, but docs/INVENTORY.md headline still said "(27 shipped)". inventory-counts.test.cjs failed with: AssertionError: docs/INVENTORY.md "CLI Modules (27 shipped)" disagrees with get-shit-done/bin/lib/ file count (28) Rebased branch onto current origin/main (picks up drift.cjs row, which was already added by #2605) and bumped the headline to 28. Full suite: 5200/5200 pass.
…eview integrations (closes #2529) (#2604) * feat(#2529): /gsd-settings-integrations — third-party integrations command Adds /gsd-settings-integrations for configuring API keys, code-review CLI routing, and agent-skill injection. Distinct from /gsd-settings (workflow toggles) because these are connectivity, not pipeline shape. Three sections: - Search Integrations: brave_search / firecrawl / exa_search API keys, plus search_gitignored toggle. - Code Review CLI Routing: review.models.{claude,codex,gemini,opencode} shell-command strings. - Agent Skills Injection: agent_skills.<agent-type> free-text input, validated against [a-zA-Z0-9_-]+. Security: - New secrets.cjs module with ****<last-4> masking convention. - cmdConfigSet now masks value/previousValue in CLI output for secret keys. - Plaintext is written only to .planning/config.json; never echoed to stdout/stderr, never written to audit/log files by this flow. - Slug validators reject path separators, whitespace, shell metacharacters. Tests (tests/settings-integrations.test.cjs — 25 cases): - Artifact presence / frontmatter. - Field round-trips via gsd-tools config-set for all four search keys, review.models.<cli>, agent_skills.<agent-type>. - Config-merge safety: unrelated keys preserved across writes. - Masking: config-set output never contains plaintext sentinel. - Logging containment: plaintext secret sentinel appears only in config.json under .planning/, nowhere else on disk. - Negative: path-traversal, shell-metachar, and empty-slug rejected. - /gsd:settings workflow mentions /gsd:settings-integrations. Docs: - docs/COMMANDS.md: new command entry with security note. - docs/CONFIGURATION.md: integration settings section (keys, routing, skills injection) with masking documentation. - docs/CLI-TOOLS.md: reviewer CLI routing and secret-handling sections. - docs/INVENTORY.md + INVENTORY-MANIFEST.json regenerated. Closes #2529 * fix(#2529): mask secrets in config-get; address CodeRabbit review cmdConfigGet was emitting plaintext for brave_search/firecrawl/exa_search. Apply the same isSecretKey/maskSecret treatment used by config-set so the CLI surface never echoes raw API keys; plaintext still lives only in config.json on disk. Also addresses CodeRabbit review items in the same PR area: - #3127146188: config-get plaintext leak (root fix above) - #3127146211: rename test sentinels to concat-built markers so secret scanners stop flagging the test file. Behavior preserved. - #3127146207: add explicit 'text' language to fenced code blocks (MD040). - nitpick: unify masked-value wording in read_current legend ('****<last-4>' instead of '**** already set'). - nitpick: extend round-trip test to cover search_gitignored toggle. New regression test 'config-get masks secrets and never echoes plaintext' verifies the fix for all three secret keys. * docs(#2529): bump INVENTORY counts post-rebase (commands 84→85, workflows 82→83) * fix(test): bump CLI Modules count 27→28 after rebase onto main (CI #24811455435) PR #2604 was rebased onto main before #2605 (drift.cjs) merged. The pull_request CI runs against the merge ref (refs/pull/2604/merge), which now contains 28 .cjs files in get-shit-done/bin/lib/, but docs/INVENTORY.md headline still said "(27 shipped)". inventory-counts.test.cjs failed with: AssertionError: docs/INVENTORY.md "CLI Modules (27 shipped)" disagrees with get-shit-done/bin/lib/ file count (28) Rebased branch onto current origin/main (picks up drift.cjs row, which was already added by #2605) and bumped the headline to 28. Full suite: 5200/5200 pass.
…r progressive disclosure (closes #2551) (#2607) * refactor(workflows): extract discuss-phase modes/templates/advisor for progressive disclosure (closes #2551) Splits 1,347-line workflows/discuss-phase.md into a 495-line dispatcher plus per-mode files in workflows/discuss-phase/modes/ and templates in workflows/discuss-phase/templates/. Mirrors the progressive-disclosure pattern that #2361 enforced for agents. - Per-mode files: power, all, auto, chain, text, batch, analyze, default, advisor - Templates lazy-loaded at the step that produces the artifact (CONTEXT.md template at write_context, DISCUSSION-LOG.md template at git_commit, checkpoint.json schema when checkpointing) - Advisor mode gated behind `[ -f $HOME/.claude/get-shit-done/USER-PROFILE.md ]` — inverse of #2174's --advisor flag (don't pay the cost when unused) - scout_codebase phase-type→map selection table extracted to references/scout-codebase.md - New tests/workflow-size-budget.test.cjs enforces tiered budgets across all workflows/*.md (XL=1700 / LARGE=1500 / DEFAULT=1000) plus the explicit <500 ceiling for discuss-phase.md per #2551 - Existing tests updated to read from the new file locations after the split (functional equivalence preserved — content moved, not removed) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(#2607): align modes/auto.md check_existing with parent (Update it, not Skip) CodeRabbit flagged drift between the parent step (which auto-selects "Update it") and modes/auto.md (which documented "Skip"). The pre-refactor file had both — line 182 said "Skip" in the overview, line 250 said "Update it" in the actual step. The step is authoritative. Fix the new mode file to match. Refs: PR #2607 review comment 3127783430 * test(#2607): harden discuss-phase regression tests after #2551 split CodeRabbit identified four test smells where the split weakened coverage: - workflow-size-budget: assertion was unreachable (entered if-block on match, then asserted occurrences === 0 — always failed). Now unconditional. - bug-2549-2550-2552: bounded-read assertion checked concatenated source, so src.includes('3') was satisfied by unrelated content in scout-codebase.md (e.g., "3-5 most relevant files"). Now reads parent only with a stricter regex. Also asserts SCOUT_REF exists. - chain-flag-plan-phase: filter(existsSync) silently skipped a missing modes/chain.md. Now fails loudly via explicit asserts. - discuss-checkpoint: same silent-filter pattern across three sources. Now asserts each required path before reading. Refs: PR #2607 review comments 3127783457, 3127783452, plus nitpicks for chain-flag-plan-phase.test.cjs:21-24 and discuss-checkpoint.test.cjs:22-27 * docs(#2607): fix INVENTORY count, context.md placeholders, scout grep portability - INVENTORY.md: subdirectory note said "50 top-level references" but the section header now says 51. Updated to 51. - templates/context.md: footer hardcoded XX-name instead of declared placeholders [X]/[Name], which would leak sample text into generated CONTEXT.md files. Now uses the declared placeholders. - references/scout-codebase.md: no-maps fallback used grep -rl with "\\|" alternation (GNU grep only — silent on BSD/macOS grep). Switched to grep -rlE with extended regex for portability. Refs: PR #2607 review comments 3127783404, 3127783448, plus nitpick for scout-codebase.md:32-40 * docs(#2607): label fenced examples + clarify overlay/advisor precedence - analyze.md / text.md / default.md: add language tags (markdown/text) to fenced example blocks to silence markdownlint MD040 warnings flagged by CodeRabbit (one fence in analyze.md, two in text.md, five in default.md). - discuss-phase.md: document overlay stacking rules in discuss_areas — fixed outer→inner order --analyze → --batch → --text, with a pointer to each overlay file for mode-specific precedence. - advisor.md: add tie-breaker rules for NON_TECHNICAL_OWNER signals — explicit technical_background overrides inferred signals; otherwise OR-aggregate; contradictory explanation_depth values resolve by most-recent-wins. Refs: PR #2607 review comments 3127783415, 3127783437, plus nitpicks for default.md:24, discuss-phase.md:345-365, and advisor.md:51-56 * fix(#2607): extract codebase_drift_gate body to keep execute-phase under XL budget PR #2605 added 80 lines to execute-phase.md (1622 -> 1702), pushing it over the XL_BUDGET=1700 line cap enforced by tests/workflow-size-budget.test.cjs (introduced by this PR). Per the test's own remediation hint and #2551's progressive-disclosure pattern, extract the codebase_drift_gate step body to get-shit-done/workflows/execute-phase/steps/codebase-drift-gate.md and leave a brief pointer in the workflow. execute-phase.md is now 1633 lines. Budget is NOT relaxed; the offending workflow is tightened. --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #2003
Summary
Adds a post-execute structural drift detector. After the last wave of
/gsd:execute-phasecommits, the workflow compares the diff betweenlast_mapped_commit(stored in YAML frontmatter on each.planning/codebase/*.mdmap file) andHEADagainst the contents of.planning/codebase/STRUCTURE.md. When the number of new structuralelements meets the configured threshold, GSD either warns the user or
auto-remaps the affected subtrees.
Four drift categories, with priority ordering (
migration > route > barrel > new_dir) so each file is counted once at the most specificlevel:
(packages|apps)/<name>/src/index.*routes/orapi/Non-blocking by contract. Four layers (detector try/catch, CLI
try/catch, SDK handler try/catch, workflow
|| echofallback) ensure anyfailure path — missing STRUCTURE.md, non-git cwd, mapper spawn failure,
invalid config — returns
{ skipped: true }and the phase continues toverification. Drift detection cannot fail
/gsd:execute-phase.Changes
get-shit-done/bin/lib/drift.cjs(new). Pure:accepts parsed git-diff input + STRUCTURE.md content, returns a
structured result. Exports
classifyFile,detectDrift,chooseAffectedPaths,sanitizePaths,readMappedCommit,writeMappedCommit.verify codebase-driftsubcommand ingsd-tools.cjs,implemented by
cmdVerifyCodebaseDriftinverify.cjs.verifyCodebaseDrifthandler insdk/src/query/verify.ts,registered as
verify.codebase-driftinsdk/src/query/index.ts.Delegates to the CJS implementation to keep drift logic in one place.
workflow.drift_threshold(integer, default 3) andworkflow.drift_action(warn|auto-remap, defaultwarn) addedto
VALID_CONFIG_KEYSwith enum/integer validation inconfig-set.codebase_drift_gatestep inexecute-phase.mdbetween
schema_drift_gateandverify_phase_goal.--paths <p1,p2,...>scope hint documented in bothagents/gsd-codebase-mapper.mdandget-shit-done/workflows/map-codebase.md,with traversal / absolute / metacharacter rejection.
last_mapped_commitstored at the top ofeach
.planning/codebase/*.mdfile.Documentation
docs/CONFIGURATION.md— rows for both new keys (docs-parity passes).docs/ARCHITECTURE.md— post-execute drift gate section.docs/AGENTS.md—--pathsflag ongsd-codebase-mapper.docs/USER-GUIDE.md— behavior note + toggle commands.docs/FEATURES.md— new 27a section with REQ-DRIFT-01..06.docs/INVENTORY.md+docs/INVENTORY-MANIFEST.json—drift.cjslisted (count 26 → 27).
Tests
tests/drift-detection.test.cjs(new) — 55 tests: classification,threshold gating at 2/3/4 elements, warn vs. auto-remap routing,
affected-path scoping,
--pathssanitization, frontmatterround-trip, defensive paths (missing STRUCTURE.md, malformed input,
non-git repos), CLI JSON output, documentation parity.
Design decisions
last_mapped_commit: travelswith git moves, survives per-document regeneration, no separate file
lifecycle.
isPathMapped: the mapis free-form markdown, not a structured manifest. Cheap, no parser.
migration > route > barrel > new_dir: eachfile counts once at the most specific level.
4b825dc6…) whenlast_mapped_commitis absent: "no baseline means everything is drift," deterministic.
all wrap their logic so any single layer failing still produces a
valid skipped result.
gsd-tools.cjsrather than re-portingthe detector to TypeScript, keeping drift logic in one canonical
place.
Test plan
npm test— full suite greennode --test tests/drift-detection.test.cjs— 55 new tests pass.planning/codebase/STRUCTURE.md→gsd-sdk query verify.codebase-driftreturnsaction_required: falseimmediately after mappingaction_required: truewith
directive: warnworkflow.drift_action=auto-remap→ rerun → confirmspawn_mapper: trueandaffected_pathspopulatedconfig-set workflow.drift_action sometimesrejected;config-set workflow.drift_threshold manyrejected🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests
Chores