@danmoisan/drm-copilot-mcp 1.1.9 → 1.1.11

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 (46) hide show
  1. package/out/mcp-server.js +3526 -1037
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +15 -2
  4. package/resources/claude-customizations/.claude/agents/parallel-planner.md +3 -0
  5. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +49 -14
  6. package/resources/claude-customizations/.claude/hooks/enforce-epic-worktree-removal-gate.ps1 +52 -3
  7. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +7 -1
  8. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +103 -6
  9. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +58 -3
  10. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +37 -5
  11. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +13 -2
  12. package/resources/claude-customizations/.claude/hooks/enforce-promotion-mcp-only.ps1 +35 -7
  13. package/resources/claude-customizations/.claude/hooks/hook-command-invocation.ps1 +483 -0
  14. package/resources/claude-customizations/.claude/hooks/hook-command-scanner.ps1 +483 -0
  15. package/resources/claude-customizations/.claude/hooks/validate-bash.ps1 +254 -5
  16. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +18 -75
  17. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConflict.psm1 +290 -0
  18. package/resources/claude-customizations/.claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1 +415 -0
  19. package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMerge.psm1 +355 -0
  20. package/resources/claude-customizations/.claude/lib/project-file-merge/ProjectFileMergeGrammar.psm1 +318 -0
  21. package/resources/claude-customizations/.claude/lib/project-file-merge/Resolve-MergeableConflict.ps1 +229 -0
  22. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +66 -2
  23. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +311 -16
  24. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +42 -0
  25. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +3 -1
  26. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +36 -2
  27. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +6 -0
  28. package/resources/claude-customizations/.claude/skills/powershell-orchestration-state-machine/SKILL.md +29 -1
  29. package/resources/claude-customizations/config/blast-radius.json +7 -0
  30. package/resources/claude-customizations/pack-manifests/core.json +7 -0
  31. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +41 -0
  32. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-state/SKILL.md +41 -0
  33. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +26 -0
  34. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  35. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +49 -3
  36. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +57 -12
  37. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +34 -8
  38. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -1
  39. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +34 -7
  40. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-invocation.ps1 +483 -0
  41. package/resources/codex-and-agents-customizations/.codex/hooks/hook-command-scanner.ps1 +483 -0
  42. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +130 -2
  43. package/resources/codex-and-agents-customizations/pack-manifests/core.json +6 -1
  44. package/resources/config/orchestration-handoff-registry.json +138 -0
  45. package/resources/config/orchestration-handoff.schema.json +472 -0
  46. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +38 -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,25 @@ 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
+ It is not a pull-request number, and the only requirement on it is that the body-file
182
+ path, the receipt's `number` field, and the body bytes all agree. This skill never
183
+ authors or creates the PR itself.
184
+
185
+ 5. **Wait for merge and verify git-natively.** The consolidation PR's merge is
186
+ **human-performed**: the operator merges it outside the agent session; this skill
187
+ never issues the merge itself. This is why: `gh pr merge` is absent from this skill's
188
+ `allowed-tools`; the project permission allow-list (`permissions.allow` in
189
+ `.claude/settings.json`) carries no `gh` entry; and `.claude/hooks/enforce-epic-merge-gate.ps1`
190
+ would deny the command with `EPIC_MERGE_GATE_BLOCKED` if it were attempted. At this
191
+ boundary the agent reports the consolidation pull request's URL or number to the
192
+ operator and stops. The ruleset on `main` sets `strict_required_status_checks_policy`,
193
+ so the branch must be up to date with `main` before the merge becomes available to the
194
+ operator; the wait for the merge is unbounded within a session. After the consolidation
195
+ PR merges, verify it with `git fetch` followed by
109
196
  `git merge-base --is-ancestor documentationandmemories main`. Exit 0 confirms every
110
197
  consolidated commit is now reachable from `main`; that is the only state that unlocks
111
198
  deletion of branches whose unique content was consolidated.
@@ -119,6 +206,31 @@ The script emits pipe-delimited, `LC_ALL=C`-ordered records, one per line:
119
206
  branch classified `NOT_MERGED` or `HAS_UNIQUE_RESIDUALS` — is not abandoned; it moves
120
207
  to the Dirty Worktree Triage Procedure below.
121
208
 
