aether-colony 5.1.0 → 5.3.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 (185) hide show
  1. package/.aether/aether-utils.sh +157 -42
  2. package/.aether/agents/aether-ambassador.md +140 -0
  3. package/.aether/agents/aether-archaeologist.md +108 -0
  4. package/.aether/agents/aether-architect.md +133 -0
  5. package/.aether/agents/aether-auditor.md +144 -0
  6. package/.aether/agents/aether-builder.md +184 -0
  7. package/.aether/agents/aether-chaos.md +115 -0
  8. package/.aether/agents/aether-chronicler.md +122 -0
  9. package/.aether/agents/aether-gatekeeper.md +116 -0
  10. package/.aether/agents/aether-includer.md +117 -0
  11. package/.aether/agents/aether-keeper.md +177 -0
  12. package/.aether/agents/aether-measurer.md +128 -0
  13. package/.aether/agents/aether-oracle.md +137 -0
  14. package/.aether/agents/aether-probe.md +133 -0
  15. package/.aether/agents/aether-queen.md +286 -0
  16. package/.aether/agents/aether-route-setter.md +130 -0
  17. package/.aether/agents/aether-sage.md +106 -0
  18. package/.aether/agents/aether-scout.md +101 -0
  19. package/.aether/agents/aether-surveyor-disciplines.md +391 -0
  20. package/.aether/agents/aether-surveyor-nest.md +329 -0
  21. package/.aether/agents/aether-surveyor-pathogens.md +264 -0
  22. package/.aether/agents/aether-surveyor-provisions.md +334 -0
  23. package/.aether/agents/aether-tracker.md +137 -0
  24. package/.aether/agents/aether-watcher.md +174 -0
  25. package/.aether/agents/aether-weaver.md +130 -0
  26. package/.aether/commands/claude/archaeology.md +334 -0
  27. package/.aether/commands/claude/build.md +65 -0
  28. package/.aether/commands/claude/chaos.md +336 -0
  29. package/.aether/commands/claude/colonize.md +259 -0
  30. package/.aether/commands/claude/continue.md +60 -0
  31. package/.aether/commands/claude/council.md +507 -0
  32. package/.aether/commands/claude/data-clean.md +81 -0
  33. package/.aether/commands/claude/dream.md +268 -0
  34. package/.aether/commands/claude/entomb.md +498 -0
  35. package/.aether/commands/claude/export-signals.md +57 -0
  36. package/.aether/commands/claude/feedback.md +96 -0
  37. package/.aether/commands/claude/flag.md +151 -0
  38. package/.aether/commands/claude/flags.md +169 -0
  39. package/.aether/commands/claude/focus.md +76 -0
  40. package/.aether/commands/claude/help.md +154 -0
  41. package/.aether/commands/claude/history.md +140 -0
  42. package/.aether/commands/claude/import-signals.md +71 -0
  43. package/.aether/commands/claude/init.md +505 -0
  44. package/.aether/commands/claude/insert-phase.md +105 -0
  45. package/.aether/commands/claude/interpret.md +278 -0
  46. package/.aether/commands/claude/lay-eggs.md +210 -0
  47. package/.aether/commands/claude/maturity.md +113 -0
  48. package/.aether/commands/claude/memory-details.md +77 -0
  49. package/.aether/commands/claude/migrate-state.md +171 -0
  50. package/.aether/commands/claude/oracle.md +642 -0
  51. package/.aether/commands/claude/organize.md +232 -0
  52. package/.aether/commands/claude/patrol.md +620 -0
  53. package/.aether/commands/claude/pause-colony.md +233 -0
  54. package/.aether/commands/claude/phase.md +115 -0
  55. package/.aether/commands/claude/pheromones.md +156 -0
  56. package/.aether/commands/claude/plan.md +693 -0
  57. package/.aether/commands/claude/preferences.md +65 -0
  58. package/.aether/commands/claude/quick.md +100 -0
  59. package/.aether/commands/claude/redirect.md +76 -0
  60. package/.aether/commands/claude/resume-colony.md +197 -0
  61. package/.aether/commands/claude/resume.md +388 -0
  62. package/.aether/commands/claude/run.md +231 -0
  63. package/.aether/commands/claude/seal.md +774 -0
  64. package/.aether/commands/claude/skill-create.md +286 -0
  65. package/.aether/commands/claude/status.md +410 -0
  66. package/.aether/commands/claude/swarm.md +349 -0
  67. package/.aether/commands/claude/tunnels.md +426 -0
  68. package/.aether/commands/claude/update.md +132 -0
  69. package/.aether/commands/claude/verify-castes.md +143 -0
  70. package/.aether/commands/claude/watch.md +239 -0
  71. package/.aether/commands/colonize.yaml +4 -0
  72. package/.aether/commands/council.yaml +205 -0
  73. package/.aether/commands/init.yaml +46 -13
  74. package/.aether/commands/insert-phase.yaml +4 -0
  75. package/.aether/commands/opencode/archaeology.md +331 -0
  76. package/.aether/commands/opencode/build.md +1168 -0
  77. package/.aether/commands/opencode/chaos.md +329 -0
  78. package/.aether/commands/opencode/colonize.md +195 -0
  79. package/.aether/commands/opencode/continue.md +1436 -0
  80. package/.aether/commands/opencode/council.md +437 -0
  81. package/.aether/commands/opencode/data-clean.md +77 -0
  82. package/.aether/commands/opencode/dream.md +260 -0
  83. package/.aether/commands/opencode/entomb.md +377 -0
  84. package/.aether/commands/opencode/export-signals.md +54 -0
  85. package/.aether/commands/opencode/feedback.md +99 -0
  86. package/.aether/commands/opencode/flag.md +149 -0
  87. package/.aether/commands/opencode/flags.md +167 -0
  88. package/.aether/commands/opencode/focus.md +73 -0
  89. package/.aether/commands/opencode/help.md +157 -0
  90. package/.aether/commands/opencode/history.md +136 -0
  91. package/.aether/commands/opencode/import-signals.md +68 -0
  92. package/.aether/commands/opencode/init.md +518 -0
  93. package/.aether/commands/opencode/insert-phase.md +111 -0
  94. package/.aether/commands/opencode/interpret.md +272 -0
  95. package/.aether/commands/opencode/lay-eggs.md +213 -0
  96. package/.aether/commands/opencode/maturity.md +108 -0
  97. package/.aether/commands/opencode/memory-details.md +83 -0
  98. package/.aether/commands/opencode/migrate-state.md +165 -0
  99. package/.aether/commands/opencode/oracle.md +593 -0
  100. package/.aether/commands/opencode/organize.md +226 -0
  101. package/.aether/commands/opencode/patrol.md +626 -0
  102. package/.aether/commands/opencode/pause-colony.md +203 -0
  103. package/.aether/commands/opencode/phase.md +113 -0
  104. package/.aether/commands/opencode/pheromones.md +162 -0
  105. package/.aether/commands/opencode/plan.md +684 -0
  106. package/.aether/commands/opencode/preferences.md +71 -0
  107. package/.aether/commands/opencode/quick.md +91 -0
  108. package/.aether/commands/opencode/redirect.md +84 -0
  109. package/.aether/commands/opencode/resume-colony.md +190 -0
  110. package/.aether/commands/opencode/resume.md +394 -0
  111. package/.aether/commands/opencode/run.md +237 -0
  112. package/.aether/commands/opencode/seal.md +452 -0
  113. package/.aether/commands/opencode/skill-create.md +63 -0
  114. package/.aether/commands/opencode/status.md +307 -0
  115. package/.aether/commands/opencode/swarm.md +15 -0
  116. package/.aether/commands/opencode/tunnels.md +400 -0
  117. package/.aether/commands/opencode/update.md +127 -0
  118. package/.aether/commands/opencode/verify-castes.md +139 -0
  119. package/.aether/commands/opencode/watch.md +227 -0
  120. package/.aether/commands/plan.yaml +53 -2
  121. package/.aether/commands/quick.yaml +104 -0
  122. package/.aether/commands/resume-colony.yaml +6 -4
  123. package/.aether/commands/resume.yaml +9 -0
  124. package/.aether/commands/run.yaml +37 -1
  125. package/.aether/commands/seal.yaml +9 -0
  126. package/.aether/commands/status.yaml +45 -1
  127. package/.aether/docs/command-playbooks/build-full.md +3 -2
  128. package/.aether/docs/command-playbooks/build-prep.md +12 -4
  129. package/.aether/docs/command-playbooks/build-verify.md +51 -0
  130. package/.aether/docs/command-playbooks/continue-advance.md +115 -6
  131. package/.aether/docs/command-playbooks/continue-full.md +1 -0
  132. package/.aether/docs/command-playbooks/continue-verify.md +33 -0
  133. package/.aether/utils/clash-detect.sh +239 -0
  134. package/.aether/utils/council.sh +425 -0
  135. package/.aether/utils/error-handler.sh +3 -3
  136. package/.aether/utils/flag.sh +23 -12
  137. package/.aether/utils/hive.sh +2 -2
  138. package/.aether/utils/hooks/clash-pre-tool-use.js +99 -0
  139. package/.aether/utils/immune.sh +508 -0
  140. package/.aether/utils/learning.sh +2 -2
  141. package/.aether/utils/merge-driver-lockfile.sh +35 -0
  142. package/.aether/utils/midden.sh +712 -0
  143. package/.aether/utils/pheromone.sh +1376 -108
  144. package/.aether/utils/queen.sh +31 -21
  145. package/.aether/utils/session.sh +264 -0
  146. package/.aether/utils/spawn-tree.sh +7 -7
  147. package/.aether/utils/spawn.sh +2 -2
  148. package/.aether/utils/state-api.sh +216 -5
  149. package/.aether/utils/swarm.sh +1 -1
  150. package/.aether/utils/worktree.sh +189 -0
  151. package/.claude/commands/ant/colonize.md +2 -0
  152. package/.claude/commands/ant/council.md +205 -0
  153. package/.claude/commands/ant/init.md +53 -14
  154. package/.claude/commands/ant/insert-phase.md +4 -0
  155. package/.claude/commands/ant/plan.md +27 -1
  156. package/.claude/commands/ant/quick.md +100 -0
  157. package/.claude/commands/ant/resume-colony.md +3 -2
  158. package/.claude/commands/ant/resume.md +9 -0
  159. package/.claude/commands/ant/run.md +37 -1
  160. package/.claude/commands/ant/seal.md +9 -0
  161. package/.claude/commands/ant/status.md +45 -1
  162. package/.opencode/commands/ant/colonize.md +2 -0
  163. package/.opencode/commands/ant/council.md +143 -0
  164. package/.opencode/commands/ant/init.md +53 -13
  165. package/.opencode/commands/ant/insert-phase.md +4 -0
  166. package/.opencode/commands/ant/plan.md +26 -1
  167. package/.opencode/commands/ant/quick.md +91 -0
  168. package/.opencode/commands/ant/resume-colony.md +3 -2
  169. package/.opencode/commands/ant/resume.md +9 -0
  170. package/.opencode/commands/ant/run.md +37 -1
  171. package/.opencode/commands/ant/status.md +2 -0
  172. package/CHANGELOG.md +116 -0
  173. package/README.md +34 -8
  174. package/bin/cli.js +103 -61
  175. package/bin/lib/banner.js +14 -0
  176. package/bin/lib/init.js +8 -7
  177. package/bin/lib/interactive-setup.js +251 -0
  178. package/bin/npx-entry.js +21 -0
  179. package/bin/npx-install.js +9 -167
  180. package/bin/validate-package.sh +23 -0
  181. package/package.json +11 -3
  182. package/.aether/docs/plans/pheromone-display-plan.md +0 -257
  183. package/.aether/schemas/example-prompt-builder.xml +0 -234
  184. package/.aether/scripts/incident-test-add.sh +0 -47
  185. package/.aether/scripts/weekly-audit.sh +0 -79
