@danmoisan/drm-copilot-mcp 1.1.10 → 1.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/out/mcp-server.js +3517 -1104
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +1 -1
  4. package/resources/claude-customizations/.claude/agents/epic-planner.md +4 -1
  5. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +15 -2
  6. package/resources/claude-customizations/.claude/agents/parallel-planner.md +3 -0
  7. package/resources/claude-customizations/.claude/agents/pr-author.md +15 -0
  8. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-authorization.ps1 +443 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +82 -51
  10. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +52 -3
  11. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +120 -5
  12. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +127 -0
  13. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +163 -15
  14. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +21 -28
  15. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +103 -6
  16. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +124 -8
  17. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +171 -15
  18. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +50 -10
  19. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +3 -1
  20. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +332 -0
  21. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +211 -182
  22. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +35 -7
  23. package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +483 -0
  24. package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +483 -0
  25. package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +218 -6
  26. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +18 -75
  27. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +290 -0
  28. package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +415 -0
  29. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +1 -0
  30. package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMerge.psm1 +355 -0
  31. package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMergeGrammar.psm1 +318 -0
  32. package/resources/claude-customizations/.claude/lib/project-file-merge/Resolve-MergeableConflict.ps1 +229 -0
  33. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeReadiness.psm1 +176 -0
  34. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +369 -0
  35. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +392 -0
  36. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeResolution.psm1 +500 -0
  37. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeTargetResolution.psm1 +347 -0
  38. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +27 -0
  39. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +66 -2
  40. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +323 -16
  41. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +14 -1
  42. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +4 -0
  43. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +46 -1
  44. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +3 -1
  45. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +75 -8
  46. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +6 -0
  47. package/resources/claude-customizations/.claude/skills/powershell-orchestration-state-machine/SKILL.md +29 -1
  48. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +15 -0
  49. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +221 -0
  50. package/resources/claude-customizations/config/blast-radius.json +7 -0
  51. package/resources/claude-customizations/pack-manifests/core.json +16 -0
  52. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +13 -14
  53. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +41 -0
  54. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -0
  55. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +26 -0
  56. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  57. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +242 -3
  58. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +67 -12
  59. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +34 -8
  60. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +163 -15
  61. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -1
  62. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +34 -7
  63. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +483 -0
  64. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +483 -0
  65. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +130 -2
  66. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexDeployment.ps1 +87 -0
  67. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +96 -0
  68. package/resources/codex-and-agents-customizations/.codex/scripts/codex-routing-cli-common.ps1 +370 -0
  69. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-contract.ps1 +475 -0
  70. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-runtime.ps1 +445 -0
  71. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-persistence-runtime.ps1 +118 -0
  72. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-sandbox-preflight.ps1 +69 -0
  73. package/resources/codex-and-agents-customizations/.codex/scripts/launch-epic-child-wave.ps1 +471 -0
  74. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +280 -0
  75. package/resources/codex-and-agents-customizations/.codex/scripts/resume-epic-child.ps1 +263 -0
  76. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +2 -1
  77. package/resources/codex-and-agents-customizations/pack-manifests/core.json +23 -1
  78. package/resources/config/orchestration-handoff-registry.json +138 -0
  79. package/resources/config/orchestration-handoff.schema.json +472 -0
  80. package/resources/lib/codex-routing/CodexDeployment.psm1 +315 -0
  81. package/resources/lib/codex-routing/CodexTopology.psm1 +394 -0
  82. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +64 -0
@@ -17,6 +17,7 @@ allowed-tools:
17
17
  - "Bash(git diff *)"
18
18
  - "Bash(git branch -r*)"
19
19
  - "Bash(git worktree list*)"
20
+ - "Bash(git worktree remove *)"
20
21
  - "Bash(gh issue view *)"
21
22
  - mcp__drm-copilot__new_potential_bug_entry
22
23
  - mcp__drm-copilot__potential_to_issue
@@ -64,11 +65,83 @@ The script emits pipe-delimited, `LC_ALL=C`-ordered records, one per line:
64
65
  in `EQUIVALENT | CONTENT_ON_MAIN | EMPTY | UNIQUE | CONFLICT`. A `UNIQUE` COMMIT record
65
66
  is a cherry-pick candidate for editorial triage.
66
67
  - `WORKTREE|<path>|<branch-or-DETACHED>|<flags>` — worktree registrations.
68
+ - `WORKTREE|<path>|DETACHED|<state>|<flags>` — a detached-HEAD worktree registration,
69
+ classified on its own HEAD SHA. This five-field record replaces the four-field record
70
+ above for a detached registration; it is not emitted in addition to it. `state` is one
71
+ of `MERGED_CLEAN | MERGED_CONTENT_NEUTRAL | MERGED_EQUIVALENT | NOT_MERGED |
72
+ HAS_UNIQUE_RESIDUALS | PROTECTED_CURRENT | ANCESTRY_ERROR`, where `ANCESTRY_ERROR` is
73
+ the fail-closed verdict for a hard git failure at any rung of the classification. The
74
+ fifth field carries the porcelain flag set unchanged, so the `locked` and `prunable`
75
+ markers are preserved in the record.
67
76
  - `WARN|main-divergence|<local-sha>|<origin-sha>` — local `main` differs from
68
77
  `origin/main` (advisory; classification still runs).
69
78
  - `DIRTY|<worktree-path>|<status-porcelain-line>` — a dirty worktree that blocked