209
+ A consolidation branch whose tip equals `main`, which is the state of
210
+ `documentationandmemories` between its creation off `main` and its first commit, is
211
+ reported `NOT_ANCESTOR` by a tip-equality pre-check that runs before any network fetch
212
+ and is therefore not delete-eligible; the post-merge cleanup described above is
213
+ unaffected, because a merged consolidation branch's tip differs from `main`, and an
214
+ empty or unresolvable tip on either side is reported `ANCESTRY_ERROR` rather than
215
+ treated as equality.
216
+
217
+ Detached-HEAD worktrees are handled on the same terms. Apply mode classifies each on
218
+ its own HEAD SHA, emits the five-field `WORKTREE|<path>|DETACHED|<state>|<flags>`
219
+ record from that verdict, and acts only on the delete-eligible states `MERGED_CLEAN`,
220
+ `MERGED_CONTENT_NEUTRAL`, and `MERGED_EQUIVALENT`. A delete-eligible detached worktree
221
+ is re-verified in the same process immediately before the destructive action and is
222
+ then removed without force. A locked one is skipped with the result token
223
+ `ACTION|worktree-remove|<path>|BLOCKED-LOCKED`; the locked test reads the porcelain
224
+ flags already in hand and precedes every git invocation, so no `git worktree remove`
225
+ command is issued for it. A prunable one is report-only: it produces its registration
226
+ record and no further line, with no removal and no prune. A dirty one reaches the same
227
+ non-forced removal path as a branch-backed worktree and is reported `BLOCKED-DIRTY`
228
+ with its accompanying `DIRTY|` lines.
229
+ In apply mode a blocked detached removal — `BLOCKED-DIRTY`, `BLOCKED-LOCKED`, or
230
+ `BLOCKED-REVERIFY` — sets a non-zero exit status, so a checkout holding dirty or locked
231
+ detached worktrees exits non-zero from `--apply` where the same checkout previously
232
+ exited 0.
233
+
122
234
  ## Nothing to Consolidate (Short Path)
123
235
 
124
236
  When report mode classifies every candidate as `MERGED_CLEAN` or `MERGED_EQUIVALENT`
@@ -134,7 +246,12 @@ accompanying `DIRTY|<path>|<status-porcelain-line>` records), or a branch classi
134
246
  script correctly refuses to discard. That refusal is correct and this procedure never
135
247
  overrides it — a dirty worktree is never force-removed. This procedure is the systematic
136
248
  follow-up: deciding, per worktree, whether that content is disposable or must be
137
- preserved before the worktree can ever be deleted.
249
+ preserved before the worktree can ever be deleted. Report mode now precedes this
250
+ procedure with a machine-readable first pass: one `DIRTFILE|` record per status entry
251
+ giving that entry's verdict, and one `DIRTSUM|` record per worktree giving the aggregate
252
+ `ALL_DISPOSABLE` or `HAS_UNIQUE`. Steps 1-9 below apply to the worktrees carrying at
253
+ least one `UNIQUE` verdict, which are exactly the ones the classifier could not establish
254
+ as disposable and which therefore still require human editorial judgement.
138
255
 
139
256
  Steps 1-7 are read-only investigation. Run them per worktree, or fan out one
140
257
  `Agent(general-purpose)` investigation per worktree (or small batch) concurrently per
@@ -185,7 +302,12 @@ specific files or commit SHAs, before step 9 acts on any finding.
185
302
  build artifacts (a modified `.csproj`/`packages.config`/`app.config` from a build run
186
303
  in that worktree) rather than documentation. Diff a representative sample against
187
304
  `main` (`git diff main -- <path>`) to characterize the change before deciding it is
188
- disposable.
305
+ disposable. The classifier scopes this class narrowly: it labels a project file
306
+ `DISPOSABLE_BUILD_ARTIFACT` only when every changed line in that entry's diff is an
307
+ analyzer `HintPath` rewrite. A project file whose diff touches anything else — a
308
+ `Compile Include` registration, a package reference, a target or property change — is
309
+ reported `UNIQUE` and is not disposable, so this step's manual characterization is
310
+ still required for it.
189
311
 
190
312
  7. **Recognize orphaned non-worktree directories.** A path can still exist on disk
191
313
  under a worktree-tracking folder after `git worktree remove` partially ran or
@@ -210,13 +332,32 @@ specific files or commit SHAs, before step 9 acts on any finding.
210
332
  `mcp__drm-copilot__potential_to_issue` per
211
333
  `.claude/skills/feature-promotion-lifecycle/SKILL.md`. For a `SAFE_TO_DELETE`
212
334
  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.