@@ -98,7 +98,8 @@ If the command fails (non-zero exit or JSON has ok: false):
98
98
  If successful:
99
99
  1. Parse the state JSON from result field
100
100
  2. Check if goal is null - if so: "No colony initialized. Run /ant:init first." and stop
101
- 3. Extract current_phase and phase name from plan.phases[current_phase - 1].name
101
+ 3. Check if `milestone` == `"Crowned Anthill"` - if so: "This colony has been sealed. Start a new colony with `/ant:init \"new goal\"`." and stop
102
+ 4. Extract current_phase and phase name from plan.phases[current_phase - 1].name
102
103
  4. Display brief resumption context:
103
104
  ```
104
105
  🔄 Resuming: Phase X - Name
@@ -225,7 +226,7 @@ Parse the JSON result (`.result.blockers`):
225
226
 
226
227
  ### Step 2: Update State
227
228
 
228
- Read then update `.aether/data/COLONY_STATE.json`:
229
+ Update `.aether/data/COLONY_STATE.json` using state-mutate (atomic, locked, validated):
229
230
  - Set `state` to `"EXECUTING"`
230
231
  - Set `current_phase` to the phase number
231
232
  - Set the phase's `status` to `"in_progress"` in `plan.phases[N]`
@@ -234,7 +235,14 @@ Read then update `.aether/data/COLONY_STATE.json`:
234
235
 
235
236
  If `events` exceeds 100 entries, keep only the last 100.
236
237
 
237
- Write COLONY_STATE.json.
238
+ Run using the Bash tool with description "Updating colony state for build...":
239
+ ```bash
240
+ bash .aether/aether-utils.sh state-mutate \
241
+ --argjson phase "$PHASE_NUMBER" \
242
+ --arg phase_name "$PHASE_NAME" \
243
+ --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
244
+ '.state = "EXECUTING" | .current_phase = $phase | .plan.phases |= map(if .id == $phase then .status = "in_progress" else . end) | .build_started_at = $timestamp | .events = ((.events[-99:]) + [$timestamp + "|phase_started|build|Phase " + ($phase|tostring) + ": " + $phase_name + " started"])'
245
+ ```
238
246
 
239
247
  Validate the state file:
240
248
  Run using the Bash tool with description "Validating colony state...":
@@ -253,7 +261,7 @@ git rev-parse --git-dir 2>/dev/null
253
261
 
254
262
  - **If succeeds** (is a git repo):
255
263
  1. Check for changes in Aether-managed directories only: `.aether .claude/commands/ant .claude/commands/st .opencode bin`
256
- 2. **If changes exist**: Run using the Bash tool with description "Creating git checkpoint...": `git stash push -m "aether-checkpoint: pre-phase-$PHASE_NUMBER" -- .aether .claude/commands/ant .claude/commands/st .opencode bin`
264
+ 2. **If changes exist**: Run using the Bash tool with description "Creating git checkpoint...": `git stash push -m "aether-checkpoint: pre-phase-$PHASE_NUMBER" -- .aether .claude/commands/ant .claude/commands/st .opencode bin ":(exclude).aether/data/"`
257
265
  - IMPORTANT: Never use `--include-untracked` — it stashes ALL files including user work!
258
266
  - Run using the Bash tool with description "Verifying checkpoint...": `git stash list | head -1 | grep "aether-checkpoint"` — warn if empty
259
267
  - Store checkpoint as `{type: "stash", ref: "aether-checkpoint: pre-phase-$PHASE_NUMBER"}`
@@ -1,3 +1,16 @@
1
+ ### Step 5.3.5: Generate Compact Review Context for Review Cycles
2
+
3
+ Before spawning review agents, generate compact colony context for CI/autopilot agent review cycles.
4
+
5
+ Run using the Bash tool with description "Generating compact review context...":
6
+ ```bash
7
+ REVIEW_CONTEXT=$(bash "$AETHER_UTILS" pr-context --compact 2>/dev/null) || true
8
+ ```
9
+
10
+ - If empty, continue without review context (non-blocking)
11
+ - Provides compact colony context for CI/autopilot agent review cycles
12
+ - Output is available for Watcher and subsequent review agents but does not gate them
13
+
1
14
  ### Step 5.4: Spawn Watcher for Verification
2
15
 
3
16
  **MANDATORY: Always spawn a Watcher — testing must be independent.**
@@ -403,3 +416,41 @@ bash .aether/aether-utils.sh memory-capture \
403
416
  ```