70
- removal.
79
+ removal. This record is apply mode only, and its three-field shape is unchanged by the
80
+ dirt classifier: the classifier adds the two records below rather than altering or
81
+ replacing this one.
82
+ - `DIRTFILE|<worktree-path>|<verdict>|<detail>|<xy>|<file-path>` — one record per
83
+ `git status --porcelain` entry in a dirty worktree, emitted by report mode in porcelain
84
+ order immediately after that worktree's `WORKTREE|` record. `verdict` is exactly one of
85
+ `DISPOSABLE_BUILD_ARTIFACT | DISPOSABLE_SESSION_ARTIFACT | CONTENT_ON_MAIN |
86
+ CONTENT_IN_HISTORY | STAGED_TREE_IS_COMMIT | UNIQUE`, and no other verdict token is ever
87
+ produced. `detail` carries the commit SHA that justifies the verdict for
88
+ `STAGED_TREE_IS_COMMIT` and `CONTENT_IN_HISTORY`, and is empty for the other four
89
+ verdicts. `xy` is the porcelain two-character status field, preserved verbatim including
90
+ its space. The file path is the last field, so a path containing a pipe character cannot
91
+ shift any later field. A classification read that fails maps its entry to `UNIQUE`: that
92
+ is the fail-closed direction, because an entry the tool could not classify is treated as
93
+ content that must be preserved. `CONTENT_ON_MAIN` is emitted for a tracked entry
94
+ only when `main` contains the path, so an entry whose content exists only as a staged
95
+ blob is reported `UNIQUE` rather than as content that is already on `main`. An entry
96
+ whose porcelain status shows content in both the index and the working tree is likewise
97
+ reported `UNIQUE`, because the rungs that could otherwise resolve it compare working-tree
98
+ content only and would leave the differing staged blob unaccounted for.
99
+ `DISPOSABLE_SESSION_ARTIFACT` matches three fixed
100
+ repository paths under `artifacts/`, is repository-agnostic, and cannot fire in a checkout
101
+ that gitignores `artifacts/` — which drm-copilot does at `.gitignore:6` — because the
102
+ status read never carries `--ignored`; the verdict is retained for consumer checkouts
103
+ where those paths are not ignored, and it must not be made reachable by adding
104
+ `--ignored`.
105
+ - `DIRTSUM|<worktree-path>|<aggregate>|<detail>` — exactly one record per dirty worktree,
106
+ emitted by report mode immediately after that worktree's `DIRTFILE|` records.
107
+ `aggregate` is `ALL_DISPOSABLE` if and only if the worktree has at least one status
108
+ entry and none of them is `UNIQUE`, and `HAS_UNIQUE` otherwise. `detail` carries the
109
+ first non-empty per-entry detail. A worktree with zero status entries emits neither this
110
+ record nor any `DIRTFILE|` record. Both records are read-only: an `ALL_DISPOSABLE`
111
+ aggregate unlocks nothing on its own, and clearing happens only when the operator
112
+ supplies `--clear-disposable` in apply mode.
113
+
114
+ Report-mode exit status. When a candidate worktree's `git status --porcelain` read
115
+ fails, report mode emits no `DIRTFILE|` and no `DIRTSUM|` record for that worktree and
116
+ returns git's non-zero exit code. A checkout containing such a worktree therefore exits
117
+ non-zero from report mode where the same checkout previously exited 0 and produced a
118
+ complete report. This is deliberate. A worktree whose status read failed produces no dirt
119
+ records at all, so a report that also exited 0 would be indistinguishable from a report
120
+ about a clean worktree, and an operator would make a deletion decision on silently
121
+ incomplete data. The non-zero exit is the only channel that carries the incompleteness to
122
+ a wrapping script. See the analogous apply-mode note in the End-to-End Workflow, where a
123
+ blocked detached removal sets a non-zero exit status for the same reason.
71
124
  - `ACTION|<verb>|<target>|<result>` — apply-mode action results.
125
+ - `PRESERVE|<worktree-path>|<source-path>|<verdict>` — a manifest `preserved_files[]`
126
+ finding staged onto the consolidation branch; `verdict` in `DEAD_ONE_OFF |
127
+ ALREADY_SOLVED_ELSEWHERE | STALE_OR_CONTRADICTED | GENUINELY_NEW | STILL_RELEVANT`. The
128
+ per-file outcome is reported by the companion `ACTION|preserve-stage|...` record.
129
+ - `ORPHAN_DIR|<path>|<size>` — a directory under a worktree-tracking root that carries
130
+ no `.git` pointer file and no `git worktree list` entry. `<size>` is best-effort and
131
+ may be the literal `unknown`. The record is advisory: it reports the directory, and
132
+ nothing in apply mode acts on it. For the disposition, see the Dirty Worktree Triage
133
+ Procedure's step 7, which governs how an orphaned directory is handled.
134
+ - `STALE_REF|<refname>` — a `refs/remotes/<name>/*` ref whose `<name>` is not a
135
+ configured remote, named in full ref form. Advisory only; no ref is ever pruned by
136
+ this tool.
137
+ - `CHILD_OF|<branch>|<ancestor>` — `branch` is a git ancestor of `ancestor`, and both
138
+ resolved exactly `NOT_MERGED` through their own full ladders. It is emitted alongside,
139
+ never instead of, the branch's own `BRANCH|<branch>|NOT_MERGED` line, and names the
140
+ containment relationship so an operator can see that the branch's work is not lost
141
+ when the named ancestor is retained.
142
+ - `WARN|registration-lost|<path>` — a worktree directory whose `.git` pointer file names
143
+ a gitdir target that no longer exists. Git commands run inside such a directory fail
144
+ in confusing ways. Advisory only.
72
145
 
73
146
  ## End-to-End Workflow
74
147
 
@@ -101,11 +174,31 @@ The script emits pipe-delimited, `LC_ALL=C`-ordered records, one per line:
101
174
  (`artifacts/orchestration/orchestrator-state.json`) with `--require-pr-creation-ready`
102
175
  and record the `pr_author_preflight` result; delegation is prohibited when that
103
176
  validation fails. Then delegate PR creation to `Agent(pr-author)` per
