@friedbotstudio/create-baseline 0.1.0

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 (197) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +222 -0
  3. package/bin/cli.js +247 -0
  4. package/obj/template/.claude/agents/swarm-worker.md +52 -0
  5. package/obj/template/.claude/bin/LICENSE +201 -0
  6. package/obj/template/.claude/bin/NOTICE +48 -0
  7. package/obj/template/.claude/commands/approve-spec.md +29 -0
  8. package/obj/template/.claude/commands/approve-swarm.md +27 -0
  9. package/obj/template/.claude/commands/grant-commit.md +19 -0
  10. package/obj/template/.claude/commands/init-project.md +191 -0
  11. package/obj/template/.claude/hooks/artifact_template_guard.sh +141 -0
  12. package/obj/template/.claude/hooks/consent_gate_grant.sh +89 -0
  13. package/obj/template/.claude/hooks/destructive_cmd_guard.sh +42 -0
  14. package/obj/template/.claude/hooks/env_guard.sh +36 -0
  15. package/obj/template/.claude/hooks/git_commit_guard.sh +93 -0
  16. package/obj/template/.claude/hooks/harness_continuation.sh +121 -0
  17. package/obj/template/.claude/hooks/lib/__pycache__/resume_writer.cpython-314.pyc +0 -0
  18. package/obj/template/.claude/hooks/lib/common.sh +328 -0
  19. package/obj/template/.claude/hooks/lib/resume_writer.py +341 -0
  20. package/obj/template/.claude/hooks/lint_runner.sh +55 -0
  21. package/obj/template/.claude/hooks/memory_pre_compact.sh +36 -0
  22. package/obj/template/.claude/hooks/memory_session_start.sh +244 -0
  23. package/obj/template/.claude/hooks/memory_stop.sh +173 -0
  24. package/obj/template/.claude/hooks/plantuml_syntax_guard.sh +161 -0
  25. package/obj/template/.claude/hooks/process_lifecycle_guard.sh +89 -0
  26. package/obj/template/.claude/hooks/setup_guard.sh +50 -0
  27. package/obj/template/.claude/hooks/spec_approval_guard.sh +81 -0
  28. package/obj/template/.claude/hooks/spec_design_calls_guard.sh +183 -0
  29. package/obj/template/.claude/hooks/spec_diagram_presence_guard.sh +141 -0
  30. package/obj/template/.claude/hooks/swarm_approval_guard.sh +39 -0
  31. package/obj/template/.claude/hooks/swarm_boundary_guard.sh +136 -0
  32. package/obj/template/.claude/hooks/tdd_order_guard.sh +176 -0
  33. package/obj/template/.claude/hooks/test_runner.sh +75 -0
  34. package/obj/template/.claude/hooks/tests/fixtures/ac008_byte_equal_reference.txt +12 -0
  35. package/obj/template/.claude/hooks/tests/memory_session_start_test.sh +285 -0
  36. package/obj/template/.claude/hooks/track_guard.sh +127 -0
  37. package/obj/template/.claude/hooks/verify_pass_guard.sh +88 -0
  38. package/obj/template/.claude/memory/README.md +108 -0
  39. package/obj/template/.claude/memory/_pending.md +15 -0
  40. package/obj/template/.claude/memory/_resume.md +12 -0
  41. package/obj/template/.claude/memory/conventions.md +26 -0
  42. package/obj/template/.claude/memory/decisions.md +29 -0
  43. package/obj/template/.claude/memory/landmarks.md +26 -0
  44. package/obj/template/.claude/memory/landmines.md +27 -0
  45. package/obj/template/.claude/memory/libraries.md +27 -0
  46. package/obj/template/.claude/memory/pending-questions.md +28 -0
  47. package/obj/template/.claude/project.json +221 -0
  48. package/obj/template/.claude/settings.json +110 -0
  49. package/obj/template/.claude/skills/archive/SKILL.md +48 -0
  50. package/obj/template/.claude/skills/archive/archive.sh +145 -0
  51. package/obj/template/.claude/skills/audit-baseline/SKILL.md +80 -0
  52. package/obj/template/.claude/skills/audit-baseline/audit.sh +919 -0
  53. package/obj/template/.claude/skills/brd/SKILL.md +44 -0
  54. package/obj/template/.claude/skills/brd/template.md +83 -0
  55. package/obj/template/.claude/skills/chore/SKILL.md +99 -0
  56. package/obj/template/.claude/skills/claude-automation-recommender/LICENSE +202 -0
  57. package/obj/template/.claude/skills/claude-automation-recommender/NOTICE +69 -0
  58. package/obj/template/.claude/skills/claude-automation-recommender/SKILL.md +358 -0
  59. package/obj/template/.claude/skills/claude-automation-recommender/references/hooks-patterns.md +226 -0
  60. package/obj/template/.claude/skills/claude-automation-recommender/references/mcp-servers.md +263 -0
  61. package/obj/template/.claude/skills/claude-automation-recommender/references/plugins-reference.md +98 -0
  62. package/obj/template/.claude/skills/claude-automation-recommender/references/skills-reference.md +408 -0
  63. package/obj/template/.claude/skills/claude-automation-recommender/references/subagent-templates.md +181 -0
  64. package/obj/template/.claude/skills/code-structure/SKILL.md +204 -0
  65. package/obj/template/.claude/skills/commit/SKILL.md +21 -0
  66. package/obj/template/.claude/skills/copywriting/SKILL.md +252 -0
  67. package/obj/template/.claude/skills/copywriting/evals/evals.json +111 -0
  68. package/obj/template/.claude/skills/copywriting/references/ai-writing-detection.md +200 -0
  69. package/obj/template/.claude/skills/copywriting/references/copy-frameworks.md +344 -0
  70. package/obj/template/.claude/skills/copywriting/references/natural-transitions.md +272 -0
  71. package/obj/template/.claude/skills/design-ui/SKILL.md +175 -0
  72. package/obj/template/.claude/skills/design-ui/references/design-vs-development.md +89 -0
  73. package/obj/template/.claude/skills/design-ui/references/intent-table.md +64 -0
  74. package/obj/template/.claude/skills/design-ui/references/orchestration.md +121 -0
  75. package/obj/template/.claude/skills/design-ui/references/state-machine.md +125 -0
  76. package/obj/template/.claude/skills/document/SKILL.md +66 -0
  77. package/obj/template/.claude/skills/documentation/SKILL.md +50 -0
  78. package/obj/template/.claude/skills/harness/SKILL.md +169 -0
  79. package/obj/template/.claude/skills/humanizer/SKILL.md +489 -0
  80. package/obj/template/.claude/skills/humanizer/references/ai-writing-detection.md +208 -0
  81. package/obj/template/.claude/skills/impeccable/PROJECT_NOTES.md +22 -0
  82. package/obj/template/.claude/skills/impeccable/SKILL.md +153 -0
  83. package/obj/template/.claude/skills/impeccable/agents/openai.yaml +4 -0
  84. package/obj/template/.claude/skills/impeccable/reference/adapt.md +190 -0
  85. package/obj/template/.claude/skills/impeccable/reference/animate.md +173 -0
  86. package/obj/template/.claude/skills/impeccable/reference/audit.md +134 -0
  87. package/obj/template/.claude/skills/impeccable/reference/bolder.md +113 -0
  88. package/obj/template/.claude/skills/impeccable/reference/brand.md +104 -0
  89. package/obj/template/.claude/skills/impeccable/reference/clarify.md +174 -0
  90. package/obj/template/.claude/skills/impeccable/reference/cognitive-load.md +106 -0
  91. package/obj/template/.claude/skills/impeccable/reference/color-and-contrast.md +105 -0
  92. package/obj/template/.claude/skills/impeccable/reference/colorize.md +154 -0
  93. package/obj/template/.claude/skills/impeccable/reference/craft.md +138 -0
  94. package/obj/template/.claude/skills/impeccable/reference/critique.md +213 -0
  95. package/obj/template/.claude/skills/impeccable/reference/delight.md +302 -0
  96. package/obj/template/.claude/skills/impeccable/reference/distill.md +111 -0
  97. package/obj/template/.claude/skills/impeccable/reference/document.md +427 -0
  98. package/obj/template/.claude/skills/impeccable/reference/extract.md +70 -0
  99. package/obj/template/.claude/skills/impeccable/reference/harden.md +347 -0
  100. package/obj/template/.claude/skills/impeccable/reference/heuristics-scoring.md +234 -0
  101. package/obj/template/.claude/skills/impeccable/reference/interaction-design.md +195 -0
  102. package/obj/template/.claude/skills/impeccable/reference/layout.md +141 -0
  103. package/obj/template/.claude/skills/impeccable/reference/live.md +513 -0
  104. package/obj/template/.claude/skills/impeccable/reference/motion-design.md +99 -0
  105. package/obj/template/.claude/skills/impeccable/reference/onboard.md +234 -0
  106. package/obj/template/.claude/skills/impeccable/reference/optimize.md +258 -0
  107. package/obj/template/.claude/skills/impeccable/reference/overdrive.md +130 -0
  108. package/obj/template/.claude/skills/impeccable/reference/personas.md +178 -0
  109. package/obj/template/.claude/skills/impeccable/reference/polish.md +232 -0
  110. package/obj/template/.claude/skills/impeccable/reference/product.md +62 -0
  111. package/obj/template/.claude/skills/impeccable/reference/quieter.md +99 -0
  112. package/obj/template/.claude/skills/impeccable/reference/responsive-design.md +114 -0
  113. package/obj/template/.claude/skills/impeccable/reference/shape.md +136 -0
  114. package/obj/template/.claude/skills/impeccable/reference/spatial-design.md +100 -0
  115. package/obj/template/.claude/skills/impeccable/reference/teach.md +137 -0
  116. package/obj/template/.claude/skills/impeccable/reference/typeset.md +124 -0
  117. package/obj/template/.claude/skills/impeccable/reference/typography.md +159 -0
  118. package/obj/template/.claude/skills/impeccable/reference/ux-writing.md +107 -0
  119. package/obj/template/.claude/skills/impeccable/scripts/cleanup-deprecated.mjs +284 -0
  120. package/obj/template/.claude/skills/impeccable/scripts/command-metadata.json +94 -0
  121. package/obj/template/.claude/skills/impeccable/scripts/design-parser.mjs +820 -0
  122. package/obj/template/.claude/skills/impeccable/scripts/detect-csp.mjs +198 -0
  123. package/obj/template/.claude/skills/impeccable/scripts/is-generated.mjs +69 -0
  124. package/obj/template/.claude/skills/impeccable/scripts/live-accept.mjs +465 -0
  125. package/obj/template/.claude/skills/impeccable/scripts/live-browser.js +4684 -0
  126. package/obj/template/.claude/skills/impeccable/scripts/live-inject.mjs +436 -0
  127. package/obj/template/.claude/skills/impeccable/scripts/live-poll.mjs +187 -0
  128. package/obj/template/.claude/skills/impeccable/scripts/live-server.mjs +679 -0
  129. package/obj/template/.claude/skills/impeccable/scripts/live-wrap.mjs +395 -0
  130. package/obj/template/.claude/skills/impeccable/scripts/live.mjs +247 -0
  131. package/obj/template/.claude/skills/impeccable/scripts/load-context.mjs +93 -0
  132. package/obj/template/.claude/skills/impeccable/scripts/modern-screenshot.umd.js +14 -0
  133. package/obj/template/.claude/skills/impeccable/scripts/pin.mjs +214 -0
  134. package/obj/template/.claude/skills/implement/SKILL.md +83 -0
  135. package/obj/template/.claude/skills/intake/SKILL.md +46 -0
  136. package/obj/template/.claude/skills/intake/template.md +61 -0
  137. package/obj/template/.claude/skills/integrate/SKILL.md +62 -0
  138. package/obj/template/.claude/skills/memory-flush/SKILL.md +172 -0
  139. package/obj/template/.claude/skills/memory-flush/sweep.py +286 -0
  140. package/obj/template/.claude/skills/memory-flush/tests/run.sh +327 -0
  141. package/obj/template/.claude/skills/prose/SKILL.md +119 -0
  142. package/obj/template/.claude/skills/rca/SKILL.md +42 -0
  143. package/obj/template/.claude/skills/rca/template.md +83 -0
  144. package/obj/template/.claude/skills/research/SKILL.md +75 -0
  145. package/obj/template/.claude/skills/scenario/SKILL.md +64 -0
  146. package/obj/template/.claude/skills/scout/SKILL.md +72 -0
  147. package/obj/template/.claude/skills/security/SKILL.md +75 -0
  148. package/obj/template/.claude/skills/simplify/SKILL.md +67 -0
  149. package/obj/template/.claude/skills/spec/SKILL.md +69 -0
  150. package/obj/template/.claude/skills/spec/template.md +274 -0
  151. package/obj/template/.claude/skills/spec-diagram-review/SKILL.md +81 -0
  152. package/obj/template/.claude/skills/spec-lint/SKILL.md +55 -0
  153. package/obj/template/.claude/skills/spec-lint/lint.sh +218 -0
  154. package/obj/template/.claude/skills/spec-render/SKILL.md +45 -0
  155. package/obj/template/.claude/skills/spec-render/render.sh +109 -0
  156. package/obj/template/.claude/skills/spec-traceability-review/SKILL.md +72 -0
  157. package/obj/template/.claude/skills/swarm-dispatch/SKILL.md +212 -0
  158. package/obj/template/.claude/skills/swarm-dispatch/swarm_merge.sh +154 -0
  159. package/obj/template/.claude/skills/swarm-plan/SKILL.md +90 -0
  160. package/obj/template/.claude/skills/swarm-plan/validate.sh +181 -0
  161. package/obj/template/.claude/skills/tdd/SKILL.md +100 -0
  162. package/obj/template/.claude/skills/technical-tutorials/SKILL.md +569 -0
  163. package/obj/template/.claude/skills/technical-tutorials/references/audience-context-README.md +53 -0
  164. package/obj/template/.claude/skills/technical-tutorials/references/audience-context.md +246 -0
  165. package/obj/template/.claude/skills/technical-tutorials/references/audience-example.md +175 -0
  166. package/obj/template/.claude/skills/technical-tutorials/references/audience-template.md +152 -0
  167. package/obj/template/.claude/skills/triage/SKILL.md +55 -0
  168. package/obj/template/.claude/skills/verify/SKILL.md +74 -0
  169. package/obj/template/.mcp.json +24 -0
  170. package/obj/template/CLAUDE.md +327 -0
  171. package/obj/template/docs/init/seed.md +585 -0
  172. package/obj/template/manifest.json +214 -0
  173. package/package.json +48 -0
  174. package/src/.mcp.template.json +24 -0
  175. package/src/.npmrc.template +2 -0
  176. package/src/CLAUDE.template.md +327 -0
  177. package/src/agents/swarm-worker.template.md +51 -0
  178. package/src/cli/conflict.js +31 -0
  179. package/src/cli/doctor.js +152 -0
  180. package/src/cli/install.js +93 -0
  181. package/src/cli/io.js +27 -0
  182. package/src/cli/manifest.js +38 -0
  183. package/src/cli/mcp.js +54 -0
  184. package/src/cli/merge.js +107 -0
  185. package/src/cli/plantuml.js +121 -0
  186. package/src/cli/util.js +10 -0
  187. package/src/memory/_pending.template.md +15 -0
  188. package/src/memory/_resume.template.md +12 -0
  189. package/src/memory/conventions.template.md +26 -0
  190. package/src/memory/decisions.template.md +29 -0
  191. package/src/memory/landmarks.template.md +26 -0
  192. package/src/memory/landmines.template.md +27 -0
  193. package/src/memory/libraries.template.md +27 -0
  194. package/src/memory/pending-questions.template.md +28 -0
  195. package/src/project.template.json +221 -0
  196. package/src/seed.template.md +585 -0
  197. package/src/settings.template.json +110 -0