404
417
 
405
418
  This ensures verification failures are persisted as blockers that survive context resets. Chaos Ant findings are flagged in Step 5.7.
419
+
420
+ ### Step 5.9: Midden Collection for Merged Branches (NON-BLOCKING)
421
+
422
+ **Per D-04: Wire midden-collect into /ant:run flow (build-verify phase).**
423
+
424
+ If this build is running on main after a merge (detected via COLONY_STATE or git log), attempt midden collection:
425
+
426
+ Run using the Bash tool with description "Collecting midden from merged branch...":
427
+ ```bash
428
+ # Only runs if merge context is available
429
+ if [[ -n "${last_merged_branch:-}" && -n "${last_merge_sha:-}" ]]; then
430
+ collect_result=$(bash .aether/aether-utils.sh midden-collect \
431
+ --branch "$last_merged_branch" --merge-sha "$last_merge_sha" \
432
+ 2>/dev/null || echo '{"ok":false}')
433
+ collect_ok=$(echo "$collect_result" | jq -r '.ok // false' 2>/dev/null)
434
+ if [[ "$collect_ok" == "true" ]]; then
435
+ collect_status=$(echo "$collect_result" | jq -r '.result.status // "unknown"' 2>/dev/null)
436
+ new_entries=$(echo "$collect_result" | jq -r '.result.entries_collected // 0' 2>/dev/null)
437
+ if [[ "$collect_status" == "collected" && "$new_entries" -gt 0 ]]; then
438
+ echo "Midden: collected $new_entries entries from $last_merged_branch"
439
+ fi
440
+ fi
441
+
442
+ # Run cross-PR analysis after collection (per D-05)
443
+ analysis_result=$(bash .aether/aether-utils.sh midden-cross-pr-analysis --window 14 \
444
+ 2>/dev/null || echo '{"ok":false}')
445
+ analysis_ok=$(echo "$analysis_result" | jq -r '.ok // false' 2>/dev/null)
446
+ if [[ "$analysis_ok" == "true" ]]; then
447
+ systemic=$(echo "$analysis_result" | jq -r '.result.systemic_categories // [] | length' 2>/dev/null || echo "0")
448
+ if [[ "$systemic" -gt 0 ]]; then
449
+ categories=$(echo "$analysis_result" | jq -r '.result.systemic_categories | join(", ")' 2>/dev/null)
450
+ echo "Midden: cross-PR systemic: $categories"
451
+ fi
452
+ fi
453
+ fi
454
+ ```
455
+
456
+ This step is NON-BLOCKING -- build verification proceeds regardless of collection or analysis outcome.
@@ -1,3 +1,16 @@
1
+ ### Step 2.0.7: Generate Review Context (pr-context)
2
+
3
+ After verification passes, generate structured colony context for the review and learning steps that follow.
4
+
5
+ Run using the Bash tool with description "Generating review context...":
6
+ ```bash
7
+ REVIEW_CONTEXT=$(bash "$AETHER_UTILS" pr-context 2>/dev/null) || true
8
+ ```
9
+
10
+ - If REVIEW_CONTEXT is empty or fails, continue without it (non-blocking)
11
+ - This provides structured colony context for review/learning steps
12
+ - Output is available for subsequent steps but does not gate them
13
+
1
14
  ### Step 2: Update State