335
+ clear the dirty working tree, delete a disposable `NOT_MERGED`/`HAS_UNIQUE_RESIDUALS`
336
+ branch directly, or remove the worktree itself through a
337
+ manifest-authorized removal. A manifest-authorized removal is a single
338
+ `git worktree remove <path>` covering one worktree, issued as its own Bash tool call,
339
+ one call per worktree, and it is authorized only when the Sanctioned Removal Manifest
340
+ below carries a record for that exact path whose `removal_disposition` is
341
+ `SAFE_TO_DELETE` and whose `branch_state` is `NOT_MERGED` or `HAS_UNIQUE_RESIDUALS`.
342
+ Never pass a force flag to that command: a dirty worktree blocks deletion and is
343
+ reported for manual handling, and it is never force-removed. This is never automated:
344
+ the script's classification ladder and apply-mode allowlist are never changed to
345
+ accept these states, so a `--apply` run never deletes them on its own, before or
346
+ after triage. If discarding the working-tree content changes the branch's
347
+ classification (for example to content-neutral against `main`), a follow-up
348
+ report/apply pass then picks it up through the normal deterministic path.
349
+
350
+ Distinguish two different discards. The sentence above governs the editorial discard
351
+ of `UNIQUE` content — content a human judged safe to lose — and that discard stays
352
+ manual and individually confirmed. Separately, dirt the classifier established as
353
+ disposable, meaning a worktree whose `DIRTSUM|` aggregate is `ALL_DISPOSABLE`, can be
354
+ cleared by the tool itself under the opt-in `--clear-disposable` flag in apply mode.
355
+ That path never touches a `UNIQUE` verdict: one `UNIQUE` entry refuses the clear for
356
+ the whole worktree, and a classification read that errors produces a fail-closed
357
+ `UNIQUE` with the same effect. It is also not force-removal — it clears the working
358
+ tree and retries the same unforced `git worktree remove` after a fresh in-process
359
+ re-verification. The classification ladder and the apply-mode allowlist remain
360
+ unchanged by it.
220
361
 
221
362
  10. **After local branch deletion, check origin too.** This skill is local-only by
222
363
  design (see "When to Use This Skill"), which leaves stale branches on the remote for
@@ -228,13 +369,148 @@ specific files or commit SHAs, before step 9 acts on any finding.
228
369
  cleanup, and never rely on this skill's general `Bash(git push *)` allowance to
229
370
  perform it silently.
230
371
 