104
- `.claude/skills/pr-author/SKILL.md`, using `<N> = 396` for the body-file and receipt
105
- contract. This skill never authors or creates the PR itself.
106
-
107
- 5. **Wait for merge and verify git-natively.** After the consolidation PR merges,
108
- verify it with `git fetch` followed by
177
+ `.claude/skills/pr-author/SKILL.md`, which owns the body-file and receipt contract;
178
+ defer to that skill for it rather than restating any value from it here. For `<N>`,
179
+ use the GitHub issue number the run executes under when the run has one. When the run
180
+ has none, `<N>` is an arbitrary run-scoped identifier chosen by the pr-author agent.
181
+ The delegation prompt MUST carry a `branch: <name>` label naming the consolidation branch,
182
+ and the canonical issue number line when the run has an issue number.
183
+ `enforce-model-routing-receipt.ps1` identifies the item from those two lines and denies a
184
+ gated delegation it cannot identify. A cleanup run with no issue number is therefore
185
+ identified by the branch label alone, which is why the label is required and the issue line
186
+ is not.
187
+ It is not a pull-request number, and the only requirement on it is that the body-file
188
+ path, the receipt's `number` field, and the body bytes all agree. This skill never
189
+ authors or creates the PR itself.
190
+
191
+ 5. **Wait for merge and verify git-natively.** The consolidation PR's merge is
192
+ **human-performed**: the operator merges it outside the agent session; this skill
193
+ never issues the merge itself. This is why: `gh pr merge` is absent from this skill's
194
+ `allowed-tools`; the project permission allow-list (`permissions.allow` in
195
+ `.claude/settings.json`) carries no `gh` entry; and `.claude/hooks/enforce-epic-merge-gate.ps1`
196
+ would deny the command with `EPIC_MERGE_GATE_BLOCKED` if it were attempted. At this
197
+ boundary the agent reports the consolidation pull request's URL or number to the
198
+ operator and stops. The ruleset on `main` sets `strict_required_status_checks_policy`,
199
+ so the branch must be up to date with `main` before the merge becomes available to the
200
+ operator; the wait for the merge is unbounded within a session. After the consolidation
201
+ PR merges, verify it with `git fetch` followed by
109
202
  `git merge-base --is-ancestor documentationandmemories main`. Exit 0 confirms every
110
203
  consolidated commit is now reachable from `main`; that is the only state that unlocks
111
204
  deletion of branches whose unique content was consolidated.
@@ -119,6 +212,31 @@ The script emits pipe-delimited, `LC_ALL=C`-ordered records, one per line:
119
212
  branch classified `NOT_MERGED` or `HAS_UNIQUE_RESIDUALS` — is not abandoned; it moves
120
213
  to the Dirty Worktree Triage Procedure below.
121
214
 
215
+ A consolidation branch whose tip equals `main`, which is the state of
216
+ `documentationandmemories` between its creation off `main` and its first commit, is
217
+ reported `NOT_ANCESTOR` by a tip-equality pre-check that runs before any network fetch
218
+ and is therefore not delete-eligible; the post-merge cleanup described above is
219
+ unaffected, because a merged consolidation branch's tip differs from `main`, and an
220
+ empty or unresolvable tip on either side is reported `ANCESTRY_ERROR` rather than
221
+ treated as equality.
222
+
223
+ Detached-HEAD worktrees are handled on the same terms. Apply mode classifies each on
224
+ its own HEAD SHA, emits the five-field `WORKTREE|<path>|DETACHED|<state>|<flags>`
225
+ record from that verdict, and acts only on the delete-eligible states `MERGED_CLEAN`,
226
+ `MERGED_CONTENT_NEUTRAL`, and `MERGED_EQUIVALENT`. A delete-eligible detached worktree
227
+ is re-verified in the same process immediately before the destructive action and is
228
+ then removed without force. A locked one is skipped with the result token
229
+ `ACTION|worktree-remove|<path>|BLOCKED-LOCKED`; the locked test reads the porcelain
230
+ flags already in hand and precedes every git invocation, so no `git worktree remove`
231
+ command is issued for it. A prunable one is report-only: it produces its registration
232
+ record and no further line, with no removal and no prune. A dirty one reaches the same
233
+ non-forced removal path as a branch-backed worktree and is reported `BLOCKED-DIRTY`
234
+ with its accompanying `DIRTY|` lines.
235
+ In apply mode a blocked detached removal — `BLOCKED-DIRTY`, `BLOCKED-LOCKED`, or
236
+ `BLOCKED-REVERIFY` — sets a non-zero exit status, so a checkout holding dirty or locked
237
+ detached worktrees exits non-zero from `--apply` where the same checkout previously
238
+ exited 0.
239
+
122
240
  ## Nothing to Consolidate (Short Path)
123
241
 
124
242
  When report mode classifies every candidate as `MERGED_CLEAN` or `MERGED_EQUIVALENT`
@@ -134,7 +252,12 @@ accompanying `DIRTY|<path>|<status-porcelain-line>` records), or a branch classi
134
252
  script correctly refuses to discard. That refusal is correct and this procedure never
135
253
  overrides it — a dirty worktree is never force-removed. This procedure is the systematic
136
254
  follow-up: deciding, per worktree, whether that content is disposable or must be
137
- preserved before the worktree can ever be deleted.
255
+ preserved before the worktree can ever be deleted. Report mode now precedes this
256
+ procedure with a machine-readable first pass: one `DIRTFILE|` record per status entry
257
+ giving that entry's verdict, and one `DIRTSUM|` record per worktree giving the aggregate
258
+ `ALL_DISPOSABLE` or `HAS_UNIQUE`. Steps 1-9 below apply to the worktrees carrying at
259
+ least one `UNIQUE` verdict, which are exactly the ones the classifier could not establish
260
+ as disposable and which therefore still require human editorial judgement.
138
261
 