2
15
 
3
16
  Find current phase in `plan.phases`.
@@ -285,20 +298,116 @@ Update COLONY_STATE.json:
285
298
  - Keep max 30 instincts (remove lowest confidence)
286
299
  - Keep max 100 events
287
300
 
288
- Write the updated state through the locked subcommand. Construct the full updated COLONY_STATE.json content as a variable, then pipe it to state-write:
301
+ Write the updated state through targeted `state-mutate` calls. Each call acquires a lock, creates a backup, applies the jq expression, validates, and writes atomically. Do NOT use the Write tool or `state-write` with full JSON content — always use `state-mutate` to avoid stale-context corruption.
289
302
 
290
- Run using the Bash tool with description "Writing colony state...":
303
+ Run using the Bash tool with description "Advancing colony state...":
291
304
  ```bash
292
- cat << 'STATEOF' | bash .aether/aether-utils.sh state-write
293
- <the full JSON content>
294
- STATEOF
305
+ # Mark current phase completed
306
+ bash .aether/aether-utils.sh state-mutate --argjson pid "$current_phase" \
307
+ '.plan.phases |= map(if .id == $pid then .status = "completed" else . end)'
308
+
309
+ # Append learning (if any — skip if no learnings were extracted in Step 2)
310
+ # bash .aether/aether-utils.sh state-mutate --argjson learning "$learning_json" \
311
+ # '.memory.phase_learnings += [$learning]'
312
+
313
+ # Advance to next phase
314
+ bash .aether/aether-utils.sh state-mutate \
315
+ --argjson pid "$current_phase" \
316
+ --argjson next "$next_phase" \
317
+ --arg timestamp "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
318
+ '.current_phase = $next | .state = "READY" | .build_started_at = null | .events += [$timestamp + "|phase_advanced|continue|Completed Phase " + ($pid|tostring) + ", advancing to Phase " + ($next|tostring)]'
295
319
  ```