372
+ ## Sanctioned Removal Manifest
373
+
374
+ **Write the manifest before step 9 of the Dirty Worktree Triage Procedure acts on any
375
+ `SAFE_TO_DELETE` verdict.** The run records its triage verdicts in
376
+ `artifacts/orchestration/cleanup-worktrees-manifest.json`. The two PreToolUse gates
377
+ `.claude/hooks/enforce-epic-worktree-removal-gate.ps1` and
378
+ `.claude/hooks/enforce-parallel-worktree-removal-gate.ps1` read that document and admit a
379
+ `git worktree remove <path>` command only when a record in it covers that exact path. A removal
380
+ issued with no covering record is denied with each gate's existing, unchanged reason code.
381
+
382
+ The document is UTF-8 JSON with `snake_case` keys. It carries six top-level fields. Its `removals`
383
+ and `preserved_files` entries are sibling arrays and are never nested: removal records are keyed by
384
+ worktree path, preserve records by the pair of worktree path and file path, and the gates must
385
+ never traverse preserve data.
386
+
387
+ ### Top-level fields
388
+
389
+ - `tool` — string, required, exactly `cleanup-merged-worktrees`. The self-identifying
390
+ discriminator. Absent, non-string, or any other value and no record authorizes anything.
391
+ - `schema_version` — integer, required, exactly `1`. Absent, non-integer, or any other value fails
392
+ closed; a forward version is not accepted by silence.
393
+ - `generated_at` — string, required, an ISO-8601 UTC timestamp recording when the run wrote the
394
+ document. Absent, unparseable, in the future, or older than the 24-hour freshness bound fails
395
+ closed.
396
+ - `run_id` — string, required, non-empty. An opaque per-run identifier for audit correlation.
397
+ - `removals` — array, required, of removal records. It may be empty; absent, non-array, or empty
398
+ authorizes no removal.
399
+ - `preserved_files` — array, required, of preserve records. It may be empty. Neither gate ever
400
+ reads it, and its absence or malformation must not affect any gate decision.
401
+
402
+ ### `removals[]` record fields
403
+
404
+ - `worktree_path` — string, required, non-empty. The absolute path of the worktree to remove. A
405
+ record with it absent or empty is skipped.
406
+ - `branch` — string or `null`, key required. The worktree's branch, or `null` for a detached
407
+ worktree. An absent key means the record does not authorize; `null` is a valid, meaningful value.
408
+ - `branch_state` — string, required. The classification the report emitted for that branch: one of
409
+ `MERGED_CLEAN`, `MERGED_CONTENT_NEUTRAL`, `MERGED_EQUIVALENT`, `NOT_MERGED`,
410
+ `HAS_UNIQUE_RESIDUALS`, or `PROTECTED_CURRENT`. Only `NOT_MERGED` and `HAS_UNIQUE_RESIDUALS`
411
+ authorize a removal; every other member, and any value outside the vocabulary, does not.
412
+ - `removal_disposition` — string, required. The triage disposition that authorizes removal. The
413
+ allowed set is exactly the single member `SAFE_TO_DELETE`. This key is deliberately distinct from
414
+ the `merge_status` key the orchestration checkpoints carry.
415
+ - `verdict` — string, required. The step-5 content classification that produced the disposition:
416
+ one of `DEAD_ONE_OFF`, `ALREADY_SOLVED_ELSEWHERE`, `STALE_OR_CONTRADICTED`, `GENUINELY_NEW`, or
417
+ `STILL_RELEVANT`. Absent, out of vocabulary, or preserve-implying and the record does not
418
+ authorize.
419
+ - `evidence` — string, required, non-empty. The justification step 9 requires, citing specific
420
+ files or commit SHAs. Absent, empty, or whitespace-only and the record does not authorize.
421
+
422
+ ### `preserved_files[]` record fields
423
+
424
+ These records serve the consolidation consumer and are never read by either gate.
425
+
426
+ - `worktree_path` — string, required, non-empty. The worktree holding the file, and where the
427
+ consumer reads that worktree's `MEMORY.md`.
428
+ - `source_path` — string, required, repo-relative within that worktree. The file to preserve.
429
+ - `change_class` — string, required, either `untracked` or `modified`, recording whether staging
430
+ adds a new file or carries a working-tree modification.
431
+ - `disposition` — string, required, exactly `PRESERVE`.
432
+ - `verdict` — string, required, a member of the step-5 verdict vocabulary listed above.
433
+ - `target_path` — string, required, repo-relative on the consolidation branch. Required because the
434
+ same relative path may already exist on `main` with different content and a lesson file may be
435
+ re-namespaced; the consumer must not guess it.
436
+ - `memory_index_line` — string or `null`, key required. The source worktree's `MEMORY.md` index
437
+ line, or `null` when the file is not a memory entry.
438
+ - `line_ending` — string, required, one of `crlf`, `lf`, or `absent`. Advisory only: it records the
439
+ target file's existing convention, and the consumer re-derives and compares rather than trusting
440
+ it.
441
+ - `host_token_scan` — object, required, carrying a `result` member and a `pattern_set_id` member.
442
+ It records the scan outcome and the identifier of the pattern set used, never the patterns
443
+ themselves.
444
+ - `evidence` — string, required, non-empty.
445
+
446
+ ### Example
447
+
448
+ ```json
449
+ {
450
+ "tool": "cleanup-merged-worktrees",
451
+ "schema_version": 1,
452
+ "generated_at": "2026-09-07T03:40:00Z",
453
+ "run_id": "cleanup-2026-09-07T03-40-00Z-a47a5e33",
454
+ "removals": [
455
+ {
456
+ "worktree_path": "C:/Users/DanMoisan/repos/drm-copilot/.claude/worktrees/agent-0f1c2d",
457
+ "branch": "drm-copilot-wt-2026-08-14T09-02",
458
+ "branch_state": "HAS_UNIQUE_RESIDUALS",
459
+ "removal_disposition": "SAFE_TO_DELETE",
460
+ "verdict": "ALREADY_SOLVED_ELSEWHERE",
461
+ "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."
462
+ }
463
+ ],
464
+ "preserved_files": [
465
+ {
466
+ "worktree_path": "C:/Users/DanMoisan/repos/drm-copilot/.claude/worktrees/agent-91ee43",
467
+ "source_path": ".claude/agent-memory/general-purpose/hook-payload-anomaly.md",
468
+ "change_class": "untracked",
469
+ "disposition": "PRESERVE",
470
+ "verdict": "GENUINELY_NEW",
471
+ "target_path": ".claude/agent-memory/general-purpose/hook-payload-anomaly-envelope.md",
472
+ "memory_index_line": "- [Hook payload anomaly envelope](hook-payload-anomaly-envelope.md) - the deny path a malformed envelope takes",
473
+ "line_ending": "crlf",
474
+ "host_token_scan": {
475
+ "result": "clean",
476
+ "pattern_set_id": "child-f-host-tokens-v1"
477
+ },
478
+ "evidence": "No equivalent file on main under .claude/agent-memory/**; grep for 'payload anomaly' returns only this worktree."
479
+ }
480
+ ]
481
+ }
482
+ ```
483
+
484
+ ### Accepted residual
485
+
486
+ Both gates read the command text of the `Bash` tool call they are given. A removal routed
487
+ indirectly — for example by writing the command into a file and invoking that file with `bash
488
+ <file>` — presents command text the gates do not match, so the manifest requirement does not reach
489
+ it. This design does not close that indirection, and the requirement is recorded here as an
490
+ accepted residual rather than left implicit.
491
+
492
+ The manifest requirement is a policy-level integrity check, on the same terms
493
+ `.claude/hooks/enforce-pr-author-skill.ps1` records for its own receipt mechanism: it prevents
494
+ accidental bypass and requires a deliberate, documented act to circumvent. It is not a
495
+ cryptographic or security boundary, and it must not be described as tamper-proof. Routing a
496
+ removal through such an indirection in order to avoid the manifest requirement is a deliberate act
497
+ and is prohibited by this skill, which is the term the residual rests on.
498
+
231
499
  ## Prohibited Shortcuts