@@ -0,0 +1,212 @@
1
+ ---
2
+ name: swarm-dispatch
3
+ owner: baseline
4
+ description: Execute a swarm plan wave by wave with filesystem isolation via git worktrees. For each wave, main context decides the scenario recipe + implementation contract for every task, then spawns one swarm-worker per task in parallel. Each worker executes its recipe and reports JSON status. Worktree merge-audit verifies write-set discipline before changes land on main. Aborts remaining waves on any audit or task failure.
5
+ argument-hint: "<slug — matches .claude/state/swarm/<slug>.json>"
6
+ ---
7
+
8
+ # swarm-dispatch — wave runner with worktree isolation
9
+
10
+ Invoked after `/swarm-plan` + `/approve-swarm`. The architecture is the user's principle made concrete:
11
+
12
+ > **Main context decides. Workers execute.**
13
+
14
+ Per task, before dispatch, you (main context) produce two recipes:
15
+ 1. The **scenario recipe** — exactly which failing tests the worker should write.
16
+ 2. The **implementation contract** — exactly which source files the worker may touch and what behavior they must implement.
17
+
18
+ The worker's prompt contains both recipes verbatim. The worker invokes `Skill(scenario)` then `Skill(implement)` and reports JSON. It makes no design decisions.
19
+
20
+ ## Isolation modes
21
+
22
+ Read `project.json → swarm.isolation` (default `"auto"`):
23
+
24
+ - `"auto"` → choose `worktree` if the project root is inside a git repo (`git rev-parse --is-inside-work-tree` succeeds), else `shared`.
25
+ - `"worktree"` → require a git repo; bail if absent.
26
+ - `"shared"` → never use worktrees; rely on `swarm_boundary_guard` for runtime enforcement.
27
+
28
+ **Default path is `worktree`.** The rest of this document describes that mode. The `shared` fallback is at the end.
29
+
30
+ ## Prereqs (worktree mode)
31
+
32
+ Verify in order, abort on any failure:
33
+
34
+ 1. `.claude/state/swarm/<slug>.json` exists, has `status: "planned"`, and a non-null `waves` array.
35
+ 2. `.claude/state/swarm_approvals/<slug>.approval` exists and begins with `APPROVED`.
36
+ 3. `.claude/state/swarm/active_wave.json` does **not** already exist (stale/racing dispatch — ask before clobbering).
37
+ 4. `git rev-parse --is-inside-work-tree` succeeds at the project root.
38
+ 5. Working tree is clean (`git status --porcelain` empty) **if** `project.json → swarm.refuse_dirty_tree` is true (default).
39
+
40
+ Record the baseline: `git rev-parse HEAD` → this SHA is the reference every worktree will be compared against at merge time.
41
+
42
+ ## Per-wave loop
43
+
44
+ For each wave in `plan.waves`, in order:
45
+
46
+ ### 1. Decide the recipes (main context)
47
+
48
+ For every task in the wave, produce:
49
+
50
+ - **Scenario recipe** — list of failing tests to write. Each: `name`, `covers`, `assertion`, `fixtures`. Plus `out-of-scope` list and `test target paths`.
51
+ - **Implementation contract** — `failing_tests` (the paths the scenario step will produce), `write_set` (from the plan), behavior contract (the spec's §Behavior excerpts for the task's ACs, plus §Design data model + contracts), project conventions (from `project.json`).
52
+ - **Style anchors** — 1–2 existing test files and 1–2 existing source files in the touched modules so the worker matches the project's idioms.
53
+
54
+ This is where the heavy thinking lives. Do it before dispatch — once a worker is running, the recipe cannot be changed.
55
+
56
+ ### 2. Raise the barrier
57
+
58
+ Write `.claude/state/swarm/active_wave.json`:
59
+
60
+ ```json
61
+ {
62
+ "slug": "<slug>",
63
+ "wave": <n>,
64
+ "isolation": "worktree",
65
+ "baseline_ref": "<HEAD SHA>",
66
+ "started_at": <epoch>,
67
+ "write_sets": [
68
+ {"task_id": "T-001", "files": [...]},
69
+ {"task_id": "T-003", "files": [...]}
70
+ ]
71
+ }
72
+ ```
73
+
74
+ In worktree mode this file is consumed by `swarm_merge.sh` (which reads `baseline_ref`). `swarm_boundary_guard` is dormant — writes happen inside worktrees that don't contain `active_wave.json`.
75
+
76
+ ### 3. Update plan status
77
+
78
+ Set each wave task's `status` to `"running"` inside `.claude/state/swarm/<slug>.json`.
79
+
80
+ ### 4. Dispatch the wave
81
+
82
+ One message, N parallel `Agent` calls — one per task. Each uses:
83
+
84
+ - `subagent_type: "swarm-worker"`
85
+ - `isolation: "worktree"`
86
+ - `run_in_background: true`
87
+
88
+ Worker prompt template (self-contained — the worker has no memory of this conversation):
89
+
90
+ ```
91
+ You are executing swarm task <T-XXX> from plan <slug>, in your own isolated
92
+ git worktree. Your write_set is the ONLY set of files you may modify.
93
+
94
+ # Task metadata
95
+ - task_id: <T-XXX>
96
+ - slug: <slug>
97
+ - ACs covered: <AC list>
98
+ - Component: <component id>
99
+
100
+ # Spec excerpt (behavior contract)
101
+ <paste §Behavior sequences for this task's ACs + §Design data-model/contract
102
+ rows the task touches. Keep under ~200 lines.>
103
+
104
+ # Scenario recipe — what tests to write
105
+ out-of-scope: [<scenarios explicitly NOT to write>]
106
+ test target paths: <test file paths>
107
+ style anchors: <1-2 existing test files>
108
+
109
+ scenarios:
110
+ - name: test_when_X_then_Y
111
+ covers: AC-001
112
+ assertion: "<one plain sentence>"
113
+ fixtures: [<paths/factories>]
114
+ - name: test_when_A_then_B
115
+ covers: AC-002
116
+ assertion: "..."
117
+ fixtures: [...]
118
+ - ...
119
+
120
+ # Implementation contract
121
+ write_set (STRICT — anywhere else fails the merge audit):
122
+ - <file 1>
123
+ - <file 2>
124
+ - ...
125
+
126
+ read_set (advisory):
127
+ - <file 1>
128
+ - ...
129
+
130
+ style anchors: <1-2 existing source files>
131
+ project conventions:
132
+ test.cmd: <...>
133
+ lint.cmd: <...>
134
+ tdd.test_globs: <...>
135
+
136
+ # Your job
137
+ 1. Invoke Skill(scenario) with the scenario recipe + test target paths.
138
+ 2. If all expected tests are RED, invoke Skill(implement) with the failing test
139
+ paths, the write_set, the behavior contract above, and the project
140
+ conventions.
141
+ 3. Report JSON on your final line per the swarm protocol:
142
+ {"task_id": "<T-XXX>", "status": "done" | "failed",
143
+ "files_touched": [...], "note": "<one short line>"}
144
+ ```
145
+
146
+ The `swarm-worker` agent's body already knows the protocol. The prompt contains the recipes; the worker executes them.
147
+
148
+ ### 5. Wait
149
+
150
+ Do not respond to the user until every task in the wave has completed. Each `Agent` return gives you the worktree path (if the worker made changes) and the JSON summary line.
151
+
152
+ ### 6. Per-task merge-audit
153
+
154
+ For each completed task:
155
+
156
+ ```
157
+ .claude/skills/swarm-dispatch/swarm_merge.sh \
158
+ .claude/state/swarm/<slug>.json \
159
+ <task-id> \
160
+ <worktree-path>
161
+ ```
162
+
163
+ Outcomes:
164
+ - **Exit 0**: audit passed, patch applied to main, worktree removed. Update task `status: "done"`.
165
+ - **Exit 1**: audit failed OR `git apply` failed. Worktree preserved for inspection. Update task `status: "failed"` with a `note` naming the offending file(s).
166
+ - **No worktree path returned** (worker made no changes): the harness auto-cleans the empty worktree. Mark task per the worker's self-reported JSON.
167
+
168
+ ### 7. Clear the barrier
169
+
170
+ Delete `.claude/state/swarm/active_wave.json`.
171
+
172
+ ### 8. Decide the wave's fate
173
+
174
+ - Every task `done` → advance to the next wave.
175
+ - Any task `failed` (worker-reported OR audit failure) → set plan `status: "failed"`, stop, surface the failed task(s) with their `note` and (for audit failures) the preserved worktree path.
176
+
177
+ ## After the last wave
178
+
179
+ 1. Set plan `status: "complete"`.
180
+ 2. Run `/integrate` on the full codebase — per-wave success is necessary but not sufficient; cross-component integration must be re-verified.
181
+ 3. If `/integrate` passes: tell the user "Swarm `<slug>` complete. `<N>` tasks across `<M>` waves. Next: `/document`."
182
+
183
+ ## Shared-mode fallback
184
+
185
+ When isolation is `"shared"`:
186
+
187
+ - No worktrees. Each `Agent` call uses `isolation` omitted or `"none"`.
188
+ - `active_wave.json` carries `isolation: "shared"` and the union of write_sets (no `baseline_ref`).
189
+ - `swarm_boundary_guard` is the runtime enforcer: writes in enforced paths must be in the union of active write_sets, else denied.
190
+ - **No per-task merge-audit** (no worktrees to diff). The guard catches drift out of the wave; cross-task bleed within the wave is a known limitation.
191
+ - After each wave: clear `active_wave.json`, update per-task status from the worker's self-reported JSON.
192
+
193
+ Use shared mode deliberately — it trades real safety (physical isolation) for runtime permissiveness. Worktree mode is preferred whenever git is available.
194
+
195
+ ## Failure recovery
196
+
197
+ - Plan stays in `"failed"` state for user inspection.
198
+ - In worktree mode, failed tasks' worktrees are preserved. The user can `cd` in, read the worker's changes, and either:
199
+ - Manually finish + commit to main, then mark the task done in the plan.
200
+ - Drop the worktree (`git worktree remove --force <path>`) and re-plan.
201
+ - In shared mode, partial writes may have landed on main. `git status` shows them; revert or keep as appropriate.
202
+ - **Never auto-retry a failed task.** Failures warrant human attention.
203
+
204
+ ## Constraints
205
+
206
+ - **Recipes are decided before dispatch.** Once a worker is running, you cannot change its recipe. Plan with that in mind.
207
+ - **`run_in_background: true` is mandatory** on every `Agent` call inside a wave. Foreground calls would serialize the wave.
208
+ - **`isolation: "worktree"` is mandatory** in worktree mode. Without it, the merge-audit guarantee collapses.
209
+ - **One message, N parallel `Agent` calls.** Sequential issuance defeats parallelism.
210
+ - **`subagent_type` is always `swarm-worker`.** No per-stack variants — stack-specific skill loading is handled by the worker template's `{{SKILLS}}` token at `/init-project` time, not by spawning different agents.
211
+ - **Never touch source files from this skill.** This orchestrator only reads and updates `.claude/state/`. File edits happen inside workers; merges happen via `swarm_merge.sh`.
212
+ - **`active_wave.json` lingering** after abnormal termination is recoverable: delete it, inspect per-task status, re-dispatch the first incomplete wave.
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env bash
2
+ # swarm_merge.sh — post-task merge + audit tool for worktree-isolated swarm tasks.
3
+ #
4
+ # Usage: swarm_merge.sh <plan-path> <task-id> <worktree-path>
5
+ #
6
+ # Inputs:
7
+ # <plan-path> .claude/state/swarm/<slug>.json
8
+ # <task-id> e.g. "T-001" — must exist in plan.tasks[].id
9
+ # <worktree-path> absolute path to the git worktree created by Agent(isolation="worktree")
10
+ #
11
+ # Preconditions:
12
+ # .claude/state/swarm/active_wave.json exists and contains `baseline_ref`
13
+ # (the commit SHA recorded when the wave started). The audit diffs the
14
+ # worktree against this baseline.
15
+ #
16
+ # Behaviour:
17
+ # 1. Loads the task's write_set from the plan.
18
+ # 2. Computes changed files: `git -C <worktree> diff <baseline> --name-only`.
19
+ # 3. AUDIT: every changed file must be in write_set. Any violation → fail loud,
20
+ # preserve the worktree, exit 1.
21
+ # 4. If clean: `git -C <worktree> diff <baseline>` | `git -C <main> apply` to
22
+ # land the changes on main.
23
+ # 5. Removes the worktree on success (`git worktree remove`).
24
+ #
25
+ # Exit codes:
26
+ # 0 merge applied successfully (or task made no changes)
27
+ # 1 audit failed, apply failed, or worktree could not be read
28
+ # 2 bad invocation / missing inputs
29
+
30
+ set -u
31
+
32
+ if [ "${1:-}" = "" ] || [ "${2:-}" = "" ] || [ "${3:-}" = "" ]; then
33
+ echo "usage: swarm_merge.sh <plan-path> <task-id> <worktree-path>" >&2
34
+ exit 2
35
+ fi
36
+
37
+ PLAN="$1"
38
+ TASK_ID="$2"
39
+ WT="$3"
40
+
41
+ ROOT="${CLAUDE_PROJECT_DIR:-$(pwd)}"
42
+
43
+ if [ ! -f "$PLAN" ]; then
44
+ echo "swarm_merge: plan not found at $PLAN" >&2
45
+ exit 2
46
+ fi
47
+
48
+ if [ ! -d "$WT" ]; then
49
+ echo "swarm_merge: worktree not found at $WT" >&2
50
+ exit 2
51
+ fi
52
+
53
+ PLAN="$PLAN" TASK_ID="$TASK_ID" WT="$WT" ROOT="$ROOT" python3 <<'PY'
54
+ import json, os, subprocess, sys
55
+
56
+ plan_path = os.environ['PLAN']
57
+ task_id = os.environ['TASK_ID']
58
+ wt = os.environ['WT']
59
+ root = os.environ['ROOT']
60
+
61
+ plan = json.load(open(plan_path))
62
+
63
+ task = next((t for t in plan.get('tasks', []) if t.get('id') == task_id), None)
64
+ if task is None:
65
+ print(f"swarm_merge: task {task_id} not found in plan", file=sys.stderr)
66
+ sys.exit(2)
67
+
68
+ write_set = set(task.get('write_set') or [])
69
+ if not write_set:
70
+ print(f"swarm_merge: task {task_id} has empty write_set — refusing to merge", file=sys.stderr)
71
+ sys.exit(2)
72
+
73
+ # Read baseline from active_wave.json
74
+ active_path = os.path.join(root, '.claude/state/swarm/active_wave.json')
75
+ try:
76
+ active = json.load(open(active_path))
77
+ except Exception as e:
78
+ print(f"swarm_merge: active_wave.json unreadable: {e}", file=sys.stderr)
79
+ sys.exit(2)
80
+
81
+ baseline = active.get('baseline_ref')
82
+ if not baseline:
83
+ print("swarm_merge: active_wave.json missing baseline_ref", file=sys.stderr)
84
+ sys.exit(2)
85
+
86
+ # Change detection: diff worktree against baseline.
87
+ r = subprocess.run(
88
+ ['git', '-C', wt, 'diff', baseline, '--name-only'],
89
+ capture_output=True
90
+ )
91
+ if r.returncode != 0:
92
+ print(f"swarm_merge: `git diff` in worktree failed: {r.stderr.decode(errors='replace')}",
93
+ file=sys.stderr)
94
+ sys.exit(1)
95
+
96
+ changed = [f for f in r.stdout.decode().splitlines() if f.strip()]
97
+
98
+ # If task made no changes, nothing to merge — clean up and exit OK.
99
+ if not changed:
100
+ rm = subprocess.run(['git', '-C', root, 'worktree', 'remove', wt], capture_output=True)
101
+ if rm.returncode != 0:
102
+ print(f"swarm_merge: worktree removal warned: {rm.stderr.decode(errors='replace').strip()}",
103
+ file=sys.stderr)
104
+ print(f"swarm_merge: OK — task {task_id} made no changes; worktree cleaned up")
105
+ sys.exit(0)
106
+
107
+ # AUDIT: every changed file must be in the declared write_set.
108
+ violations = [f for f in changed if f not in write_set]
109
+ if violations:
110
+ print(f"swarm_merge: AUDIT FAIL — task {task_id} modified files outside its declared write_set:")
111
+ for v in sorted(violations):
112
+ print(f" + {v}")
113
+ print(f"Declared write_set ({len(write_set)} file(s)):")
114
+ for f in sorted(write_set):
115
+ print(f" - {f}")
116
+ print(f"Worktree preserved for inspection at: {wt}")
117
+ print(f"Branch: swarm/{task_id} (inspect with `git log swarm/{task_id}` or `git diff {baseline}..swarm/{task_id}`)")
118
+ sys.exit(1)
119
+
120
+ # Audit passed. Extract full patch and apply to main.
121
+ r = subprocess.run(['git', '-C', wt, 'diff', baseline], capture_output=True)
122
+ if r.returncode != 0:
123
+ print(f"swarm_merge: `git diff` (full patch) failed: {r.stderr.decode(errors='replace')}",
124
+ file=sys.stderr)
125
+ sys.exit(1)
126
+
127
+ patch = r.stdout
128
+ if not patch.strip():
129
+ # Diff --name-only found files but full diff is empty — bizarre, but bail safely.
130
+ print(f"swarm_merge: diff was empty despite changed files. Worktree preserved at {wt}",
131
+ file=sys.stderr)
132
+ sys.exit(1)
133
+
134
+ apply_r = subprocess.run(
135
+ ['git', '-C', root, 'apply', '--whitespace=nowarn', '-'],
136
+ input=patch, capture_output=True
137
+ )
138
+ if apply_r.returncode != 0:
139
+ print(f"swarm_merge: APPLY FAIL — patch from {wt} did not apply cleanly to main:")
140
+ print(apply_r.stderr.decode(errors='replace').strip())
141
+ print(f"Worktree preserved for inspection at: {wt}")
142
+ sys.exit(1)
143
+
144
+ # Remove the worktree.
145
+ rm = subprocess.run(['git', '-C', root, 'worktree', 'remove', wt], capture_output=True)
146
+ if rm.returncode != 0:
147
+ # Not fatal — warn but consider the merge successful.
148
+ print(f"swarm_merge: WARNING — could not remove worktree at {wt}: "
149
+ f"{rm.stderr.decode(errors='replace').strip()}", file=sys.stderr)
150
+
151
+ print(f"swarm_merge: OK — task {task_id} merged ({len(changed)} file(s))")
152
+ for f in sorted(changed):
153
+ print(f" + {f}")
154
+ PY
@@ -0,0 +1,90 @@
1
+ ---
2
+ name: swarm-plan
3
+ owner: baseline
4
+ description: Decompose an approved spec into a dependency-ordered swarm plan — one task per component, each with an explicit write_set. Produces `.claude/state/swarm/<slug>.json` with tasks + waves. The wave scheduler guarantees pairwise-disjoint write_sets within each wave so parallel dispatch is provably conflict-free.
5
+ ---
6
+
7
+ # swarm-plan — Phase 5.5: decompose for parallel execution
8
+
9
+ Invoked after `/approve-spec` and before `/tdd` on any spec that has ≥3 independent components worth parallelizing (per `project.json → swarm.min_tasks_worth_swarming`). For smaller specs, skip swarm and go straight to `/tdd` solo.
10
+
11
+ ## Prereqs
12
+
13
+ 1. `.claude/state/spec_approvals/<slug>.approval` exists (spec is human-approved).
14
+ 2. `docs/specs/<slug>.md` exists and passes `/spec-lint`.
15
+ 3. `docs/scout/<slug>.md` exists (component → file mapping is its job).
16
+
17
+ If any prereq is missing, stop and surface what's needed.
18
+
19
+ ## Output contract
20
+
21
+ `.claude/state/swarm/<slug>.json`:
22
+
23
+ ```json
24
+ {
25
+ "slug": "<slug>",
26
+ "spec": "docs/specs/<slug>.md",
27
+ "created_at": <epoch>,
28
+ "status": "planned",
29
+ "tasks": [
30
+ {
31
+ "id": "T-001",
32
+ "title": "<what the task does — one line>",
33
+ "component": "<component id from C4 Component diagram>",
34
+ "acs": ["AC-001", "AC-002"],
35
+ "write_set": ["src/foo/bar.py", "tests/foo/test_bar.py"],
36
+ "read_set": ["src/common/http.py"],
37
+ "depends_on": []
38
+ }
39
+ ],
40
+ "waves": null
41
+ }
42
+ ```
43
+
44
+ You produce `tasks[]`. The validator (`validate.sh`) computes `waves[]` deterministically via Kahn-with-disjointness.
45
+
46
+ ## Steps
47
+
48
+ 1. **Read upstream**: the spec, the scout report, the approval token. If an older `.claude/state/swarm/<slug>.json` exists, confirm with the user whether to overwrite (replan) or abort.
49
+ 2. **Extract inputs** from the spec:
50
+ - **Components**: every `Component(id, …)` in the C4 Component diagram (there may be multiple C4 Component diagrams, one per container).
51
+ - **ACs**: every row in the Acceptance criteria table.
52
+ - **Dependency edges**: every `A --> B` in the spec's dependency-graph fence.
53
+ - **Behavior ↔ component mapping**: for each AC, the sequence diagram it references names participants; treat each non-actor, non-external participant as a component this AC touches.
54
+ 3. **Get file mapping** from the scout report: for each component id, the files that back it. If the spec introduces greenfield components not in the scout, propose new file paths and flag them under a "new_paths" note in your plan summary — they will be accepted by the boundary guard when declared in `write_set`.
55
+ 4. **Construct tasks** — one per component (per `swarm.granularity: component`):
56
+ - `id`: T-001, T-002, … in stable order.
57
+ - `title`: one-line imperative description.
58
+ - `component`: the C4 component id.
59
+ - `acs`: every AC whose sequence names this component.
60
+ - `write_set`: union of (component files) + (test files covering those ACs). Every file must be explicit; no globs.
61
+ - `read_set`: files this task will consult but not modify. Advisory; not enforced.
62
+ - `depends_on`: for each component B such that this component depends on B (per the dependency graph), include the task id of the task owning B.
63
+ 5. **Merge overlapping tasks where forced**:
64
+ - If two tasks share any file AND have no `depends_on` relationship, either introduce a `depends_on` edge (making them sequential across waves) or merge them into one task. Merging is preferred when they're on the same component.
65
+ 6. **Validate the plan**:
66
+ ```
67
+ .claude/skills/swarm-plan/validate.sh docs/specs/<slug>.md .claude/state/swarm/<slug>.json
68
+ ```
69
+ The validator checks: required fields, depends_on references resolve, DAG is acyclic, and assigns `waves[]`. If validation fails, it prints a precise error — fix the plan and re-run.
70
+ 7. **Surface the plan** to the user as a table:
71
+
72
+ ```
73
+ Swarm plan for <slug> — <N> tasks across <M> waves
74
+
75
+ wave 1:
76
+ T-001 webhook-worker [AC-001, AC-002] 3 files
77
+ T-003 backoff-policy [AC-004] 2 files
78
+ wave 2:
79
+ T-002 webhook-retry [AC-003] 2 files (needs T-001)
80
+ ```
81
+
82
+ 8. Tell the user: "Swarm planned at `.claude/state/swarm/<slug>.json`. Review it, then run `/approve-swarm <slug>`. After approval, run `/swarm-dispatch <slug>`."
83
+
84
+ ## Constraints
85
+
86
+ - **Never dispatch from this skill.** Planning and execution are separated by a human consent gate (`/approve-swarm`).
87
+ - **Every file in a `write_set` must be a concrete path**, not a glob. The boundary guard does string-level membership checks.
88
+ - **The `validate.sh` script is the source of truth for wave assignment.** Do not hand-write `waves[]`.
89
+ - **Greenfield files are allowed** in `write_set` even if they don't exist yet — the guard checks declared ownership, not disk presence.
90
+ - **If validation keeps failing**, the problem is usually that two tasks share a file with no dependency. Either merge or introduce a dependency edge.
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env bash
2
+ # swarm-plan validator — verifies a draft plan and assigns waves deterministically.
3
+ #
4
+ # Usage: validate.sh <spec-path> <plan-path>
5
+ #
6
+ # Reads plan-path (JSON), performs:
7
+ # - schema check: required fields on every task
8
+ # - reference check: depends_on ids all resolve to tasks in the plan
9
+ # - acyclicity: DAG has no cycles (Kahn's algorithm)
10
+ # - wave assignment: topological sort with pairwise-disjoint write_set constraint
11
+ #
12
+ # On success, rewrites plan-path with `waves` populated. Exit 0.
13
+ # On failure, prints the precise violation to stderr and exits non-zero.
14
+
15
+ set -u
16
+
17
+ if [ "${1:-}" = "" ] || [ "${2:-}" = "" ]; then
18
+ echo "usage: validate.sh <spec-path> <plan-path>" >&2
19
+ exit 2
20
+ fi
21
+ SPEC="$1"
22
+ PLAN="$2"
23
+
24
+ if [ ! -f "$PLAN" ]; then
25
+ echo "validate: plan not found at $PLAN" >&2
26
+ exit 2
27
+ fi
28
+
29
+ SPEC="$SPEC" PLAN="$PLAN" python3 <<'PY'
30
+ import json, os, sys, time
31
+
32
+ plan_path = os.environ["PLAN"]
33
+ try:
34
+ plan = json.load(open(plan_path))
35
+ except Exception as e:
36
+ print(f"validate: plan is not valid JSON: {e}", file=sys.stderr)
37
+ sys.exit(1)
38
+
39
+ errs = []
40
+
41
+ # Top-level fields.
42
+ for k in ("slug", "spec", "tasks"):
43
+ if k not in plan:
44
+ errs.append(f"missing top-level field: {k}")
45
+
46
+ tasks = plan.get("tasks") or []
47
+ if not isinstance(tasks, list) or not tasks:
48
+ errs.append("tasks[] must be a non-empty array")
49
+
50
+ # Per-task schema.
51
+ REQ = {"id", "title", "component", "acs", "write_set", "depends_on"}
52
+ ids = set()
53
+ for i, t in enumerate(tasks):
54
+ if not isinstance(t, dict):
55
+ errs.append(f"task[{i}] is not an object"); continue
56
+ missing = REQ - set(t.keys())
57
+ if missing:
58
+ errs.append(f"task[{i}] missing fields: {sorted(missing)}")
59
+ continue
60
+ if not isinstance(t["id"], str) or not t["id"]:
61
+ errs.append(f"task[{i}].id must be a non-empty string")
62
+ if t["id"] in ids:
63
+ errs.append(f"duplicate task id: {t['id']}")
64
+ ids.add(t["id"])
65
+ for listfield in ("acs", "write_set", "depends_on"):
66
+ v = t.get(listfield)
67
+ if not isinstance(v, list) or not all(isinstance(x, str) for x in v):
68
+ errs.append(f"task {t.get('id', '?')}.{listfield} must be a list of strings")
69
+ if not t["write_set"]:
70
+ errs.append(f"task {t['id']}.write_set is empty — every task must declare at least one file")
71
+
72
+ if errs:
73
+ for e in errs: print(f"validate: {e}", file=sys.stderr)
74
+ sys.exit(1)
75
+
76
+ # depends_on references resolve.
77
+ for t in tasks:
78
+ for d in t["depends_on"]:
79
+ if d not in ids:
80
+ errs.append(f"task {t['id']}.depends_on references unknown id: {d}")
81
+ if d == t["id"]:
82
+ errs.append(f"task {t['id']} depends on itself")
83
+
84
+ if errs:
85
+ for e in errs: print(f"validate: {e}", file=sys.stderr)
86
+ sys.exit(1)
87
+
88
+ # Cycle detection (Kahn's).
89
+ indeg = {t["id"]: 0 for t in tasks}
90
+ outedges = {t["id"]: [] for t in tasks}
91
+ for t in tasks:
92
+ for d in t["depends_on"]:
93
+ # edge: d -> t (t depends on d; d must finish first).
94
+ outedges[d].append(t["id"])
95
+ indeg[t["id"]] += 1
96
+
97
+ by_id = {t["id"]: t for t in tasks}
98
+ ready = sorted([tid for tid, deg in indeg.items() if deg == 0])
99
+ visited = 0
100
+ topo_order = []
101
+ indeg_work = dict(indeg)
102
+ ready_work = list(ready)
103
+ while ready_work:
104
+ nxt = ready_work.pop(0)
105
+ topo_order.append(nxt)
106
+ visited += 1
107
+ for n in sorted(outedges[nxt]):
108
+ indeg_work[n] -= 1
109
+ if indeg_work[n] == 0:
110
+ ready_work.append(n)
111
+ ready_work.sort()
112
+
113
+ if visited != len(tasks):
114
+ unvisited = [tid for tid in indeg if indeg_work[tid] > 0]
115
+ print(f"validate: dependency graph has a cycle among: {unvisited}", file=sys.stderr)
116
+ sys.exit(1)
117
+
118
+ # Wave assignment: greedy topological layering with pairwise-disjoint write_set.
119
+ # Within a wave, all tasks share no files. Tasks whose deps are done AND whose
120
+ # write_set doesn't clash with already-chosen wave members go in; others wait.
121
+ indeg2 = dict(indeg)
122
+ placed = set()
123
+ waves = []
124
+ remaining = set(ids)
125
+ while remaining:
126
+ # Candidates: remaining tasks with indeg2 == 0.
127
+ candidates = sorted([tid for tid in remaining if indeg2[tid] == 0])
128
+ if not candidates:
129
+ # Shouldn't happen given acyclicity, but defensive.
130
+ print(f"validate: internal error — no candidates but tasks remain: {remaining}", file=sys.stderr)
131
+ sys.exit(1)
132
+
133
+ wave = []
134
+ wave_files = set()
135
+ # Greedy: heaviest task first (most files), so overflow tasks get pushed with
136
+ # smaller write_sets and can pack into later waves.
137
+ candidates.sort(key=lambda tid: (-len(by_id[tid]["write_set"]), tid))
138
+ overflow = []
139
+ for tid in candidates:
140
+ files = set(by_id[tid]["write_set"])
141
+ if files & wave_files:
142
+ overflow.append(tid)
143
+ else:
144
+ wave.append(tid)
145
+ wave_files |= files
146
+ if not wave:
147
+ # Every candidate conflicts with every other — impossible unless a single
148
+ # candidate had an internal duplicate; but then it's still placeable alone.
149
+ wave = [candidates[0]]
150
+ overflow = candidates[1:]
151
+ wave_files = set(by_id[candidates[0]]["write_set"])
152
+
153
+ wave.sort()
154
+ waves.append(wave)
155
+ for tid in wave:
156
+ remaining.discard(tid)
157
+ # Decrement indeg of descendants AFTER the whole wave is placed so the
158
+ # remaining wave members don't become each other's dependents mid-wave.
159
+ for tid in wave:
160
+ for n in outedges[tid]:
161
+ indeg2[n] -= 1
162
+
163
+ plan["waves"] = waves
164
+ plan["status"] = "planned"
165
+ plan["validated_at"] = int(time.time())
166
+
167
+ # Write back.
168
+ with open(plan_path, "w") as f:
169
+ json.dump(plan, f, indent=2)
170
+
171
+ # Human summary to stdout.
172
+ print(f"validate: OK — {len(tasks)} task(s) in {len(waves)} wave(s).")
173
+ for i, wave in enumerate(waves, start=1):
174
+ print(f" wave {i}:")
175
+ for tid in wave:
176
+ t = by_id[tid]
177
+ nfiles = len(t["write_set"])
178
+ acs = ",".join(t["acs"]) if t["acs"] else "-"
179
+ deps = ",".join(t["depends_on"]) if t["depends_on"] else "-"
180
+ print(f" {tid} {t['component']:<24} [{acs}] {nfiles} file(s) deps={deps}")
181
+ PY