296
320
 
297
- This acquires a lock, creates a rolling backup, validates JSON, and writes atomically. Do NOT use the Write tool to write COLONY_STATE.json directly — always go through state-write.
321
+ Run using the Bash tool with description "Enforcing memory caps...":
322
+ ```bash
323
+ # Cap enforcement — keep arrays bounded
324
+ bash .aether/aether-utils.sh state-mutate \
325
+ '.memory.phase_learnings = (.memory.phase_learnings[-20:]) | .memory.decisions = (.memory.decisions[-30:]) | .memory.instincts = (.memory.instincts | sort_by(.confidence) | .[-30:]) | .events = (.events[-100:])'
326
+ ```
298
327
 
299
328
  Validate the state file:
300
329
  Run using the Bash tool with description "Validating colony state...": `bash .aether/aether-utils.sh validate-state colony`
301
330
 
331
+ ### Step 2.0.5: Pheromone Merge-Back (SILENT, NON-BLOCKING)
332
+
333
+ If a `pheromone-branch-export.json` exists in `.aether/exchange/` (written by seal ceremony on a PR branch and merged to main), run merge-back to collect branch-discovered signals into main's pheromone store. This entire step is silent and non-blocking -- continue proceeds even if merge-back fails.
334
+
335
+ Run using the Bash tool with description "Checking for pheromone merge-back file...":
336
+ ```bash
337
+ # Check if a branch pheromone export was merged into main
338
+ export_file=".aether/exchange/pheromone-branch-export.json"
339
+ if [[ -f "$export_file" ]]; then
340
+ merge_result=$(bash .aether/aether-utils.sh pheromone-merge-back --export-file "$export_file" 2>/dev/null || echo '{"ok":false}')
341
+ merge_ok=$(echo "$merge_result" | jq -r '.ok // false' 2>/dev/null)
342
+ if [[ "$merge_ok" == "true" ]]; then
343
+ new_count=$(echo "$merge_result" | jq -r '.result.new_signals_written // 0' 2>/dev/null)
344
+ skipped=$(echo "$merge_result" | jq -r '.result.skipped_count // 0' 2>/dev/null)
345
+ conflicts=$(echo "$merge_result" | jq -r '.result.conflicts_resolved // [] | length' 2>/dev/null)
346
+ if [[ "$new_count" -gt 0 || "$conflicts" -gt 0 ]]; then
347
+ echo "Pheromone merge-back: $new_count new, $conflicts conflicts resolved, $skipped skipped (from merged branch)"
348
+ fi
349
+ # Clean up export file after successful merge
350
+ rm -f "$export_file" 2>/dev/null || true
351
+ else
352
+ echo "Pheromone merge-back: failed (non-blocking)"
353
+ fi
354
+ fi
355
+ ```
356
+
357
+ ### Step 2.0.6: Midden Collection (NON-BLOCKING)
358
+
359
+ After pheromone merge-back, collect failure records from any recently merged branch worktrees. This step is silent and non-blocking -- continue proceeds even if collection fails.
360
+
361
+ **Per D-04: Wire midden-collect into /ant:continue flow.**
362
+
363
+ If the colony uses a PR-based workflow and a merge just happened, attempt to collect the branch's midden entries:
364
+
365
+ Run using the Bash tool with description "Collecting branch midden entries...":
366
+ ```bash
367
+ # Check if there's a recently merged branch to collect from
368
+ # The merge info comes from git log or COLONY_STATE context
369
+ last_merge_branch="${last_merged_branch:-}"
370
+ last_merge_sha="${last_merge_sha:-}"
371
+
372
+ if [[ -n "$last_merge_branch" && -n "$last_merge_sha" ]]; then
373
+ collect_result=$(bash .aether/aether-utils.sh midden-collect \
374
+ --branch "$last_merge_branch" --merge-sha "$last_merge_sha" \
375
+ 2>/dev/null || echo '{"ok":false}')
376
+ collect_ok=$(echo "$collect_result" | jq -r '.ok // false' 2>/dev/null)
377
+ if [[ "$collect_ok" == "true" ]]; then
378
+ collect_status=$(echo "$collect_result" | jq -r '.result.status // "unknown"' 2>/dev/null)
379
+ if [[ "$collect_status" == "collected" ]]; then
380
+ new_entries=$(echo "$collect_result" | jq -r '.result.entries_collected // 0' 2>/dev/null)
381
+ echo "Midden: collected $new_entries failure entries from branch $last_merge_branch"
382
+ fi
383
+ fi
384
+ fi
385
+ ```
386
+
387
+ This step is NON-BLOCKING -- continue proceeds regardless of collection outcome. If `last_merge_branch` and `last_merge_sha` are not set (e.g., no recent merge), this step is silently skipped.
388
+
389
+ ### Step 2.0.7: Cross-PR Midden Analysis (NON-BLOCKING)
390
+
391
+ After midden collection (Step 2.0.6), run cross-PR analysis to detect systemic failure patterns across multiple merged branches. Auto-emits REDIRECT pheromones to hub if systemic patterns found.
392
+
393
+ **Per D-05: Wire midden-cross-pr-analysis into /ant:continue flow.**
394
+
395
+ Run using the Bash tool with description "Running cross-PR midden analysis...":
396
+ ```bash
397
+ analysis_result=$(bash .aether/aether-utils.sh midden-cross-pr-analysis --window 14 \
398
+ 2>/dev/null || echo '{"ok":false}')
399
+ analysis_ok=$(echo "$analysis_result" | jq -r '.ok // false' 2>/dev/null)
400
+ if [[ "$analysis_ok" == "true" ]]; then
401
+ systemic=$(echo "$analysis_result" | jq -r '.result.systemic_categories // [] | length' 2>/dev/null || echo "0")
402
+ if [[ "$systemic" -gt 0 ]]; then
403
+ categories=$(echo "$analysis_result" | jq -r '.result.systemic_categories | join(", ")' 2>/dev/null)
404
+ echo "Midden: cross-PR systemic pattern detected in: $categories (REDIRECT emitted)"
405
+ fi
406
+ fi
407
+ ```
408
+
409
+ This step is NON-BLOCKING -- advance proceeds regardless of analysis outcome.
410
+
302
411
  ### Step 2.1: Auto-Emit Phase Pheromones (SILENT)