232
500
 
233
501
  - Never invoke `gh pr create` or `gh pr edit --body*` from this skill or the scripts. PR
234
502
  authoring is `Agent(pr-author)`'s exclusive responsibility and is enforced by the
235
503
  `enforce-pr-author-skill.ps1` PreToolUse hook.
236
504
  - 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.
505
+ is reported for manual handling; it is never force-removed. The `--clear-disposable`
506
+ flag is not an exception to this rule and is not force-removal: it clears the working
507
+ tree first and then retries the same unforced `git worktree remove`, so the removal it
508
+ retries is the identical unforced call that was blocked. It runs only when every
509
+ per-file verdict for that worktree is non-`UNIQUE`, only in apply mode, only when the
510
+ operator explicitly requests it, and only after a fresh in-process re-verification that
511
+ the branch is still delete-eligible. A single `UNIQUE` verdict refuses the clear for the
512
+ whole worktree, including the fail-closed `UNIQUE` assigned when a classification read
513
+ errors.
238
514
  - Never execute `git worktree prune`. Prunable registrations are report-only.
239
515
  - Never act on `NOT_MERGED`, `HAS_UNIQUE_RESIDUALS`, or `PROTECTED_CURRENT` candidates
240
516
  through the script or its apply-mode allowlist; `--apply` never mutates them, and the
@@ -249,6 +525,20 @@ specific files or commit SHAs, before step 9 acts on any finding.
249
525
  worktree-tracking directory, without explicit per-item user confirmation — both are
250
526
  outside this skill's pre-approved tool surface regardless of how the triage verdict
251
527
  came out.
528
+ - Never issue the consolidation merge command, and never write or edit an orchestration
529
+ checkpoint in order to satisfy `.claude/hooks/enforce-epic-merge-gate.ps1`. This
530
+ forecloses the specific evasion of writing an
531
+ `artifacts/orchestration/orchestrator-state.json` whose `epic_mode` and `step9_status`
532
+ fields the gate's child-feature accept shape would honour for any pull-request number.
533
+ - Never widen the definition of disposable dirt. The session-artifact list is a fixed
534
+ in-script array of exactly three paths with no configuration override, no environment
535
+ variable, and no per-repository extension point; adding a path to it is a change to the
536
+ classifier, not a setting. The build-artifact rule requires both conditions together —
537
+ the `*.csproj` path pattern and the content confinement that every changed line in the
538
+ entry's diff is an analyzer `HintPath` rewrite — and a project file whose diff touches
539
+ anything else is `UNIQUE`. Ignored files are never cleared, because they are never
540
+ classified: `git status --porcelain` is read without `--ignored`, so an ignored file
541
+ never becomes an entry, and `git clean` is never given `-x`, `-X`, or `-ff`.
252
542
 
253
543
  ## Cross-References
254
544
 
@@ -262,3 +552,8 @@ specific files or commit SHAs, before step 9 acts on any finding.
262
552
  - `.claude/skills/feature-promotion-lifecycle/SKILL.md` — the potential-entry-to-issue
263
553
  promotion path used by the Dirty Worktree Triage Procedure's step 9 for `PRESERVE`
264
554
  findings that describe unresolved product scope.
555
+ - `.claude/hooks/enforce-epic-merge-gate.ps1` — the PreToolUse gate on the consolidation
556
+ merge command, backed by orchestration checkpoints. A cleanup run satisfies none of its
557
+ three checkpoint shapes: it is neither a per-feature orchestration, nor an epic
558
+ integration, nor a parallel run, so it writes none of the three checkpoints the gate
559
+ reads.
@@ -34,6 +34,48 @@ On every invocation, the main session must:
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
36
 