139
262
  Steps 1-7 are read-only investigation. Run them per worktree, or fan out one
140
263
  `Agent(general-purpose)` investigation per worktree (or small batch) concurrently per
@@ -185,7 +308,12 @@ specific files or commit SHAs, before step 9 acts on any finding.
185
308
  build artifacts (a modified `.csproj`/`packages.config`/`app.config` from a build run
186
309
  in that worktree) rather than documentation. Diff a representative sample against
187
310
  `main` (`git diff main -- <path>`) to characterize the change before deciding it is
188
- disposable.
311
+ disposable. The classifier scopes this class narrowly: it labels a project file
312
+ `DISPOSABLE_BUILD_ARTIFACT` only when every changed line in that entry's diff is an
313
+ analyzer `HintPath` rewrite. A project file whose diff touches anything else — a
314
+ `Compile Include` registration, a package reference, a target or property change — is
315
+ reported `UNIQUE` and is not disposable, so this step's manual characterization is
316
+ still required for it.
189
317
 
190
318
  7. **Recognize orphaned non-worktree directories.** A path can still exist on disk
191
319
  under a worktree-tracking folder after `git worktree remove` partially ran or
@@ -210,13 +338,32 @@ specific files or commit SHAs, before step 9 acts on any finding.
210
338
  `mcp__drm-copilot__potential_to_issue` per
211
339
  `.claude/skills/feature-promotion-lifecycle/SKILL.md`. For a `SAFE_TO_DELETE`
212
340
  verdict, discard the content as a distinct, individually confirmed manual action —
213
- clear the dirty working tree, or delete a disposable `NOT_MERGED`/
214
- `HAS_UNIQUE_RESIDUALS` branch directly. This is never automated: the script's
215
- classification ladder and apply-mode allowlist are never changed to accept these
216
- states, so a `--apply` run never deletes them on its own, before or after triage. If
217
- discarding the working-tree content changes the branch's classification (for example
218
- to content-neutral against `main`), a follow-up report/apply pass then picks it up
219
- through the normal deterministic path.
341
+ clear the dirty working tree, delete a disposable `NOT_MERGED`/`HAS_UNIQUE_RESIDUALS`
342
+ branch directly, or remove the worktree itself through a
343
+ manifest-authorized removal. A manifest-authorized removal is a single
344
+ `git worktree remove <path>` covering one worktree, issued as its own Bash tool call,
345
+ one call per worktree, and it is authorized only when the Sanctioned Removal Manifest
346
+ below carries a record for that exact path whose `removal_disposition` is
347
+ `SAFE_TO_DELETE` and whose `branch_state` is `NOT_MERGED` or `HAS_UNIQUE_RESIDUALS`.
348
+ Never pass a force flag to that command: a dirty worktree blocks deletion and is
349
+ reported for manual handling, and it is never force-removed. This is never automated:
350
+ the script's classification ladder and apply-mode allowlist are never changed to
351
+ accept these states, so a `--apply` run never deletes them on its own, before or
352
+ after triage. If discarding the working-tree content changes the branch's
353
+ classification (for example to content-neutral against `main`), a follow-up
354
+ report/apply pass then picks it up through the normal deterministic path.
355
+
356
+ Distinguish two different discards. The sentence above governs the editorial discard
357
+ of `UNIQUE` content — content a human judged safe to lose — and that discard stays
358
+ manual and individually confirmed. Separately, dirt the classifier established as
359
+ disposable, meaning a worktree whose `DIRTSUM|` aggregate is `ALL_DISPOSABLE`, can be
360
+ cleared by the tool itself under the opt-in `--clear-disposable` flag in apply mode.
361
+ That path never touches a `UNIQUE` verdict: one `UNIQUE` entry refuses the clear for
362
+ the whole worktree, and a classification read that errors produces a fail-closed
363
+ `UNIQUE` with the same effect. It is also not force-removal — it clears the working
364
+ tree and retries the same unforced `git worktree remove` after a fresh in-process
365
+ re-verification. The classification ladder and the apply-mode allowlist remain
366
+ unchanged by it.
220
367
 
221
368
  10. **After local branch deletion, check origin too.** This skill is local-only by
222
369
  design (see "When to Use This Skill"), which leaves stale branches on the remote for
@@ -228,13 +375,148 @@ specific files or commit SHAs, before step 9 acts on any finding.
228
375
  cleanup, and never rely on this skill's general `Bash(git push *)` allowance to
229
376
  perform it silently.
230
377
 
