aether-colony 3.1.4 → 3.1.15

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 (124) hide show
  1. package/.claude/commands/ant/archaeology.md +12 -0
  2. package/.claude/commands/ant/build.md +382 -319
  3. package/.claude/commands/ant/chaos.md +23 -1
  4. package/.claude/commands/ant/colonize.md +147 -87
  5. package/.claude/commands/ant/continue.md +213 -23
  6. package/.claude/commands/ant/council.md +22 -0
  7. package/.claude/commands/ant/dream.md +18 -0
  8. package/.claude/commands/ant/entomb.md +178 -6
  9. package/.claude/commands/ant/init.md +87 -13
  10. package/.claude/commands/ant/lay-eggs.md +45 -5
  11. package/.claude/commands/ant/oracle.md +82 -9
  12. package/.claude/commands/ant/organize.md +2 -2
  13. package/.claude/commands/ant/pause-colony.md +86 -28
  14. package/.claude/commands/ant/phase.md +26 -0
  15. package/.claude/commands/ant/plan.md +204 -111
  16. package/.claude/commands/ant/resume-colony.md +23 -1
  17. package/.claude/commands/ant/resume.md +159 -0
  18. package/.claude/commands/ant/seal.md +177 -3
  19. package/.claude/commands/ant/swarm.md +78 -97
  20. package/.claude/commands/ant/verify-castes.md +7 -7
  21. package/.claude/commands/ant/watch.md +17 -0
  22. package/.opencode/agents/aether-ambassador.md +97 -0
  23. package/.opencode/agents/aether-archaeologist.md +91 -0
  24. package/.opencode/agents/aether-architect.md +66 -0
  25. package/.opencode/agents/aether-auditor.md +111 -0
  26. package/.opencode/agents/aether-builder.md +28 -10
  27. package/.opencode/agents/aether-chaos.md +98 -0
  28. package/.opencode/agents/aether-chronicler.md +80 -0
  29. package/.opencode/agents/aether-gatekeeper.md +107 -0
  30. package/.opencode/agents/aether-guardian.md +107 -0
  31. package/.opencode/agents/aether-includer.md +108 -0
  32. package/.opencode/agents/aether-keeper.md +106 -0
  33. package/.opencode/agents/aether-measurer.md +119 -0
  34. package/.opencode/agents/aether-probe.md +91 -0
  35. package/.opencode/agents/aether-queen.md +72 -19
  36. package/.opencode/agents/aether-route-setter.md +85 -0
  37. package/.opencode/agents/aether-sage.md +98 -0
  38. package/.opencode/agents/aether-scout.md +33 -15
  39. package/.opencode/agents/aether-surveyor-disciplines.md +334 -0
  40. package/.opencode/agents/aether-surveyor-nest.md +272 -0
  41. package/.opencode/agents/aether-surveyor-pathogens.md +209 -0
  42. package/.opencode/agents/aether-surveyor-provisions.md +277 -0
  43. package/.opencode/agents/aether-tracker.md +91 -0
  44. package/.opencode/agents/aether-watcher.md +30 -12
  45. package/.opencode/agents/aether-weaver.md +87 -0
  46. package/.opencode/agents/workers.md +1034 -0
  47. package/.opencode/commands/ant/archaeology.md +44 -26
  48. package/.opencode/commands/ant/build.md +327 -295
  49. package/.opencode/commands/ant/chaos.md +32 -4
  50. package/.opencode/commands/ant/colonize.md +119 -93
  51. package/.opencode/commands/ant/continue.md +98 -10
  52. package/.opencode/commands/ant/council.md +28 -0
  53. package/.opencode/commands/ant/dream.md +24 -0
  54. package/.opencode/commands/ant/entomb.md +73 -1
  55. package/.opencode/commands/ant/feedback.md +8 -2
  56. package/.opencode/commands/ant/flag.md +9 -3
  57. package/.opencode/commands/ant/flags.md +8 -2
  58. package/.opencode/commands/ant/focus.md +8 -2
  59. package/.opencode/commands/ant/help.md +12 -0
  60. package/.opencode/commands/ant/init.md +49 -4
  61. package/.opencode/commands/ant/lay-eggs.md +30 -2
  62. package/.opencode/commands/ant/oracle.md +39 -7
  63. package/.opencode/commands/ant/organize.md +9 -3
  64. package/.opencode/commands/ant/pause-colony.md +54 -1
  65. package/.opencode/commands/ant/phase.md +36 -4
  66. package/.opencode/commands/ant/plan.md +225 -117
  67. package/.opencode/commands/ant/redirect.md +8 -2
  68. package/.opencode/commands/ant/resume-colony.md +51 -26
  69. package/.opencode/commands/ant/seal.md +76 -0
  70. package/.opencode/commands/ant/status.md +50 -20
  71. package/.opencode/commands/ant/swarm.md +108 -104
  72. package/.opencode/commands/ant/tunnels.md +107 -2
  73. package/CHANGELOG.md +21 -0
  74. package/README.md +199 -86
  75. package/bin/cli.js +142 -25
  76. package/bin/generate-commands.sh +100 -16
  77. package/bin/lib/caste-colors.js +5 -5
  78. package/bin/lib/errors.js +16 -0
  79. package/bin/lib/file-lock.js +279 -44
  80. package/bin/lib/state-sync.js +206 -23
  81. package/bin/lib/update-transaction.js +206 -24
  82. package/bin/sync-to-runtime.sh +129 -0
  83. package/package.json +2 -2
  84. package/runtime/CONTEXT.md +160 -0
  85. package/runtime/aether-utils.sh +1421 -55
  86. package/runtime/docs/AETHER-2.0-IMPLEMENTATION-PLAN.md +1343 -0
  87. package/runtime/docs/AETHER-PHEROMONE-SYSTEM-MASTER-SPEC.md +2642 -0
  88. package/runtime/docs/PHEROMONE-INJECTION.md +240 -0
  89. package/runtime/docs/PHEROMONE-INTEGRATION.md +192 -0
  90. package/runtime/docs/PHEROMONE-SYSTEM-DESIGN.md +426 -0
  91. package/runtime/docs/README.md +94 -0
  92. package/runtime/docs/VISUAL-OUTPUT-SPEC.md +219 -0
  93. package/runtime/docs/biological-reference.md +272 -0
  94. package/runtime/docs/codebase-review.md +399 -0
  95. package/runtime/docs/command-sync.md +164 -0
  96. package/runtime/docs/implementation-learnings.md +89 -0
  97. package/runtime/docs/known-issues.md +217 -0
  98. package/runtime/docs/namespace.md +148 -0
  99. package/runtime/docs/planning-discipline.md +159 -0
  100. package/runtime/lib/queen-utils.sh +729 -0
  101. package/runtime/model-profiles.yaml +100 -0
  102. package/runtime/recover.sh +136 -0
  103. package/runtime/templates/QUEEN.md.template +79 -0
  104. package/runtime/utils/atomic-write.sh +5 -5
  105. package/runtime/utils/chamber-utils.sh +6 -3
  106. package/runtime/utils/error-handler.sh +200 -0
  107. package/runtime/utils/queen-to-md.xsl +395 -0
  108. package/runtime/utils/spawn-tree.sh +428 -0
  109. package/runtime/utils/spawn-with-model.sh +56 -0
  110. package/runtime/utils/state-loader.sh +215 -0
  111. package/runtime/utils/swarm-display.sh +5 -5
  112. package/runtime/utils/watch-spawn-tree.sh +90 -22
  113. package/runtime/utils/xml-compose.sh +247 -0
  114. package/runtime/utils/xml-core.sh +186 -0
  115. package/runtime/utils/xml-utils.sh +2161 -0
  116. package/runtime/verification-loop.md +1 -1
  117. package/runtime/workers-new-castes.md +516 -0
  118. package/runtime/workers.md +20 -8
  119. package/.aether/visualizations/anthill-stages/brood-stable.txt +0 -26
  120. package/.aether/visualizations/anthill-stages/crowned-anthill.txt +0 -30
  121. package/.aether/visualizations/anthill-stages/first-mound.txt +0 -18
  122. package/.aether/visualizations/anthill-stages/open-chambers.txt +0 -24
  123. package/.aether/visualizations/anthill-stages/sealed-chambers.txt +0 -28
  124. package/.aether/visualizations/anthill-stages/ventilated-nest.txt +0 -27
@@ -27,6 +27,20 @@ CURRENT_LOCK=${CURRENT_LOCK:-""}
27
27
  [[ -f "$SCRIPT_DIR/utils/atomic-write.sh" ]] && source "$SCRIPT_DIR/utils/atomic-write.sh"
28
28
  [[ -f "$SCRIPT_DIR/utils/error-handler.sh" ]] && source "$SCRIPT_DIR/utils/error-handler.sh"
29
29
  [[ -f "$SCRIPT_DIR/utils/chamber-utils.sh" ]] && source "$SCRIPT_DIR/utils/chamber-utils.sh"
30
+ [[ -f "$SCRIPT_DIR/utils/xml-utils.sh" ]] && source "$SCRIPT_DIR/utils/xml-utils.sh"
31
+
32
+ # Fallback error constants if error-handler.sh wasn't sourced
33
+ # This prevents "unbound variable" errors in older installations
34
+ : "${E_UNKNOWN:=E_UNKNOWN}"
35
+ : "${E_HUB_NOT_FOUND:=E_HUB_NOT_FOUND}"
36
+ : "${E_REPO_NOT_INITIALIZED:=E_REPO_NOT_INITIALIZED}"
37
+ : "${E_FILE_NOT_FOUND:=E_FILE_NOT_FOUND}"
38
+ : "${E_JSON_INVALID:=E_JSON_INVALID}"
39
+ : "${E_LOCK_FAILED:=E_LOCK_FAILED}"
40
+ : "${E_GIT_ERROR:=E_GIT_ERROR}"
41
+ : "${E_VALIDATION_FAILED:=E_VALIDATION_FAILED}"
42
+ : "${E_FEATURE_UNAVAILABLE:=E_FEATURE_UNAVAILABLE}"
43
+ : "${E_BASH_ERROR:=E_BASH_ERROR}"
30
44
 
31
45
  # Feature detection for graceful degradation
32
46
  # These checks run silently - failures are logged but don't block operation
@@ -74,20 +88,379 @@ fi
74
88
  # --- Caste emoji helper ---