37
+ ## Prepared-State Portable Handoff Intake
38
+
39
+ Treat a portable prepared-state checkpoint as a request to resume the recorded
40
+ lifecycle, not as a new orchestration intake. Before any destination launch:
41
+
42
+ 1. Validate the envelope through the published, workspace-explicit handoff
43
+ authority. Require exact repository, workspace, branch lineage, issue,
44
+ feature folder, work mode, scheduler, capability, and transition bindings.
45
+ Supply the complete caller-controlled independent expected context on every
46
+ call to `resolve_orchestration_topology`, `resolve_provider_routing`, and
47
+ `transition_prepared_orchestration`: `expected_repository_id`,
48
+ `expected_workspace_root`, `expected_branch`, `expected_source_head_sha`,
49
+ `allowed_head_relationship`, `expected_issue_number`,
50
+ `expected_feature_folder`, `expected_work_mode`, `expected_plan_path`, and
51
+ `expected_plan_sha256`. Derive each value from the destination checkout and
52
+ the caller's own record. A call that omits a value, or that copies one from
53
+ the envelope under validation, is rejected before any service invocation.
54
+ 2. Prove the plan using only the independently expected repository-relative
55
+ path and raw-byte SHA-256 the caller supplied in `expected_plan_path` and
56
+ `expected_plan_sha256`. Do not rediscover, rename, or substitute a plan, and
57
+ do not read the plan identity from the envelope.
58
+ 3. Use `transition_prepared_orchestration` as the only authority that may
59
+ materialize a provider-native destination checkpoint. Dry run validates and
60
+ projects state without source archival or canonical-checkpoint replacement.
61
+ 4. Preserve the envelope's completed phases and reject replay before mutation.
62
+ Promotion, research, feature-document authoring, atomic planning, and
63
+ preflight remain complete when the source recorded them as complete.
64
+ 5. Keep source-provider model, reasoning, profile, topology, launch, worktree,
65
+ and receipt evidence opaque. Create destination routing evidence only for
66
+ the first new delegation after materialization.
67
+
68
+ When Claude is the destination, resolve its model through the existing Claude
69
+ model-routing procedure and launch with the existing `Agent(...)` and Claude
70
+ worktree mechanisms. Do not translate Codex deployment receipts into Claude
71
+ launch evidence. When Claude is the source, preserve Claude launch and
72
+ worktree evidence in the portable source expression and allow the Codex
73
+ destination to resolve its own topology and model. An ordinary handoff whose
74
+ recorded transition is `atomic_execution` resumes the exact approved plan at
75
+ that transition without invoking a parent scheduler. Any validation,
76
+ authority, plan, replay, or materialization failure returns the deterministic
77
+ blocked result before launch.
78
+
37
79
  ### Model-choice reconciliation on resume
38
80
 
39
81
  Because model selection is required once delegation occurs (see `## Model Selection`), a resuming orchestrator must repair a missing model choice deterministically before delegating at a delegating `next_step`. When the resumed `next_step` is a delegating step:
@@ -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
@@ -344,6 +344,29 @@ Remediation is child-owned and parent-initiated. The conflict is always between
344
344
  branch and `origin/main`; there is no integration branch and therefore no fan-in conflict path on
345
345
  this surface.
346
346
 