378
+ ## Sanctioned Removal Manifest
379
+
380
+ **Write the manifest before step 9 of the Dirty Worktree Triage Procedure acts on any
381
+ `SAFE_TO_DELETE` verdict.** The run records its triage verdicts in
382
+ `artifacts/orchestration/cleanup-worktrees-manifest.json`. The two PreToolUse gates
383
+ `.claude/hooks/enforce-epic-worktree-removal-gate.ps1` and
384
+ `.claude/hooks/enforce-parallel-worktree-removal-gate.ps1` read that document and admit a
385
+ `git worktree remove <path>` command only when a record in it covers that exact path. A removal
386
+ issued with no covering record is denied with each gate's existing, unchanged reason code.
387
+
388
+ The document is UTF-8 JSON with `snake_case` keys. It carries six top-level fields. Its `removals`
389
+ and `preserved_files` entries are sibling arrays and are never nested: removal records are keyed by
390
+ worktree path, preserve records by the pair of worktree path and file path, and the gates must
391
+ never traverse preserve data.
392
+
393
+ ### Top-level fields
394
+
395
+ - `tool` — string, required, exactly `cleanup-merged-worktrees`. The self-identifying
396
+ discriminator. Absent, non-string, or any other value and no record authorizes anything.
397
+ - `schema_version` — integer, required, exactly `1`. Absent, non-integer, or any other value fails
398
+ closed; a forward version is not accepted by silence.
399
+ - `generated_at` — string, required, an ISO-8601 UTC timestamp recording when the run wrote the
400
+ document. Absent, unparseable, in the future, or older than the 24-hour freshness bound fails
401
+ closed.
402
+ - `run_id` — string, required, non-empty. An opaque per-run identifier for audit correlation.
403
+ - `removals` — array, required, of removal records. It may be empty; absent, non-array, or empty
404
+ authorizes no removal.
405
+ - `preserved_files` — array, required, of preserve records. It may be empty. Neither gate ever
406
+ reads it, and its absence or malformation must not affect any gate decision.
407
+
408
+ ### `removals[]` record fields
409
+
410
+ - `worktree_path` — string, required, non-empty. The absolute path of the worktree to remove. A
411
+ record with it absent or empty is skipped.
412
+ - `branch` — string or `null`, key required. The worktree's branch, or `null` for a detached
413
+ worktree. An absent key means the record does not authorize; `null` is a valid, meaningful value.
414
+ - `branch_state` — string, required. The classification the report emitted for that branch: one of
415
+ `MERGED_CLEAN`, `MERGED_CONTENT_NEUTRAL`, `MERGED_EQUIVALENT`, `NOT_MERGED`,
416
+ `HAS_UNIQUE_RESIDUALS`, or `PROTECTED_CURRENT`. Only `NOT_MERGED` and `HAS_UNIQUE_RESIDUALS`
417
+ authorize a removal; every other member, and any value outside the vocabulary, does not.
418
+ - `removal_disposition` — string, required. The triage disposition that authorizes removal. The
419
+ allowed set is exactly the single member `SAFE_TO_DELETE`. This key is deliberately distinct from
420
+ the `merge_status` key the orchestration checkpoints carry.
421
+ - `verdict` — string, required. The step-5 content classification that produced the disposition:
422
+ one of `DEAD_ONE_OFF`, `ALREADY_SOLVED_ELSEWHERE`, `STALE_OR_CONTRADICTED`, `GENUINELY_NEW`, or
423
+ `STILL_RELEVANT`. Absent, out of vocabulary, or preserve-implying and the record does not
424
+ authorize.
425
+ - `evidence` — string, required, non-empty. The justification step 9 requires, citing specific
426
+ files or commit SHAs. Absent, empty, or whitespace-only and the record does not authorize.
427
+
428
+ ### `preserved_files[]` record fields
429
+
430
+ These records serve the consolidation consumer and are never read by either gate.
431
+
432
+ - `worktree_path` — string, required, non-empty. The worktree holding the file, and where the
433
+ consumer reads that worktree's `MEMORY.md`.
434
+ - `source_path` — string, required, repo-relative within that worktree. The file to preserve.
435
+ - `change_class` — string, required, either `untracked` or `modified`, recording whether staging
436
+ adds a new file or carries a working-tree modification.
437
+ - `disposition` — string, required, exactly `PRESERVE`.
438
+ - `verdict` — string, required, a member of the step-5 verdict vocabulary listed above.
439
+ - `target_path` — string, required, repo-relative on the consolidation branch. Required because the
440
+ same relative path may already exist on `main` with different content and a lesson file may be
441
+ re-namespaced; the consumer must not guess it.
442
+ - `memory_index_line` — string or `null`, key required. The source worktree's `MEMORY.md` index
443
+ line, or `null` when the file is not a memory entry.
444
+ - `line_ending` — string, required, one of `crlf`, `lf`, or `absent`. Advisory only: it records the
445
+ target file's existing convention, and the consumer re-derives and compares rather than trusting
446
+ it.
447
+ - `host_token_scan` — object, required, carrying a `result` member and a `pattern_set_id` member.
448
+ It records the scan outcome and the identifier of the pattern set used, never the patterns
449
+ themselves.
450
+ - `evidence` — string, required, non-empty.
451
+
452
+ ### Example
453
+
454
+ ```json
455
+ {
456
+ "tool": "cleanup-merged-worktrees",
457
+ "schema_version": 1,
458
+ "generated_at": "2026-09-07T03:40:00Z",
459
+ "run_id": "cleanup-2026-09-07T03-40-00Z-a47a5e33",
460
+ "removals": [
461
+ {
462
+ "worktree_path": "C:/Users/DanMoisan/repos/drm-copilot/.claude/worktrees/agent-0f1c2d",
463
+ "branch": "drm-copilot-wt-2026-08-14T09-02",
464
+ "branch_state": "HAS_UNIQUE_RESIDUALS",
465
+ "removal_disposition": "SAFE_TO_DELETE",
466
+ "verdict": "ALREADY_SOLVED_ELSEWHERE",
467
+ "evidence": "Unique residual commit 3f9a1c2 records the cleanup-worktrees ancestry error; main already fixes it at scripts/bash/cleanup_worktrees_lib.sh:214-231 under issue #612."
468
+ }
469
+ ],
470
+ "preserved_files": [
471
+ {
472
+ "worktree_path": "C:/Users/DanMoisan/repos/drm-copilot/.claude/worktrees/agent-91ee43",
473
+ "source_path": ".claude/agent-memory/general-purpose/hook-payload-anomaly.md",
474
+ "change_class": "untracked",
475
+ "disposition": "PRESERVE",
476
+ "verdict": "GENUINELY_NEW",
477
+ "target_path": ".claude/agent-memory/general-purpose/hook-payload-anomaly-envelope.md",
478
+ "memory_index_line": "- [Hook payload anomaly envelope](hook-payload-anomaly-envelope.md) - the deny path a malformed envelope takes",
479
+ "line_ending": "crlf",
480
+ "host_token_scan": {
481
+ "result": "clean",
482
+ "pattern_set_id": "child-f-host-tokens-v1"
483
+ },
484
+ "evidence": "No equivalent file on main under .claude/agent-memory/**; grep for 'payload anomaly' returns only this worktree."
485
+ }
486
+ ]
487
+ }
488
+ ```
489
+
490
+ ### Accepted residual
491
+
492
+ Both gates read the command text of the `Bash` tool call they are given. A removal routed
493
+ indirectly — for example by writing the command into a file and invoking that file with `bash
494
+ <file>` — presents command text the gates do not match, so the manifest requirement does not reach
495
+ it. This design does not close that indirection, and the requirement is recorded here as an
496
+ accepted residual rather than left implicit.
497
+
498
+ The manifest requirement is a policy-level integrity check, on the same terms
499
+ `.claude/hooks/enforce-pr-author-skill.ps1` records for its own receipt mechanism: it prevents
500
+ accidental bypass and requires a deliberate, documented act to circumvent. It is not a
501
+ cryptographic or security boundary, and it must not be described as tamper-proof. Routing a
502
+ removal through such an indirection in order to avoid the manifest requirement is a deliberate act
503
+ and is prohibited by this skill, which is the term the residual rests on.
504
+
231
505
  ## Prohibited Shortcuts