303
412
 
304
413
  **This entire step produces NO user-visible output.** All pheromone operations run silently — learnings are deposited in the background. If any pheromone call fails, log the error and continue. Phase advancement must never fail due to pheromone errors.
@@ -26,6 +26,7 @@ Extract: `goal`, `state`, `current_phase`, `plan.phases`, `errors`, `memory`, `e
26
26
 
27
27
  **Validation:**
28
28
  - If `goal: null` -> output "No colony initialized. Run /ant:init first." and stop.
29
+ - If `milestone` == `"Crowned Anthill"` -> output "This colony has been sealed. Start a new colony with `/ant:init \"new goal\"`." and stop.
29
30
  - If `plan.phases` is empty -> output "No project plan. Run /ant:plan first." and stop.
30
31
 
31
32
  ### Step 1.5: Load State and Show Resumption Context
@@ -26,6 +26,7 @@ Extract: `goal`, `state`, `current_phase`, `plan.phases`, `errors`, `memory`, `e
26
26
 
27
27
  **Validation:**
28
28
  - If `goal: null` -> output "No colony initialized. Run /ant:init first." and stop.
29
+ - If `milestone` == `"Crowned Anthill"` -> output "This colony has been sealed. Start a new colony with `/ant:init \"new goal\"`." and stop.
29
30
  - If `plan.phases` is empty -> output "No project plan. Run /ant:plan first." and stop.
30
31
 
31
32
  ### Step 1.5: Load State and Show Resumption Context
@@ -404,3 +405,35 @@ Cross-reference worker claims against reality. This step catches fabricated succ
404
405
  **CRITICAL:** Do NOT proceed to Step 1.6. Do NOT advance the phase. The verification failure is a hard block just like a test failure.
405
406
 
406
407
  Continue to Step 1.6.