347
+ Project-file conflicts are resolved by the parent first (issue #643); the numbered steps below are
348
+ the escalation path.
349
+
350
+ - (a) On a conflicted `gh pr merge --merge`, the parent runs the two commands
351
+ `git -C <worktree_path> fetch origin main` and
352
+ `git -C <worktree_path> merge --no-commit origin/main`.
353
+ - (b) The parent then runs
354
+ `pwsh -NoProfile -File .claude/lib/project-file-merge/Resolve-MergeableConflict.ps1 -Worktree <worktree_path>`
355
+ and parses its single JSON object. A `result` of `escalate` makes the parent run
356
+ `git -C <worktree_path> merge --abort`
357
+ and continue with step 1 below, including `escalate_paths` in the Blocking finding.
358
+ - (c) On `resolved` the parent stages and commits the resolved paths with a message body listing
359
+ every entry added from each side and every version choice, then runs the three commands
360
+ `dotnet tool restore --tool-manifest <worktree_path>/.config/dotnet-tools.json`,
361
+ `dotnet csharpier check <worktree_path>`, and
362
+ `dotnet build <worktree_path>/<solution>`, each with path arguments rather than `cd`.
363
+ A failing check reverts with
364
+ `git -C <worktree_path> reset --hard HEAD~1`
365
+ and escalates with the tool output as the finding.
366
+ - (d) On success the parent pushes the item branch, records `mergeable_conflicts_resolved` on the
367
+ item, sets `merge_status: pr_open`, regenerates `parallel-status.md`, and re-enters
368
+ `## Per-Item Merge to Main (Merge-on-Green)` at the durable `gh pr checks` confirmation step.
369
+
347
370
  1. On a conflicted `gh pr merge --merge`, the parent detects the failure and re-delegates that item's
348
371
  child orchestration, passing the conflict signal and the instruction to resolve against
349
372
  `origin/main`. The conflict capture and the finding write both belong to the child's
@@ -427,6 +450,14 @@ whose rows appear only once F6 populates that array; section `## Drift Events` p
427
450
  `drift_events[]`, which only F8 populates. An empty array renders an empty section rather than an
428
451
  omitted one.
429
452
 
453
+ The projection's `## Mergeable Conflicts Resolved` heading renders
454
+ `items[].mergeable_conflicts_resolved`: for each entry the path, the `resolved_at` stamp, the
455
+ `merged_against` ref, the `merge_commit_sha`, the entries added from each side, and the version
456
+ resolutions. The same resolution is cited in three further places: the body of the resolution
457
+ commit message, the pull-request body, and an evidence artifact at
458
+ `docs/features/parallel/<slug>/evidence/other/mergeable-conflicts.<yyyy-MM-ddTHH-mm>.md` carrying
459
+ `Timestamp`, `Command`, `EXIT_CODE`, and the script's JSON output verbatim.
460
+
430
461
  Regeneration boundaries — regenerate at each of the following, not only at final completion:
431
462
 
432
463
  - Run kickoff, seeding the initial projection from the manifest and the seeded cohorts.
@@ -435,6 +466,7 @@ Regeneration boundaries — regenerate at each of the following, not only at fin
435
466
  - Every `recolor_generation` increment.
436
467
  - Every append to `mutations[]`.
437
468
  - Every append to `drift_events[]`.
469
+ - Every append to an item's `mergeable_conflicts_resolved`.
438
470
  - Run completion in `closed` mode, or run close in `open` mode.
439
471
 
440
472
  Defining the `mutations[]` and `drift_events[]` appends as regeneration boundaries here means F6 and
@@ -444,8 +476,10 @@ F8 need no amendment to these projection rules.
444
476
 
445
477
  This section is consumption documentation only. The checkpoint schema is owned by F3, defined once
446
478
  as prose invariants in `.claude/rules/parallel-orchestration.md`, and enforced by
447
- `scripts/dev_tools/validate_parallel_orchestrator_state.py`. Consume that schema; add no field to it
448
- and extend no enum in it.
479
+ `scripts/dev_tools/validate_parallel_orchestrator_state.py`. Consume that schema;
480
+ add no field to it that `.claude/rules/parallel-orchestration.md` does not declare,
481
+ and extend no enum in it. `mergeable_conflicts_resolved` is the one declared optional item
482
+ field.
449
483
 
450
484
  Fields `parallel-orchestrator` writes to `artifacts/orchestration/parallel-orchestrator-state.json`:
451
485
  `objective`, `route_id: "parallel"`, `parallel_slug`, `parallel_manifest_path`,
@@ -196,6 +196,10 @@ The facade re-exports the five functions this skill needs: `Get-PlanPaths` (port
196
196
  call to `Test-BlastRadius` in `@(...)`: it writes its findings to the pipeline, so a zero-element
197
197
  result writes nothing and a one-element result writes a single object.
198
198
 
199
+ `Test-BlastRadiusConflict` reads the optional `mergeable_paths` list from that truth table and
200
+ contributes no `path_overlap` edge for a path matching it, while the path itself stays in the
201
+ declared radius and is still read by every audit.
202
+
199
203
  The truth table the port reads is `config/blast-radius.json`, which push-down publishes into the
200
204
  destination workspace alongside `.claude`.
201
205
 
@@ -312,6 +316,8 @@ The library returns the partition; the planner supplies the record fields.
312
316
  `Test-BlastRadiusConflict` to every unordered pair of `declared` radii, then pass the pairs as
313
317
  `--edges "<a>:<b> ..."` and the item keys as `--keys "<k1> <k2> ..."`.
314
318
  Read the verdict from the conflict key of the returned hashtable.
319
+ `Test-BlastRadiusConflict` contributes no `path_overlap` edge for a path matching the truth
320
+ table's optional `mergeable_paths` list, and the path stays in the declared radius.
315
321
  The hashtable itself is always truthy, so a bare boolean test on the result treats every pair as
316
322
  conflicting and serializes the whole run. This is the sibling hazard to the `@(...)` warning
317
323
  above for `Test-BlastRadius`: that function writes an `IList`-shaped pipeline result whose
@@ -42,6 +42,35 @@ For short-path runs, also persist:
42
42
  - `phase0_execution_summary`
43
43
  - `resume_after_manual_bootstrap` (next step token)
44
44
 
45
+ ## Portable Prepared-State Projection
46
+
47
+ When the PowerShell delivery flow resumes from a provider-neutral prepared-state
48
+ handoff, the active destination projection is
49
+ `artifacts/orchestration/orchestrator-state.json`. Validate and retain:
50
+
51
+ - the destination provider, checkpoint expression, selected projector,
52
+ `plan-path`, and exact recorded `next_step`;
53
+ - the portable handoff ID, envelope and latest-history SHA-256 values, selected
54
+ adapter, source validator, identity, repository/workspace/branch binding,
55
+ exact plan proof, lifecycle, capabilities, and scheduler context;
56
+ - the source checkpoint path/hash/archive facts and opaque historical receipt
57
+ references without converting them into Claude receipts; and
58
+ - `destination_evidence: { status: "pending_first_delegation", receipts: [] }`
59
+ until the first new Claude delegation after materialization.
60
+
61
+ Do not rediscover the plan or rerun any phase listed in
62
+ `portable_handoff.lifecycle.completed_phases`. Resume only the recorded
63
+ transition. Record Claude model, launch, worktree, and receipt evidence through
64
+ Claude-native fields for new work; functional parity does not require copying
65
+ Codex field names or launch representations.
66
+
67
+ For a scheduled parallel or epic child, validate the run/item, parent
68
+ checkpoint path/hash, scheduler and child owners, return contract, exact plan
69
+ hash, child checkpoint hash, and result hash before returning a result. The
70
+ ordinary child may update only its bounded status/result projection. Cohort or
71
+ wave order, barriers, fan-in, integration, cleanup, worktree lifecycle, and
72
+ parent completion remain owned by the parent scheduler.
73
+
45
74
  ## Update Protocol
46
75
 
47
76
  - Write checkpoint after every completed orchestration sub-step.
@@ -55,4 +84,3 @@ On invocation:
55
84
  2) If incomplete, resume from `next_step` without re-running `completed_steps`.
56
85
  3) If missing or completed, start at phase-0 intake.