232
506
 
233
507
  - Never invoke `gh pr create` or `gh pr edit --body*` from this skill or the scripts. PR
234
508
  authoring is `Agent(pr-author)`'s exclusive responsibility and is enforced by the
235
509
  `enforce-pr-author-skill.ps1` PreToolUse hook.
236
510
  - Never pass a force flag to `git worktree remove`. A dirty worktree blocks deletion and
237
- is reported for manual handling; it is never force-removed.
511
+ is reported for manual handling; it is never force-removed. The `--clear-disposable`
512
+ flag is not an exception to this rule and is not force-removal: it clears the working
513
+ tree first and then retries the same unforced `git worktree remove`, so the removal it
514
+ retries is the identical unforced call that was blocked. It runs only when every
515
+ per-file verdict for that worktree is non-`UNIQUE`, only in apply mode, only when the
516
+ operator explicitly requests it, and only after a fresh in-process re-verification that
517
+ the branch is still delete-eligible. A single `UNIQUE` verdict refuses the clear for the
518
+ whole worktree, including the fail-closed `UNIQUE` assigned when a classification read
519
+ errors.
238
520
  - Never execute `git worktree prune`. Prunable registrations are report-only.
239
521
  - Never act on `NOT_MERGED`, `HAS_UNIQUE_RESIDUALS`, or `PROTECTED_CURRENT` candidates
240
522
  through the script or its apply-mode allowlist; `--apply` never mutates them, and the
@@ -243,12 +525,32 @@ specific files or commit SHAs, before step 9 acts on any finding.
243
525
  only a distinct, individually confirmed manual action outside that automated path for
244
526
  `NOT_MERGED`/`HAS_UNIQUE_RESIDUALS` — never a change to the classification ladder
245
527
  itself, and never for `PROTECTED_CURRENT`.
528
+ - Never delete the base branch `main` and never remove a worktree checked out on it.
529
+ `compute_protected` protects `main` by name in every checkout topology, so it
530
+ classifies `PROTECTED_CURRENT` even when no protected worktree has it checked out, and
531
+ `delete_candidate` refuses a deletion request for it with
532
+ `ACTION|delete|main|BLOCKED-PROTECTED-BASE` before any re-verification, worktree
533
+ removal, or branch deletion runs.
246
534
  - Never use commit-message text matching as a classification input, and never
247
535
  auto-resolve cherry-pick conflicts.
248
536
  - Never delete an origin branch, or run plain filesystem removal on an orphaned
249
537
  worktree-tracking directory, without explicit per-item user confirmation — both are
250
538
  outside this skill's pre-approved tool surface regardless of how the triage verdict
251
539
  came out.
540
+ - Never issue the consolidation merge command, and never write or edit an orchestration
541
+ checkpoint in order to satisfy `.claude/hooks/enforce-epic-merge-gate.ps1`. This
542
+ forecloses the specific evasion of writing an
543
+ `artifacts/orchestration/orchestrator-state.json` whose `epic_mode` and `step9_status`
544
+ fields the gate's child-feature accept shape would honour for any pull-request number.
545
+ - Never widen the definition of disposable dirt. The session-artifact list is a fixed
546
+ in-script array of exactly three paths with no configuration override, no environment
547
+ variable, and no per-repository extension point; adding a path to it is a change to the
548
+ classifier, not a setting. The build-artifact rule requires both conditions together —
549
+ the `*.csproj` path pattern and the content confinement that every changed line in the
550
+ entry's diff is an analyzer `HintPath` rewrite — and a project file whose diff touches
551
+ anything else is `UNIQUE`. Ignored files are never cleared, because they are never
552
+ classified: `git status --porcelain` is read without `--ignored`, so an ignored file
553
+ never becomes an entry, and `git clean` is never given `-x`, `-X`, or `-ff`.
252
554
 
253
555
  ## Cross-References
254
556
 
@@ -262,3 +564,8 @@ specific files or commit SHAs, before step 9 acts on any finding.
262
564
  - `.claude/skills/feature-promotion-lifecycle/SKILL.md` — the potential-entry-to-issue
263
565
  promotion path used by the Dirty Worktree Triage Procedure's step 9 for `PRESERVE`
264
566
  findings that describe unresolved product scope.
567
+ - `.claude/hooks/enforce-epic-merge-gate.ps1` — the PreToolUse gate on the consolidation
568
+ merge command, backed by orchestration checkpoints. A cleanup run satisfies none of its
569
+ three checkpoint shapes: it is neither a per-feature orchestration, nor an epic
570
+ integration, nor a parallel run, so it writes none of the three checkpoints the gate
571
+ reads.
@@ -170,6 +170,13 @@ frontmatter default — `opus` for these workers — which suppresses a `fable`
170
170
  MUST NOT hard-code `model=opus` in a way that overrides the resolved routing model, mirroring