75
89
  get_caste_emoji() {
76
90
  case "$1" in
77
- *Queen*|*QUEEN*|*queen*) echo "👑" ;;
78
- *Builder*|*builder*|*Bolt*|*Hammer*|*Forge*|*Mason*|*Brick*|*Anvil*|*Weld*) echo "🔨" ;;
79
- *Watcher*|*watcher*|*Vigil*|*Sentinel*|*Guard*|*Keen*|*Sharp*|*Hawk*|*Alert*) echo "👁️" ;;
80
- *Scout*|*scout*|*Swift*|*Dash*|*Ranger*|*Track*|*Seek*|*Path*|*Roam*|*Quest*) echo "🔍" ;;
81
- *Colonizer*|*colonizer*|*Pioneer*|*Map*|*Chart*|*Venture*|*Explore*|*Compass*|*Atlas*|*Trek*) echo "🗺️" ;;
82
- *Architect*|*architect*|*Blueprint*|*Draft*|*Design*|*Plan*|*Schema*|*Frame*|*Sketch*|*Model*) echo "🏛️" ;;
83
- *Chaos*|*chaos*|*Probe*|*Stress*|*Shake*|*Twist*|*Snap*|*Breach*|*Surge*|*Jolt*) echo "🎲" ;;
84
- *Archaeologist*|*archaeologist*|*Relic*|*Fossil*|*Dig*|*Shard*|*Epoch*|*Strata*|*Lore*|*Glyph*) echo "🏺" ;;
85
- *Oracle*|*oracle*|*Sage*|*Seer*|*Vision*|*Augur*|*Mystic*|*Sibyl*|*Delph*|*Pythia*) echo "🔮" ;;
86
- *Route*|*route*) echo "📋" ;;
91
+ *Queen*|*QUEEN*|*queen*) echo "👑🐜" ;;
92
+ *Builder*|*builder*|*Bolt*|*Hammer*|*Forge*|*Mason*|*Brick*|*Anvil*|*Weld*) echo "🔨🐜" ;;
93
+ *Watcher*|*watcher*|*Vigil*|*Sentinel*|*Guard*|*Keen*|*Sharp*|*Hawk*|*Alert*) echo "👁️🐜" ;;
94
+ *Scout*|*scout*|*Swift*|*Dash*|*Ranger*|*Track*|*Seek*|*Path*|*Roam*|*Quest*) echo "🔍🐜" ;;
95
+ *Colonizer*|*colonizer*|*Pioneer*|*Map*|*Chart*|*Venture*|*Explore*|*Compass*|*Atlas*|*Trek*) echo "🗺️🐜" ;;
96
+ *Surveyor*|*surveyor*|*Chart*|*Plot*|*Survey*|*Measure*|*Assess*|*Gauge*|*Sound*|*Fathom*) echo "📊🐜" ;;
97
+ *Architect*|*architect*|*Blueprint*|*Draft*|*Design*|*Plan*|*Schema*|*Frame*|*Sketch*|*Model*) echo "🏛️🐜" ;;
98
+ *Chaos*|*chaos*|*Probe*|*Stress*|*Shake*|*Twist*|*Snap*|*Breach*|*Surge*|*Jolt*) echo "🎲🐜" ;;
99
+ *Archaeologist*|*archaeologist*|*Relic*|*Fossil*|*Dig*|*Shard*|*Epoch*|*Strata*|*Lore*|*Glyph*) echo "🏺🐜" ;;
100
+ *Oracle*|*oracle*|*Sage*|*Seer*|*Vision*|*Augur*|*Mystic*|*Sibyl*|*Delph*|*Pythia*) echo "🔮🐜" ;;
101
+ *Route*|*route*) echo "📋🐜" ;;
102
+ *Ambassador*|*ambassador*|*Bridge*|*Connect*|*Link*|*Diplomat*|*Network*|*Protocol*) echo "🔌🐜" ;;
103
+ *Auditor*|*auditor*|*Review*|*Inspect*|*Examine*|*Scrutin*|*Critical*|*Verify*) echo "👥🐜" ;;
104
+ *Chronicler*|*chronicler*|*Document*|*Record*|*Write*|*Chronicle*|*Archive*|*Scribe*) echo "📝🐜" ;;
105
+ *Gatekeeper*|*gatekeeper*|*Guard*|*Protect*|*Secure*|*Shield*|*Depend*|*Supply*) echo "📦🐜" ;;
106
+ *Guardian*|*guardian*|*Defend*|*Patrol*|*Secure*|*Vigil*|*Watch*|*Safety*|*Security*) echo "🛡️🐜" ;;
107
+ *Includer*|*includer*|*Access*|*Inclusive*|*A11y*|*WCAG*|*Barrier*|*Universal*) echo "♿🐜" ;;
108
+ *Keeper*|*keeper*|*Archive*|*Store*|*Curate*|*Preserve*|*Knowledge*|*Wisdom*|*Pattern*) echo "📚🐜" ;;
109
+ *Measurer*|*measurer*|*Metric*|*Benchmark*|*Profile*|*Optimize*|*Performance*|*Speed*) echo "⚡🐜" ;;
110
+ *Probe*|*probe*|*Test*|*Excavat*|*Uncover*|*Edge*|*Case*|*Mutant*) echo "🧪🐜" ;;
111
+ *Tracker*|*tracker*|*Debug*|*Trace*|*Follow*|*Bug*|*Hunt*|*Root*) echo "🐛🐜" ;;
112
+ *Weaver*|*weaver*|*Refactor*|*Restruct*|*Transform*|*Clean*|*Pattern*|*Weave*) echo "🔄🐜" ;;
87
113
  *) echo "🐜" ;;
88
114
  esac
89
115
  }
90
116
 