57
86
  4) If user explicitly requests restart, reset checkpoint and start phase-0.
58
-
@@ -22,6 +22,13 @@
22
22
  ".agents/skills/**",
23
23
  "scripts/vscode/**"
24
24
  ],
25
+ "mergeable_paths": [
26
+ "**/*.csproj",
27
+ "**/packages.config",
28
+ "**/app.config",
29
+ "**/*.vbproj",
30
+ "**/*.props"
31
+ ],
25
32
  "modules": {
26
33
  "config": ["config/**"]
27
34
  },
@@ -46,6 +46,8 @@
46
46
  ".claude/hooks/enforce-pr-author-skill-helpers.ps1",
47
47
  ".claude/hooks/enforce-prd-feature-before-planner.ps1",
48
48
  ".claude/hooks/enforce-promotion-mcp-only.ps1",
49
+ ".claude/hooks/hook-command-invocation.ps1",
50
+ ".claude/hooks/hook-command-scanner.ps1",
49
51
  ".claude/hooks/persist-session-id.ps1",
50
52
  ".claude/hooks/validate-bash.ps1",
51
53
  ".claude/hooks/validate-discovery-artifact-gate.ps1",
@@ -134,6 +136,11 @@
134
136
  ".claude/lib/blast-radius/BlastRadius.psm1",
135
137
  ".claude/lib/blast-radius/BlastRadiusNormalization.psm1",
136
138
  ".claude/lib/blast-radius/BlastRadiusTokenShape.psm1",
139
+ ".claude/lib/cleanup-manifest/CleanupWorktreeManifest.psm1",
140
+ ".claude/lib/blast-radius/BlastRadiusConflict.psm1",
141
+ ".claude/lib/project-file-merge/ProjectFileMergeGrammar.psm1",
142
+ ".claude/lib/project-file-merge/ProjectFileMerge.psm1",
143
+ ".claude/lib/project-file-merge/Resolve-MergeableConflict.ps1",
137
144
  ".claude/rules/parallel-orchestration.md",
138
145
  ".claude/rules/shell.md",
139
146
  ".claude/lib/bash/compute-cohorts.sh",