171
171
  step 5 of `## Model Selection` in `.claude/skills/orchestrate/SKILL.md`.
172
172
 
173
+ Every such spawn of `Agent(pr-author)` also carries the canonical issue number line and a
174
+ `branch: <name>` label naming the branch checked out in the worktree the pull request is opened
175
+ from, per `## Issue Number Consistency` in `.claude/skills/orchestrate/SKILL.md`.
176
+ `enforce-model-routing-receipt.ps1` identifies the item from those two lines and denies a gated
177
+ delegation it cannot identify, so a spawn that omits both is refused rather than validated against
178
+ whichever checkpoint occupies the calling session's root.
179
+
173
180
  `route` is never an input to model selection; `route` remains file-count driven and governs only
174
181
  agents, skills, and MCP tools. A skill whose frontmatter `context` field holds the value `fork`
175
182
  inherits the parent model and ignores a model override, so model selection applies to agent
@@ -281,7 +288,9 @@ checkpoint JSON remains the durable, machine-authoritative source.
281
288
  `epic_feature_folder`, `epic_manifest_path` (which points at
282
289
  `docs/features/epics/<epic-slug>/epic.md`), `epic_status_doc_path`, `integration_branch`,
283
290
  `completed_steps`, `next_step`, `last_updated`, `current_wave`, `waves[]`, `features[]`,