408
+
409
+ ### Step 2.0.6: Midden Collection (NON-BLOCKING)
410
+
411
+ After verification passes, collect failure records from any recently merged branch worktrees. This step is silent and non-blocking -- continue proceeds even if collection fails.
412
+
413
+ **Per D-04: Wire midden-collect into /ant:continue flow.**
414
+
415
+ If the colony uses a PR-based workflow and a merge just happened, attempt to collect the branch's midden entries:
416
+
417
+ Run using the Bash tool with description "Collecting branch midden entries...":
418
+ ```bash
419
+ # Check if there's a recently merged branch to collect from
420
+ # The merge info comes from git log or COLONY_STATE context
421
+ last_merge_branch="${last_merged_branch:-}"
422
+ last_merge_sha="${last_merge_sha:-}"
423
+
424
+ if [[ -n "$last_merge_branch" && -n "$last_merge_sha" ]]; then
425
+ collect_result=$(bash .aether/aether-utils.sh midden-collect \
426
+ --branch "$last_merge_branch" --merge-sha "$last_merge_sha" \
427
+ 2>/dev/null || echo '{"ok":false}')
428
+ collect_ok=$(echo "$collect_result" | jq -r '.ok // false' 2>/dev/null)
429
+ if [[ "$collect_ok" == "true" ]]; then
430
+ collect_status=$(echo "$collect_result" | jq -r '.result.status // "unknown"' 2>/dev/null)
431
+ if [[ "$collect_status" == "collected" ]]; then
432
+ new_entries=$(echo "$collect_result" | jq -r '.result.entries_collected // 0' 2>/dev/null)
433
+ echo "Midden: collected $new_entries failure entries from branch $last_merge_branch"
434
+ fi
435
+ fi
436
+ fi
437
+ ```
438
+
439
+ This step is NON-BLOCKING -- continue proceeds regardless of collection outcome. If `last_merge_branch` and `last_merge_sha` are not set (e.g., no recent merge), this step is silently skipped.
@@ -0,0 +1,239 @@
1
+ #!/bin/bash
2
+ # Clash detection utility -- detects file conflicts across git worktrees.
3
+ #
4
+ # When multiple agents work in parallel worktrees, they could accidentally
5
+ # edit the same file from different worktrees. This script checks for that.
6
+ #
7
+ # Usage: clash-detect --file <path> [--worktree <path>]
8
+ # Returns JSON: {ok:true, result:{conflict:false}} or
9
+ # {ok:true, result:{conflict:true, conflicting_worktrees:[...]}}
10
+ #
11
+ # Environment:
12
+ # AETHER_ROOT - repo root (auto-detected if not set)
13
+ # WORKTREE_DIR - current worktree path (to exclude self from checks)
14
+ #
15
+ # When sourced by aether-utils.sh, provides _clash_detect function.
16
+ # When run directly, executes _clash_detect with provided arguments.
17
+
18
+ # Only set strict mode when run directly (not sourced)
19
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
20
+ set -uo pipefail
21
+ fi
22
+
23
+ # Set AETHER_ROOT if not already set (e.g., when sourced)
24
+ : "${AETHER_ROOT:=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
25
+
26
+ # Fallback error constant for standalone execution (when not sourced by aether-utils.sh)
27
+ : "${E_VALIDATION_FAILED:=E_VALIDATION_FAILED}"
28
+
29
+ # Allowlist of path patterns that bypass clash detection (branch-local state)
30
+ # These files live in .aether/data/ and are unique per worktree/branch
31
+ _CLASH_ALLOWLIST=(
32
+ ".aether/data/"
33
+ )
34
+
35
+ # Check if a file path matches the allowlist
36
+ _clash_is_allowlisted() {
37
+ local file="$1"
38
+ for pattern in "${_CLASH_ALLOWLIST[@]}"; do
39
+ if [[ "$file" == "$pattern"* ]]; then
40
+ return 0
41
+ fi
42
+ done
43
+ return 1
44
+ }
45
+
46
+ # Normalize a path for comparison on macOS (handles /var vs /private/var)
47
+ _clash_normalize_path() {
48
+ local p="$1"
49
+ if [[ -z "$p" ]]; then
50
+ echo ""
51
+ return
52
+ fi
53
+ if command -v realpath >/dev/null 2>&1; then
54
+ realpath "$p" 2>/dev/null && return
55
+ fi
56
+ (cd "$p" 2>/dev/null && pwd) || echo "$p"
57
+ }
58
+
59
+ # JSON output helpers (standalone, for direct execution mode)
60
+ _clash_json_ok() {
61
+ echo "{\"ok\":true,\"result\":$1}"
62
+ }
63
+
64
+ _clash_json_err() {
65
+ echo "{\"ok\":false,\"error\":$1}" >&2
66
+ exit 1
67
+ }
68
+
69
+ # Bridge: use aether-utils JSON helpers when sourced, standalone when direct
70
+ _cok() {
71
+ if type json_ok &>/dev/null; then
72
+ json_ok "$1"
73
+ else
74
+ _clash_json_ok "$1"
75
+ fi
76
+ }
77
+
78
+ _cerr() {
79
+ if type json_err &>/dev/null; then
80
+ json_err "${2:-$E_UNKNOWN}" "$1"
81
+ else
82
+ _clash_json_err "\"$1\""
83
+ fi
84
+ }
85
+
86
+ # Main clash detection logic
87
+ _clash_detect() {
88
+ local file=""
89
+ local worktree="${WORKTREE_DIR:-}"
90
+
91
+ while [[ $# -gt 0 ]]; do
92
+ case "$1" in
93
+ --file) file="${2:-}"; shift 2 ;;
94
+ --worktree) worktree="${2:-}"; shift 2 ;;
95
+ *) shift ;;
96
+ esac
97
+ done
98
+
99
+ if [[ -z "$file" ]]; then
100
+ _cerr "Usage: clash-detect --file <path> [--worktree <path>]" "$E_VALIDATION_FAILED"
101
+ fi
102
+
103
+ # Allowlisted files never clash (branch-local state)
104
+ if _clash_is_allowlisted "$file"; then
105
+ _cok '{"conflict":false}'
106
+ return
107
+ fi
108
+
109
+ # Verify we're in a git repo
110
+ if ! git -C "$AETHER_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
111
+ _cok '{"conflict":false}'
112
+ return
113
+ fi
114
+
115
+ # Normalize the current worktree path for comparison
116
+ if [[ -n "$worktree" ]]; then
117
+ worktree="$(_clash_normalize_path "$worktree")" || worktree=""
118
+ fi
119
+
120
+ # Enumerate all worktrees
121
+ local conflicting=()
122
+ while IFS= read -r line; do
123
+ local wt_path
124
+ wt_path=$(echo "$line" | awk '{print $1}')
125
+ [[ -z "$wt_path" ]] && continue
126
+
127
+ local abs_wt_path
128
+ abs_wt_path="$(_clash_normalize_path "$wt_path")" || continue
129
+
130
+ # Skip if this is the current worktree (self-check)
131
+ if [[ -n "$worktree" && "$abs_wt_path" == "$worktree" ]]; then
132
+ continue
133
+ fi
134
+
135
+ # Check if this worktree has uncommitted changes to the target file
136
+ local file_status
137
+ file_status=$(git -C "$abs_wt_path" status --porcelain -- "$file" 2>/dev/null) || continue
138
+
139
+ if [[ -n "$file_status" ]]; then
140
+ local branch_name
141
+ branch_name=$(git -C "$abs_wt_path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
142
+ conflicting+=("\"$branch_name\"")
143
+ fi
144
+ done < <(git -C "$AETHER_ROOT" worktree list 2>/dev/null | tail -n +2)
145
+
146
+ if [[ ${#conflicting[@]} -eq 0 ]]; then
147
+ _cok '{"conflict":false}'
148
+ else
149
+ local conflict_list
150
+ conflict_list=$(printf '%s,' "${conflicting[@]}")
151
+ conflict_list="[${conflict_list%,}]"
152
+ _cok "{\"conflict\":true,\"conflicting_worktrees\":$conflict_list}"
153
+ fi
154
+ }
155
+
156
+ # _clash_setup
157
+ # Install or uninstall the clash detection PreToolUse hook.
158
+ #
159
+ # Usage: clash-setup [--install] [--uninstall]
160
+ # Returns JSON: {ok:true, result:{hook_installed:true/false}}
161
+ #
162
+ # Environment:
163
+ # CLASH_SETTINGS_PATH - path to settings.json (default: .claude/settings.json)
164
+ _clash_setup() {
165
+ local action=""
166
+ local settings_path="${CLASH_SETTINGS_PATH:-$AETHER_ROOT/.claude/settings.json}"
167
+
168
+ while [[ $# -gt 0 ]]; do
169
+ case "$1" in
170
+ --install) action="install"; shift ;;
171
+ --uninstall) action="uninstall"; shift ;;
172
+ *) shift ;;
173
+ esac
174
+ done
175
+
176
+ if [[ -z "$action" ]]; then
177
+ _cerr "Usage: clash-setup [--install] [--uninstall]" "$E_VALIDATION_FAILED"
178
+ fi
179
+
180
+ # Ensure settings file exists
181
+ if [[ ! -f "$settings_path" ]]; then
182
+ echo '{}' > "$settings_path" 2>/dev/null || {
183
+ _cerr "Cannot create settings file at $settings_path" "$E_FILE_NOT_FOUND"
184
+ }
185
+ fi
186
+
187
+ # Read current settings
188
+ local settings
189
+ settings=$(cat "$settings_path" 2>/dev/null) || settings='{}'
190
+
191
+ local hook_command='node .aether/utils/hooks/clash-pre-tool-use.js'
192
+ local hook_entry="{\"type\":\"command\",\"command\":\"$hook_command\",\"timeout\":5}"
193
+
194
+ if [[ "$action" == "install" ]]; then
195
+ # Check if hook already exists in PreToolUse
196
+ if echo "$settings" | jq -e '.hooks.PreToolUse[]?.hooks[]?.command' 2>/dev/null \
197
+ | grep -q "clash-pre-tool-use"; then
198
+ # Already installed
199
+ _cok '{"hook_installed":true}'
200
+ return
201
+ fi
202
+
203
+ # Add the hook to PreToolUse array
204
+ settings=$(echo "$settings" | jq --arg entry "$hook_entry" '
205
+ if .hooks.PreToolUse then
206
+ .hooks.PreToolUse += [{"matcher": "Edit|Write", "hooks": [($entry | fromjson)]}]
207
+ else
208
+ .hooks.PreToolUse = [{"matcher": "Edit|Write", "hooks": [($entry | fromjson)]}]
209
+ end
210
+ ' 2>/dev/null)
211
+
212
+ if [[ $? -ne 0 ]] || [[ -z "$settings" ]]; then
213
+ _cerr "Failed to update settings file" "$E_UNKNOWN"
214
+ fi
215
+
216
+ echo "$settings" > "$settings_path"
217
+
218
+ _cok '{"hook_installed":true}'
219
+
220
+ elif [[ "$action" == "uninstall" ]]; then
221
+ # Remove the hook from PreToolUse
222
+ settings=$(echo "$settings" | jq '
223
+ if .hooks.PreToolUse then
224
+ .hooks.PreToolUse = [.hooks.PreToolUse[] |
225
+ select(.hooks[]?.command | . != "node .aether/utils/hooks/clash-pre-tool-use.js")
226
+ ]
227
+ end
228
+ ' 2>/dev/null)
229
+
230
+ echo "$settings" > "$settings_path"
231
+
232
+ _cok '{"hook_installed":false}'
233
+ fi
234
+ }
235
+
236
+ # Run if executed directly (not sourced)
237
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
238
+ _clash_detect "$@"
239
+ fi