@danmoisan/drm-copilot-mcp 1.0.21 → 1.0.23

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 (43) hide show
  1. package/out/mcp-server.js +1730 -139
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/MEMORY.md +5 -1
  4. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_commit_push_memory_before_pr.md +48 -2
  5. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_no_sendmessage_tool.md +35 -0
  6. package/resources/claude-customizations/.claude/agent-memory/epic-orchestrator/feedback_worktree_isolation_branches_from_main.md +45 -0
  7. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +257 -0
  8. package/resources/claude-customizations/.claude/agents/parallel-planner.md +183 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +23 -11
  10. package/resources/claude-customizations/.claude/hooks/enforce-parallel-abandon-gate.ps1 +259 -0
  11. package/resources/claude-customizations/.claude/hooks/enforce-parallel-cohort-barrier.ps1 +499 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate-helpers.ps1 +302 -0
  13. package/resources/claude-customizations/.claude/hooks/enforce-parallel-drift-gate.ps1 +359 -0
  14. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +244 -0
  15. package/resources/claude-customizations/.claude/lib/bash/compute-cohorts.sh +143 -0
  16. package/resources/claude-customizations/.claude/lib/bash/compute-concurrency-batches.sh +122 -0
  17. package/resources/claude-customizations/.claude/lib/bash/parallel-cohorts.sh +330 -0
  18. package/resources/claude-customizations/.claude/lib/bash/parallel-common.sh +238 -0
  19. package/resources/claude-customizations/.claude/lib/bash/parallel-items-validate.sh +244 -0
  20. package/resources/claude-customizations/.claude/lib/bash/parallel-manifest-validate.sh +187 -0
  21. package/resources/claude-customizations/.claude/lib/bash/parallel-yaml-emit.sh +340 -0
  22. package/resources/claude-customizations/.claude/lib/bash/parallel-yaml-scan.sh +335 -0
  23. package/resources/claude-customizations/.claude/lib/bash/validate-parallel-manifest.sh +134 -0
  24. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +379 -0
  25. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusConfig.psm1 +491 -0
  26. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +490 -0
  27. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusGlob.psm1 +429 -0
  28. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +366 -0
  29. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +184 -0
  30. package/resources/claude-customizations/.claude/rules/shell.md +7 -2
  31. package/resources/claude-customizations/.claude/settings.json +28 -0
  32. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +152 -0
  33. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +93 -0
  34. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +971 -0
  35. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +461 -0
  36. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +176 -0
  37. package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +56 -0
  38. package/resources/claude-customizations/config/blast-radius.json +16 -0
  39. package/resources/claude-customizations/config/orchestration-routing.json +355 -0
  40. package/resources/claude-customizations/pack-manifests/core.json +32 -1
  41. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  42. package/resources/config/orchestration-routing.json +22 -0
  43. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +29 -0