117
+ # ============================================
118
+ # CONTEXT UPDATE HELPER FUNCTION
119
+ # (Defined outside case block to fix SC2168: local outside function)
120
+ # ============================================
121
+ _cmd_context_update() {
122
+ local ctx_action="${1:-}"
123
+ local ctx_file="${AETHER_ROOT:-.}/.aether/CONTEXT.md"
124
+ local ctx_tmp="${ctx_file}.tmp"
125
+ local ctx_ts
126
+ ctx_ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
127
+
128
+ ensure_context_dir() {
129
+ local dir
130
+ dir=$(dirname "$ctx_file")
131
+ [[ -d "$dir" ]] || mkdir -p "$dir"
132
+ }
133
+
134
+ read_colony_state() {
135
+ local state_file="${AETHER_ROOT:-.}/.aether/data/COLONY_STATE.json"
136
+ if [[ -f "$state_file" ]]; then
137
+ current_phase=$(jq -r '.current_phase // "unknown"' "$state_file" 2>/dev/null)
138
+ milestone=$(jq -r '.milestone // "unknown"' "$state_file" 2>/dev/null)
139
+ goal=$(jq -r '.goal // ""' "$state_file" 2>/dev/null)
140
+ else
141
+ current_phase="unknown"
142
+ milestone="unknown"
143
+ goal=""
144
+ fi
145
+ }
146
+
147
+ case "$ctx_action" in
148
+ init)
149
+ local init_goal="${2:-}"
150
+ ensure_context_dir
151
+ read_colony_state
152
+
153
+ cat > "$ctx_file" << EOF
154
+ # Aether Colony — Current Context
155
+
156
+ > **This document is the colony's memory. If context collapses, read this file first.**
157
+
158
+ ---
159
+
160
+ ## 🚦 System Status
161
+
162
+ | Field | Value |
163
+ |-------|-------|
164
+ | **Last Updated** | $ctx_ts |
165
+ | **Current Phase** | 1 |
166
+ | **Phase Name** | initialization |
167
+ | **Milestone** | First Mound |
168
+ | **Colony Status** | initializing |
169
+ | **Safe to Clear?** | ⚠️ NO — Colony just initialized |
170
+
171
+ ---
172
+
173
+ ## 🎯 Current Goal
174
+
175
+ $init_goal
176
+
177
+ ---
178
+
179
+ ## 📍 What's In Progress
180
+
181
+ Colony initialization in progress...
182
+
183
+ ---
184
+
185
+ ## ⚠️ Active Constraints (REDIRECT Signals)
186
+
187
+ | Constraint | Source | Date Set |
188
+ |------------|--------|----------|
189
+ | In the Aether repo, \`.aether/\` IS the source of truth — \`runtime/\` is auto-populated on publish | CLAUDE.md | Permanent |
190
+ | Never push without explicit user approval | CLAUDE.md Safety | Permanent |
191
+
192
+ ---
193
+
194
+ ## 💭 Active Pheromones (FOCUS Signals)
195
+
196
+ *None active*
197
+
198
+ ---
199
+
200
+ ## 📝 Recent Decisions
201
+
202
+ | Date | Decision | Rationale | Made By |
203
+ |------|----------|-----------|---------|
204
+
205
+ ---
206
+
207
+ ## 📊 Recent Activity (Last 10 Actions)
208
+
209
+ | Timestamp | Command | Result | Files Changed |
210
+ |-----------|---------|--------|---------------|
211
+ | $ctx_ts | init | Colony initialized | — |
212
+
213
+ ---
214
+
215
+ ## 🔄 Next Steps
216
+
217
+ 1. Run \`/ant:plan\` to generate phases for the goal
218
+ 2. Run \`/ant:build 1\` to start building
219
+
220
+ ---
221
+
222
+ ## 🆘 If Context Collapses
223
+
224
+ **READ THIS SECTION FIRST**
225
+
226
+ ### Immediate Recovery
227
+
228
+ 1. **Read this file** — You're looking at it. Good.
229
+ 2. **Check git status** — \`git status\` and \`git log --oneline -5\`
230
+ 3. **Verify COLONY_STATE.json** — \`cat .aether/data/COLONY_STATE.json | jq .current_phase\`
231
+ 4. **Resume work** — Continue from "Next Steps" above
232
+
233
+ ### What We Were Doing
234
+
235
+ Colony was just initialized with goal: $init_goal
236
+
237
+ ### Is It Safe to Continue?
238
+
239
+ - ✅ Colony is initialized
240
+ - ⚠️ No work completed yet
241
+ - ✅ All state in COLONY_STATE.json
242
+
243
+ **You can proceed safely.**
244
+
245
+ ---
246
+
247
+ ## 🐜 Colony Health
248
+
249
+ \`\`\`
250
+ Milestone: First Mound ░░░░░░░░░░ 0%
251
+ Phase: 1 ░░░░░░░░░░ initializing
252
+ Context: Active ░░░░░░░░░░ 0%
253
+ Git Commits: 0
254
+ \`\`\`
255
+
256
+ ---
257
+
258
+ *This document updates automatically with every ant command. If you see old timestamps, run \`/ant:status\` to refresh.*
259
+
260
+ **Colony Memory Active** 🧠🐜
261
+ EOF
262
+ json_ok "{\"updated\":true,\"action\":\"init\",\"file\":\"$ctx_file\"}"
263
+ ;;
264
+
265
+ update-phase)
266
+ local new_phase="${2:-}"
267
+ local new_phase_name="${3:-}"
268
+ local safe_clear="${4:-NO}"
269
+ local safe_reason="${5:-Phase in progress}"
270
+
271
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found. Run context-update init first."; }
272
+
273
+ sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
274
+ sed -i.bak "s/| \*\*Current Phase\*\* | .*/| **Current Phase** | $new_phase |/" "$ctx_file" && rm -f "$ctx_file.bak"
275
+ sed -i.bak "s/| \*\*Phase Name\*\* | .*/| **Phase Name** | $new_phase_name |/" "$ctx_file" && rm -f "$ctx_file.bak"
276
+ sed -i.bak "s/| \*\*Safe to Clear?\*\* | .*/| **Safe to Clear?** | $safe_clear — $safe_reason |/" "$ctx_file" && rm -f "$ctx_file.bak"
277
+
278
+ json_ok "{\"updated\":true,\"action\":\"update-phase\",\"phase\":$new_phase}"
279
+ ;;
280
+
281
+ activity)
282
+ local cmd="${2:-}"
283
+ local result="${3:-}"
284
+ local files_changed="${4:-—}"
285
+
286
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
287
+
288
+ sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
289
+
290
+ local activity_line="| $ctx_ts | $cmd | $result | $files_changed |"
291
+
292
+ awk -v line="$activity_line" '
293
+ /\| Timestamp \| Command \| Result \| Files Changed \|/ {
294
+ print
295
+ getline
296
+ print
297
+ print line
298
+ next
299
+ }
300
+ /^## 🆘 If Context Collapses/ { exit }
301
+ { print }
302
+ ' "$ctx_file" > "$ctx_tmp"
303
+
304
+ mv "$ctx_tmp" "$ctx_file"
305
+ json_ok "{\"updated\":true,\"action\":\"activity\",\"command\":\"$cmd\"}"
306
+ ;;
307
+
308
+ safe-to-clear)
309
+ local safe="${2:-NO}"
310
+ local reason="${3:-Unknown state}"
311
+
312
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
313
+
314
+ sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
315
+ sed -i.bak "s/| \*\*Safe to Clear?\*\* | .*/| **Safe to Clear?** | $safe — $reason |/" "$ctx_file" && rm -f "$ctx_file.bak"
316
+
317
+ json_ok "{\"updated\":true,\"action\":\"safe-to-clear\",\"safe\":\"$safe\"}"
318
+ ;;
319
+
320
+ constraint)
321
+ local c_type="${2:-}"
322
+ local c_message="${3:-}"
323
+ local c_source="${4:-User}"
324
+
325
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
326
+
327
+ sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
328
+
329
+ if [[ "$c_type" == "redirect" ]]; then
330
+ sed -i.bak "/^## ⚠️ Active Constraints/,/^## /{ /^| Constraint |/a\\
331
+ | $c_message | $c_source | $ctx_ts |
332
+ }" "$ctx_file" && rm -f "$ctx_file.bak"
333
+ elif [[ "$c_type" == "focus" ]]; then
334
+ sed -i.bak "/^## 💭 Active Pheromones/,/^## /{ /^| Signal |/a\\
335
+ | FOCUS | $c_message | normal |
336
+ }" "$ctx_file" && rm -f "$ctx_file.bak"
337
+ fi
338
+
339
+ json_ok "{\"updated\":true,\"action\":\"constraint\",\"type\":\"$c_type\"}"
340
+ ;;
341
+
342
+ decision)
343
+ local decision="${2:-}"
344
+ local rationale="${3:-}"
345
+ local made_by="${4:-Colony}"
346
+
347
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
348
+
349
+ sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
350
+
351
+ local decision_line="| $(echo $ctx_ts | cut -dT -f1) | $decision | $rationale | $made_by |"
352
+
353
+ awk -v line="$decision_line" '
354
+ /^## 📝 Recent Decisions/ { in_section=1 }
355
+ in_section && /^\| [0-9]{4}-[0-9]{2}-[0-9]{2} / { last_decision=NR }
356
+ in_section && /^## 📊 Recent Activity/ { in_section=0 }
357
+ { lines[NR] = $0 }
358
+ END {
359
+ for (i=1; i<=NR; i++) {
360
+ if (i == last_decision) {
361
+ print lines[i]
362
+ print line
363
+ } else {
364
+ print lines[i]
365
+ }
366
+ }
367
+ }
368
+ ' "$ctx_file" > "$ctx_tmp"
369
+
370
+ mv "$ctx_tmp" "$ctx_file"
371
+ json_ok "{\"updated\":true,\"action\":\"decision\"}"
372
+ ;;
373
+
374
+ build-start)
375
+ local phase_id="${2:-}"
376
+ local worker_count="${3:-0}"
377
+ local tasks_count="${4:-0}"
378
+
379
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
380
+
381
+ sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
382
+ sed -i.bak "s/## 📍 What's In Progress/## 📍 What's In Progress\n\n**Phase $phase_id Build IN PROGRESS**\n- Workers: $worker_count | Tasks: $tasks_count\n- Started: $ctx_ts/" "$ctx_file" && rm -f "$ctx_file.bak"
383
+ sed -i.bak "s/| \*\*Safe to Clear?\*\* | .*/| **Safe to Clear?** | ⚠️ NO — Build in progress |/" "$ctx_file" && rm -f "$ctx_file.bak"
384
+
385
+ json_ok "{\"updated\":true,\"action\":\"build-start\",\"workers\":$worker_count}"
386
+ ;;
387
+
388
+ worker-spawn)
389
+ local ant_name="${2:-}"
390
+ local caste="${3:-}"
391
+ local task="${4:-}"
392
+
393
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
394
+
395
+ awk -v ant="$ant_name" -v caste="$caste" -v task="$task" -v ts="$ctx_ts" '
396
+ /^## 📍 What'\''s In Progress/ { in_progress=1 }
397
+ in_progress && /^## / && $0 !~ /What'\''s In Progress/ { in_progress=0 }
398
+ in_progress && /Workers:/ {
399
+ print
400
+ print " - " ts ": Spawned " ant " (" caste ") for: " task
401
+ next
402
+ }
403
+ { print }
404
+ ' "$ctx_file" > "$ctx_tmp" && mv "$ctx_tmp" "$ctx_file"
405
+
406
+ json_ok "{\"updated\":true,\"action\":\"worker-spawn\",\"ant\":\"$ant_name\"}"
407
+ ;;
408
+
409
+ worker-complete)
410
+ local ant_name="${2:-}"
411
+ local status="${3:-completed}"
412
+
413
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
414
+
415
+ sed -i.bak "s/- .*$ant_name .*$/- $ant_name: $status (updated $ctx_ts)/" "$ctx_file" && rm -f "$ctx_file.bak"
416
+
417
+ json_ok "{\"updated\":true,\"action\":\"worker-complete\",\"ant\":\"$ant_name\"}"
418
+ ;;
419
+
420
+ build-progress)
421
+ local completed="${2:-0}"
422
+ local total="${3:-1}"
423
+ local percentage=$(( completed * 100 / total ))
424
+
425
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
426
+
427
+ sed -i.bak "s/Build IN PROGRESS/Build IN PROGRESS ($percentage% complete)/" "$ctx_file" && rm -f "$ctx_file.bak"
428
+
429
+ json_ok "{\"updated\":true,\"action\":\"build-progress\",\"percent\":$percentage}"
430
+ ;;
431
+
432
+ build-complete)
433
+ local status="${2:-completed}"
434
+ local result="${3:-success}"
435
+
436
+ [[ -f "$ctx_file" ]] || { json_err "CONTEXT.md not found"; }
437
+
438
+ sed -i.bak "s/| \*\*Last Updated\*\* | .*/| **Last Updated** | $ctx_ts |/" "$ctx_file" && rm -f "$ctx_file.bak"
439
+
440
+ awk -v status="$status" -v result="$result" '
441
+ /^## 📍 What'\''s In Progress/ { in_progress=1 }
442
+ in_progress && /^## / && $0 !~ /What'\''s In Progress/ { in_progress=0 }
443
+ in_progress && /Build IN PROGRESS/ {
444
+ print "## 📍 What'\''s In Progress"
445
+ print ""
446
+ print "**Build " status "** — " result
447
+ next
448
+ }
449
+ in_progress { next }
450
+ { print }
451
+ ' "$ctx_file" > "$ctx_tmp" && mv "$ctx_tmp" "$ctx_file"
452
+
453
+ sed -i.bak "s/| \*\*Safe to Clear?\*\* | .*/| **Safe to Clear?** | ✅ YES — Build $status |/" "$ctx_file" && rm -f "$ctx_file.bak"
454
+
455
+ json_ok "{\"updated\":true,\"action\":\"build-complete\",\"status\":\"$status\"}"
456
+ ;;
457
+
458
+ *)
459
+ json_err "$E_VALIDATION_FAILED" "Unknown context action: $ctx_action"
460
+ ;;
461
+ esac
462
+ }
463
+
91
464
  # --- Subcommand dispatch ---
92
465
  cmd="${1:-help}"
93
466
  shift 2>/dev/null || true
@@ -95,7 +468,7 @@ shift 2>/dev/null || true
95
468
  case "$cmd" in
96
469
  help)
97
470
  cat <<'EOF'
98
- {"ok":true,"commands":["help","version","validate-state","load-state","unload-state","error-add","error-pattern-check","error-summary","activity-log","activity-log-init","activity-log-read","learning-promote","learning-inject","generate-ant-name","spawn-log","spawn-complete","spawn-can-spawn","spawn-get-depth","spawn-tree-load","spawn-tree-active","spawn-tree-depth","update-progress","check-antipattern","error-flag-pattern","signature-scan","signature-match","flag-add","flag-check-blockers","flag-resolve","flag-acknowledge","flag-list","flag-auto-resolve","autofix-checkpoint","autofix-rollback","spawn-can-spawn-swarm","swarm-findings-init","swarm-findings-add","swarm-findings-read","swarm-solution-set","swarm-cleanup","swarm-activity-log","swarm-display-init","swarm-display-update","swarm-display-get","swarm-timing-start","swarm-timing-get","swarm-timing-eta","view-state-init","view-state-get","view-state-set","view-state-toggle","view-state-expand","view-state-collapse","grave-add","grave-check","generate-commit-message","version-check","registry-add","bootstrap-system","model-profile","model-get","model-list","chamber-create","chamber-verify","chamber-list","milestone-detect"],"description":"Aether Colony Utility Layer — deterministic ops for the ant colony"}
471
+ {"ok":true,"commands":["help","version","validate-state","load-state","unload-state","error-add","error-pattern-check","error-summary","activity-log","activity-log-init","activity-log-read","learning-promote","learning-inject","generate-ant-name","spawn-log","spawn-complete","spawn-can-spawn","spawn-get-depth","spawn-tree-load","spawn-tree-active","spawn-tree-depth","update-progress","check-antipattern","error-flag-pattern","signature-scan","signature-match","flag-add","flag-check-blockers","flag-resolve","flag-acknowledge","flag-list","flag-auto-resolve","autofix-checkpoint","autofix-rollback","spawn-can-spawn-swarm","swarm-findings-init","swarm-findings-add","swarm-findings-read","swarm-solution-set","swarm-cleanup","swarm-activity-log","swarm-display-init","swarm-display-update","swarm-display-get","swarm-timing-start","swarm-timing-get","swarm-timing-eta","view-state-init","view-state-get","view-state-set","view-state-toggle","view-state-expand","view-state-collapse","grave-add","grave-check","generate-commit-message","version-check","registry-add","bootstrap-system","model-profile","model-get","model-list","chamber-create","chamber-verify","chamber-list","milestone-detect","queen-init","queen-read","queen-promote","survey-load","survey-verify","pheromone-export"],"description":"Aether Colony Utility Layer — deterministic ops for the ant colony"}
99
472
  EOF
100
473
  ;;
101
474
  version)
@@ -253,7 +626,7 @@ EOF
253
626
  json_ok "$(echo "$content" | jq -Rs '.')"
254
627
  ;;
255
628
  learning-promote)
256
- [[ $# -ge 3 ]] || json_err "Usage: learning-promote <content> <source_project> <source_phase> [tags]"
629
+ [[ $# -ge 3 ]] || json_err "$E_VALIDATION_FAILED" "Usage: learning-promote <content> <source_project> <source_phase> [tags]"
257
630
  content="$1"
258
631
  source_project="$2"
259
632
  source_phase="$3"
@@ -291,13 +664,13 @@ EOF
291
664
  tags: $tags,
292
665
  promoted_at: $ts
293
666
  }]
294
- ' "$global_file") || json_err "Failed to update learnings.json"
667
+ ' "$global_file") || json_err "$E_JSON_INVALID" "Failed to update learnings.json"
295
668
 
296
669
  echo "$updated" > "$global_file"
297
670
  json_ok "{\"promoted\":true,\"id\":\"$id\",\"count\":$((current_count + 1)),\"cap\":50}"
298
671
  ;;
299
672
  learning-inject)
300
- [[ $# -ge 1 ]] || json_err "Usage: learning-inject <tech_keywords_csv>"
673
+ [[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: learning-inject <tech_keywords_csv>"
301
674
  keywords="$1"
302
675
 
303
676
  global_file="$DATA_DIR/learnings.json"
@@ -325,7 +698,7 @@ EOF
325
698
  task_summary="${4:-}"
326
699
  model="${5:-default}"
327
700
  status="${6:-spawned}"
328
- [[ -z "$parent_id" || -z "$child_caste" || -z "$task_summary" ]] && json_err "Usage: spawn-log <parent_id> <child_caste> <child_name> <task_summary> [model] [status]"
701
+ [[ -z "$parent_id" || -z "$child_caste" || -z "$task_summary" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-log <parent_id> <child_caste> <child_name> <task_summary> [model] [status]"
329
702
  mkdir -p "$DATA_DIR"
330
703
  ts=$(date -u +"%H:%M:%S")
331
704
  ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
@@ -335,14 +708,15 @@ EOF
335
708
  echo "[$ts] ⚡ SPAWN $parent_emoji $parent_id -> $emoji $child_name ($child_caste): $task_summary [model: $model]" >> "$DATA_DIR/activity.log"
336
709
  # Log to spawn tree file for visualization (NEW FORMAT: includes model field)
337
710
  echo "$ts_full|$parent_id|$child_caste|$child_name|$task_summary|$model|$status" >> "$DATA_DIR/spawn-tree.txt"
338
- json_ok '"logged"'
711
+ # Return emoji-formatted result for display
712
+ json_ok "\"⚡ $emoji $child_name spawned\""
339
713
  ;;
340
714
  spawn-complete)
341
715
  # Usage: spawn-complete <ant_name> <status> [summary]
342
716
  ant_name="${1:-}"
343
717
  status="${2:-completed}"
344
718
  summary="${3:-}"
345
- [[ -z "$ant_name" ]] && json_err "Usage: spawn-complete <ant_name> <status> [summary]"
719
+ [[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-complete <ant_name> <status> [summary]"
346
720
  mkdir -p "$DATA_DIR"
347
721
  ts=$(date -u +"%H:%M:%S")
348
722
  ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
@@ -353,7 +727,8 @@ EOF
353
727
  echo "[$ts] $status_icon $emoji $ant_name: $status${summary:+ - $summary}" >> "$DATA_DIR/activity.log"
354
728
  # Update spawn tree
355
729
  echo "$ts_full|$ant_name|$status|$summary" >> "$DATA_DIR/spawn-tree.txt"
356
- json_ok '"logged"'
730
+ # Return emoji-formatted result for display
731
+ json_ok "\"$status_icon $emoji $ant_name: ${summary:-$status}\""
357
732
  ;;
358
733
  spawn-can-spawn)
359
734
  # Check if spawning is allowed at given depth
@@ -492,7 +867,7 @@ EOF
492
867
  pattern_name="${1:-}"
493
868
  description="${2:-}"
494
869
  severity="${3:-warning}"
495
- [[ -z "$pattern_name" || -z "$description" ]] && json_err "Usage: error-flag-pattern <pattern_name> <description> [severity]"
870
+ [[ -z "$pattern_name" || -z "$description" ]] && json_err "$E_VALIDATION_FAILED" "Usage: error-flag-pattern <pattern_name> <description> [severity]"
496
871
 
497
872
  patterns_file="$DATA_DIR/error-patterns.json"
498
873
  mkdir -p "$DATA_DIR"
@@ -515,7 +890,7 @@ EOF
515
890
  .last_seen = $ts |
516
891
  .projects = ((.projects + [$proj]) | unique)
517
892
  else . end]
518
- ' "$patterns_file") || json_err "Failed to update pattern"
893
+ ' "$patterns_file") || json_err "$E_JSON_INVALID" "Failed to update pattern"
519
894
  echo "$updated" > "$patterns_file"
520
895
  count=$(echo "$updated" | jq --arg name "$pattern_name" '.patterns[] | select(.name == $name) | .occurrences')
521
896
  json_ok "{\"updated\":true,\"pattern\":\"$pattern_name\",\"occurrences\":$count}"
@@ -532,7 +907,7 @@ EOF
532
907
  "projects": [$proj],
533
908
  "resolved": false
534
909
  }]
535
- ' "$patterns_file") || json_err "Failed to add pattern"
910
+ ' "$patterns_file") || json_err "$E_JSON_INVALID" "Failed to add pattern"
536
911
  echo "$updated" > "$patterns_file"
537
912
  json_ok "{\"created\":true,\"pattern\":\"$pattern_name\"}"
538
913
  fi
@@ -557,7 +932,7 @@ EOF
557
932
  # Usage: check-antipattern <file_path>
558
933
  # Returns JSON with critical issues and warnings
559
934
  file_path="${1:-}"
560
- [[ -z "$file_path" ]] && json_err "Usage: check-antipattern <file_path>"
935
+ [[ -z "$file_path" ]] && json_err "$E_VALIDATION_FAILED" "Usage: check-antipattern <file_path>"
561
936
  [[ ! -f "$file_path" ]] && json_ok '{"critical":[],"warnings":[],"clean":true}'
562
937
 
563
938
  criticals=()
@@ -632,7 +1007,7 @@ EOF
632
1007
  # Exit code 0 if no match, 1 if match found
633
1008
  target_file="${1:-}"
634
1009
  signature_name="${2:-}"
635
- [[ -z "$target_file" || -z "$signature_name" ]] && json_err "Usage: signature-scan <target_file> <signature_name>"
1010
+ [[ -z "$target_file" || -z "$signature_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: signature-scan <target_file> <signature_name>"
636
1011
 
637
1012
  # Handle missing target file gracefully
638
1013
  if [[ ! -f "$target_file" ]]; then
@@ -687,14 +1062,14 @@ EOF
687
1062
  if [[ -z "$file_pattern" ]]; then
688
1063
  file_pattern="*"
689
1064
  fi
690
- [[ -z "$target_dir" ]] && json_err "Usage: signature-match <directory> [file_pattern]"
1065
+ [[ -z "$target_dir" ]] && json_err "$E_VALIDATION_FAILED" "Usage: signature-match <directory> [file_pattern]"
691
1066
 
692
1067
  # Validate directory exists
693
- [[ ! -d "$target_dir" ]] && json_err "Directory not found: $target_dir"
1068
+ [[ ! -d "$target_dir" ]] && json_err "$E_FILE_NOT_FOUND" "Directory not found: $target_dir"
694
1069
 
695
1070
  # Path to signatures file
696
1071
  signatures_file="$DATA_DIR/signatures.json"
697
- [[ ! -f "$signatures_file" ]] && json_err "Signatures file not found"
1072
+ [[ ! -f "$signatures_file" ]] && json_err "$E_FILE_NOT_FOUND" "Signatures file not found"
698
1073
 
699
1074
  # Read high-confidence signatures (confidence >= 0.7) using jq -c for compact single-line output
700
1075
  high_conf_signatures=$(jq -c '.signatures[] | select(.confidence_threshold >= 0.7)' "$signatures_file" 2>/dev/null)
@@ -784,7 +1159,7 @@ EOF
784
1159
  desc="${3:-}"
785
1160
  source="${4:-manual}"
786
1161
  phase="${5:-null}"
787
- [[ -z "$title" ]] && json_err "Usage: flag-add <type> <title> <description> [source] [phase]"
1162
+ [[ -z "$title" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-add <type> <title> <description> [source] [phase]"
788
1163
 
789
1164
  mkdir -p "$DATA_DIR"
790
1165
  flags_file="$DATA_DIR/flags.json"
@@ -797,6 +1172,7 @@ EOF
797
1172
  ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
798
1173
 
799
1174
  # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
1175
+ lock_acquired=false
800
1176
  if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
801
1177
  json_warn "W_DEGRADED" "File locking disabled - proceeding without lock: $(type _feature_reason &>/dev/null && _feature_reason file_locking || echo 'unknown')"
802
1178
  else
@@ -808,6 +1184,9 @@ EOF
808
1184
  exit 1
809
1185
  fi
810
1186
  }
1187
+ lock_acquired=true
1188
+ # Ensure lock is always released on exit (BUG-002 fix)
1189
+ trap 'release_lock "$flags_file" 2>/dev/null || true' EXIT
811
1190
  fi
812
1191
 
813
1192
  # Map type to severity
@@ -842,10 +1221,11 @@ EOF
842
1221
  resolution: null,
843
1222
  auto_resolve_on: (if $type == "blocker" and ($source | test("chaos") | not) then "build_pass" else null end)
844
1223
  }]
845
- ' "$flags_file") || { release_lock "$flags_file"; json_err "Failed to add flag"; }
1224
+ ' "$flags_file") || { json_err "$E_JSON_INVALID" "Failed to add flag"; }
846
1225
 
847
1226
  atomic_write "$flags_file" "$updated"
848
- release_lock "$flags_file"
1227
+ # Lock released by trap on exit (BUG-002 fix)
1228
+ trap - EXIT
849
1229
  json_ok "{\"id\":\"$id\",\"type\":\"$type\",\"severity\":\"$severity\"}"
850
1230
  ;;
851
1231
  flag-check-blockers)
@@ -882,10 +1262,10 @@ EOF
882
1262
  # Usage: flag-resolve <flag_id> [resolution_message]
883
1263
  flag_id="${1:-}"
884
1264
  resolution="${2:-Resolved}"
885
- [[ -z "$flag_id" ]] && json_err "Usage: flag-resolve <flag_id> [resolution_message]"
1265
+ [[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-resolve <flag_id> [resolution_message]"
886
1266
 
887
1267
  flags_file="$DATA_DIR/flags.json"
888
- [[ ! -f "$flags_file" ]] && json_err "No flags file found"
1268
+ [[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
889
1269
 
890
1270
  ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
891
1271
 
@@ -916,10 +1296,10 @@ EOF
916
1296
  # Acknowledge a flag (issue continues but noted)
917
1297
  # Usage: flag-acknowledge <flag_id>
918
1298
  flag_id="${1:-}"
919
- [[ -z "$flag_id" ]] && json_err "Usage: flag-acknowledge <flag_id>"
1299
+ [[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-acknowledge <flag_id>"
920
1300
 
921
1301
  flags_file="$DATA_DIR/flags.json"
922
- [[ ! -f "$flags_file" ]] && json_err "No flags file found"
1302
+ [[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
923
1303
 
924
1304
  ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
925
1305
 
@@ -996,16 +1376,22 @@ EOF
996
1376
  ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
997
1377
 
998
1378
  # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
1379
+ lock_acquired=false
999
1380
  if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
1000
1381
  json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
1001
1382
  else
1002
1383
  acquire_lock "$flags_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
1384
+ lock_acquired=true
1385
+ # Ensure lock is always released on exit (BUG-005/BUG-011 fix)
1386
+ trap 'release_lock "$flags_file" 2>/dev/null || true' EXIT
1003
1387
  fi
1004
1388
 
1005
1389
  # Count how many will be resolved
1006
1390
  count=$(jq --arg trigger "$trigger" '
1007
1391
  [.flags[] | select(.auto_resolve_on == $trigger and .resolved_at == null)] | length
1008
- ' "$flags_file")
1392
+ ' "$flags_file") || {
1393
+ json_err "$E_JSON_INVALID" "Failed to count flags for auto-resolve"
1394
+ }
1009
1395
 
1010
1396
  # Resolve them
1011
1397
  updated=$(jq --arg trigger "$trigger" --arg ts "$ts" '
@@ -1013,10 +1399,15 @@ EOF
1013
1399
  .resolved_at = $ts |
1014
1400
  .resolution = "Auto-resolved on " + $trigger
1015
1401
  else . end]
1016
- ' "$flags_file")
1402
+ ' "$flags_file") || {
1403
+ json_err "$E_JSON_INVALID" "Failed to auto-resolve flags"
1404
+ }
1017
1405
 
1018
1406
  atomic_write "$flags_file" "$updated"
1019
- release_lock "$flags_file"
1407
+ # Lock released by trap on exit (BUG-005/BUG-011 fix)
1408
+ if [[ "$lock_acquired" == "true" ]]; then
1409
+ trap - EXIT
1410
+ fi
1020
1411
  json_ok "{\"resolved\":$count,\"trigger\":\"$trigger\"}"
1021
1412
  ;;
1022
1413
  generate-ant-name)
@@ -1032,6 +1423,17 @@ EOF
1032
1423
  chaos) prefixes=("Probe" "Stress" "Shake" "Twist" "Snap" "Breach" "Surge" "Jolt") ;;
1033
1424
  archaeologist) prefixes=("Relic" "Fossil" "Dig" "Shard" "Epoch" "Strata" "Lore" "Glyph") ;;
1034
1425
  oracle) prefixes=("Sage" "Seer" "Vision" "Augur" "Mystic" "Sibyl" "Delph" "Pythia") ;;
1426
+ ambassador) prefixes=("Bridge" "Connect" "Link" "Diplomat" "Protocol" "Network" "Port" "Socket") ;;
1427
+ auditor) prefixes=("Review" "Inspect" "Exam" "Scrutin" "Verify" "Check" "Audit" "Assess") ;;
1428
+ chronicler) prefixes=("Record" "Write" "Document" "Chronicle" "Scribe" "Archive" "Script" "Ledger") ;;
1429
+ gatekeeper) prefixes=("Guard" "Protect" "Secure" "Shield" "Defend" "Bar" "Gate" "Checkpoint") ;;
1430
+ guardian) prefixes=("Defend" "Patrol" "Watch" "Vigil" "Shield" "Guard" "Armor" "Fort") ;;
1431
+ includer) prefixes=("Access" "Include" "Open" "Welcome" "Reach" "Universal" "Equal" "A11y") ;;
1432
+ keeper) prefixes=("Archive" "Store" "Curate" "Preserve" "Guard" "Keep" "Hold" "Save") ;;
1433
+ measurer) prefixes=("Metric" "Gauge" "Scale" "Measure" "Benchmark" "Track" "Count" "Meter") ;;
1434
+ probe) prefixes=("Test" "Probe" "Excavat" "Uncover" "Edge" "Mutant" "Trial" "Check") ;;
1435
+ tracker) prefixes=("Track" "Trace" "Debug" "Hunt" "Follow" "Trail" "Find" "Seek") ;;
1436
+ weaver) prefixes=("Weave" "Knit" "Spin" "Twine" "Transform" "Mend" "Weave" "Weave") ;;
1035
1437
  *) prefixes=("Ant" "Worker" "Drone" "Toiler" "Marcher" "Runner" "Carrier" "Helper") ;;
1036
1438
  esac
1037
1439
  # Pick random prefix and add random number
@@ -1174,10 +1576,10 @@ EOF
1174
1576
  confidence="${3:-0.5}"
1175
1577
  finding="${4:-}"
1176
1578
 
1177
- [[ -z "$swarm_id" || -z "$scout_type" || -z "$finding" ]] && json_err "Usage: swarm-findings-add <swarm_id> <scout_type> <confidence> <finding_json>"
1579
+ [[ -z "$swarm_id" || -z "$scout_type" || -z "$finding" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-add <swarm_id> <scout_type> <confidence> <finding_json>"
1178
1580
 
1179
1581
  findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
1180
- [[ ! -f "$findings_file" ]] && json_err "Swarm findings file not found: $swarm_id"
1582
+ [[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
1181
1583
 
1182
1584
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
1183
1585
 
@@ -1200,10 +1602,10 @@ EOF
1200
1602
  # Read all findings for a swarm
1201
1603
  # Usage: swarm-findings-read <swarm_id>
1202
1604
  swarm_id="${1:-}"
1203
- [[ -z "$swarm_id" ]] && json_err "Usage: swarm-findings-read <swarm_id>"
1605
+ [[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-read <swarm_id>"
1204
1606
 
1205
1607
  findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
1206
- [[ ! -f "$findings_file" ]] && json_err "Swarm findings file not found: $swarm_id"
1608
+ [[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
1207
1609
 
1208
1610
  json_ok "$(cat "$findings_file")"
1209
1611
  ;;
@@ -1214,10 +1616,10 @@ EOF
1214
1616
  swarm_id="${1:-}"
1215
1617
  solution="${2:-}"
1216
1618
 
1217
- [[ -z "$swarm_id" || -z "$solution" ]] && json_err "Usage: swarm-solution-set <swarm_id> <solution_json>"
1619
+ [[ -z "$swarm_id" || -z "$solution" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-solution-set <swarm_id> <solution_json>"
1218
1620
 
1219
1621
  findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
1220
- [[ ! -f "$findings_file" ]] && json_err "Swarm findings file not found: $swarm_id"
1622
+ [[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
1221
1623
 
1222
1624
  ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
1223
1625
 
@@ -1237,7 +1639,7 @@ EOF
1237
1639
  swarm_id="${1:-}"
1238
1640
  archive="${2:-}"
1239
1641
 
1240
- [[ -z "$swarm_id" ]] && json_err "Usage: swarm-cleanup <swarm_id> [--archive]"
1642
+ [[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-cleanup <swarm_id> [--archive]"
1241
1643
 
1242
1644
  findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
1243
1645
 
@@ -1258,7 +1660,7 @@ EOF
1258
1660
  grave-add)
1259
1661
  # Record a grave marker when a builder fails at a file
1260
1662
  # Usage: grave-add <file> <ant_name> <task_id> <phase> <failure_summary> [function] [line]
1261
- [[ $# -ge 5 ]] || json_err "Usage: grave-add <file> <ant_name> <task_id> <phase> <failure_summary> [function] [line]"
1663
+ [[ $# -ge 5 ]] || json_err "$E_VALIDATION_FAILED" "Usage: grave-add <file> <ant_name> <task_id> <phase> <failure_summary> [function] [line]"
1262
1664
  [[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
1263
1665
  file="$1"
1264
1666
  ant_name="$2"
@@ -1309,7 +1711,7 @@ EOF
1309
1711
  # Query for grave markers near a file path
1310
1712
  # Usage: grave-check <file_path>
1311
1713
  # Read-only, never modifies state
1312
- [[ $# -ge 1 ]] || json_err "Usage: grave-check <file_path>"
1714
+ [[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: grave-check <file_path>"
1313
1715
  [[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
1314
1716
  check_file="$1"
1315
1717
  check_dir=$(dirname "$check_file")
@@ -1333,14 +1735,15 @@ EOF
1333
1735
 
1334
1736
  generate-commit-message)
1335
1737
  # Generate an intelligent commit message from colony context
1336
- # Usage: generate-commit-message <type> <phase_id> <phase_name> [summary]
1337
- # Types: "milestone" | "pause" | "fix"
1338
- # Returns: {"message": "...", "body": "...", "files_changed": N}
1738
+ # Usage: generate-commit-message <type> <phase_id> <phase_name> [summary|ai_description] [plan_num]
1739
+ # Types: "milestone" | "pause" | "fix" | "contextual"
1740
+ # Returns: {"message": "...", "body": "...", "files_changed": N, ...}
1339
1741
 
1340
1742
  msg_type="${1:-milestone}"
1341
1743
  phase_id="${2:-0}"
1342
1744
  phase_name="${3:-unknown}"
1343
- summary="${4:-}"
1745
+ summary="${4:-}" # For milestone/fix types, or ai_description for contextual type
1746
+ plan_num="${5:-01}" # Optional: plan number for contextual type (e.g., "01")
1344
1747
 
1345
1748
  # Count changed files
1346
1749
  files_changed=0
@@ -1373,6 +1776,33 @@ EOF
1373
1776
  fi
1374
1777
  body="Swarm-verified fix applied and tested."
1375
1778
  ;;
1779
+ contextual)
1780
+ # NEW: Contextual commit with AI description and structured metadata
1781
+ # Derive subsystem from phase name (e.g., "11-foraging-specialization" -> "foraging")
1782
+ subsystem=$(echo "$phase_name" | sed -E 's/^[0-9]+-//' | sed -E 's/-[0-9]+.*$//' | tr '-' ' ')
1783
+ [[ -z "$subsystem" ]] && subsystem="phase"
1784
+
1785
+ # Build message with AI description (summary parameter is reused as ai_description)
1786
+ if [[ -n "$summary" ]]; then
1787
+ message="aether-milestone: ${summary}"
1788
+ else
1789
+ # Fallback if no AI description provided
1790
+ message="aether-milestone: phase ${phase_id}.${plan_num} complete -- ${phase_name}"
1791
+ fi
1792
+
1793
+ # Build structured body with metadata
1794
+ body="Scope: ${phase_id}.${plan_num}
1795
+ Files: ${files_changed} files changed"
1796
+
1797
+ # Truncate message if needed BEFORE JSON construction
1798
+ if [[ ${#message} -gt 72 ]]; then
1799
+ message="${message:0:69}..."
1800
+ fi
1801
+
1802
+ # Return enhanced JSON with additional metadata
1803
+ json_ok "{\"message\":\"$message\",\"body\":\"$body\",\"files_changed\":$files_changed,\"subsystem\":\"$subsystem\",\"scope\":\"${phase_id}.${plan_num}\"}"
1804
+ exit 0
1805
+ ;;
1376
1806
  *)
1377
1807
  message="aether-checkpoint: phase ${phase_id}"
1378
1808
  body=""
@@ -1387,6 +1817,32 @@ EOF
1387
1817
  json_ok "{\"message\":\"$message\",\"body\":\"$body\",\"files_changed\":$files_changed}"
1388
1818
  ;;
1389
1819
 
1820
+ # ============================================
1821
+ # CONTEXT PERSISTENCE SYSTEM
1822
+ # ============================================
1823
+
1824
+ context-update)
1825
+ # Update .aether/CONTEXT.md with current colony state
1826
+ # Usage: context-update <action> [args...]
1827
+ #
1828
+ # Actions:
1829
+ # init <goal> - Initialize new context
1830
+ # update-phase <phase_id> <name> - Update current phase
1831
+ # activity <command> <result> [files] - Log activity
1832
+ # constraint <type> <message> [source] - Add constraint (redirect/focus)
1833
+ # decision <description> [rationale] [who] - Log decision
1834
+ # safe-to-clear <yes|no> <reason> - Set safe-to-clear status
1835
+ # build-start <phase_id> <workers> <tasks> - Mark build starting
1836
+ # worker-spawn <ant_name> <caste> <task> - Log worker spawn
1837
+ # worker-complete <ant_name> <status> - Log worker completion
1838
+ # build-progress <completed> <total> - Update build progress
1839
+ # build-complete <status> <result> - Mark build complete
1840
+ #
1841
+ # Always call with explicit arguments - never rely on current directory
1842
+ # CONTEXT_FILE must be passed or detected from AETHER_ROOT
1843
+ _cmd_context_update "$@"
1844
+ ;;
1845
+
1390
1846
  # ============================================
1391
1847
  # REGISTRY & UPDATE UTILITIES
1392
1848
  # ============================================
@@ -1409,7 +1865,8 @@ EOF
1409
1865
  if [[ "$local_ver" == "$hub_ver" ]]; then
1410
1866
  json_ok '""'
1411
1867
  else
1412
- json_ok "\"Update available: $local_ver -> $hub_ver (run /ant:update)\""
1868
+ printf -v msg 'Update available: %s to %s (run /ant:update)' "$local_ver" "$hub_ver"
1869
+ json_ok "$msg"
1413
1870
  fi
1414
1871
  ;;
1415
1872
 
@@ -1418,7 +1875,7 @@ EOF
1418
1875
  # Usage: registry-add <repo_path> <version>
1419
1876
  repo_path="${1:-}"
1420
1877
  repo_version="${2:-}"
1421
- [[ -z "$repo_path" || -z "$repo_version" ]] && json_err "Usage: registry-add <repo_path> <version>"
1878
+ [[ -z "$repo_path" || -z "$repo_version" ]] && json_err "$E_VALIDATION_FAILED" "Usage: registry-add <repo_path> <version>"
1422
1879
 
1423
1880
  registry_file="$HOME/.aether/registry.json"
1424
1881
  mkdir -p "$HOME/.aether"
@@ -1439,7 +1896,7 @@ EOF
1439
1896
  .version = $ver |
1440
1897
  .updated_at = $ts
1441
1898
  else . end]
1442
- ' "$registry_file") || json_err "Failed to update registry"
1899
+ ' "$registry_file") || json_err "$E_JSON_INVALID" "Failed to update registry"
1443
1900
  else
1444
1901
  # Add new entry
1445
1902
  updated=$(jq --arg path "$repo_path" --arg ver "$repo_version" --arg ts "$ts" '
@@ -1449,7 +1906,7 @@ EOF
1449
1906
  "registered_at": $ts,
1450
1907
  "updated_at": $ts
1451
1908
  }]
1452
- ' "$registry_file") || json_err "Failed to update registry"
1909
+ ' "$registry_file") || json_err "$E_JSON_INVALID" "Failed to update registry"
1453
1910
  fi
1454
1911
 
1455
1912
  echo "$updated" > "$registry_file"
@@ -1462,7 +1919,7 @@ EOF
1462
1919
  hub_system="$HOME/.aether/system"
1463
1920
  local_aether="$AETHER_ROOT/.aether"
1464
1921
 
1465
- [[ ! -d "$hub_system" ]] && json_err "Hub system directory not found: $hub_system"
1922
+ [[ ! -d "$hub_system" ]] && json_err "$E_HUB_NOT_FOUND" "Hub system directory not found: $hub_system"
1466
1923
 
1467
1924
  # Allowlist of system files to copy (relative to system/)
1468
1925
  allowlist=(
@@ -2236,7 +2693,916 @@ NODESCRIPT
2236
2693
  json_ok "{\"item\":\"$item\",\"state\":\"collapsed\",\"view\":\"$view_name\"}"
2237
2694
  ;;
2238
2695
 
2696
+ queen-init)
2697
+ # Initialize QUEEN.md from template
2698
+ # Creates .aether/QUEEN.md from template if missing
2699
+ queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
2700
+
2701
+ # Check multiple locations for template
2702
+ # Order: dev (runtime/) -> npm install (hub) -> legacy
2703
+ template_file=""
2704
+ for path in \
2705
+ "$AETHER_ROOT/runtime/templates/QUEEN.md.template" \
2706
+ "$HOME/.aether/templates/QUEEN.md.template" \
2707
+ "$AETHER_ROOT/.aether/templates/QUEEN.md.template"; do
2708
+ if [[ -f "$path" ]]; then
2709
+ template_file="$path"
2710
+ break
2711
+ fi
2712
+ done
2713
+
2714
+ # Ensure docs directory exists
2715
+ mkdir -p "$AETHER_ROOT/.aether/docs"
2716
+
2717
+ # Check if QUEEN.md already exists and has content
2718
+ if [[ -f "$queen_file" ]] && [[ -s "$queen_file" ]]; then
2719
+ json_ok '{"created":false,"path":".aether/docs/QUEEN.md","reason":"already_exists"}'
2720
+ exit 0
2721
+ fi
2722
+
2723
+ # Check if template was found
2724
+ if [[ -z "$template_file" ]]; then
2725
+ json_err "$E_FILE_NOT_FOUND" "Template not found" '{"templates_checked":["runtime/templates/QUEEN.md.template","~/.aether/templates/QUEEN.md.template",".aether/templates/QUEEN.md.template"]}'
2726
+ exit 1
2727
+ fi
2728
+
2729
+ # Create QUEEN.md from template with timestamp substitution
2730
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
2731
+ sed -e "s/{TIMESTAMP}/$timestamp/g" "$template_file" > "$queen_file"
2732
+
2733
+ if [[ -f "$queen_file" ]]; then
2734
+ json_ok "{\"created\":true,\"path\":\".aether/docs/QUEEN.md\",\"source\":\"$template_file\"}"
2735
+ else
2736
+ json_err "$E_FILE_NOT_FOUND" "Failed to create QUEEN.md" '{"path":".aether/docs/QUEEN.md"}'
2737
+ exit 1
2738
+ fi
2739
+ ;;
2740
+
2741
+ queen-read)
2742
+ # Read QUEEN.md and return wisdom as JSON for worker priming
2743
+ # Extracts METADATA block and sections for colony guidance
2744
+ queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
2745
+
2746
+ # Check if QUEEN.md exists
2747
+ if [[ ! -f "$queen_file" ]]; then
2748
+ json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"path":".aether/docs/QUEEN.md"}'
2749
+ exit 1
2750
+ fi
2751
+
2752
+ # Extract METADATA JSON block (between <!-- METADATA and -->)
2753
+ metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_file" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
2754
+
2755
+ # If no metadata found, return empty structure
2756
+ if [[ -z "$metadata" ]]; then
2757
+ metadata='{"version":"unknown","last_evolved":null,"colonies_contributed":[],"promotion_thresholds":{},"stats":{}}'
2758
+ fi
2759
+
2760
+ # Extract sections content for worker priming
2761
+ # Use awk to parse markdown sections - remove header line and trailing section header
2762
+ philosophies=$(awk '/^## 📜 Philosophies$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
2763
+ patterns=$(awk '/^## 🧭 Patterns$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
2764
+ redirects=$(awk '/^## ⚠️ Redirects$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
2765
+ stack_wisdom=$(awk '/^## 🔧 Stack Wisdom$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
2766
+ decrees=$(awk '/^## 🏛️ Decrees$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
2767
+
2768
+ # Build JSON output
2769
+ result=$(jq -n \
2770
+ --argjson meta "$metadata" \
2771
+ --arg philosophies "$philosophies" \
2772
+ --arg patterns "$patterns" \
2773
+ --arg redirects "$redirects" \
2774
+ --arg stack_wisdom "$stack_wisdom" \
2775
+ --arg decrees "$decrees" \
2776
+ '{
2777
+ metadata: $meta,
2778
+ wisdom: {
2779
+ philosophies: $philosophies,
2780
+ patterns: $patterns,
2781
+ redirects: $redirects,
2782
+ stack_wisdom: $stack_wisdom,
2783
+ decrees: $decrees
2784
+ },
2785
+ priming: {
2786
+ has_philosophies: ($philosophies | length) > 0 and $philosophies != "*No philosophies recorded yet.*\n",
2787
+ has_patterns: ($patterns | length) > 0 and $patterns != "*No patterns recorded yet.*\n",
2788
+ has_redirects: ($redirects | length) > 0 and $redirects != "*No redirects recorded yet.*\n",
2789
+ has_stack_wisdom: ($stack_wisdom | length) > 0 and $stack_wisdom != "*No stack wisdom recorded yet.*\n",
2790
+ has_decrees: ($decrees | length) > 0 and $decrees != "*No decrees recorded yet.*\n"
2791
+ }
2792
+ }')
2793
+
2794
+ json_ok "$result"
2795
+ ;;
2796
+
2797
+ queen-promote)
2798
+ # Promote a learning to QUEEN.md wisdom
2799
+ # Usage: queen-promote <type> <content> <colony_name>
2800
+ # Types: philosophy, pattern, redirect, stack, decree
2801
+ wisdom_type="${1:-}"
2802
+ content="${2:-}"
2803
+ colony_name="${3:-}"
2804
+
2805
+ # Validate required arguments
2806
+ [[ -z "$wisdom_type" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"type"}'
2807
+ [[ -z "$content" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"content"}'
2808
+ [[ -z "$colony_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"colony_name"}'
2809
+
2810
+ # Validate type
2811
+ valid_types=("philosophy" "pattern" "redirect" "stack" "decree")
2812
+ type_valid=false
2813
+ for vt in "${valid_types[@]}"; do
2814
+ [[ "$wisdom_type" == "$vt" ]] && type_valid=true && break
2815
+ done
2816
+ [[ "$type_valid" == "false" ]] && json_err "$E_VALIDATION_FAILED" "Invalid type: $wisdom_type" '{"valid_types":["philosophy","pattern","redirect","stack","decree"]}'
2817
+
2818
+ queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
2819
+
2820
+ # Check if QUEEN.md exists
2821
+ if [[ ! -f "$queen_file" ]]; then
2822
+ json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"path":".aether/docs/QUEEN.md"}'
2823
+ exit 1
2824
+ fi
2825
+
2826
+ # Extract METADATA to get promotion thresholds
2827
+ metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_file" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
2828
+
2829
+ # Get threshold for this type (default: philosophy=5, pattern=3, redirect=2, stack=1, decree=0)
2830
+ threshold=$(echo "$metadata" | jq -r ".promotion_thresholds.${wisdom_type} // null")
2831
+ if [[ "$threshold" == "null" ]]; then
2832
+ case "$wisdom_type" in
2833
+ philosophy) threshold=5 ;;
2834
+ pattern) threshold=3 ;;
2835
+ redirect) threshold=2 ;;
2836
+ stack) threshold=1 ;;
2837
+ decree) threshold=0 ;;
2838
+ *) threshold=1 ;;
2839
+ esac
2840
+ fi
2841
+
2842
+ # For decrees, always promote immediately (threshold 0)
2843
+ # For other types, we assume validation count is passed or threshold is met
2844
+ # In a real implementation, this would check a validation counter
2845
+ # For now, we append if threshold allows (decrees always, others need external validation)
2846
+
2847
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
2848
+
2849
+ # Map type to section header and emoji
2850
+ case "$wisdom_type" in
2851
+ philosophy) section_header="## 📜 Philosophies" ;;
2852
+ pattern) section_header="## 🧭 Patterns" ;;
2853
+ redirect) section_header="## ⚠️ Redirects" ;;
2854
+ stack) section_header="## 🔧 Stack Wisdom" ;;
2855
+ decree) section_header="## 🏛️ Decrees" ;;
2856
+ esac
2857
+
2858
+ # Build the new entry
2859
+ entry="- **${colony_name}** (${ts}): ${content}"
2860
+
2861
+ # Create temp file for atomic write
2862
+ tmp_file="${queen_file}.tmp.$$"
2863
+
2864
+ # Find line numbers for section boundaries
2865
+ section_line=$(grep -n "^${section_header}$" "$queen_file" | head -1 | cut -d: -f1)
2866
+ next_section_line=$(tail -n +$((section_line + 1)) "$queen_file" | grep -n "^## " | head -1 | cut -d: -f1)
2867
+ if [[ -n "$next_section_line" ]]; then
2868
+ section_end=$((section_line + next_section_line - 1))
2869
+ else
2870
+ section_end=$(wc -l < "$queen_file")
2871
+ fi
2872
+
2873
+ # Check if section has placeholder (grep returns 1 when no matches, handle with || true)
2874
+ has_placeholder=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -c "No.*recorded yet" || true)
2875
+ has_placeholder=${has_placeholder:-0}
2876
+
2877
+ if [[ "$has_placeholder" -gt 0 ]]; then
2878
+ # Replace placeholder with entry - only within the target section
2879
+ # Find the specific line number of the placeholder within the section
2880
+ placeholder_line=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -n "^\\*No .* recorded yet" | head -1 | cut -d: -f1)
2881
+ if [[ -n "$placeholder_line" ]]; then
2882
+ actual_line=$((section_line + placeholder_line - 1))
2883
+ sed "${actual_line}c\\
2884
+ ${entry}" "$queen_file" > "$tmp_file"
2885
+ else
2886
+ # Fallback: insert after section header
2887
+ sed "${section_line}a\\
2888
+ ${entry}" "$queen_file" > "$tmp_file"
2889
+ fi
2890
+ else
2891
+ # Insert entry after the description paragraph (after the second empty line in section)
2892
+ # The structure is: header, blank, description, blank, [entries...]
2893
+ # We want to insert after the blank line following the description
2894
+ empty_lines=$(sed -n "$((section_line + 1)),${section_end}p" "$queen_file" | grep -n "^$" | cut -d: -f1)
2895
+ # Get the second empty line (after description)
2896
+ insert_line=$(echo "$empty_lines" | sed -n '2p')
2897
+ if [[ -n "$insert_line" ]]; then
2898
+ insert_line=$((section_line + insert_line))
2899
+ else
2900
+ # Fallback: use first empty line
2901
+ insert_line=$(echo "$empty_lines" | head -1)
2902
+ if [[ -n "$insert_line" ]]; then
2903
+ insert_line=$((section_line + insert_line))
2904
+ else
2905
+ insert_line=$((section_line + 1))
2906
+ fi
2907
+ fi
2908
+ # Insert the entry after the found line
2909
+ sed "${insert_line}a\\
2910
+ ${entry}" "$queen_file" > "$tmp_file"
2911
+ fi
2912
+
2913
+ # Update Evolution Log in temp file
2914
+ ev_entry="| ${ts} | ${colony_name} | promoted_${wisdom_type} | Added: ${content:0:50}... |"
2915
+ # Find the line after the separator in Evolution Log table
2916
+ ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1)
2917
+
2918
+ # Use awk for cross-platform insertion
2919
+ awk -v line="$ev_separator" -v entry="$ev_entry" 'NR==line{print; print entry; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
2920
+
2921
+ # Update METADATA stats in temp file
2922
+ # Map wisdom_type to stat key (irregular plurals handled)
2923
+ case "$wisdom_type" in
2924
+ stack) stat_key="total_stack_entries" ;;
2925
+ philosophy) stat_key="total_philosophies" ;;
2926
+ *) stat_key="total_${wisdom_type}s" ;;
2927
+ esac
2928
+ # Read current count from temp file (which has the latest state)
2929
+ current_count=$(grep "\"${stat_key}\":" "$tmp_file" 2>/dev/null | grep -o '[0-9]*' | head -1 || true)
2930
+ current_count=${current_count:-0}
2931
+ new_count=$((current_count + 1))
2932
+
2933
+ # Update last_evolved using awk
2934
+ awk -v ts="$ts" '/"last_evolved":/ { gsub(/"last_evolved": "[^"]*"/, "\"last_evolved\": \"" ts "\""); } {print}' "$tmp_file" > "${tmp_file}.meta" && mv "${tmp_file}.meta" "$tmp_file"
2935
+
2936
+ # Update stats count using awk
2937
+ awk -v type="$stat_key" -v count="$new_count" '{
2938
+ gsub("\"" type "\": [0-9]*", "\"" type "\": " count)
2939
+ print
2940
+ }' "$tmp_file" > "${tmp_file}.stats" && mv "${tmp_file}.stats" "$tmp_file"
2941
+
2942
+ # Add colony to colonies_contributed if not present
2943
+ if ! grep -q "\"${colony_name}\"" "$tmp_file"; then
2944
+ # Add to colonies_contributed array using awk - handle empty and non-empty arrays
2945
+ awk -v colony="$colony_name" '
2946
+ /"colonies_contributed": \[\]/ {
2947
+ gsub(/"colonies_contributed": \[\]/, "\"colonies_contributed\": [\"" colony "\"]")
2948
+ print
2949
+ next
2950
+ }
2951
+ /"colonies_contributed": \[/ && !/\]/ {
2952
+ # Multi-line array, add at next closing bracket
2953
+ print
2954
+ next
2955
+ }
2956
+ /"colonies_contributed": \[/ {
2957
+ # Single-line array with elements
2958
+ gsub(/\]$/, "\"" colony "\", ]")
2959
+ print
2960
+ next
2961
+ }
2962
+ { print }
2963
+ ' "$tmp_file" > "${tmp_file}.col" && mv "${tmp_file}.col" "$tmp_file"
2964
+ fi
2965
+
2966
+ # Atomic move
2967
+ mv "$tmp_file" "$queen_file"
2968
+
2969
+ json_ok "{\"promoted\":true,\"type\":\"$wisdom_type\",\"colony\":\"$colony_name\",\"timestamp\":\"$ts\",\"threshold\":$threshold,\"new_count\":$new_count}"
2970
+ ;;
2971
+
2972
+ survey-load)
2973
+ phase_type="${1:-}"
2974
+ survey_dir=".aether/data/survey"
2975
+
2976
+ if [[ ! -d "$survey_dir" ]]; then
2977
+ json_err "$E_FILE_NOT_FOUND" "No survey found"
2978
+ fi
2979
+
2980
+ docs=""
2981
+ case "$phase_type" in
2982
+ *frontend*|*component*|*UI*|*page*|*button*)
2983
+ docs="DISCIPLINES.md,CHAMBERS.md"
2984
+ ;;
2985
+ *API*|*endpoint*|*backend*|*route*)
2986
+ docs="BLUEPRINT.md,DISCIPLINES.md"
2987
+ ;;
2988
+ *database*|*schema*|*model*|*migration*)
2989
+ docs="BLUEPRINT.md,PROVISIONS.md"
2990
+ ;;
2991
+ *test*|*spec*|*coverage*)
2992
+ docs="SENTINEL-PROTOCOLS.md,DISCIPLINES.md"
2993
+ ;;
2994
+ *integration*|*external*|*client*)
2995
+ docs="TRAILS.md,PROVISIONS.md"
2996
+ ;;
2997
+ *refactor*|*cleanup*|*debt*)
2998
+ docs="PATHOGENS.md,BLUEPRINT.md"
2999
+ ;;
3000
+ *setup*|*config*|*initialize*)
3001
+ docs="PROVISIONS.md,CHAMBERS.md"
3002
+ ;;
3003
+ *)
3004
+ docs="PROVISIONS.md,BLUEPRINT.md"
3005
+ ;;
3006
+ esac
3007
+
3008
+ json_ok "{\"ok\":true,\"docs\":\"$docs\",\"dir\":\"$survey_dir\"}"
3009
+ ;;
3010
+
3011
+ survey-verify)
3012
+ survey_dir=".aether/data/survey"
3013
+ required="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
3014
+ missing=""
3015
+ counts=""
3016
+
3017
+ for doc in $required; do
3018
+ if [[ ! -f "$survey_dir/$doc" ]]; then
3019
+ missing="$missing $doc"
3020
+ else
3021
+ lines=$(wc -l < "$survey_dir/$doc" | tr -d ' ')
3022
+ counts="$counts $doc:$lines"
3023
+ fi
3024
+ done
3025
+
3026
+ if [[ -n "$missing" ]]; then
3027
+ json_err "$E_FILE_NOT_FOUND" "Missing survey documents" "{\"missing\":\"$missing\"}"
3028
+ fi
3029
+
3030
+ json_ok "{\"ok\":true,\"counts\":\"$counts\"}"
3031
+ ;;
3032
+
3033
+ checkpoint-check)
3034
+ allowlist_file="$DATA_DIR/checkpoint-allowlist.json"
3035
+
3036
+ if [[ ! -f "$allowlist_file" ]]; then
3037
+ json_err "$E_FILE_NOT_FOUND" "Allowlist not found" "{\"path\":\"$allowlist_file\"}"
3038
+ fi
3039
+
3040
+ # Get dirty files from git (staged or unstaged)
3041
+ dirty_files=$(git status --porcelain 2>/dev/null | awk '{print $2}' || true)
3042
+
3043
+ if [[ -z "$dirty_files" ]]; then
3044
+ json_ok '{"ok":true,"system_files":[],"user_files":[],"has_user_files":false}'
3045
+ exit 0
3046
+ fi
3047
+
3048
+ # Temporary files for building JSON
3049
+ system_files_tmp=$(mktemp)
3050
+ user_files_tmp=$(mktemp)
3051
+
3052
+ # Check each file against allowlist patterns
3053
+ for file in $dirty_files; do
3054
+ is_system=false
3055
+
3056
+ # Check against system file patterns
3057
+ if [[ "$file" == ".aether/aether-utils.sh" ]]; then
3058
+ is_system=true
3059
+ elif [[ "$file" == ".aether/workers.md" ]]; then
3060
+ is_system=true
3061
+ elif [[ "$file" == .aether/docs/*.md ]]; then
3062
+ is_system=true
3063
+ elif [[ "$file" == .claude/commands/ant/*.md ]] || [[ "$file" == .claude/commands/ant/**/*.md ]]; then
3064
+ is_system=true
3065
+ elif [[ "$file" == .claude/commands/st/*.md ]] || [[ "$file" == .claude/commands/st/**/*.md ]]; then
3066
+ is_system=true
3067
+ elif [[ "$file" == .opencode/commands/ant/*.md ]] || [[ "$file" == .opencode/commands/ant/**/*.md ]]; then
3068
+ is_system=true
3069
+ elif [[ "$file" == .opencode/agents/*.md ]] || [[ "$file" == .opencode/agents/**/*.md ]]; then
3070
+ is_system=true
3071
+ elif [[ "$file" == runtime/* ]]; then
3072
+ is_system=true
3073
+ elif [[ "$file" == bin/* ]]; then
3074
+ is_system=true
3075
+ fi
3076
+
3077
+ if [[ "$is_system" == "true" ]]; then
3078
+ echo "$file" >> "$system_files_tmp"
3079
+ else
3080
+ echo "$file" >> "$user_files_tmp"
3081
+ fi
3082
+ done
3083
+
3084
+ # Build JSON using jq if available, otherwise use simple format
3085
+ if command -v jq >/dev/null 2>&1; then
3086
+ result=$(jq -n \
3087
+ --argjson system "$(jq -R . < "$system_files_tmp" 2>/dev/null | jq -s .)" \
3088
+ --argjson user "$(jq -R . < "$user_files_tmp" 2>/dev/null | jq -s .)" \
3089
+ '{ok: true, system_files: $system, user_files: $user, has_user_files: ($user | length > 0)}')
3090
+ else
3091
+ # Fallback without jq - simple output
3092
+ system_count=$(wc -l < "$system_files_tmp" 2>/dev/null | tr -d ' ' || echo "0")
3093
+ user_count=$(wc -l < "$user_files_tmp" 2>/dev/null | tr -d ' ' || echo "0")
3094
+ has_user=false
3095
+ [[ "$user_count" -gt 0 ]] && has_user=true
3096
+ result="{\"ok\":true,\"system_files\":[],\"user_files\":[],\"has_user_files\":$has_user}"
3097
+ fi
3098
+
3099
+ rm -f "$system_files_tmp" "$user_files_tmp"
3100
+ echo "$result"
3101
+ exit 0
3102
+ ;;
3103
+
3104
+ normalize-args)
3105
+ # Normalize arguments from Claude Code ($ARGUMENTS) or OpenCode ($@)
3106
+ # Usage: bash .aether/aether-utils.sh normalize-args [args...]
3107
+ # Or: eval "$(bash .aether/aether-utils.sh normalize-args)"
3108
+ #
3109
+ # Claude Code passes args in $ARGUMENTS variable
3110
+ # OpenCode passes args in $@ (positional parameters)
3111
+ # This command outputs the normalized arguments as a single string
3112
+
3113
+ normalized=""
3114
+
3115
+ # Try Claude Code style first ($ARGUMENTS environment variable)
3116
+ if [ -n "${ARGUMENTS:-}" ]; then
3117
+ normalized="$ARGUMENTS"
3118
+ # Fall back to OpenCode style ($@ positional params)
3119
+ elif [ $# -gt 0 ]; then
3120
+ # Preserve arguments with spaces by quoting
3121
+ for arg in "$@"; do
3122
+ if [[ "$arg" == *" "* ]] || [[ "$arg" == *"\t"* ]] || [[ "$arg" == *"\n"* ]]; then
3123
+ # Quote arguments containing whitespace
3124
+ normalized="$normalized \"$arg\""
3125
+ else
3126
+ normalized="$normalized $arg"
3127
+ fi
3128
+ done
3129
+ # Trim leading space
3130
+ normalized="${normalized# }"
3131
+ fi
3132
+
3133
+ # Output normalized arguments
3134
+ echo "$normalized"
3135
+ exit 0
3136
+ ;;
3137
+
3138
+ # Backward compatibility wrappers for session commands
3139
+ survey-verify-fresh)
3140
+ # Backward compatibility: delegate to session-verify-fresh --command survey
3141
+ # Usage: bash .aether/aether-utils.sh survey-verify-fresh [--force] <survey_start_unixtime>
3142
+
3143
+ force_mode=""
3144
+ survey_start_time=""
3145
+
3146
+ # Parse arguments
3147
+ for arg in "$@"; do
3148
+ if [[ "$arg" == "--force" ]]; then
3149
+ force_mode="--force"
3150
+ elif [[ "$arg" =~ ^[0-9]+$ ]]; then
3151
+ survey_start_time="$arg"
3152
+ fi
3153
+ done
3154
+
3155
+ # Delegate to generic command
3156
+ if [[ -n "$force_mode" ]]; then
3157
+ $0 session-verify-fresh --command survey --force "$survey_start_time"
3158
+ else
3159
+ $0 session-verify-fresh --command survey "$survey_start_time"
3160
+ fi
3161
+ ;;
3162
+
3163
+ survey-clear)
3164
+ # Backward compatibility: delegate to session-clear --command survey
3165
+ # Usage: bash .aether/aether-utils.sh survey-clear [--dry-run]
3166
+
3167
+ dry_run=""
3168
+
3169
+ # Parse arguments
3170
+ for arg in "$@"; do
3171
+ if [[ "$arg" == "--dry-run" ]]; then
3172
+ dry_run="--dry-run"
3173
+ fi
3174
+ done
3175
+
3176
+ # Delegate to generic command
3177
+ if [[ "$dry_run" == "--dry-run" ]]; then
3178
+ $0 session-clear --command survey --dry-run
3179
+ else
3180
+ $0 session-clear --command survey
3181
+ fi
3182
+ ;;
3183
+
3184
+ session-verify-fresh)
3185
+ # Generic session freshness verification
3186
+ # Usage: bash .aether/aether-utils.sh session-verify-fresh --command <name> [--force] <session_start_unixtime>
3187
+ # Returns: JSON with pass/fail status and file details
3188
+
3189
+ # Parse arguments
3190
+ command_name=""
3191
+ force_mode=""
3192
+ session_start_time=""
3193
+
3194
+ while [[ $# -gt 0 ]]; do
3195
+ case "$1" in
3196
+ --command) command_name="$2"; shift 2 ;;
3197
+ --force) force_mode="--force"; shift ;;
3198
+ *) session_start_time="$1"; shift ;;
3199
+ esac
3200
+ done
3201
+
3202
+ # Validate command name
3203
+ [[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-verify-fresh --command <name> [--force] <session_start>"
3204
+
3205
+ # Map command to directory and files (using env var override pattern)
3206
+ case "$command_name" in
3207
+ survey)
3208
+ session_dir="${SURVEY_DIR:-.aether/data/survey}"
3209
+ required_docs="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
3210
+ ;;
3211
+ oracle)
3212
+ session_dir="${ORACLE_DIR:-.aether/oracle}"
3213
+ required_docs="progress.md research.json"
3214
+ ;;
3215
+ watch)
3216
+ session_dir="${WATCH_DIR:-.aether/data}"
3217
+ required_docs="watch-status.txt watch-progress.txt"
3218
+ ;;
3219
+ swarm)
3220
+ session_dir="${SWARM_DIR:-.aether/data/swarm}"
3221
+ required_docs="findings.json"
3222
+ ;;
3223
+ init)
3224
+ session_dir="${INIT_DIR:-.aether/data}"
3225
+ required_docs="COLONY_STATE.json constraints.json"
3226
+ ;;
3227
+ seal|entomb)
3228
+ session_dir="${ARCHIVE_DIR:-.aether/data/archive}"
3229
+ required_docs="manifest.json"
3230
+ ;;
3231
+ *)
3232
+ json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name" '{"commands":["survey","oracle","watch","swarm","init","seal","entomb"]}'
3233
+ ;;
3234
+ esac
3235
+
3236
+ # Initialize result arrays
3237
+ fresh_docs=""
3238
+ stale_docs=""
3239
+ missing_docs=""
3240
+ total_lines=0
3241
+
3242
+ for doc in $required_docs; do
3243
+ doc_path="$session_dir/$doc"
3244
+
3245
+ if [[ ! -f "$doc_path" ]]; then
3246
+ missing_docs="${missing_docs:+$missing_docs }$doc"
3247
+ continue
3248
+ fi
3249
+
3250
+ # Get line count
3251
+ lines=$(wc -l < "$doc_path" 2>/dev/null | tr -d ' ' || echo "0")
3252
+ total_lines=$((total_lines + lines))
3253
+
3254
+ # In force mode, accept any existing file
3255
+ if [[ "$force_mode" == "--force" ]]; then
3256
+ fresh_docs="${fresh_docs:+$fresh_docs }$doc"
3257
+ continue
3258
+ fi
3259
+
3260
+ # Check timestamp if session_start_time provided
3261
+ if [[ -n "$session_start_time" ]]; then
3262
+ # Cross-platform stat: macOS uses -f %m, Linux uses -c %Y
3263
+ file_mtime=$(stat -f %m "$doc_path" 2>/dev/null || stat -c %Y "$doc_path" 2>/dev/null || echo "0")
3264
+
3265
+ if [[ "$file_mtime" -ge "$session_start_time" ]]; then
3266
+ fresh_docs="${fresh_docs:+$fresh_docs }$doc"
3267
+ else
3268
+ stale_docs="${stale_docs:+$stale_docs }$doc"
3269
+ fi
3270
+ else
3271
+ # No start time provided - accept existing file (backward compatible)
3272
+ fresh_docs="${fresh_docs:+$fresh_docs }$doc"
3273
+ fi
3274
+ done
3275
+
3276
+ # Determine pass/fail
3277
+ # pass = true if: no stale files (fresh files can coexist with missing files)
3278
+ # missing files are ok - they will be created during the session
3279
+ pass=false
3280
+ if [[ "$force_mode" == "--force" ]] || [[ -z "$stale_docs" ]]; then
3281
+ pass=true
3282
+ fi
3283
+
3284
+ # Build JSON response
3285
+ fresh_json=""
3286
+ for item in $fresh_docs; do fresh_json="$fresh_json\"$item\","; done
3287
+ fresh_json="[${fresh_json%,}]"
3288
+
3289
+ stale_json=""
3290
+ for item in $stale_docs; do stale_json="$stale_json\"$item\","; done
3291
+ stale_json="[${stale_json%,}]"
3292
+
3293
+ missing_json=""
3294
+ for item in $missing_docs; do missing_json="$missing_json\"$item\","; done
3295
+ missing_json="[${missing_json%,}]"
3296
+
3297
+ echo "{\"ok\":$pass,\"command\":\"$command_name\",\"fresh\":$fresh_json,\"stale\":$stale_json,\"missing\":$missing_json,\"total_lines\":$total_lines}"
3298
+ exit 0
3299
+ ;;
3300
+
3301
+ session-clear)
3302
+ # Generic session file clearing
3303
+ # Usage: bash .aether/aether-utils.sh session-clear --command <name> [--dry-run]
3304
+
3305
+ # Parse arguments
3306
+ command_name=""
3307
+ dry_run=""
3308
+
3309
+ while [[ $# -gt 0 ]]; do
3310
+ case "$1" in
3311
+ --command) command_name="$2"; shift 2 ;;
3312
+ --dry-run) dry_run="--dry-run"; shift ;;
3313
+ *) shift ;;
3314
+ esac
3315
+ done
3316
+
3317
+ [[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-clear --command <name> [--dry-run]"
3318
+
3319
+ # Map command to directory and files
3320
+ case "$command_name" in
3321
+ survey)
3322
+ session_dir="${SURVEY_DIR:-.aether/data/survey}"
3323
+ files="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
3324
+ ;;
3325
+ oracle)
3326
+ session_dir="${ORACLE_DIR:-.aether/oracle}"
3327
+ files="progress.md research.json .stop"
3328
+ # Also clear discoveries subdirectory
3329
+ subdir_files="discoveries/*"
3330
+ ;;
3331
+ watch)
3332
+ session_dir="${WATCH_DIR:-.aether/data}"
3333
+ files="watch-status.txt watch-progress.txt"
3334
+ ;;
3335
+ swarm)
3336
+ session_dir="${SWARM_DIR:-.aether/data/swarm}"
3337
+ files="findings.json display.json timing.json"
3338
+ ;;
3339
+ init)
3340
+ # Init clear is destructive - blocked for auto-clear
3341
+ json_err "$E_VALIDATION_FAILED" "Command 'init' is protected and cannot be auto-cleared. Use manual removal of COLONY_STATE.json if absolutely necessary."
3342
+ ;;
3343
+ seal|entomb)
3344
+ # Archive operations should never be auto-cleared
3345
+ json_err "$E_VALIDATION_FAILED" "Command '$command_name' is protected and cannot be auto-cleared. Archives and chambers must be managed manually."
3346
+ ;;
3347
+ *)
3348
+ json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name"
3349
+ ;;
3350
+ esac
3351
+
3352
+ cleared=""
3353
+ errors=""
3354
+
3355
+ if [[ -d "$session_dir" && -n "$files" ]]; then
3356
+ for doc in $files; do
3357
+ doc_path="$session_dir/$doc"
3358
+ if [[ -f "$doc_path" ]]; then
3359
+ if [[ "$dry_run" == "--dry-run" ]]; then
3360
+ cleared="$cleared $doc"
3361
+ else
3362
+ if rm -f "$doc_path" 2>/dev/null; then
3363
+ cleared="$cleared $doc"
3364
+ else
3365
+ errors="$errors $doc"
3366
+ fi
3367
+ fi
3368
+ fi
3369
+ done
3370
+
3371
+ # Handle oracle discoveries subdirectory
3372
+ if [[ "$command_name" == "oracle" && -d "$session_dir/discoveries" ]]; then
3373
+ if [[ "$dry_run" == "--dry-run" ]]; then
3374
+ cleared="$cleared discoveries/"
3375
+ else
3376
+ rm -rf "$session_dir/discoveries" 2>/dev/null && cleared="$cleared discoveries/" || errors="$errors discoveries/"
3377
+ fi
3378
+ fi
3379
+ fi
3380
+
3381
+ json_ok "{\"command\":\"$command_name\",\"cleared\":\"${cleared// /}\",\"errors\":\"${errors// /}\",\"dry_run\":$([[ "$dry_run" == "--dry-run" ]] && echo "true" || echo "false")}"
3382
+ ;;
3383
+
3384
+ pheromone-export)
3385
+ # Export pheromones to eternal XML format
3386
+ # Usage: pheromone-export [input_json] [output_xml]
3387
+ # input_json: Path to pheromones.json (default: .aether/data/pheromones.json)
3388
+ # output_xml: Path to output XML (default: ~/.aether/eternal/pheromones.xml)
3389
+
3390
+ input_json="${1:-.aether/data/pheromones.json}"
3391
+ output_xml="${2:-$HOME/.aether/eternal/pheromones.xml}"
3392
+ schema_file="${3:-$SCRIPT_DIR/schemas/pheromone.xsd}"
3393
+
3394
+ # Ensure xml-utils.sh is sourced
3395
+ if ! type pheromone-export &>/dev/null; then
3396
+ [[ -f "$SCRIPT_DIR/utils/xml-utils.sh" ]] && source "$SCRIPT_DIR/utils/xml-utils.sh"
3397
+ fi
3398
+
3399
+ if type pheromone-export &>/dev/null; then
3400
+ pheromone-export "$input_json" "$output_xml" "$schema_file"
3401
+ else
3402
+ json_err "$E_DEPENDENCY_MISSING" "xml-utils.sh not available for pheromone export"
3403
+ fi
3404
+ ;;
3405
+
3406
+ # ============================================================================
3407
+ # Session Continuity Commands
3408
+ # ============================================================================
3409
+
3410
+ session-init)
3411
+ # Initialize a new session tracking file
3412
+ # Usage: session-init [session_id] [goal]
3413
+ session_id="${2:-$(date +%s)_$(openssl rand -hex 4 2>/dev/null || echo $$)}"
3414
+ goal="${3:-}"
3415
+
3416
+ session_file="$DATA_DIR/session.json"
3417
+
3418
+ cat > "$session_file" << EOF
3419
+ {
3420
+ "session_id": "$session_id",
3421
+ "started_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
3422
+ "last_command": null,
3423
+ "last_command_at": null,
3424
+ "colony_goal": "$goal",
3425
+ "current_phase": 0,
3426
+ "current_milestone": "First Mound",
3427
+ "suggested_next": "/ant:plan",
3428
+ "context_cleared": false,
3429
+ "resumed_at": null,
3430
+ "active_todos": [],
3431
+ "summary": "Session initialized"
3432
+ }
3433
+ EOF
3434
+ json_ok "{\"session_id\":\"$session_id\",\"goal\":\"$goal\",\"file\":\"$session_file\"}"
3435
+ ;;
3436
+
3437
+ session-update)
3438
+ # Update session with latest activity
3439
+ # Usage: session-update <command> [suggested_next] [summary]
3440
+ cmd_run="${2:-}"
3441
+ suggested="${3:-}"
3442
+ summary="${4:-}"
3443
+
3444
+ session_file="$DATA_DIR/session.json"
3445
+
3446
+ if [[ ! -f "$session_file" ]]; then
3447
+ # Auto-initialize if doesn't exist
3448
+ bash "$0" session-init "auto_$(date +%s)" ""
3449
+ fi
3450
+
3451
+ # Read current session
3452
+ current_session=$(cat "$session_file" 2>/dev/null || echo '{}')
3453
+
3454
+ # Extract current values for preservation
3455
+ current_goal=$(echo "$current_session" | jq -r '.colony_goal // empty')
3456
+ current_phase=$(echo "$current_session" | jq -r '.current_phase // 0')
3457
+ current_milestone=$(echo "$current_session" | jq -r '.current_milestone // "First Mound"')
3458
+
3459
+ # Get top 3 TODOs if TO-DOs.md exists
3460
+ todos="[]"
3461
+ if [[ -f "TO-DOs.md" ]]; then
3462
+ todos=$(grep "^### " TO-DOs.md 2>/dev/null | head -3 | sed 's/^### //' | jq -R . | jq -s .)
3463
+ fi
3464
+
3465
+ # Get colony state if exists
3466
+ if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
3467
+ current_goal=$(jq -r '.goal // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_goal")
3468
+ current_phase=$(jq -r '.current_phase // 0' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_phase")
3469
+ current_milestone=$(jq -r '.milestone // "First Mound"' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_milestone")
3470
+ fi
3471
+
3472
+ # Build updated session
3473
+ echo "$current_session" | jq --arg cmd "$cmd_run" \
3474
+ --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
3475
+ --arg suggested "$suggested" \
3476
+ --arg summary "$summary" \
3477
+ --arg goal "$current_goal" \
3478
+ --argjson phase "$current_phase" \
3479
+ --arg milestone "$current_milestone" \
3480
+ --argjson todos "$todos" \
3481
+ '.last_command = $cmd |
3482
+ .last_command_at = $ts |
3483
+ .suggested_next = $suggested |
3484
+ .summary = $summary |
3485
+ .colony_goal = $goal |
3486
+ .current_phase = $phase |
3487
+ .current_milestone = $milestone |
3488
+ .active_todos = $todos' > "$session_file"
3489
+
3490
+ json_ok "{\"updated\":true,\"command\":\"$cmd_run\"}"
3491
+ ;;
3492
+
3493
+ session-read)
3494
+ # Read and return current session state
3495
+ session_file="$DATA_DIR/session.json"
3496
+
3497
+ if [[ ! -f "$session_file" ]]; then
3498
+ json_ok "{\"exists\":false,\"session\":null}"
3499
+ exit 0
3500
+ fi
3501
+
3502
+ session_data=$(cat "$session_file" 2>/dev/null || echo '{}')
3503
+
3504
+ # Check if stale (> 24 hours)
3505
+ last_cmd_ts="" is_stale="" age_hours=""
3506
+ last_cmd_ts=$(echo "$session_data" | jq -r '.last_command_at // .started_at // empty')
3507
+ if [[ -n "$last_cmd_ts" ]]; then
3508
+ last_epoch=0 now_epoch=0
3509
+ last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null || echo 0)
3510
+ now_epoch=$(date +%s)
3511
+ age_hours=$(( (now_epoch - last_epoch) / 3600 ))
3512
+ [[ $age_hours -gt 24 ]] && is_stale=true || is_stale=false
3513
+ else
3514
+ is_stale="false"
3515
+ age_hours="unknown"
3516
+ fi
3517
+
3518
+ json_ok "{\"exists\":true,\"is_stale\":$is_stale,\"age_hours\":$age_hours,\"session\":$session_data}"
3519
+ ;;
3520
+
3521
+ session-is-stale)
3522
+ # Check if session is stale (returns true/false)
3523
+ session_file="$DATA_DIR/session.json"
3524
+
3525
+ if [[ ! -f "$session_file" ]]; then
3526
+ echo "true"
3527
+ exit 0
3528
+ fi
3529
+
3530
+ last_cmd_ts=$(jq -r '.last_command_at // .started_at // empty' "$session_file" 2>/dev/null)
3531
+
3532
+ if [[ -z "$last_cmd_ts" ]]; then
3533
+ echo "true"
3534
+ exit 0
3535
+ fi
3536
+
3537
+ last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null || echo 0)
3538
+ now_epoch=$(date +%s)
3539
+ age_hours=$(( (now_epoch - last_epoch) / 3600 ))
3540
+
3541
+ [[ $age_hours -gt 24 ]] && echo "true" || echo "false"
3542
+ ;;
3543
+
3544
+ session-clear)
3545
+ # Mark session as cleared (preserves file but marks context_cleared)
3546
+ preserve="${2:-false}"
3547
+ session_file="$DATA_DIR/session.json"
3548
+
3549
+ if [[ -f "$session_file" ]]; then
3550
+ if [[ "$preserve" == "true" ]]; then
3551
+ # Just mark as cleared
3552
+ jq '.context_cleared = true' "$session_file" > "$session_file.tmp" && mv "$session_file.tmp" "$session_file"
3553
+ json_ok "{\"cleared\":true,\"preserved\":true}"
3554
+ else
3555
+ # Remove file entirely
3556
+ rm -f "$session_file"
3557
+ json_ok "{\"cleared\":true,\"preserved\":false}"
3558
+ fi
3559
+ else
3560
+ json_ok "{\"cleared\":false,\"reason\":\"no_session_exists\"}"
3561
+ fi
3562
+ ;;
3563
+
3564
+ session-mark-resumed)
3565
+ # Mark session as resumed
3566
+ session_file="$DATA_DIR/session.json"
3567
+
3568
+ if [[ -f "$session_file" ]]; then
3569
+ jq --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
3570
+ '.resumed_at = $ts | .context_cleared = false' "$session_file" > "$session_file.tmp" && mv "$session_file.tmp" "$session_file"
3571
+ json_ok "{\"resumed\":true,\"timestamp\":\"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"}"
3572
+ else
3573
+ json_err "$E_RESOURCE_NOT_FOUND" "No session to mark as resumed"
3574
+ fi
3575
+ ;;
3576
+
3577
+ session-summary)
3578
+ # Get human-readable session summary
3579
+ session_file="$DATA_DIR/session.json"
3580
+
3581
+ if [[ ! -f "$session_file" ]]; then
3582
+ echo "No active session found."
3583
+ exit 0
3584
+ fi
3585
+
3586
+ goal=$(jq -r '.colony_goal // "No goal set"' "$session_file")
3587
+ phase=$(jq -r '.current_phase // 0' "$session_file")
3588
+ milestone=$(jq -r '.current_milestone // "First Mound"' "$session_file")
3589
+ last_cmd=$(jq -r '.last_command // "None"' "$session_file")
3590
+ last_at=$(jq -r '.last_command_at // "Unknown"' "$session_file")
3591
+ suggested=$(jq -r '.suggested_next // "None"' "$session_file")
3592
+ cleared=$(jq -r '.context_cleared // false' "$session_file")
3593
+
3594
+ echo "📋 Session Summary"
3595
+ echo "=================="
3596
+ echo "Goal: $goal"
3597
+ [[ "$phase" != "0" ]] && echo "Phase: $phase"
3598
+ echo "Milestone: $milestone"
3599
+ echo "Last Command: $last_cmd"
3600
+ echo "Last Active: $last_at"
3601
+ [[ "$suggested" != "None" ]] && echo "Suggested Next: $suggested"
3602
+ [[ "$cleared" == "true" ]] && echo "Status: Context was cleared"
3603
+ ;;
3604
+
2239
3605
  *)
2240
- json_err "Unknown command: $cmd"
3606
+ json_err "$E_VALIDATION_FAILED" "Unknown command: $cmd"
2241
3607
  ;;
2242
3608
  esac