284
- `epic_merge_pr`, and the three receipt arrays (`delegation_receipts[]`, `skill_receipts[]`,
291
+ `epic_merge_pr`, `epic_issue_num` (the epic-level issue `epic-planner` promoted),
292
+ `model_routing_receipts[]` (the routing receipt for each delegation `epic-orchestrator` itself
293
+ spawns), and the three receipt arrays (`delegation_receipts[]`, `skill_receipts[]`,
285
294
  `mcp_call_receipts[]`). The
286
295
  `merge_status` enum is: `not_started`, `worktree_created`, `pr_open`, `ci_green`,
287
296
  `merge_conflict`, `blocked_conflict_loop_limit`, `merged`, `worktree_removed`. The optional
@@ -298,6 +307,10 @@ Validate the checkpoint through the
298
307
  that same call at the completion gate. The validation is implemented in
299
308
  `scripts/dev_tools/validate_epic_orchestrator_state.py`.
300
309
 
310
+ **Checkpoint hygiene (issue #673).** The coordinating session never holds a per-feature checkpoint at its own root. Before the first child delegation of a run it moves any `artifacts/orchestration/orchestrator-state.json` at its root to `artifacts/orchestration/handoff/orchestrator-state.issue-<issue-num>.<yyyy-MM-ddTHH-mm>.json`, and writes none there for the rest of the run, because each item's checkpoint lives in that item's worktree. A gated call the coordinator issues on an item's behalf is resolved by the item's issue number and branch, never by the coordinator root.
311
+
312
+ **Integration-PR checkpoint shape (issue #663).** The integration-to-`main` pull request and its `Agent(pr-author)` delegation are gated against `artifacts/orchestration/epic-orchestrator-state.json`, and no per-feature checkpoint is written for them. Before `epic-orchestrator` opens that pull request, the epic checkpoint carries `route_id: "epic"`, `integration_branch`, `epic_issue_num`, and a `features[]` array in which every entry's `merge_status` is `merged` or `worktree_removed`. Before the `Agent(pr-author)` delegation, `epic-orchestrator` records the `pr-author` routing receipt in that file's `model_routing_receipts[]`. The pr-author gate, the epic base-branch check, the model-routing gate, and the preimplementation gate treat a call as epic scope when its branch signal (or, for a staging command or file edit, the worktree HEAD) equals `integration_branch`; the pull request base must then be `main`.
313
+
301
314
  ## Completion Requirements
302
315
 
303
316
  `epic-orchestrator` must not report completion until:
@@ -66,6 +66,10 @@ At manifest-authoring time child issues do not exist yet, so `issue_num` values
66
66
  placeholders and back-filled from each child's promotion receipt as preparation completes. The
67
67
  manifest is committed in final, resolved form before the kickoff artifact is written.
68
68
 
69
+ ## Epic-Level Issue Promotion
70
+
71
+ Issue #663. Before the first preparation-mode child delegation, `epic-planner` promotes one epic-level GitHub issue for the epic itself. It authors a potential entry under `docs/features/potential/` describing the epic and promotes it through `mcp__drm-copilot__potential_to_issue` with `promotion_type: epic`; `gh issue create` is not used. It records the resulting number as `epic_issue_num` in the epic planning checkpoint (`artifacts/orchestration/epic-planner-state.json`) and in the kickoff prompt artifact, and `epic-orchestrator` copies it into the epic checkpoint `artifacts/orchestration/epic-orchestrator-state.json` at its first checkpoint write. The integration-to-`main` pull request uses `epic_issue_num` as its canonical issue number.
72
+
69
73
  ## Complexity Assessment
70
74
 
71
75
  Assess each child feature's complexity band (`C1`-`C4`) using the `model_policy` scale and
@@ -33,6 +33,49 @@ On every invocation, the main session must:
33
33
  1. Read `artifacts/orchestration/orchestrator-state.json` to check for existing state.
34
34
  2. If a valid checkpoint exists with a matching objective, resume from the recorded `next_step`.
35
35
  3. If no checkpoint exists or the objective is new, begin the orchestration lifecycle from the start.
36
+ 4. **Checkpoint hygiene (issue #673).** When the checkpoint at `artifacts/orchestration/orchestrator-state.json` records an `issue-num` other than the item this invocation is about, move it to `artifacts/orchestration/handoff/orchestrator-state.issue-<issue-num>.<yyyy-MM-ddTHH-mm>.json` before writing a new checkpoint, rather than overwriting it or leaving it in place. Apply the same move when this session hands an item whose checkpoint it holds to another session or worktree. A checkpoint left at a session root is read by gates that resolve against that root, so a foreign one there is state a gate can answer from.
37
+
38
+ ## Prepared-State Portable Handoff Intake
39
+
40
+ Treat a portable prepared-state checkpoint as a request to resume the recorded
41
+ lifecycle, not as a new orchestration intake. Before any destination launch:
42
+
43
+ 1. Validate the envelope through the published, workspace-explicit handoff
44
+ authority. Require exact repository, workspace, branch lineage, issue,
45
+ feature folder, work mode, scheduler, capability, and transition bindings.
46
+ Supply the complete caller-controlled independent expected context on every
47
+ call to `resolve_orchestration_topology`, `resolve_provider_routing`, and
48
+ `transition_prepared_orchestration`: `expected_repository_id`,
49
+ `expected_workspace_root`, `expected_branch`, `expected_source_head_sha`,
50
+ `allowed_head_relationship`, `expected_issue_number`,
51
+ `expected_feature_folder`, `expected_work_mode`, `expected_plan_path`, and
52
+ `expected_plan_sha256`. Derive each value from the destination checkout and
53
+ the caller's own record. A call that omits a value, or that copies one from
54
+ the envelope under validation, is rejected before any service invocation.
55
+ 2. Prove the plan using only the independently expected repository-relative
56
+ path and raw-byte SHA-256 the caller supplied in `expected_plan_path` and
57
+ `expected_plan_sha256`. Do not rediscover, rename, or substitute a plan, and
58
+ do not read the plan identity from the envelope.
59
+ 3. Use `transition_prepared_orchestration` as the only authority that may
60
+ materialize a provider-native destination checkpoint. Dry run validates and
61
+ projects state without source archival or canonical-checkpoint replacement.
62
+ 4. Preserve the envelope's completed phases and reject replay before mutation.
63
+ Promotion, research, feature-document authoring, atomic planning, and
64
+ preflight remain complete when the source recorded them as complete.
65
+ 5. Keep source-provider model, reasoning, profile, topology, launch, worktree,
66
+ and receipt evidence opaque. Create destination routing evidence only for
67
+ the first new delegation after materialization.
68
+
69
+ When Claude is the destination, resolve its model through the existing Claude
70
+ model-routing procedure and launch with the existing `Agent(...)` and Claude
71
+ worktree mechanisms. Do not translate Codex deployment receipts into Claude
72
+ launch evidence. When Claude is the source, preserve Claude launch and
73
+ worktree evidence in the portable source expression and allow the Codex
74
+ destination to resolve its own topology and model. An ordinary handoff whose
75
+ recorded transition is `atomic_execution` resumes the exact approved plan at
76
+ that transition without invoking a parent scheduler. Any validation,
77
+ authority, plan, replay, or materialization failure returns the deterministic
78
+ blocked result before launch.
36
79
 
37
80
  ### Model-choice reconciliation on resume
38
81
 
@@ -213,10 +256,12 @@ A bounded loop consisting of five steps. The loop variable `remediation_pass` st
213
256
 
214
257
  The canonical issue number is derived once from the active feature folder name: extract the trailing integer from the folder base name (e.g., `2026-04-26-push-down-claude-customizations-162` yields `162`). Record as `issue_num` in the checkpoint.
215
258
 
216
- Every delegation prompt to `atomic-planner`, `atomic-executor`, and `feature-review` must include the line:
259
+ Every delegation prompt to a receipt-gated subagent type — `atomic-planner`, `atomic-executor`, `feature-review`, `task-researcher`, `prd-feature`, and `pr-author` — must include the line:
217
260
 
218
261
  > `Canonical issue number for this feature is <issue_num>. All artifact content, file paths, and cross-references must use this number.`
219
262
 
263
+ Each such prompt also carries a `branch: <name>` label naming the branch checked out in the item's worktree, written as the first `branch:` occurrence in the prompt. Before promotion assigns an issue number, the branch label alone is required. Two gates, `enforce-model-routing-receipt.ps1` and `enforce-prd-feature-before-planner.ps1`, identify the item from these two lines: they deny a gated delegation they cannot identify rather than checking it against whichever checkpoint occupies the calling session's root, and they never use a feature-folder path to choose the worktree, because a merged feature folder exists in every checkout branched from main.
264
+
220
265
  If a subagent artifact references a different issue number, the orchestrator rejects it, requests correction, and records the discrepancy under `artifact_errors` in the checkpoint.
221
266
 
222
267
  ## Step S9 — CI Green Gate
@@ -66,7 +66,9 @@ re-derivation is mandatory and is not an optimization to skip when the checkpoin
66
66
  (Join-Path $repoRoot '.claude/lib/blast-radius/BlastRadius.psm1') -Force -ErrorAction Stop`).
67
67
  Its two radius arguments are the two items' radius hashtables, not strings, and the third
68
68
  argument is the required parsed `config/blast-radius.json` mapping, which push-down publishes
69
- into the destination workspace. `conflicts(a, b, config)` in
69
+ into the destination workspace. That mapping's optional `mergeable_paths` list is read by
70
+ `Test-BlastRadiusConflict`, which contributes no `path_overlap` edge for a path matching it
71
+ while the path stays in the declared radius. `conflicts(a, b, config)` in
70
72
  `scripts/dev_tools/compute_blast_radius.py` (defined in
71
73
  `scripts/dev_tools/_blast_radius_conflicts.py`) remains the repository authority and the parity
72
74
  reference. Read the verdict from `$result['conflict']`; do not test the returned hashtable