@@ -0,0 +1,971 @@
1
+ ---
2
+ name: parallel-orchestrate
3
+ description: Execute a prepared parallel run for the parallel-orchestrator agent — cohort scheduling under a max_concurrency cap, per-item fan-out onto isolated worktrees branched from origin/main, per-item merge to main after durably confirming CI green, worktree cleanup, and the generated parallel-status.md projection. There is no integration branch and no final integration pull request.
4
+ argument-hint: "[parallel-manifest-path | parallel-slug]"
5
+ context: fork
6
+ agent: parallel-orchestrator
7
+ ---
8
+
9
+ # Parallel Orchestrate Skill
10
+
11
+ A user invocation (`/parallel-orchestrate <parallel-manifest-path>`) forks the
12
+ `parallel-orchestrator` agent with this procedure in context. The parallel manifest path (or
13
+ parallel slug) for this run is:
14
+
15
+ $ARGUMENTS
16
+
17
+ This skill frames work for the `parallel-orchestrator` agent, parallel to how
18
+ `.claude/skills/orchestrate/SKILL.md` frames work for `orchestrator`. It documents manifest
19
+ consumption, cohort consumption and ordering, the cohort barrier and `max_concurrency` slot
20
+ filling, the per-item branch and worktree lifecycle, the child kickoff parameter, model selection,
21
+ per-item merge to `main`, per-item merge-conflict handling, worktree cleanup,
22
+ `parallel-status.md` maintenance, checkpoint persistence, and completion, so that the procedure is
23
+ not re-derived ad hoc on each parallel run.
24
+
25
+ Each item on this surface ships independently: an item's pull request targets `main` directly.
26
+ There is no integration branch, no final integration pull request, and no fan-in path anywhere in
27
+ this procedure. Scheduling order comes only from computed blast-radius contention recorded in the
28
+ cohort table; there is no `depends_on` field on this surface.
29
+
30
+ Every section below is self-contained. A cross-reference names another section by its exact
31
+ heading text, never by position or number, so a section can be added or extended without
32
+ reflowing, reordering, or renumbering anything else in this file. The sections
33
+ `## Mutation Protocol (F6)`, `## Enforcement Hooks (F7)`, and `## Radius Drift Detection (F8)`
34
+ are reserved for the wave-4 features named in them: each is appended to by its own feature and
35
+ must not be relocated.
36
+
37
+ ## Prerequisites
38
+
39
+ Before proceeding, `parallel-orchestrator` must:
40
+
41
+ 1. Read `CLAUDE.md` for repository tone policy and architectural context.
42
+ 2. Read the applicable `.claude/rules/` files for the languages in scope, including
43
+ `.claude/rules/parallel-orchestration.md`, which is the schema authority for this surface.
44
+ 3. Read the policy files listed in the compliance reading order section of `CLAUDE.md`.
45
+
46
+ ## Parallel Manifest Consumption
47
+
48
+ The run manifest is the YAML frontmatter of `docs/features/parallel/<slug>/parallel.md`, authored
49
+ by `parallel-planner` on the planner-owned branch `parallel/<slug>-plan`. That branch is not an
50
+ integration branch: no item branch merges into it, it merges into no item branch, and it holds
51
+ only run-level artifacts under `docs/features/parallel/<slug>/`.
52
+
53
+ **This section is not a schema authority.** The manifest schema, the checkpoint schema, and all
54
+ nine parallel enums are defined once as prose invariants in
55
+ `.claude/rules/parallel-orchestration.md` (manifest invariants M1 through M7) and are enforced by
56
+ the F3-owned validators `scripts/dev_tools/parallel_manifest_contract.py` and
57
+ `scripts/dev_tools/validate_parallel_orchestrator_state.py`, with the manifest half reachable on the
58
+ destination-runtime path as `bash .claude/lib/bash/validate-parallel-manifest.sh`. This is a deliberate delta from
59
+ `.claude/skills/epic-orchestrate/SKILL.md`, whose manifest section carries its schema inline. Read
60
+ the schema from the rule file and the validators; consume it here and never redefine or extend it.
61
+
62
+ Consumption rules:
63
+
64
+ - `issue_num` is the primary key for every item reference. It is the stable GitHub issue number,
65
+ so it does not drift when an item's folder moves from `docs/features/active/` to
66
+ `docs/features/completed/`.
67
+ - Read `mode` (`closed` or `open`, defaulting to `closed`), `max_concurrency` (an integer from 1
68
+ through 8, defaulting to 4), and each item's identity and state: `feature_folder`, `kind`,
69
+ `state`, and `blast_radius`.
70
+ - The manifest is read-only to `parallel-orchestrator`. It is static input authored by
71
+ `parallel-planner`: never write it, rewrite it, or back-fill a field into it.
72
+ - The manifest carries no `depends_on` key at any level and no top-level `integration_branch` key.
73
+ Presence of either is an explicit rejection, not a tolerated extra field.
74
+ - A malformed manifest is rejected before any kickoff, recorded as a synthetic Blocking finding in
75
+ the checkpoint. Do not guess a repair, do not silently skip the offending item, and do not launch
76
+ a partial cohort. Validate with the destination-runtime bash entry point, which needs no Python
77
+ interpreter and is published by push-down alongside `.claude`:
78
+ `bash .claude/lib/bash/validate-parallel-manifest.sh docs/features/parallel/<slug>/parallel.md`.
79
+ Its non-zero exit is the rejection signal and its printed error list, one error per line on
80
+ stdout, is the content of the Blocking finding; exit 1 means the manifest is invalid and exit 2
81
+ means the file is unreadable or uses a YAML construct outside the supported subset. Consume
82
+ `mode` and `max_concurrency` through the same entry point's `--print-mode` and
83
+ `--print-max-concurrency` subcommands rather than reading the frontmatter directly.
84
+ `validate_parallel_manifest_text` in `scripts/dev_tools/parallel_manifest_contract.py` remains the
85
+ repository authority and the parity reference. Manifest validation is deliberately not an MCP
86
+ artifact type.
87
+
88
+ ## Cohort Consumption and Ordering
89
+
90
+ `cohorts[]` is an input, not a computation. Each entry has the shape
91
+ `{ index, generation, item_keys[] }` and is seeded by `parallel-planner` at `generation: 0` from
92
+ the deterministic Welsh-Powell coloring in `scripts/dev_tools/parallel_cohort_computation.py`.
93
+
94
+ `parallel-orchestrator` consumes the recorded partition and never computes or recolors one:
95
+
96
+ - Cohort computation belongs to the cohort-scheduler library that `parallel-planner` calls.
97
+ - Recoloring after a membership change belongs to F6; recoloring after a drift event belongs to F8.
98
+ - Read `recolor_generation` and schedule only from the cohorts whose `generation` equals it.
99
+
100
+ Items within a cohort are non-conflicting by construction — a cohort is an independent set in the
101
+ conflict graph — so they may branch from the same `main` tip and may merge in any order. After one
102
+ same-cohort item merges, another same-cohort item's pull request, based on the older tip, remains
103
+ mergeable, and GitHub produces a merge commit.
104
+
105
+ The conflict graph the cohorts project was computed by a contention relation that fails closed.
106
+ Since the blast-radius corrections of issue #452 (merged as PR #453), that relation reports more
107
+ contention rather than less: separator-free repository-root shared surfaces are reached from plan
108
+ and specification text, and path comparison honours listed-directory prefixes on both sides. Build
109
+ on that corrected behaviour. Never narrow a radius, re-derive a partition, or reinterpret an edge
110
+ in order to combine two cohorts or widen a launch batch.
111
+
112
+ ## Cohort Barrier and Max-Concurrency Slot Filling
113
+
114
+ Two independent controls govern every launch. The cohort barrier governs when a cohort may start.
115
+ `max_concurrency` governs how many items of a started cohort run at once. Neither substitutes for
116
+ the other.
117
+
118
+ **Cohort barrier.** Cohort `N+1` branches from `main` only after every cohort-`N` item is `merged`
119
+ or `worktree_removed`. Increment `current_cohort` only on durable confirmation from
120
+ `git worktree list --porcelain`, `git branch`, and
121
+ `gh pr view --json state,mergedAt,headRefOid` — never from an in-memory completion notification. A
122
+ blocked item (`blocked_ci_loop_limit` or `blocked_drift`) is neither `merged` nor
123
+ `worktree_removed`, so a blocked item holds the barrier and cohort `N+1` does not start.
124
+
125
+ **`max_concurrency` slot filling.** `max_concurrency` caps the number of simultaneously in-flight
126
+ items independently of cohort size: a cohort of twelve items executes at most `max_concurrency`
127
+ items at a time. Fill slots in ascending item-key order, keyed on `issue_num`, and refill each
128
+ freed slot with the next unstarted item of the current cohort in that same ascending item-key
129
+ order. A cohort larger than `max_concurrency` therefore launches in several batches from the same
130
+ recorded `main` tip. The batching is a pure function, reached on the destination-runtime path as
131
+ `bash .claude/lib/bash/compute-concurrency-batches.sh --keys "<k1> <k2> ..." --max-concurrency <n>`.
132
+ It prints a compact JSON array of arrays, returns the batches in order, and sorts the keys itself,
133
+ so determinism does not depend on caller ordering.
134
+ `compute_concurrency_batches(cohort_item_keys, max_concurrency)` in
135
+ `scripts/dev_tools/parallel_cohort_computation.py` remains the repository authority and the parity
136
+ reference.
137
+
138
+ **Mechanical enforcement of the barrier is F7 scope, not this feature's.** F7 delivers a two-layer
139
+ design, because no single `PreToolUse` hook can validate a batch of concurrent `Agent` calls: hooks
140
+ fire per call with no cross-call state visibility.
141
+
142
+ - Layer 1, per-call deterrent: `.claude/hooks/enforce-parallel-cohort-barrier.ps1`, a `PreToolUse`
143
+ hook on the `Agent` matcher. It fires when `subagent_type == "orchestrator"` and the serialized
144
+ prompt carries the parallel kickoff marker, resolves the target item, reads
145
+ `artifacts/orchestration/parallel-orchestrator-state.json`, and denies with reason
146
+ `PARALLEL_COHORT_BARRIER_BLOCKED` unless every conflicting item in a prior cohort is `merged` or
147
+ `worktree_removed`.
148
+ - Layer 2, retrospective backstop: a cohort-ordering invariant inside
149
+ `validate_parallel_orchestrator_state_text`, enforced at `parallel-orchestrator` `SubagentStop`
150
+ time, appending `PARALLEL_COHORT_BARRIER_VIOLATION` when the ordering invariant is violated.
151
+
152
+ Neither layer is shipped by this feature; both are named here so the obligation is legible to an
153
+ operator and to the F7 planner. Until F7 lands, the barrier is enforced by this procedure alone.
154
+
155
+ ## Per-Item Branch and Worktree Lifecycle
156
+
157
+ 1. Run one `git fetch origin main` immediately before each cohort launch, so every item in that
158
+ cohort branches from the same current remote `main` tip rather than from a stale local ref.
159
+ Record the fetched tip.
160
+ 2. Each item's worktree is created by that item's delegation spawn,
161
+ `Agent(orchestrator, isolation: "worktree", run_in_background: true)`, branched from
162
+ `origin/main`. Do not create or check out item worktrees by hand.
163
+ 3. `parallel-planner` created and pushed each item's feature branch from `origin/main` at
164
+ preparation time, and that branch is reused unchanged as the item's execution branch, so the
165
+ spawn checks out an existing branch whose base is `origin/main`. Because that base may be older
166
+ than the fetched tip, the item's own run reconciles its branch against the fetched `origin/main`
167
+ tip at execution start. Real path overlap that survives that reconciliation is drift; the
168
+ conflict outcome is handled per `## Per-Item Merge-Conflict Handling`, and drift recording
169
+ itself belongs to F8.
170
+ 4. Each item's pull request base branch is `main`. Record `worktree_path`, `branch_name`,
171
+ `pr_number`, and `pr_url` for the item, and set `merge_status: worktree_created` at spawn.
172
+ 5. Worktree removal is the terminal step of this lifecycle and is specified in
173
+ `## Worktree Cleanup`.
174
+
175
+ No integration branch is created, fetched, pushed, or referenced at any point in this lifecycle.
176
+ There is no final integration pull request and no fan-in path: the epic surface's
177
+ integration-branch lifecycle has no counterpart on this surface, and its absence is structural
178
+ rather than an omission.
179
+
180
+ ## Parallel-Mode Kickoff Parameter
181
+
182
+ When `parallel-orchestrator` delegates an item to `Agent(orchestrator)`, the delegation prompt
183
+ carries exactly these five elements.
184
+
185
+ 1. The literal marker line:
186
+
187
+ > `Parallel mode: true. parallel_slug: <slug>. parallel_checkpoint_path: artifacts/orchestration/parallel-orchestrator-state.json. cohort_index: <n>. PR base branch MUST be main; pass --base main to gh pr create.`
188
+
189
+ The token `Parallel mode: true` must appear exactly: it is the marker F7's Layer 1 barrier hook
190
+ matches on. The clause `PR base branch MUST be main` is the child's explicit base-branch
191
+ instruction, recorded as prompt text rather than left to a base-branch ancestry heuristic.
192
+ 2. The item's active feature folder path, written literally as `docs/features/active/<basename>`.
193
+ The child needs it for its own operation, and F7's Layer 1 hook resolves the target item by
194
+ scanning the prompt for exactly that path shape, so the path is emitted as a bare path token.
195
+ 3. The canonical issue number line, which is the item key.
196
+ 4. The item's committed `plan-path`, together with the resume instruction: resume at atomic
197
+ execution from that plan rather than re-running promotion, research, or planning. The item's
198
+ prepared feature folder and approved atomic plan are already committed and preflight-clear on the
199
+ item's own pushed feature branch.
200
+ 5. The model-budget marker line `model_budget.fable_policy: <disabled|available|preferred>.`
201
+
202
+ Spawn parameters, passed on the `Agent` call and never written into the prompt text:
203
+ `isolation: "worktree"`, `run_in_background: true`, branch base `origin/main`, and `model` equal to
204
+ that item's model routing receipt's resolved model.
205
+
206
+ Negative obligations on the prompt:
207
+
208
+ - It never carries `Preparation mode: true`. Preparation fan-out belongs to `parallel-planner` and
209
+ to F6's admission path; an item that reaches this procedure is already prepared.
210
+ - It never carries the epic-mode marker line that `.claude/skills/epic-orchestrate/SKILL.md`
211
+ emits — the marker whose text is `Epic mode` followed by the value `true` — so the epic
212
+ wave-barrier hook does not fire on a parallel child. That marker's value is deliberately not
213
+ written out anywhere in this file, so no file this feature delivers carries an epic-mode marker
214
+ string.
215
+ - It contains no instruction for the child to merge its own pull request. The parent performs each
216
+ item's merge, as specified in `## Per-Item Merge to Main (Merge-on-Green)`.
217
+
218
+ Excluded from the prompt as parent-side concerns: the item's declared blast radius,
219
+ `max_concurrency`, and `mode`. Keeping the prompt minimal preserves the child contract unchanged.
220
+
221
+ ## Model Selection
222
+
223
+ When `parallel-orchestrator` delegates an item to `Agent(orchestrator)`, the prompt appends the
224
+ session model-budget kickoff marker line, following the existing kickoff-marker pattern:
225
+
226
+ > `model_budget.fable_policy: <disabled|available|preferred>.`
227
+
228
+ The item's own `orchestrator` reads that line and applies the two-axis model-selection mechanism
229
+ documented in `.claude/skills/orchestrate/SKILL.md` (`## Model Selection`): it assesses a
230
+ judgment-based `complexity_band`, records `complexity_assessments[]` and
231
+ `model_routing_receipts[]`, and resolves each of its own delegations' model tier under the given
232
+ `fable_policy`. The canonical, tested reference implementations are `Get-ComplexityFloor` and
233
+ `Resolve-DelegationModel` in `.claude/lib/model-routing/ModelRouting.psm1`. Default `fable_policy`
234
+ is `disabled` when the marker is absent.
235
+
236
+ `parallel-orchestrator` spawns exactly one delegation channel, `Agent(orchestrator)`, one
237
+ delegation per item. It applies the same per-delegation resolution to that channel and passes
238
+ `model` equal to the routing receipt's `model` on the spawn call. It MUST NOT omit `model` — an
239
+ omitted `model` falls back to the delegate's frontmatter default, `opus`, which suppresses a
240
+ `fable` resolution — and MUST NOT hard-code `model=opus` in a way that overrides the resolved
241
+ routing model.
242
+
243
+ `route` is never an input to model selection. `route` remains file-count driven and governs only
244
+ agents, skills, and MCP tools. A skill whose frontmatter `context` field holds the value `fork`
245
+ inherits the parent model and ignores a model override, so model selection applies to agent
246
+ delegations, not to fork-routed skill invocations.
247
+
248
+ ## Per-Item Merge to Main (Merge-on-Green)
249
+
250
+ The parent — `parallel-orchestrator` — performs each item's merge to `main`. The child contract is
251
+ unchanged: `.claude/skills/orchestrate/SKILL.md` is **not modified by this feature**. There is no
252
+ `parallel_mode` clause in its step 9, no `parallel_merge` object in the child checkpoint, and no
253
+ additional condition on the child's PR Creation Gate.
254
+
255
+ Procedure, per item:
256
+
257
+ 1. The item's child orchestration runs unmodified, with `epic_mode` `false` or absent, and finishes
258
+ at its own DONE. The child's PR Creation Gate condition 6 already requires
259
+ `ci_gate.conclusion == "success"`, so at child DONE the item's pull request is open against
260
+ `main` and its checks are green. The child is never instructed to merge its own pull request.
261
+ 2. On child completion, durably confirm pull-request state and check conclusion with
262
+ `gh pr view --json state,mergedAt,headRefOid`, and with `gh pr checks` when the check conclusion
263
+ must be re-read — never from an in-memory completion notification — then record
264
+ `merge_status: ci_green`.
265
+ 3. Execute `gh pr merge --merge <PR>` for that item's pull request, whose base is `main`.
266
+ 4. On success, record `merge_commit_sha`, `merged_at`, and `merge_status: merged`, then regenerate
267
+ `docs/features/parallel/<slug>/parallel-status.md`.
268
+ 5. On a merge failure caused by a conflict, follow `## Per-Item Merge-Conflict Handling`.
269
+
270
+ **F7 dependency.** `.claude/hooks/enforce-epic-merge-gate.ps1` is a project-wide `PreToolUse`
271
+ Bash-matcher hook that denies any `gh pr merge --merge` unless an epic-shaped checkpoint satisfies
272
+ its allow conditions; its block reason is `EPIC_MERGE_GATE_BLOCKED`. A parallel run has no
273
+ epic-shaped checkpoint, so step 3 above is denied until F7 scopes or extends that gate's allow
274
+ conditions for the parallel case. This feature modifies no file under `.claude/hooks/` and does not
275
+ change `.claude/settings.json`, so the parallel surface is not executable end-to-end before F7
276
+ lands. That limitation is documented, not worked around.
277
+
278
+ Branch protection on `main` affects only the pacing of step 3, not its ownership: if `main`
279
+ requires branches to be up to date, an automated `gh pr update-branch` plus re-green cycle is
280
+ inserted between same-cohort merges, which serializes those merges in practice while remaining
281
+ unattended.
282
+
283
+ ## Per-Item Merge-Conflict Handling
284
+
285
+ Remediation is child-owned and parent-initiated. The conflict is always between one item's own
286
+ branch and `origin/main`; there is no integration branch and therefore no fan-in conflict path on
287
+ this surface.
288
+
289
+ 1. On a conflicted `gh pr merge --merge`, the parent detects the failure and re-delegates that item's
290
+ child orchestration, passing the conflict signal and the instruction to resolve against
291
+ `origin/main`. The conflict capture and the finding write both belong to the child's
292
+ `atomic-executor`, which works inside the item's own worktree, the only working tree holding the
293
+ item's branch. It runs `git fetch origin main`, then `git merge --no-commit origin/main`, and on
294
+ non-zero exit captures `git diff --name-only --diff-filter=U` for the conflicted-file list
295
+ together with the raw conflict-marker content of each conflicted file. The child's
296
+ `atomic-executor` then writes that evidence as a synthetic Blocking finding to the item's own
297
+ `remediation-inputs.<timestamp>.md` in the item's active feature folder under
298
+ `docs/features/active/`, not to the run's parallel folder. Assigning both the capture and the
299
+ finding write to the child's chain matches `.claude/skills/epic-orchestrate/SKILL.md`, whose
300
+ equivalent capture and finding write also belong to the child's `atomic-executor`.
301
+ 2. The parent re-delegates that item's child orchestration. The child processes the finding through
302
+ its unmodified R1 through R5 remediation loop exactly as it processes any local Blocking
303
+ finding. No new remediation loop is introduced by this procedure.
304
+ 3. The child's `remediation_pass` counter is shared with its local-finding and CI-failure passes,
305
+ with the cap of 3, unmodified.
306
+ 4. Each remediated pass ends again at child DONE with the pull request open and CI green, after
307
+ which the parent retries the merge per `## Per-Item Merge to Main (Merge-on-Green)`. During
308
+ remediation the item's `merge_status` legitimately remains `pr_open` or `ci_green`: the
309
+ `merge_status` enum is F3-owned, carries no conflict member, and is not extended by this
310
+ feature.
311
+ 5. On loop exhaustion, the parent records the terminal `merge_status: blocked_ci_loop_limit` for the
312
+ item; the child's own checkpoint retains its precise blocked status. A blocked item is neither
313
+ `merged` nor `worktree_removed`, so it holds the cohort barrier defined in
314
+ `## Cohort Barrier and Max-Concurrency Slot Filling`.
315
+
316
+ Boundary with F8: a merge conflict between two same-cohort items is evidence that the declared
317
+ blast radius under-reported, and this feature records the child's blocked or remediated outcome
318
+ only, leaving drift recording in `drift_events[]`, quiesce of admission, conflict recomputation
319
+ against the observed radius, and requeue of the later-started item to F8.
320
+
321
+ ## Worktree Cleanup
322
+
323
+ After an item reaches `merge_status: merged` and that state is durably confirmed by
324
+ `gh pr view --json state,mergedAt,headRefOid`, the parent — running from the main repository
325
+ checkout, never from inside a child worktree — issues `git worktree remove <worktree_path>`. On
326
+ success it records `merge_status: worktree_removed` and `worktree_removed_at`, then regenerates
327
+ `docs/features/parallel/<slug>/parallel-status.md`.
328
+
329
+ Mechanical gating of this command for parallel worktrees is F7 scope.
330
+ `.claude/hooks/enforce-epic-worktree-removal-gate.ps1` is a project-wide `PreToolUse` Bash-matcher
331
+ hook that denies any `git worktree remove` unless the epic checkpoint carries a matching
332
+ `features[]` record whose `merge_status` is `merged` or `worktree_removed`; an unreadable checkpoint
333
+ or an absent record also denies. Its block reason is `EPIC_WORKTREE_REMOVAL_BLOCKED`. A parallel run
334
+ has no epic checkpoint record for its worktrees, so removal is denied until F7 both delivers
335
+ `enforce-parallel-worktree-removal-gate.ps1` and coordinates the epic gate's allow conditions:
336
+ `PreToolUse` denials are conjunctive, so a new allow-hook alone cannot override the existing deny.
337
+ This feature ships no hook file and makes no `.claude/settings.json` change.
338
+
339
+ ## Documentation Maintenance Boundaries
340
+
341
+ `docs/features/parallel/<slug>/parallel-status.md` is a generated projection of
342
+ `artifacts/orchestration/parallel-orchestrator-state.json`. It is regenerated in full, is never
343
+ hand-authored, and is never treated as an input. It is never the source of the cohort table and
344
+ never the source of the schedule: the run manifest and the checkpoint are authoritative. Generate it
345
+ from the template `docs/features/templates/parallel/parallel-status.md`.
346
+
347
+ Header block fields: `parallel_slug`, `mode`, `max_concurrency`, `current_cohort`,
348
+ `recolor_generation`, `last_updated`.
349
+
350
+ Item table: one row per `items[]` entry, carrying `issue_num`, `feature_folder`, cohort index,
351
+ lifecycle `state`, `merge_status`, `pr_url`, `merge_commit_sha`, and the item's lifecycle
352
+ timestamps. The cohort column takes the place of the epic status document's wave column.
353
+
354
+ Cohort table: a projection of `cohorts[] { index, generation, item_keys[] }`, so a recolored
355
+ schedule stays traceable by `generation`.
356
+
357
+ Read-only projections of F3-owned arrays, rendered by this feature but never written by it: section
358
+ `## Conflict Edges` projects `conflict_edges[]`; section `## Mutations` projects `mutations[]`,
359
+ whose rows appear only once F6 populates that array; section `## Drift Events` projects
360
+ `drift_events[]`, which only F8 populates. An empty array renders an empty section rather than an
361
+ omitted one.
362
+
363
+ Regeneration boundaries — regenerate at each of the following, not only at final completion:
364
+
365
+ - Run kickoff, seeding the initial projection from the manifest and the seeded cohorts.
366
+ - Every item `state` or `merge_status` transition.
367
+ - Every cohort transition, meaning every `current_cohort` increment.
368
+ - Every `recolor_generation` increment.
369
+ - Every append to `mutations[]`.
370
+ - Every append to `drift_events[]`.
371
+ - Run completion in `closed` mode, or run close in `open` mode.
372
+
373
+ Defining the `mutations[]` and `drift_events[]` appends as regeneration boundaries here means F6 and
374
+ F8 need no amendment to these projection rules.
375
+
376
+ ## Parallel-Level Checkpoint
377
+
378
+ This section is consumption documentation only. The checkpoint schema is owned by F3, defined once
379
+ as prose invariants in `.claude/rules/parallel-orchestration.md`, and enforced by
380
+ `scripts/dev_tools/validate_parallel_orchestrator_state.py`. Consume that schema; add no field to it
381
+ and extend no enum in it.
382
+
383
+ Fields `parallel-orchestrator` writes to `artifacts/orchestration/parallel-orchestrator-state.json`:
384
+ `objective`, `route_id: "parallel"`, `parallel_slug`, `parallel_manifest_path`,
385
+ `parallel_status_doc_path`, `mode`, `max_concurrency`, `completed_steps`, `next_step`,
386
+ `last_updated`, `current_cohort`, `recolor_generation`, `cohorts[]`, `items[]` — each item entry
387
+ carrying `issue_num`, `feature_folder`, `state`, `blast_radius`, `worktree_path`, `branch_name`,
388
+ `pr_number`, `pr_url`, `merge_status`, `merge_commit_sha`, and the lifecycle timestamps — and the
389
+ three receipt arrays `delegation_receipts[]`, `skill_receipts[]`, and `mcp_call_receipts[]`,
390
+ populated with the `parallel` route's required names from `config/orchestration-routing.json`.
391
+
392
+ The `merge_status` enum has exactly eight members: `not_started`, `worktree_created`, `pr_open`,
393
+ `ci_green`, `merged`, `worktree_removed`, `blocked_drift`, and `blocked_ci_loop_limit`. An absent
394
+ `merge_status` is treated as `not_started`.
395
+
396
+ The transition chain this feature writes: `not_started` to `worktree_created` at the item's
397
+ delegation spawn; `worktree_created` to `pr_open` when the child reports its pull request open;
398
+ `pr_open` to `ci_green` on durable confirmation after child DONE; `ci_green` to `merged` when the
399
+ parent's merge succeeds; and `merged` to `worktree_removed` after gated removal.
400
+ `blocked_ci_loop_limit` is the mapped terminal for an exhausted remediation loop.
401
+
402
+ Never written by this feature: `blocked_drift`, which only F8 writes; `conflict_edges[]`, seeded by
403
+ `parallel-planner` and recomputed only by F8; `mutations[]`, which only F6 appends to; and
404
+ `drift_events[]`, which only F8 appends to. These are read for projection and for scheduling context
405
+ and are otherwise untouched.
406
+
407
+ The checkpoint is a cache of durable state, not the source of truth. Every recorded field is
408
+ re-derivable on resume from `git worktree list --porcelain` for worktree existence and path,
409
+ `git branch` for branch existence and name, and `gh pr view --json state,mergedAt,headRefOid` for
410
+ pull-request state, merge time, and merge commit. Where the checkpoint disagrees with those three
411
+ commands, the commands win and the checkpoint is rewritten from them.
412
+
413
+ Validate through `mcp__drm-copilot__validate_orchestration_artifacts` with
414
+ `artifact_type: "parallel-orchestrator-state"`, or the equivalent CLI invocation
415
+ `poetry run python -m scripts.dev_tools.validate_orchestration_artifacts parallel-orchestrator-state <path>`,
416
+ adding `--require-complete` at the completion gate.
417
+
418
+ ## Completion Requirements
419
+
420
+ Completion is mode-dependent. Read `mode` from the manifest: it is `closed` or `open` and defaults
421
+ to `closed`.
422
+
423
+ In `closed` mode, `parallel-orchestrator` must not report completion until all of the following
424
+ hold:
425
+
426
+ 1. Every non-withdrawn item has `merge_status` of `merged` or `worktree_removed`, each durably
427
+ confirmed by `git` and `gh` rather than by an in-memory completion notification.
428
+ 2. `docs/features/parallel/<slug>/parallel-status.md` has been regenerated a final time and reflects
429
+ the completed state.
430
+ 3. The parallel checkpoint passes validation with `require_complete` for
431
+ `artifact_type: "parallel-orchestrator-state"`.
432
+ 4. Each item's acceptance criteria have been checked off in that item's own acceptance-criteria
433
+ source files by that item's own run, per the `acceptance-criteria-tracking` skill.
434
+
435
+ In `open` mode there is no automatic completion. The run is a standing queue and terminates only via
436
+ `/parallel-close`, which is owned by F6 and is neither specified nor shipped by this feature. Do not
437
+ synthesize a completion condition for an `open`-mode run.
438
+
439
+ No completion condition involves a run-level pull request. There is no final integration pull
440
+ request on this surface, so completion keys on the per-item terminal states above and on nothing
441
+ else.
442
+
443
+ ## Mutation Protocol (F6)
444
+
445
+ A parallel run is mutable while it executes. Three slash commands mutate it, and each one appends
446
+ exactly one `mutations[]` entry to the parallel-orchestrator checkpoint on success:
447
+
448
+ - `/parallel-add <issue|potential-entry>` — `.claude/skills/parallel-add/SKILL.md`. Admits one new
449
+ item: the item enters `proposed`, is prepared through a preparation-mode child
450
+ `Agent(orchestrator)` run reusing the `route_id: preparation` contract unchanged, its conflict
451
+ edges are computed against ALL items including in-flight ones, and the admission decision places
452
+ it in the current cohort only when it conflicts with no member of that cohort, pinned or
453
+ unstarted, and otherwise defers it and recolors the unstarted subgraph.
454
+ - `/parallel-remove <item> [--disposition detach|abandon]` —
455
+ `.claude/skills/parallel-remove/SKILL.md`. Removes one item per the state-dependent behavior
456
+ table: an unstarted item is withdrawn and the unstarted subgraph is recolored; an in-flight item
457
+ requires an explicit disposition and is rejected without one; a merged item is rejected.
458
+ - `/parallel-close <parallel-slug>` — `.claude/skills/parallel-close/SKILL.md`. Terminates an
459
+ `open`-mode run. Rejected while any item is `in_flight`.
460
+
461
+ Every mutation re-derives durable state (`git worktree list --porcelain`, `git branch`,
462
+ `gh pr view`) before it is applied, because the checkpoint is a cache of durable state and not the
463
+ source of truth. A rejected mutation appends no entry and changes no state.
464
+
465
+ The decision logic for all three commands is the pure engine
466
+ `scripts/dev_tools/parallel_mutation_protocol.py`. It decides; it never applies. Item keys are
467
+ integers (`items[].issue_num`) everywhere on this surface.
468
+
469
+ ### Pinning invariant
470
+
471
+ **In-flight items are pinned. Scheduling is recomputed only over the not-yet-started subgraph, and
472
+ recoloring is a pure function of `(remaining subgraph, pinned set, pinned cohort index)`.**
473
+
474
+ The recolor function takes the induced subgraph of unstarted items (states `proposed`, `admitted`,
475
+ `prepared`, `scheduled`), the pinned set (state `in_flight`), the current generation, and — as a
476
+ third scheduling input — the current cohort index `current_cohort` that the pinned items occupy. It
477
+ returns cohort assignments for unstarted items ONLY: the returned mapping's key set equals the
478
+ unstarted set exactly and contains no pinned key. A pinned item is therefore absent from the result
479
+ rather than reassigned, and that absence IS the guarantee that a mutation never moves work already
480
+ running.
481
+
482
+ **Pinned-barrier offset.** The returned indices are ABSOLUTE checkpoint cohort indices at or above
483
+ `current_cohort`, and strictly above `current_cohort` whenever any conflict edge joins an unstarted
484
+ item to a pinned item. When no such edge exists the lowest returned index equals `current_cohort`
485
+ exactly, so unstarted items may share the running cohort and `max_concurrency` slot filling is
486
+ preserved. The offset is a single uniform shift applied to every color class, so F2's distinct color
487
+ classes remain distinct cohort indices and independence within the unstarted set is preserved
488
+ exactly.
489
+
490
+ Write the returned indices VERBATIM into `cohorts[].index`; never re-base them to zero. `cohorts[]`
491
+ carries exactly ONE current-generation entry per index, so returned keys landing on index
492
+ `current_cohort` JOIN the pinned members of that one entry instead of forming a second entry with
493
+ the same index, which F3 invariant 13 rejects.
494
+
495
+ Coloring is delegated in full to the Welsh-Powell entry point
496
+ `bash .claude/lib/bash/compute-cohorts.sh --keys "<k1> ..." --edges "<a>:<b> ..."`, the
497
+ destination-runtime port of `compute_cohorts` in
498
+ `scripts/dev_tools/parallel_cohort_computation.py`, which remains the repository authority and the
499
+ parity reference. No part of the coloring, the vertex ordering, or
500
+ the tie-break is reimplemented by the mutation engine, and the offset is applied entirely inside the
501
+ mutation engine's own recolor function.
502
+
503
+ **Two design corrections (spec 1.2).** Admission previously checked only the `in_flight` subset, but
504
+ `max_concurrency` caps simultaneously in-flight items independently of cohort size and refills each
505
+ freed slot from the same current cohort — see
506
+ `## Cohort Barrier and Max-Concurrency Slot Filling` — so the current cohort durably holds
507
+ not-yet-launched `scheduled` members that a candidate can contend with. Recoloring previously
508
+ dropped the candidate-to-pinned edges together with the pinned vertices, which discarded the pinned
509
+ CONSTRAINT as well as the pinned VERTICES and returned a deferred candidate to cohort 0, the current
510
+ cohort, whenever the cohort barrier held `current_cohort` at 0.
511
+
512
+ ### Recompute boundary
513
+
514
+ `recolor_generation` increments by exactly one on a recompute and is stamped unchanged on every
515
+ other operation. The boundary is normative:
516
+
517
+ Operations that RECOMPUTE (`recolor_generation` increments by exactly one):
518
+
519
+ 1. **Deferred add** — the candidate conflicts with a member of the current cohort — pinned or
520
+ not-yet-launched — so the unstarted subgraph, including the new item, is recolored.
521
+ 2. **Remove of an unstarted item** — the vertex is dropped and the remaining unstarted subgraph is
522
+ recolored.
523
+ 3. **Drift-induced requeue** — the later-started item of a newly conflicting pair is halted and
524
+ requeued into a future cohort.
525
+
526
+ Operations that DO NOT recompute (generation stamped unchanged):
527
+
528
+ 1. **Admission into the current cohort with no conflict against any current-cohort member** — no
529
+ cohort assignment changes.
530
+ 2. **`detach`** — the detached item was pinned and was never a vertex of the unstarted subgraph, so
531
+ its departure cannot change the induced subgraph.
532
+ 3. **`abandon`** — same rationale as `detach`. An unstarted item previously deferred because of a
533
+ conflict with the now-abandoned item keeps its deferred cohort assignment: the assignment remains
534
+ valid and is at most conservative, and no opportunistic recompute is performed. This keeps
535
+ generation accounting minimal and deterministic.
536
+ 4. **`close`** — run termination changes no cohort assignment.
537
+
538
+ A non-recompute operation still appends exactly one `mutations[]` entry, stamping the current,
539
+ unchanged `recolor_generation`. A sequence of N operations from generation `g` therefore ends at
540
+ exactly `g` plus the number of recompute operations.
541
+
542
+ ### Per-op mutation-log entry contents
543
+
544
+ | Op case | `op` | `item_key` | `prior_state` | `new_state` | `disposition` | `recolor_generation` |
545
+ | --- | --- | --- | --- | --- | --- | --- |
546
+ | Add, no-conflict admit | `add` | item key | null | `scheduled` | null | `g` (unchanged) |
547
+ | Add, deferred | `add` | item key | null | `scheduled` | null | `g` + 1 |
548
+ | Remove, unstarted | `remove` | item key | prior state (`proposed`/`admitted`/`prepared`/`scheduled`) | `withdrawn` | null | `g` + 1 |
549
+ | Remove, `detach` | `remove` | item key | `in_flight` | `withdrawn` | `detach` | `g` (unchanged) |
550
+ | Remove, `abandon` | `remove` | item key | `in_flight` | `withdrawn` | `abandon` | `g` (unchanged) |
551
+ | Close | `close` | null (run-scoped) | null | null | null | `g` (unchanged) |
552
+ | Drift-induced requeue | `requeue` | item key | `in_flight` | `blocked` | null | `g` + 1 |
553
+
554
+ `prior_state` is null on BOTH add rows. The accompanying `prepared` -> `scheduled` transition is not
555
+ lost and is not recorded in the mutation entry: it is recorded as an item-state update in `items[]`
556
+ with the checkpoint's lifecycle timestamps, the same mechanism that records
557
+ `proposed` -> `admitted` -> `prepared` during preparation. `new_state` is null for `close` only, and
558
+ `item_key` is null for `close` only. `disposition` is non-null only on a `remove` entry whose
559
+ `prior_state` is `in_flight`. Every `at` timestamp comes from the engine's injected clock seam; the
560
+ engine never reads the wall clock. No field and no enum member is added to `mutations[]`; the nine
561
+ parallel enums of `.claude/rules/parallel-orchestration.md` are consumed, never extended.
562
+
563
+ Retrospective validation of the log is the helper
564
+ `scripts/dev_tools/_parallel_orchestrator_state_mutations.py`, wired into
565
+ `scripts/dev_tools/validate_parallel_orchestrator_state.py`. It requires every entry to carry all
566
+ seven fields and no eighth, and requires `recolor_generation` to be monotonically non-decreasing in
567
+ append order, which is what makes a lost update detectable after the fact.
568
+
569
+ ### Mode-dependent completion semantics
570
+
571
+ - **`closed` mode (the default).** The completion gate fires when every non-withdrawn item is
572
+ `merged` or `worktree_removed`, evaluated over per-item `merge_status`. Mid-execution mutation
573
+ remains permitted. The predicate is `is_closed_mode_complete` in the mutation engine.
574
+ - **`open` mode.** The run NEVER auto-completes. It is a standing queue and terminates only via
575
+ `/parallel-close`. The close record is the run's final mutation; nothing may be appended to
576
+ `mutations[]` after it. Do not synthesize a completion condition for an `open`-mode run.
577
+
578
+ A withdrawn item is exempt from the predicate in both modes: it left the run before reaching a merge
579
+ outcome, so requiring a terminal merge status of it would make every run that dropped an item
580
+ permanently incompletable. This is also why `detach` records `withdrawn` — the run does not wait for
581
+ a detached item.
582
+
583
+ ### Abandon confirmation-marker contract
584
+
585
+ The `abandon` disposition is destructive: it closes the item's pull request and removes its
586
+ worktree. Both side effects run through ONE deterministic CLI invocation of
587
+ `scripts/dev_tools/parallel_mutation_abandon_cli.py`, documented in full in
588
+ `.claude/skills/parallel-remove/SKILL.md`. Executing the abandon disposition through ad hoc `gh` or
589
+ `git` commands is prohibited, because an ad hoc command is not matchable and would bypass the
590
+ confirmation contract.
591
+
592
+ The contract is enforced by the PreToolUse hook
593
+ `.claude/hooks/enforce-parallel-abandon-gate.ps1` on the `Bash` matcher:
594
+
595
+ - A Bash command carrying the abandon disposition token MUST also carry the explicit confirmation
596
+ marker `--confirm-abandon` in the SAME command.
597
+ - Without the marker, the command is DENIED with a deny reason prefixed
598
+ `PARALLEL_ABANDON_BLOCKED`.
599
+ - With the marker, the command is allowed.
600
+ - A command carrying no abandon disposition token is out of scope and is allowed unchanged.
601
+
602
+ When the gate denies a command, add the confirmation marker deliberately. Do not reformulate the
603
+ command to evade the match: that defeats the only mechanism protecting a destructive operation.
604
+
605
+ ### Drift-requeue append contract
606
+
607
+ Radius drift detection consumes this protocol rather than reimplementing it. When drift halts the
608
+ later-started item of a newly conflicting pair, the requeue is recorded through the mutation
609
+ engine's `build_requeue_entry` constructor and the recolor through `recolor_unstarted`:
610
+
611
+ - Item state becomes `blocked` and per-item `merge_status` becomes `blocked_drift`.
612
+ - Exactly one `mutations[]` entry is appended with `op: requeue`, `prior_state: in_flight`,
613
+ `new_state: blocked`, `disposition: null`, and `recolor_generation` equal to `g` + 1 — the requeue
614
+ is a recompute.
615
+ - The recolor runs over the unstarted subgraph only, so no other in-flight item moves. Its call
616
+ shape is the five-argument form
617
+ `recolor_unstarted(unstarted_items, conflict_edges, pinned, current_generation, current_cohort=current_cohort)`,
618
+ where `current_cohort` is required and keyword-only.
619
+
620
+ The drift event itself is recorded in `drift_events[]`, which this protocol does not write. See
621
+ `## Radius Drift Detection (F8)`.
622
+
623
+ ## Enforcement Hooks (F7)
624
+
625
+ Three hooks enforce this surface mechanically: two new, one an additive extension of an existing epic
626
+ hook. All three fail closed — once a call is in scope, every unresolvable condition (missing or
627
+ malformed checkpoint, unresolvable target item, missing `items[]` record, missing `merge_status`)
628
+ denies. Out-of-scope calls allow without reading the checkpoint.
629
+
630
+ **Layer 1, per-call deterrent.** `.claude/hooks/enforce-parallel-cohort-barrier.ps1`, a `PreToolUse`
631
+ hook on the `Agent` matcher. It activates only when `subagent_type` is `orchestrator` and the
632
+ serialized delegation prompt carries the marker `Parallel mode: true` (emitted per
633
+ `## Parallel-Mode Kickoff Parameter`). It then resolves the target from the
634
+ `docs/features/active/<folder>` token in the prompt matched against `items[].feature_folder`, reads
635
+ `artifacts/orchestration/parallel-orchestrator-state.json`, projects cohorts to the rows whose
636
+ `generation` equals `recolor_generation`, and denies with a reason prefixed
637
+ `PARALLEL_COHORT_BARRIER_BLOCKED` unless every `conflict_edges[]` neighbour in a strictly prior
638
+ current-generation cohort has `merge_status` of `merged` or `worktree_removed`. `ci_green` does not
639
+ satisfy the barrier. Same-cohort and later-cohort neighbours do not block Layer 1.
640
+
641
+ **Layer 2, retrospective backstop.** `validate_cohort_barrier_ordering` in
642
+ `scripts/dev_tools/_parallel_orchestrator_state_cohort_barrier.py`, invoked from
643
+ `validate_parallel_orchestrator_state_text` and reached at `parallel-orchestrator` `SubagentStop` time
644
+ by the `.claude/settings.json` matcher that runs `.claude/hooks/validate-orchestrator-output.ps1` with
645
+ `-ArtifactType parallel-orchestrator-state`. It appends exactly one message per violated edge, in the
646
+ form `PARALLEL_COHORT_BARRIER_VIOLATION: <a> ran concurrently with conflicting <b>`. The check is
647
+ key-gated on `conflict_edges` and `cohorts`, so a checkpoint lacking either key yields zero errors,
648
+ and it adds no checkpoint fields.
649
+
650
+ **Why both layers are required.** A `PreToolUse` hook fires once per tool call with no cross-call or
651
+ conversation-state visibility, so it cannot see the rest of a batch of concurrent `Agent` calls and
652
+ cannot reject the batch as a whole. The retrospective validator does see the recorded batch, but only
653
+ after those calls executed. Layer 1 deters the individual out-of-order launch; Layer 2 detects the
654
+ concurrent batch Layer 1 structurally cannot. Removing either layer reopens the gap, so neither
655
+ substitutes for the other and neither substitutes for the procedure in
656
+ `## Cohort Barrier and Max-Concurrency Slot Filling`.
657
+
658
+ **Worktree removal gate.** `.claude/hooks/enforce-parallel-worktree-removal-gate.ps1`, a `PreToolUse`
659
+ hook on the `Bash` matcher, intercepts `git worktree remove` and matches the normalized target path
660
+ against `items[].worktree_path`. Removal is allowed only when that item's `merge_status` is `merged`
661
+ or `worktree_removed`; anything else — including an unreadable checkpoint or no matching record —
662
+ denies with a reason prefixed `PARALLEL_WORKTREE_REMOVAL_BLOCKED`. Commands that are not
663
+ `git worktree remove` always allow. This is the mechanical counterpart to `## Worktree Cleanup`.
664
+
665
+ **Invocation-origin extension.** `.claude/hooks/enforce-epic-invocation-origin.ps1` was extended
666
+ additively so `$script:GatedSubagentTypes` lists `epic-planner`, `epic-orchestrator`,
667
+ `parallel-planner`, and `parallel-orchestrator`. An `Agent(parallel-planner)` or
668
+ `Agent(parallel-orchestrator)` call whose caller `agent_type` is `orchestrator` is denied with a
669
+ reason prefixed `PARALLEL_INVOCATION_ORIGIN_BLOCKED`, because both parallel personas delegate to
670
+ `Agent(orchestrator)` and an orchestrator-originated invocation would nest `orchestrator` inside its
671
+ own delegation chain; invoke either persona from the main session instead. Main-thread invocations
672
+ (absent or blank caller `agent_type`) and non-orchestrator callers continue to allow. Epic behaviour
673
+ is unchanged: the `EPIC_INVOCATION_ORIGIN_BLOCKED` reason string is byte-identical for epic targets.
674
+
675
+ ## Radius Drift Detection (F8)
676
+
677
+ ### Radius Drift Detection and Drift Gate
678
+
679
+ An in-flight item whose actual diff escapes its declared `blast_radius.paths` invalidates the
680
+ concurrency guarantee for every item running beside it, which is the dominant failure mode of this
681
+ surface and the compensating control for heuristically derived radii. Detection is the
682
+ execution-time half of a paired mitigation: F1's plan-time coverage validation bounds
683
+ under-reporting when the radius is derived, and this procedure bounds it while the item runs.
684
+ Neither half eliminates the risk. Nothing in this section re-derives F1's matcher or F1's
685
+ contention relation; both are imported by the implementation.
686
+
687
+ #### Seven-Step Procedure
688
+
689
+ 1. Compare the observed changed-path list against the item's declared `blast_radius.paths`.
690
+ 2. On escape, record one `drift_events[]` entry and raise a synthetic Blocking finding in the
691
+ child's own `remediation-inputs.<yyyy-MM-ddTHH-mm>.md`.
692
+ 3. Quiesce: suspend admission of new items into the current cohort.
693
+ 4. Recompute conflicts using the observed radius in place of the drifting item's declared radius.
694
+ 5. If the escape newly conflicts with a concurrently in-flight item, halt the later-started item of
695
+ that pair, record `merge_status: blocked_drift` with item `state: blocked`, and requeue it into a
696
+ future cohort.
697
+ 6. The child's existing R1 through R5 remediation loop processes the finding unmodified.
698
+ 7. **Resolve the recorded drift.** Actor: the `parallel-orchestrator`. Trigger: the consuming
699
+ remediation cycle exiting with `blocking_count == 0`. The parent then performs exactly one of two
700
+ writes to the item's `items[].blast_radius`. Either it re-records the radius from the
701
+ post-remediation diff — the library-built value the detecting invocation already emitted as the
702
+ `observed_radius` payload key, carrying `source: observed` and a `computed_at` that must be
703
+ strictly later than the event's `at` — or, when remediation widened the declared radius instead of
704
+ narrowing the diff, it extends `blast_radius.paths` so every `escaped_paths` entry of the latest
705
+ event is covered. The radius is never hand-constructed; it is the value the command line emitted,
706
+ so the module and shared-surface levels are resolved by F1's library. **No other write clears the
707
+ derived unresolved state**: appending an event cannot, because the action enum has no `resolved`
708
+ member and a zero-escape event is rejected, and changing `merge_status` or item `state` cannot,
709
+ because the derivation reads only `blast_radius`.
710
+
711
+ Deferral of admission into a **future** cohort remains allowed during quiesce; only admission into
712
+ the current cohort is suspended.
713
+
714
+ #### Child-Side Evaluation Point
715
+
716
+ Detection is evaluated at each child's pre-feature-review commit: the moment inside the child
717
+ orchestrator's Pre-Feature-Review Commit step between the successful commit and the `feature-review`
718
+ delegation. Evaluating after the commit is what makes the observed diff complete; evaluating before
719
+ the delegation is what lets the synthetic finding reach the same review pass.
720
+
721
+ The evaluation is active only for a child whose delegation prompt carries the `Parallel mode: true`
722
+ marker emitted by `## Parallel-Mode Kickoff Parameter`. A child running outside a parallel run
723
+ carries no such marker, evaluates no drift, and is unaffected.
724
+
725
+ #### CLI Invocation
726
+
727
+ Detection logic is pure and lives in `scripts/dev_tools/parallel_drift_detection.py` (escape
728
+ detection, `drift_events[]` construction, the derived quiesce predicate, and conflict recomputation)
729
+ and `scripts/dev_tools/parallel_drift_halt.py` (halt selection and the requeue seam). All I/O is
730
+ confined to the thin wrapper `scripts/dev_tools/parallel_drift_detection_cli.py`, invoked as:
731
+
732
+ ```
733
+ poetry run python -m scripts.dev_tools.parallel_drift_detection_cli \
734
+ --item-key <issue_num> \
735
+ [--checkpoint artifacts/orchestration/parallel-orchestrator-state.json] \
736
+ [--config config/blast-radius.json] \
737
+ [--at <yyyy-MM-ddTHH-mm>] [--computed-at <yyyy-MM-ddTHH-mm>] \
738
+ <CHANGED_PATH>...
739
+ ```
740
+
741
+ Argument surface: `--item-key` is the only required argument and is the item's `issue_num`;
742
+ `--checkpoint` and `--config` default to the two paths shown; `--at` is the timestamp recorded on
743
+ the `drift_events[]` entry and `--computed-at` the timestamp recorded on the observed radius, each
744
+ defaulting at the I/O boundary so the pure functions never read a clock; and the changed paths are
745
+ positional and variadic. An empty changed-path list is legal and yields `no_escape`.
746
+
747
+ The changed-path list is an argument, not something the module derives: the caller produces it with
748
+ `git diff --name-only <merge-base(origin/main, HEAD)> HEAD` at the child's pre-review commit, and
749
+ the module executes no git command of its own. Merge-base semantics matter — comparing against the
750
+ merge base rather than against the current `origin/main` tip keeps a concurrently merged peer item's
751
+ files from appearing as spurious drift. A rename lists both the old and the new path, and both must
752
+ be covered, which is the fail-closed direction.
753
+
754
+ Stdout carries exactly one JSON object; errors go to stderr only, so stdout is unconditionally
755
+ parseable:
756
+
757
+ ```json
758
+ {
759
+ "result": "no_escape | no_new_conflict | halt_required",
760
+ "item_key": 0,
761
+ "at": "",
762
+ "computed_at": "",
763
+ "escaped_paths": [],
764
+ "newly_conflicting_pairs": [],
765
+ "halted_item_keys": [],
766
+ "drift_event": null,
767
+ "observed_radius": null
768
+ }
769
+ ```
770
+
771
+ `result` is the verdict: `no_escape` means the diff stayed inside the declared radius;
772
+ `no_new_conflict` means paths escaped but the observed radius introduced no contention, so nothing
773
+ is halted; `halt_required` means at least one pair newly conflicts and `halted_item_keys` names the
774
+ later-started item of each. `newly_conflicting_pairs` holds ascending canonical `[a, b]` item-key
775
+ pairs, the same edge identity `conflict_edges[]` records, so a recomputed pair is comparable with a
776
+ recorded one without normalization. `drift_event` is `null` exactly when `result` is `no_escape`,
777
+ because an event with zero
778
+ escaped paths is not a drift event. `observed_radius` is the serialized observed `blast_radius` the
779
+ parent writes back in step 7 of `#### Seven-Step Procedure`: it carries the six invariant-9 keys with
780
+ `source: observed`, is built by F1's library rather than by hand, and is `null` exactly when `result`
781
+ is `no_escape`, on the same precondition as `drift_event`. Exit status is `0` on success, `1` on
782
+ missing or malformed input, and argparse's `2` on a usage error.
783
+
784
+ #### Synthetic Blocking Finding
785
+
786
+ The finding is written to `docs/features/active/<child-slug>/remediation-inputs.<yyyy-MM-ddTHH-mm>.md`
787
+ — the flat form directly in the item's own active feature-folder root, matching the existing
788
+ merge-conflict finding precedent rather than any folder-per-cycle variant.
789
+
790
+ It is written by `parallel-orchestrator`, which detects the escape and owns the checkpoint, reaching
791
+ the child's checkout through the item's recorded `items[].worktree_path`. That field is optional in
792
+ the schema and may be null; an absent value means the finding cannot be written, which the drift
793
+ gate treats as unwritten.
794
+
795
+ The file must contain the literal line `- Severity: Blocking`, matched case-sensitively by the child
796
+ orchestrator's post-review outcome evaluation, together with the escaped paths, the declared
797
+ patterns, and the required action.
798
+
799
+ #### Halt the Later-Started Item
800
+
801
+ The later-started item of a newly conflicting pair is halted, and **the drifting item is never the
802
+ one halted**. Halting the drifting item is not an option: it must not be implemented, and it must not
803
+ be offered as a configuration. The prohibition is unconditional and holds even when the drifting item
804
+ is the later starter by either tie-break. Two reasons make it necessary: the drifting item's work is
805
+ already broader than planned and is more expensive to unwind, and the drifting item is mid-remediation
806
+ on its own R1 through R5 loop for the drift finding, so halting it would deadlock the very remediation
807
+ that resolves the drift.
808
+
809
+ The exclusion is applied **at the call site, before the later-started comparator runs**:
810
+ `halted_item_keys` in `scripts/dev_tools/parallel_drift_detection_cli.py` drops the drifting key from
811
+ each pair's candidate list, then halts the single remaining candidate, or applies the comparator when
812
+ two remain. Because a recomputed pair holds two distinct canonical keys, the candidate list is always
813
+ one or two entries and can never be empty. It remains true, and remains a real structural guarantee,
814
+ that the selection function itself receives only the two start markers and no drift information at
815
+ all, so a caller cannot invert the comparator; the exclusion lives one level up because that is where
816
+ the drifting key is known.
817
+
818
+ Selection among two candidates is `argmax` over `(start_unknown, worktree_created_at, item_key)`, where
819
+ `worktree_created_at` is the adopted start-of-execution marker and `item_key` is the integer
820
+ `issue_num`. The three tie-breaks:
821
+
822
+ - Equal timestamps — the normal case for a same-minute cohort fan-out — deem the larger `issue_num`
823
+ later-started, so the smaller key survives, consistent with the ascending-item-key determinism
824
+ convention used in `## Cohort Barrier and Max-Concurrency Slot Filling`.
825
+ - A start timestamp present on exactly one item makes that timestamped item earlier-started, so the
826
+ item of unknown start is halted.
827
+ - Both timestamps absent falls through to the item-key tie-break.
828
+
829
+ Identical inputs produce identical halt decisions; every timestamp is a function input and no clock
830
+ is read inside the pure functions.
831
+
832
+ #### Quiesce Is Derived State
833
+
834
+ Quiesce is derived, never stored. Admission into the current cohort is suspended while
835
+ `has_unresolved_drift(events, items)` returns `True`. **No quiesce field is written anywhere** — not
836
+ to the checkpoint, not to the manifest, not to the status document. Deriving quiesce from the event
837
+ log and the item records makes it self-clearing on resolution and keeps the F3-owned checkpoint
838
+ contract untouched, consistent with `## Parallel-Level Checkpoint`. That exported predicate is the
839
+ single seam F6's admission control consults.
840
+
841
+ #### Requeue Through the Single Recolor Seam
842
+
843
+ The requeue passes through exactly one seam, `request_requeue_via_recolor`, which **requests** the
844
+ mutation and never performs one. No second recolor implementation exists in this feature: the seam
845
+ contains no coloring, no cohort assignment, and no graph logic. F6 owns the recolor engine that pins
846
+ in-flight items, recolors the unstarted subgraph, and writes the checkpoint.
847
+
848
+ The requested intent is:
849
+
850
+ - the joint item write `merge_status: blocked_drift` **and** item `state: blocked`. Both are
851
+ required together: the schema requires a `blocked_drift` merge status to accompany item state
852
+ `blocked`, so writing the merge status alone produces a checkpoint that fails validation;
853
+ - exactly one `mutations[]` entry
854
+ `{op: "requeue", item_key, at, prior_state: "in_flight", new_state: "blocked", disposition: null,
855
+ recolor_generation: <prior + 1>}`;
856
+ - `recolor_generation` incremented by exactly one.
857
+
858
+ `new_state` is the item-state value `blocked`, **not** `blocked_drift`. `mutations[].new_state` is
859
+ validated against the item-state enum, and `blocked_drift` is a `merge_status`, so it would be
860
+ rejected in that slot. `disposition` is null because a non-null disposition is permitted only on a
861
+ `remove` entry whose `prior_state` is `in_flight`.
862
+
863
+ #### Drift-Event Recording (A8)
864
+
865
+ Exactly one `drift_events[]` entry is appended per drift occurrence, carrying the strongest action
866
+ taken. `halted_later_started_item` subsumes `raised_blocking_finding`, so an occurrence that halted a
867
+ later-started item records that one event and does **not** additionally record a
868
+ `raised_blocking_finding` event for the same occurrence. The entry shape is the six F3-owned fields
869
+ `{item_key, declared, observed, escaped_paths, at, action}`; `escaped_paths` is non-empty by
870
+ definition. `drift_events[]` is append-only, and each append is a regeneration boundary for the
871
+ status document per `## Documentation Maintenance Boundaries`.
872
+
873
+ #### Two-Layer Drift Gate
874
+
875
+ A child's transition to review, and the item's progression toward merge, are gated while an
876
+ unresolved drift event exists for that item. The gate is implemented at both enforcement layers,
877
+ because a `PreToolUse` hook fires per call with no cross-call state visibility and can be bypassed.
878
+
879
+ - **Layer 1 — per-call deterrent.** `.claude/hooks/enforce-parallel-drift-gate.ps1`, registered on
880
+ the `PreToolUse` `Agent` matcher. It fires only when `subagent_type == "feature-review"` and the
881
+ prompt carries the `Parallel mode: true` marker, resolves the target item by scanning the prompt
882
+ for a `docs/features/active/<basename>` path token, reads
883
+ `artifacts/orchestration/parallel-orchestrator-state.json`, and denies with
884
+ `PARALLEL_DRIFT_GATE_BLOCKED` while the item's latest drift event is unresolved **and** its
885
+ synthetic finding has not been written. Requiring both conditions is what keeps the R4 review of
886
+ the remediation loop from being deadlocked: resolution itself requires a review, so once the
887
+ finding file exists, review proceeds. Allowed outright: a non-`feature-review` target, a prompt
888
+ without the marker, and a resolved or never-drifted item. Denied fail-closed: a missing or
889
+ unreadable checkpoint, an unresolvable target item, and an unreadable drift-event log. The hook
890
+ performs presence gating only — checkpoint-state reads plus one finding-file existence check. It
891
+ runs no git command and no path-glob matching, so all path-matching semantics stay in the single
892
+ Python implementation.
893
+ - **Layer 2 — retrospective backstop.** A key-gated invariant in
894
+ `scripts/dev_tools/validate_parallel_orchestrator_state.py`, implemented in the helper
895
+ `scripts/dev_tools/_parallel_orchestrator_state_drift.py`. It emits one
896
+ `PARALLEL_DRIFT_GATE_VIOLATION:` error per item whose latest drift event is unresolved while its
897
+ `merge_status` is in `{pr_open, ci_green, merged, worktree_removed}`. It is additive: a checkpoint
898
+ with no `drift_events` key produces zero new errors. An item resting at `blocked_drift` produces no
899
+ error, which is what makes the gate compatible with the state the halt path writes.
900
+
901
+ The Layer-1 finding-presence check is narrowed to the **current** drift event. A matched
902
+ `remediation-inputs.<yyyy-MM-ddTHH-mm>.md` file opens the gate only when the timestamp embedded in
903
+ its name is ordinally greater than or equal to the item's latest drift event's `at`; a finding
904
+ written by an earlier, unrelated remediation cycle is therefore ignored and does not open the gate
905
+ for drifted, unsurfaced work. The check remains **presence gating only**: the timestamp is taken
906
+ with a fixed-offset substring of the directory entry's name and compared ordinally, and the hook
907
+ performs no path-glob match, no git command, and no read of any file's content. A name whose
908
+ embedded substring is absent or not canonically formatted, and a latest-event `at` that is itself
909
+ not canonically formatted, both leave the gate closed, so an unreadable timestamp on either side
910
+ denies rather than allows. The canonical `yyyy-MM-ddTHH-mm` shape is required on both sides of every
911
+ timestamp comparison the gate makes, in both runtimes: an ungated ordinal comparison fails open,
912
+ because `-` sorts below `:` and a colon-bearing value therefore compares greater than a
913
+ hyphen-bearing value naming the same instant.
914
+
915
+ #### Resolution Semantics
916
+
917
+ This is the least obvious part of the feature and is stated here in full. `drift_events[].action` has
918
+ exactly two members, `raised_blocking_finding` and `halted_later_started_item`, and there is **no
919
+ `resolved` member**. `escaped_paths` must be non-empty, so a clean re-evaluation cannot be recorded
920
+ as a drift event at all. Resolution is therefore **derived** from fields that already exist, and this
921
+ feature adds no schema field and extends no enum.
922
+
923
+ The latest event for an item key is the one with the greatest `at`, ties broken by append order so
924
+ the later-appended record wins. A latest event for item K is **unresolved** unless at least one
925
+ disjunct holds against K's currently recorded `blast_radius`:
926
+
927
+ - **(a) Radius widened to cover the escape.** Every `escaped_paths` entry of K's latest event is
928
+ subsumed by the item's current `blast_radius.paths` under F1's corrected path-subsumption
929
+ predicate, which honours exact match, listed-directory prefix, and glob match symmetrically with
930
+ the contention relation.
931
+ - **(b) Radius re-recorded from a later observed diff.** The item's `blast_radius.source` is
932
+ `observed` **and** its `blast_radius.computed_at` is strictly later than the event's `at`. This
933
+ covers remediation that narrowed the diff instead of widening the radius.
934
+
935
+ The derivation is **fail-closed**: absent an affirmative parent write, neither disjunct holds, drift
936
+ stays unresolved, and the gate keeps denying. A malformed event log is likewise reported as
937
+ unresolved.
938
+
939
+ Each disjunct has a named producer, so the gate has a release path and nothing deadlocks. The
940
+ producer is step 7 of `#### Seven-Step Procedure`: the `parallel-orchestrator` performs one of the two
941
+ writes when the consuming remediation cycle exits with `blocking_count == 0`. Non-deadlock is
942
+ therefore a consequence of that named producer rather than a property asserted without one. For
943
+ disjunct (b) the value written is not hand-constructed: the command line emits it as the
944
+ `observed_radius` key of its stdout payload, documented under `#### CLI Invocation`, and the parent
945
+ applies that library-built value verbatim with a `computed_at` strictly later than the event's `at`.
946
+ For disjunct (a) the parent extends `blast_radius.paths` to cover every escaped path. The R1 through
947
+ R5 loop that drives the remediation preceding either write is **reused
948
+ unmodified**: `atomic-planner` plans the resolution, `atomic-executor` performs preflight then
949
+ resolves, `feature-review` re-audits, and the loop exits on zero blocking findings. No new
950
+ remediation loop is authored, no line of the existing loop is modified, and the shared
951
+ `remediation_pass` cap of 3 applies. `.claude/skills/orchestrate/SKILL.md` is not modified by this
952
+ feature.
953
+
954
+ #### Layer-1 Narrowing — a Documented Limitation
955
+
956
+ The Layer-1 PowerShell hook implements only disjunct **(b)**. Disjunct (a) requires F1's glob
957
+ matcher, and duplicating that matcher in PowerShell would create exactly the divergent-matcher
958
+ failure this feature exists to prevent, so the hook omits it and evaluates the ordinal
959
+ `computed_at > at` comparison alone.
960
+
961
+ The hook is therefore strictly more conservative than the Python derivation: omitting disjunct (a)
962
+ can only report unresolved where Python reports resolved. The direction of the narrowing is
963
+ **deny-only**, never allow-only, and the finding-file allowance above keeps that conservatism from
964
+ deadlocking review. Python remains the single authority on resolution, and a cross-runtime seam test
965
+ in `tests/scripts/claude-hooks/enforce-parallel-drift-gate-helpers.Tests.ps1` runs both runtimes
966
+ over one shared checkpoint-state table and fails when they diverge. This is a real limitation,
967
+ recorded here rather than omitted.
968
+
969
+ Recovery action for a spurious Layer-1 deny — one the operator can always take: re-record the item's
970
+ `blast_radius` from the later observed diff, which satisfies both runtimes because it is disjunct
971
+ (b), the one disjunct the hook evaluates.