aether-colony 3.1.17 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/.aether/aether-utils.sh +5354 -0
  2. package/.aether/agents-claude/aether-ambassador.md +265 -0
  3. package/.aether/agents-claude/aether-archaeologist.md +327 -0
  4. package/.aether/agents-claude/aether-architect.md +236 -0
  5. package/.aether/agents-claude/aether-auditor.md +271 -0
  6. package/.aether/agents-claude/aether-builder.md +224 -0
  7. package/.aether/agents-claude/aether-chaos.md +269 -0
  8. package/.aether/agents-claude/aether-chronicler.md +305 -0
  9. package/.aether/agents-claude/aether-gatekeeper.md +330 -0
  10. package/.aether/agents-claude/aether-includer.md +374 -0
  11. package/.aether/agents-claude/aether-keeper.md +272 -0
  12. package/.aether/agents-claude/aether-measurer.md +322 -0
  13. package/.aether/agents-claude/aether-oracle.md +237 -0
  14. package/.aether/agents-claude/aether-probe.md +211 -0
  15. package/.aether/agents-claude/aether-queen.md +330 -0
  16. package/.aether/agents-claude/aether-route-setter.md +178 -0
  17. package/.aether/agents-claude/aether-sage.md +418 -0
  18. package/.aether/agents-claude/aether-scout.md +179 -0
  19. package/.aether/agents-claude/aether-surveyor-disciplines.md +417 -0
  20. package/.aether/agents-claude/aether-surveyor-nest.md +355 -0
  21. package/.aether/agents-claude/aether-surveyor-pathogens.md +289 -0
  22. package/.aether/agents-claude/aether-surveyor-provisions.md +360 -0
  23. package/.aether/agents-claude/aether-tracker.md +270 -0
  24. package/.aether/agents-claude/aether-watcher.md +280 -0
  25. package/.aether/agents-claude/aether-weaver.md +248 -0
  26. package/.aether/commands/archaeology.yaml +653 -0
  27. package/.aether/commands/build.yaml +1221 -0
  28. package/.aether/commands/chaos.yaml +653 -0
  29. package/.aether/commands/colonize.yaml +438 -0
  30. package/.aether/commands/continue.yaml +1484 -0
  31. package/.aether/commands/council.yaml +304 -0
  32. package/.aether/commands/data-clean.yaml +80 -0
  33. package/.aether/commands/dream.yaml +275 -0
  34. package/.aether/commands/entomb.yaml +863 -0
  35. package/.aether/commands/export-signals.yaml +64 -0
  36. package/.aether/commands/feedback.yaml +158 -0
  37. package/.aether/commands/flag.yaml +160 -0
  38. package/.aether/commands/flags.yaml +177 -0
  39. package/.aether/commands/focus.yaml +112 -0
  40. package/.aether/commands/help.yaml +167 -0
  41. package/.aether/commands/history.yaml +137 -0
  42. package/.aether/commands/import-signals.yaml +79 -0
  43. package/.aether/commands/init.yaml +469 -0
  44. package/.aether/commands/insert-phase.yaml +98 -0
  45. package/.aether/commands/interpret.yaml +285 -0
  46. package/.aether/commands/lay-eggs.yaml +224 -0
  47. package/.aether/commands/maturity.yaml +122 -0
  48. package/.aether/commands/memory-details.yaml +74 -0
  49. package/.aether/commands/migrate-state.yaml +174 -0
  50. package/.aether/commands/oracle.yaml +1224 -0
  51. package/.aether/commands/organize.yaml +446 -0
  52. package/.aether/commands/patrol.yaml +621 -0
  53. package/.aether/commands/pause-colony.yaml +424 -0
  54. package/.aether/commands/phase.yaml +124 -0
  55. package/.aether/commands/pheromones.yaml +153 -0
  56. package/.aether/commands/plan.yaml +1313 -0
  57. package/.aether/commands/preferences.yaml +63 -0
  58. package/.aether/commands/redirect.yaml +123 -0
  59. package/.aether/commands/resume-colony.yaml +373 -0
  60. package/.aether/commands/resume.yaml +398 -0
  61. package/.aether/commands/run.yaml +193 -0
  62. package/.aether/commands/seal.yaml +1205 -0
  63. package/.aether/commands/skill-create.yaml +337 -0
  64. package/.aether/commands/status.yaml +364 -0
  65. package/.aether/commands/swarm.yaml +352 -0
  66. package/.aether/commands/tunnels.yaml +814 -0
  67. package/.aether/commands/update.yaml +131 -0
  68. package/.aether/commands/verify-castes.yaml +159 -0
  69. package/.aether/commands/watch.yaml +454 -0
  70. package/.aether/docs/INCIDENT_TEMPLATE.md +32 -0
  71. package/.aether/docs/QUEEN-SYSTEM.md +211 -0
  72. package/.aether/docs/README.md +98 -0
  73. package/.aether/docs/caste-system.md +48 -0
  74. package/.aether/docs/command-playbooks/README.md +23 -0
  75. package/.aether/docs/command-playbooks/build-complete.md +349 -0
  76. package/.aether/docs/command-playbooks/build-context.md +282 -0
  77. package/.aether/docs/command-playbooks/build-full.md +1682 -0
  78. package/.aether/docs/command-playbooks/build-prep.md +283 -0
  79. package/.aether/docs/command-playbooks/build-verify.md +405 -0
  80. package/.aether/docs/command-playbooks/build-wave.md +749 -0
  81. package/.aether/docs/command-playbooks/continue-advance.md +524 -0
  82. package/.aether/docs/command-playbooks/continue-finalize.md +447 -0
  83. package/.aether/docs/command-playbooks/continue-full.md +1724 -0
  84. package/.aether/docs/command-playbooks/continue-gates.md +686 -0
  85. package/.aether/docs/command-playbooks/continue-verify.md +406 -0
  86. package/.aether/docs/context-continuity.md +84 -0
  87. package/{runtime → .aether/docs/disciplines}/DISCIPLINES.md +13 -11
  88. package/.aether/docs/error-codes.md +268 -0
  89. package/.aether/docs/known-issues.md +94 -0
  90. package/{runtime → .aether}/docs/pheromones.md +86 -6
  91. package/.aether/docs/plans/pheromone-display-plan.md +257 -0
  92. package/.aether/docs/queen-commands.md +98 -0
  93. package/.aether/docs/source-of-truth-map.md +132 -0
  94. package/.aether/docs/xml-utilities.md +47 -0
  95. package/{runtime → .aether}/exchange/pheromone-xml.sh +2 -1
  96. package/{runtime → .aether}/exchange/registry-xml.sh +7 -3
  97. package/{runtime → .aether}/exchange/wisdom-xml.sh +11 -4
  98. package/.aether/rules/aether-colony.md +144 -0
  99. package/.aether/schemas/example-prompt-builder.xml +234 -0
  100. package/.aether/scripts/incident-test-add.sh +47 -0
  101. package/.aether/scripts/weekly-audit.sh +79 -0
  102. package/.aether/skills/.index.json +649 -0
  103. package/.aether/skills/colony/.manifest.json +16 -0
  104. package/.aether/skills/colony/build-discipline/SKILL.md +78 -0
  105. package/.aether/skills/colony/colony-interaction/SKILL.md +56 -0
  106. package/.aether/skills/colony/colony-lifecycle/SKILL.md +77 -0
  107. package/.aether/skills/colony/colony-visuals/SKILL.md +112 -0
  108. package/.aether/skills/colony/context-management/SKILL.md +80 -0
  109. package/.aether/skills/colony/error-presentation/SKILL.md +99 -0
  110. package/.aether/skills/colony/pheromone-protocol/SKILL.md +79 -0
  111. package/.aether/skills/colony/pheromone-visibility/SKILL.md +81 -0
  112. package/.aether/skills/colony/state-safety/SKILL.md +84 -0
  113. package/.aether/skills/colony/worker-priming/SKILL.md +82 -0
  114. package/.aether/skills/domain/.manifest.json +24 -0
  115. package/.aether/skills/domain/README.md +33 -0
  116. package/.aether/skills/domain/django/SKILL.md +49 -0
  117. package/.aether/skills/domain/docker/SKILL.md +52 -0
  118. package/.aether/skills/domain/golang/SKILL.md +52 -0
  119. package/.aether/skills/domain/graphql/SKILL.md +51 -0
  120. package/.aether/skills/domain/html-css/SKILL.md +48 -0
  121. package/.aether/skills/domain/nextjs/SKILL.md +45 -0
  122. package/.aether/skills/domain/nodejs/SKILL.md +53 -0
  123. package/.aether/skills/domain/postgresql/SKILL.md +53 -0
  124. package/.aether/skills/domain/prisma/SKILL.md +59 -0
  125. package/.aether/skills/domain/python/SKILL.md +50 -0
  126. package/.aether/skills/domain/rails/SKILL.md +52 -0
  127. package/.aether/skills/domain/react/SKILL.md +45 -0
  128. package/.aether/skills/domain/rest-api/SKILL.md +58 -0
  129. package/.aether/skills/domain/svelte/SKILL.md +47 -0
  130. package/.aether/skills/domain/tailwind/SKILL.md +45 -0
  131. package/.aether/skills/domain/testing/SKILL.md +53 -0
  132. package/.aether/skills/domain/typescript/SKILL.md +58 -0
  133. package/.aether/skills/domain/vue/SKILL.md +49 -0
  134. package/.aether/templates/QUEEN.md.template +61 -0
  135. package/.aether/templates/colony-state-reset.jq.template +23 -0
  136. package/.aether/templates/colony-state.template.json +39 -0
  137. package/.aether/templates/constraints.template.json +9 -0
  138. package/.aether/templates/crowned-anthill.template.md +36 -0
  139. package/.aether/templates/handoff-build-error.template.md +30 -0
  140. package/.aether/templates/handoff-build-success.template.md +39 -0
  141. package/.aether/templates/handoff.template.md +40 -0
  142. package/.aether/templates/learning-observations.template.json +6 -0
  143. package/.aether/templates/midden.template.json +13 -0
  144. package/.aether/templates/pheromones.template.json +6 -0
  145. package/.aether/templates/session.template.json +9 -0
  146. package/{runtime → .aether}/utils/atomic-write.sh +68 -22
  147. package/{runtime → .aether}/utils/chamber-compare.sh +23 -10
  148. package/.aether/utils/chamber-utils.sh +440 -0
  149. package/.aether/utils/emoji-audit.sh +166 -0
  150. package/{runtime → .aether}/utils/error-handler.sh +34 -8
  151. package/.aether/utils/file-lock.sh +313 -0
  152. package/.aether/utils/flag.sh +267 -0
  153. package/.aether/utils/hive.sh +572 -0
  154. package/.aether/utils/learning.sh +1928 -0
  155. package/.aether/utils/midden.sh +342 -0
  156. package/.aether/utils/oracle/oracle.md +168 -0
  157. package/.aether/utils/oracle/oracle.sh +1023 -0
  158. package/.aether/utils/pheromone.sh +2029 -0
  159. package/.aether/utils/queen.sh +1698 -0
  160. package/.aether/utils/scan.sh +860 -0
  161. package/.aether/utils/semantic-cli.sh +415 -0
  162. package/.aether/utils/session.sh +552 -0
  163. package/.aether/utils/skills.sh +509 -0
  164. package/.aether/utils/spawn-tree.sh +260 -0
  165. package/.aether/utils/spawn.sh +260 -0
  166. package/.aether/utils/state-api.sh +199 -0
  167. package/{runtime → .aether}/utils/state-loader.sh +8 -6
  168. package/.aether/utils/suggest.sh +611 -0
  169. package/{runtime → .aether}/utils/swarm-display.sh +10 -1
  170. package/.aether/utils/swarm.sh +1004 -0
  171. package/{runtime → .aether}/utils/watch-spawn-tree.sh +11 -2
  172. package/{runtime → .aether}/utils/xml-compose.sh +9 -3
  173. package/.aether/utils/xml-convert.sh +277 -0
  174. package/{runtime → .aether}/utils/xml-core.sh +5 -9
  175. package/.aether/utils/xml-query.sh +201 -0
  176. package/.aether/utils/xml-utils.sh +110 -0
  177. package/{runtime → .aether}/workers.md +97 -81
  178. package/.claude/agents/ant/aether-ambassador.md +265 -0
  179. package/.claude/agents/ant/aether-archaeologist.md +327 -0
  180. package/.claude/agents/ant/aether-architect.md +236 -0
  181. package/.claude/agents/ant/aether-auditor.md +271 -0
  182. package/.claude/agents/ant/aether-builder.md +224 -0
  183. package/.claude/agents/ant/aether-chaos.md +269 -0
  184. package/.claude/agents/ant/aether-chronicler.md +305 -0
  185. package/.claude/agents/ant/aether-gatekeeper.md +330 -0
  186. package/.claude/agents/ant/aether-includer.md +374 -0
  187. package/.claude/agents/ant/aether-keeper.md +272 -0
  188. package/.claude/agents/ant/aether-measurer.md +322 -0
  189. package/.claude/agents/ant/aether-oracle.md +237 -0
  190. package/.claude/agents/ant/aether-probe.md +211 -0
  191. package/.claude/agents/ant/aether-queen.md +330 -0
  192. package/.claude/agents/ant/aether-route-setter.md +178 -0
  193. package/.claude/agents/ant/aether-sage.md +418 -0
  194. package/.claude/agents/ant/aether-scout.md +179 -0
  195. package/.claude/agents/ant/aether-surveyor-disciplines.md +417 -0
  196. package/.claude/agents/ant/aether-surveyor-nest.md +355 -0
  197. package/.claude/agents/ant/aether-surveyor-pathogens.md +289 -0
  198. package/.claude/agents/ant/aether-surveyor-provisions.md +360 -0
  199. package/.claude/agents/ant/aether-tracker.md +270 -0
  200. package/.claude/agents/ant/aether-watcher.md +280 -0
  201. package/.claude/agents/ant/aether-weaver.md +248 -0
  202. package/.claude/commands/ant/archaeology.md +16 -14
  203. package/.claude/commands/ant/build.md +43 -1028
  204. package/.claude/commands/ant/chaos.md +19 -23
  205. package/.claude/commands/ant/colonize.md +52 -31
  206. package/.claude/commands/ant/continue.md +40 -1016
  207. package/.claude/commands/ant/council.md +21 -18
  208. package/.claude/commands/ant/data-clean.md +81 -0
  209. package/.claude/commands/ant/dream.md +27 -15
  210. package/.claude/commands/ant/entomb.md +317 -225
  211. package/.claude/commands/ant/export-signals.md +57 -0
  212. package/.claude/commands/ant/feedback.md +48 -26
  213. package/.claude/commands/ant/flag.md +30 -10
  214. package/.claude/commands/ant/flags.md +34 -12
  215. package/.claude/commands/ant/focus.md +45 -19
  216. package/.claude/commands/ant/help.md +50 -8
  217. package/.claude/commands/ant/history.md +13 -0
  218. package/.claude/commands/ant/import-signals.md +71 -0
  219. package/.claude/commands/ant/init.md +345 -194
  220. package/.claude/commands/ant/insert-phase.md +101 -0
  221. package/.claude/commands/ant/interpret.md +26 -4
  222. package/.claude/commands/ant/lay-eggs.md +184 -127
  223. package/.claude/commands/ant/maturity.md +32 -11
  224. package/.claude/commands/ant/memory-details.md +77 -0
  225. package/.claude/commands/ant/migrate-state.md +20 -2
  226. package/.claude/commands/ant/oracle.md +337 -74
  227. package/.claude/commands/ant/organize.md +39 -25
  228. package/.claude/commands/ant/patrol.md +620 -0
  229. package/.claude/commands/ant/pause-colony.md +23 -27
  230. package/.claude/commands/ant/phase.md +40 -42
  231. package/.claude/commands/ant/pheromones.md +156 -0
  232. package/.claude/commands/ant/plan.md +185 -51
  233. package/.claude/commands/ant/preferences.md +65 -0
  234. package/.claude/commands/ant/redirect.md +45 -30
  235. package/.claude/commands/ant/resume-colony.md +51 -27
  236. package/.claude/commands/ant/resume.md +314 -94
  237. package/.claude/commands/ant/run.md +195 -0
  238. package/.claude/commands/ant/seal.md +650 -221
  239. package/.claude/commands/ant/skill-create.md +286 -0
  240. package/.claude/commands/ant/status.md +196 -31
  241. package/.claude/commands/ant/swarm.md +16 -46
  242. package/.claude/commands/ant/tunnels.md +280 -105
  243. package/.claude/commands/ant/update.md +73 -89
  244. package/.claude/commands/ant/verify-castes.md +100 -42
  245. package/.claude/commands/ant/watch.md +14 -12
  246. package/.opencode/agents/aether-ambassador.md +63 -20
  247. package/.opencode/agents/aether-archaeologist.md +29 -12
  248. package/.opencode/agents/aether-architect.md +103 -36
  249. package/.opencode/agents/aether-auditor.md +51 -18
  250. package/.opencode/agents/aether-builder.md +70 -20
  251. package/.opencode/agents/aether-chaos.md +29 -12
  252. package/.opencode/agents/aether-chronicler.md +60 -18
  253. package/.opencode/agents/aether-gatekeeper.md +27 -18
  254. package/.opencode/agents/aether-includer.md +27 -18
  255. package/.opencode/agents/aether-keeper.md +89 -18
  256. package/.opencode/agents/aether-measurer.md +27 -18
  257. package/.opencode/agents/aether-oracle.md +137 -0
  258. package/.opencode/agents/aether-probe.md +60 -18
  259. package/.opencode/agents/aether-queen.md +172 -24
  260. package/.opencode/agents/aether-route-setter.md +57 -12
  261. package/.opencode/agents/aether-sage.md +26 -18
  262. package/.opencode/agents/aether-scout.md +28 -20
  263. package/.opencode/agents/aether-surveyor-disciplines.md +59 -2
  264. package/.opencode/agents/aether-surveyor-nest.md +59 -2
  265. package/.opencode/agents/aether-surveyor-pathogens.md +57 -2
  266. package/.opencode/agents/aether-surveyor-provisions.md +59 -2
  267. package/.opencode/agents/aether-tracker.md +64 -18
  268. package/.opencode/agents/aether-watcher.md +66 -19
  269. package/.opencode/agents/aether-weaver.md +61 -18
  270. package/.opencode/commands/ant/archaeology.md +7 -14
  271. package/.opencode/commands/ant/build.md +437 -257
  272. package/.opencode/commands/ant/chaos.md +7 -24
  273. package/.opencode/commands/ant/colonize.md +8 -17
  274. package/.opencode/commands/ant/continue.md +661 -142
  275. package/.opencode/commands/ant/council.md +11 -22
  276. package/.opencode/commands/ant/data-clean.md +77 -0
  277. package/.opencode/commands/ant/dream.md +15 -17
  278. package/.opencode/commands/ant/entomb.md +133 -62
  279. package/.opencode/commands/ant/export-signals.md +54 -0
  280. package/.opencode/commands/ant/feedback.md +24 -5
  281. package/.opencode/commands/ant/flag.md +16 -4
  282. package/.opencode/commands/ant/flags.md +24 -10
  283. package/.opencode/commands/ant/focus.md +22 -5
  284. package/.opencode/commands/ant/help.md +41 -8
  285. package/.opencode/commands/ant/history.md +9 -0
  286. package/.opencode/commands/ant/import-signals.md +68 -0
  287. package/.opencode/commands/ant/init.md +374 -167
  288. package/.opencode/commands/ant/insert-phase.md +107 -0
  289. package/.opencode/commands/ant/interpret.md +16 -0
  290. package/.opencode/commands/ant/lay-eggs.md +184 -112
  291. package/.opencode/commands/ant/maturity.md +18 -2
  292. package/.opencode/commands/ant/memory-details.md +83 -0
  293. package/.opencode/commands/ant/migrate-state.md +12 -0
  294. package/.opencode/commands/ant/oracle.md +322 -67
  295. package/.opencode/commands/ant/organize.md +18 -16
  296. package/.opencode/commands/ant/patrol.md +626 -0
  297. package/.opencode/commands/ant/pause-colony.md +12 -29
  298. package/.opencode/commands/ant/phase.md +30 -40
  299. package/.opencode/commands/ant/pheromones.md +162 -0
  300. package/.opencode/commands/ant/plan.md +184 -56
  301. package/.opencode/commands/ant/preferences.md +71 -0
  302. package/.opencode/commands/ant/redirect.md +22 -5
  303. package/.opencode/commands/ant/resume-colony.md +38 -27
  304. package/.opencode/commands/ant/resume.md +385 -0
  305. package/.opencode/commands/ant/run.md +201 -0
  306. package/.opencode/commands/ant/seal.md +259 -45
  307. package/.opencode/commands/ant/skill-create.md +63 -0
  308. package/.opencode/commands/ant/status.md +135 -31
  309. package/.opencode/commands/ant/swarm.md +3 -345
  310. package/.opencode/commands/ant/tunnels.md +152 -9
  311. package/.opencode/commands/ant/update.md +70 -91
  312. package/.opencode/commands/ant/verify-castes.md +96 -42
  313. package/.opencode/commands/ant/watch.md +7 -0
  314. package/CHANGELOG.md +356 -0
  315. package/README.md +203 -573
  316. package/bin/cli.js +455 -527
  317. package/bin/generate-commands.js +186 -0
  318. package/bin/generate-commands.sh +127 -88
  319. package/bin/lib/init.js +13 -3
  320. package/bin/lib/spawn-logger.js +0 -15
  321. package/bin/lib/update-transaction.js +392 -140
  322. package/bin/npx-install.js +178 -0
  323. package/bin/sync-to-runtime.sh +5 -137
  324. package/bin/validate-package.sh +166 -0
  325. package/package.json +14 -7
  326. package/.opencode/agents/aether-guardian.md +0 -107
  327. package/.opencode/agents/workers.md +0 -1034
  328. package/bin/lib/model-profiles.js +0 -445
  329. package/bin/lib/model-verify.js +0 -288
  330. package/bin/lib/proxy-health.js +0 -253
  331. package/bin/lib/telemetry.js +0 -441
  332. package/runtime/CONTEXT.md +0 -160
  333. package/runtime/QUEEN_ANT_ARCHITECTURE.md +0 -402
  334. package/runtime/aether-utils.sh +0 -3879
  335. package/runtime/data/signatures.json +0 -41
  336. package/runtime/docs/AETHER-2.0-IMPLEMENTATION-PLAN.md +0 -1343
  337. package/runtime/docs/AETHER-PHEROMONE-SYSTEM-MASTER-SPEC.md +0 -2642
  338. package/runtime/docs/PHEROMONE-INJECTION.md +0 -240
  339. package/runtime/docs/PHEROMONE-INTEGRATION.md +0 -192
  340. package/runtime/docs/PHEROMONE-SYSTEM-DESIGN.md +0 -426
  341. package/runtime/docs/README.md +0 -94
  342. package/runtime/docs/VISUAL-OUTPUT-SPEC.md +0 -219
  343. package/runtime/docs/biological-reference.md +0 -272
  344. package/runtime/docs/codebase-review.md +0 -399
  345. package/runtime/docs/command-sync.md +0 -164
  346. package/runtime/docs/constraints.md +0 -116
  347. package/runtime/docs/implementation-learnings.md +0 -89
  348. package/runtime/docs/known-issues.md +0 -217
  349. package/runtime/docs/namespace.md +0 -148
  350. package/runtime/docs/pathogen-schema-example.json +0 -36
  351. package/runtime/docs/pathogen-schema.md +0 -111
  352. package/runtime/docs/planning-discipline.md +0 -159
  353. package/runtime/docs/progressive-disclosure.md +0 -184
  354. package/runtime/lib/queen-utils.sh +0 -729
  355. package/runtime/model-profiles.yaml +0 -100
  356. package/runtime/planning.md +0 -159
  357. package/runtime/recover.sh +0 -136
  358. package/runtime/templates/QUEEN.md.template +0 -79
  359. package/runtime/utils/chamber-utils.sh +0 -285
  360. package/runtime/utils/file-lock.sh +0 -122
  361. package/runtime/utils/spawn-tree.sh +0 -428
  362. package/runtime/utils/spawn-with-model.sh +0 -56
  363. package/runtime/utils/xml-utils.sh +0 -2196
  364. package/runtime/workers-new-castes.md +0 -516
  365. /package/{runtime → .aether/docs/disciplines}/coding-standards.md +0 -0
  366. /package/{runtime → .aether/docs/disciplines}/debugging.md +0 -0
  367. /package/{runtime → .aether/docs/disciplines}/learning.md +0 -0
  368. /package/{runtime → .aether/docs/disciplines}/tdd.md +0 -0
  369. /package/{runtime → .aether/docs/disciplines}/verification-loop.md +0 -0
  370. /package/{runtime → .aether/docs/disciplines}/verification.md +0 -0
  371. /package/{runtime → .aether}/schemas/aether-types.xsd +0 -0
  372. /package/{runtime → .aether}/schemas/colony-registry.xsd +0 -0
  373. /package/{runtime → .aether}/schemas/pheromone.xsd +0 -0
  374. /package/{runtime → .aether}/schemas/prompt.xsd +0 -0
  375. /package/{runtime → .aether}/schemas/queen-wisdom.xsd +0 -0
  376. /package/{runtime → .aether}/schemas/worker-priming.xsd +0 -0
  377. /package/{runtime → .aether}/utils/colorize-log.sh +0 -0
  378. /package/{runtime → .aether}/utils/queen-to-md.xsl +0 -0
@@ -1,3879 +0,0 @@
1
- #!/bin/bash
2
- # Aether Colony Utility Layer
3
- # Single entry point for deterministic colony operations
4
- #
5
- # Usage: bash .aether/aether-utils.sh <subcommand> [args...]
6
- #
7
- # All subcommands output JSON to stdout.
8
- # Non-zero exit on error with JSON error message to stderr.
9
-
10
- set -euo pipefail
11
-
12
- # Set up structured error handling for unexpected failures
13
- # This works alongside set -e but provides better context (line number, command)
14
- # The error_handler function is defined in error-handler.sh if sourced
15
- trap 'if type error_handler &>/dev/null; then error_handler ${LINENO} "$BASH_COMMAND" $?; fi' ERR
16
-
17
- SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
- AETHER_ROOT="$(cd "$SCRIPT_DIR/.." && pwd 2>/dev/null || echo "$SCRIPT_DIR")"
19
- DATA_DIR="$AETHER_ROOT/.aether/data"
20
-
21
- # Initialize lock state before sourcing (file-lock.sh trap needs these)
22
- LOCK_ACQUIRED=${LOCK_ACQUIRED:-false}
23
- CURRENT_LOCK=${CURRENT_LOCK:-""}
24
-
25
- # Source shared infrastructure if available
26
- [[ -f "$SCRIPT_DIR/utils/file-lock.sh" ]] && source "$SCRIPT_DIR/utils/file-lock.sh"
27
- [[ -f "$SCRIPT_DIR/utils/atomic-write.sh" ]] && source "$SCRIPT_DIR/utils/atomic-write.sh"
28
- [[ -f "$SCRIPT_DIR/utils/error-handler.sh" ]] && source "$SCRIPT_DIR/utils/error-handler.sh"
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}"
44
-
45
- # Feature detection for graceful degradation
46
- # These checks run silently - failures are logged but don't block operation
47
- if type feature_disable &>/dev/null; then
48
- # Check if DATA_DIR is writable for activity logging
49
- [[ -w "$DATA_DIR" ]] 2>/dev/null || feature_disable "activity_log" "DATA_DIR not writable"
50
-
51
- # Check if git is available for git integration
52
- command -v git &>/dev/null || feature_disable "git_integration" "git not installed"
53
-
54
- # Check if jq is available for JSON processing
55
- command -v jq &>/dev/null || feature_disable "json_processing" "jq not installed"
56
-
57
- # Check if lock utilities are available
58
- [[ -f "$SCRIPT_DIR/utils/file-lock.sh" ]] || feature_disable "file_locking" "lock utilities not available"
59
- fi
60
-
61
- # Fallback atomic_write if not sourced (uses temp file + mv for true atomicity)
62
- if ! type atomic_write &>/dev/null; then
63
- atomic_write() {
64
- local target="$1"
65
- local content="$2"
66
- local temp
67
- temp=$(mktemp)
68
- echo "$content" > "$temp"
69
- mv "$temp" "$target"
70
- }
71
- fi
72
-
73
- # --- JSON output helpers ---
74
- # Success: JSON to stdout, exit 0
75
- json_ok() { printf '{"ok":true,"result":%s}\n' "$1"; }
76
-
77
- # Error: JSON to stderr, exit 1
78
- # Use enhanced json_err from error-handler.sh if available, otherwise fallback
79
- if ! type json_err &>/dev/null; then
80
- # Fallback: simple error format for backward compatibility
81
- json_err() {
82
- local message="${2:-$1}"
83
- printf '{"ok":false,"error":"%s"}\n' "$message" >&2
84
- exit 1
85
- }
86
- fi
87
-
88
- # --- Caste emoji helper ---
89
- get_caste_emoji() {
90
- case "$1" in
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 "🔄🐜" ;;
113
- *) echo "🐜" ;;
114
- esac
115
- }
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
-
464
- # --- Subcommand dispatch ---
465
- cmd="${1:-help}"
466
- shift 2>/dev/null || true
467
-
468
- case "$cmd" in
469
- help)
470
- cat <<'EOF'
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"}
472
- EOF
473
- ;;
474
- version)
475
- json_ok '"1.0.0"'
476
- ;;
477
- validate-state)
478
- case "${1:-}" in
479
- colony)
480
- [[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
481
- json_ok "$(jq '
482
- def chk(f;t): if has(f) then (if (.[f]|type) as $a | t | any(. == $a) then "pass" else "fail: \(f) is \(.[f]|type), expected \(t|join("|"))" end) else "fail: missing \(f)" end;
483
- def opt(f;t): if has(f) then (if (.[f]|type) as $a | t | any(. == $a) then "pass" else "fail: \(f) is \(.[f]|type), expected \(t|join("|"))" end) else "pass" end;
484
- {file:"COLONY_STATE.json", checks:[
485
- chk("goal";["null","string"]),
486
- chk("state";["string"]),
487
- chk("current_phase";["number"]),
488
- chk("plan";["object"]),
489
- chk("memory";["object"]),
490
- chk("errors";["object"]),
491
- chk("events";["array"]),
492
- opt("session_id";["string","null"]),
493
- opt("initialized_at";["string","null"]),
494
- opt("build_started_at";["string","null"])
495
- ]} | . + {pass: ([.checks[] | select(. == "pass")] | length) == (.checks | length)}
496
- ' "$DATA_DIR/COLONY_STATE.json")"
497
- ;;
498
- constraints)
499
- [[ -f "$DATA_DIR/constraints.json" ]] || json_err "$E_FILE_NOT_FOUND" "constraints.json not found" '{"file":"constraints.json"}'
500
- json_ok "$(jq '
501
- def arr(f): if has(f) and (.[f]|type) == "array" then "pass" else "fail: \(f) not array" end;
502
- {file:"constraints.json", checks:[
503
- arr("focus"),
504
- arr("constraints")
505
- ]} | . + {pass: ([.checks[] | select(. == "pass")] | length) == (.checks | length)}
506
- ' "$DATA_DIR/constraints.json")"
507
- ;;
508
- all)
509
- results=()
510
- for target in colony constraints; do
511
- results+=("$(bash "$SCRIPT_DIR/aether-utils.sh" validate-state "$target" 2>/dev/null || echo '{"ok":false}')")
512
- done
513
- combined=$(printf '%s\n' "${results[@]}" | jq -s '[.[] | .result // {file:"unknown",pass:false}]')
514
- all_pass=$(echo "$combined" | jq 'all(.pass)')
515
- json_ok "{\"pass\":$all_pass,\"files\":$combined}"
516
- ;;
517
- *)
518
- json_err "$E_VALIDATION_FAILED" "Usage: validate-state colony|constraints|all"
519
- ;;
520
- esac
521
- ;;
522
- error-add)
523
- [[ $# -ge 3 ]] || json_err "$E_VALIDATION_FAILED" "Usage: error-add <category> <severity> <description> [phase]"
524
- [[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
525
- id="err_$(date -u +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
526
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
527
- phase_val="${4:-null}"
528
- if [[ "$phase_val" =~ ^[0-9]+$ ]]; then
529
- phase_jq="$phase_val"
530
- else
531
- phase_jq="null"
532
- fi
533
- updated=$(jq --arg id "$id" --arg cat "$1" --arg sev "$2" --arg desc "$3" --argjson phase "$phase_jq" --arg ts "$ts" '
534
- .errors.records += [{id:$id, category:$cat, severity:$sev, description:$desc, root_cause:null, phase:$phase, task_id:null, timestamp:$ts}] |
535
- if (.errors.records|length) > 50 then .errors.records = .errors.records[-50:] else . end
536
- ' "$DATA_DIR/COLONY_STATE.json") || json_err "$E_JSON_INVALID" "Failed to update COLONY_STATE.json"
537
- atomic_write "$DATA_DIR/COLONY_STATE.json" "$updated"
538
- json_ok "\"$id\""
539
- ;;
540
- error-pattern-check)
541
- [[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
542
- json_ok "$(jq '
543
- .errors.records | group_by(.category) | map(select(length >= 3) |
544
- {category: .[0].category, count: length,
545
- first_seen: (sort_by(.timestamp) | first.timestamp),
546
- last_seen: (sort_by(.timestamp) | last.timestamp)})
547
- ' "$DATA_DIR/COLONY_STATE.json")"
548
- ;;
549
- error-summary)
550
- [[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
551
- json_ok "$(jq '{
552
- total: (.errors.records | length),
553
- by_category: (.errors.records | group_by(.category) | map({key: .[0].category, value: length}) | from_entries),
554
- by_severity: (.errors.records | group_by(.severity) | map({key: .[0].severity, value: length}) | from_entries)
555
- }' "$DATA_DIR/COLONY_STATE.json")"
556
- ;;
557
- activity-log)
558
- # Usage: activity-log <action> <caste_or_name> <description>
559
- # The caste_or_name can be: "Builder", "Hammer-42 (Builder)", etc.
560
- action="${1:-}"
561
- caste="${2:-}"
562
- description="${3:-}"
563
- [[ -z "$action" || -z "$caste" || -z "$description" ]] && json_err "$E_VALIDATION_FAILED" "Usage: activity-log <action> <caste_or_name> <description>"
564
-
565
- # Graceful degradation: check if activity logging is enabled
566
- if type feature_enabled &>/dev/null && ! feature_enabled "activity_log"; then
567
- json_warn "W_DEGRADED" "Activity logging disabled: $(type _feature_reason &>/dev/null && _feature_reason activity_log || echo 'unknown')"
568
- exit 0
569
- fi
570
-
571
- log_file="$DATA_DIR/activity.log"
572
- mkdir -p "$DATA_DIR"
573
- ts=$(date -u +"%H:%M:%S")
574
- emoji=$(get_caste_emoji "$caste")
575
- echo "[$ts] $emoji $action $caste: $description" >> "$log_file"
576
- json_ok '"logged"'
577
- ;;
578
- activity-log-init)
579
- phase_num="${1:-}"
580
- phase_name="${2:-}"
581
- [[ -z "$phase_num" ]] && json_err "$E_VALIDATION_FAILED" "Usage: activity-log-init <phase_num> [phase_name]"
582
-
583
- # Graceful degradation: check if activity logging is enabled
584
- if type feature_enabled &>/dev/null && ! feature_enabled "activity_log"; then
585
- json_warn "W_DEGRADED" "Activity logging disabled: $(type _feature_reason &>/dev/null && _feature_reason activity_log || echo 'unknown')"
586
- exit 0
587
- fi
588
- log_file="$DATA_DIR/activity.log"
589
- mkdir -p "$DATA_DIR"
590
- archive_file="$DATA_DIR/activity-phase-${phase_num}.log"
591
- # Copy current log to per-phase archive (preserve combined log intact)
592
- if [ -f "$log_file" ] && [ -s "$log_file" ]; then
593
- # Handle retry scenario: don't overwrite existing archive
594
- if [ -f "$archive_file" ]; then
595
- archive_file="$DATA_DIR/activity-phase-${phase_num}-$(date -u +%s).log"
596
- fi
597
- cp "$log_file" "$archive_file"
598
- fi
599
- # Append phase header to combined log (NOT truncate)
600
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
601
- echo "" >> "$log_file"
602
- echo "🐜 ═══════════════════════════════════════════════════" >> "$log_file"
603
- echo " P H A S E $phase_num: ${phase_name:-unnamed}" >> "$log_file"
604
- echo " $ts" >> "$log_file"
605
- echo "═══════════════════════════════════════════════════ 🐜" >> "$log_file"
606
- archived_flag="false"
607
- [ -f "$archive_file" ] && archived_flag="true"
608
- json_ok "{\"archived\":$archived_flag}"
609
- ;;
610
- activity-log-read)
611
- caste_filter="${1:-}"
612
-
613
- # Graceful degradation: check if activity logging is enabled
614
- if type feature_enabled &>/dev/null && ! feature_enabled "activity_log"; then
615
- json_warn "W_DEGRADED" "Activity logging disabled: $(type _feature_reason &>/dev/null && _feature_reason activity_log || echo 'unknown')"
616
- exit 0
617
- fi
618
-
619
- log_file="$DATA_DIR/activity.log"
620
- [[ -f "$log_file" ]] || json_err "$E_FILE_NOT_FOUND" "activity.log not found" '{"file":"activity.log"}'
621
- if [ -n "$caste_filter" ]; then
622
- content=$(grep "$caste_filter" "$log_file" | tail -20)
623
- else
624
- content=$(cat "$log_file")
625
- fi
626
- json_ok "$(echo "$content" | jq -Rs '.')"
627
- ;;
628
- learning-promote)
629
- [[ $# -ge 3 ]] || json_err "$E_VALIDATION_FAILED" "Usage: learning-promote <content> <source_project> <source_phase> [tags]"
630
- content="$1"
631
- source_project="$2"
632
- source_phase="$3"
633
- tags="${4:-}"
634
-
635
- mkdir -p "$DATA_DIR"
636
- global_file="$DATA_DIR/learnings.json"
637
-
638
- if [[ ! -f "$global_file" ]]; then
639
- echo '{"learnings":[],"version":1}' > "$global_file"
640
- fi
641
-
642
- id="global_$(date -u +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
643
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
644
-
645
- if [[ -n "$tags" ]]; then
646
- tags_json=$(echo "$tags" | jq -R 'split(",")')
647
- else
648
- tags_json="[]"
649
- fi
650
-
651
- current_count=$(jq '.learnings | length' "$global_file")
652
- if [[ $current_count -ge 50 ]]; then
653
- json_ok "{\"promoted\":false,\"reason\":\"cap_reached\",\"current_count\":$current_count,\"cap\":50}"
654
- exit 0
655
- fi
656
-
657
- updated=$(jq --arg id "$id" --arg content "$content" --arg sp "$source_project" \
658
- --arg phase "$source_phase" --argjson tags "$tags_json" --arg ts "$ts" '
659
- .learnings += [{
660
- id: $id,
661
- content: $content,
662
- source_project: $sp,
663
- source_phase: $phase,
664
- tags: $tags,
665
- promoted_at: $ts
666
- }]
667
- ' "$global_file") || json_err "$E_JSON_INVALID" "Failed to update learnings.json"
668
-
669
- echo "$updated" > "$global_file"
670
- json_ok "{\"promoted\":true,\"id\":\"$id\",\"count\":$((current_count + 1)),\"cap\":50}"
671
- ;;
672
- learning-inject)
673
- [[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: learning-inject <tech_keywords_csv>"
674
- keywords="$1"
675
-
676
- global_file="$DATA_DIR/learnings.json"
677
-
678
- if [[ ! -f "$global_file" ]]; then
679
- json_ok '{"learnings":[],"count":0}'
680
- exit 0
681
- fi
682
-
683
- json_ok "$(jq --arg kw "$keywords" '
684
- ($kw | split(",") | map(ascii_downcase | ltrimstr(" ") | rtrimstr(" "))) as $keywords |
685
- .learnings | map(
686
- select(
687
- .tags as $tags |
688
- ($keywords | any(. as $k | $tags | any(ascii_downcase | contains($k))))
689
- )
690
- ) | {learnings: ., count: length}
691
- ' "$global_file")"
692
- ;;
693
- spawn-log)
694
- # Usage: spawn-log <parent_id> <child_caste> <child_name> <task_summary> [model] [status]
695
- parent_id="${1:-}"
696
- child_caste="${2:-}"
697
- child_name="${3:-}"
698
- task_summary="${4:-}"
699
- model="${5:-default}"
700
- status="${6:-spawned}"
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]"
702
- mkdir -p "$DATA_DIR"
703
- ts=$(date -u +"%H:%M:%S")
704
- ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
705
- emoji=$(get_caste_emoji "$child_caste")
706
- parent_emoji=$(get_caste_emoji "$parent_id")
707
- # Log to activity log with spawn format, emojis, and model info
708
- echo "[$ts] ⚡ SPAWN $parent_emoji $parent_id -> $emoji $child_name ($child_caste): $task_summary [model: $model]" >> "$DATA_DIR/activity.log"
709
- # Log to spawn tree file for visualization (NEW FORMAT: includes model field)
710
- echo "$ts_full|$parent_id|$child_caste|$child_name|$task_summary|$model|$status" >> "$DATA_DIR/spawn-tree.txt"
711
- # Return emoji-formatted result for display
712
- json_ok "\"⚡ $emoji $child_name spawned\""
713
- ;;
714
- spawn-complete)
715
- # Usage: spawn-complete <ant_name> <status> [summary]
716
- ant_name="${1:-}"
717
- status="${2:-completed}"
718
- summary="${3:-}"
719
- [[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-complete <ant_name> <status> [summary]"
720
- mkdir -p "$DATA_DIR"
721
- ts=$(date -u +"%H:%M:%S")
722
- ts_full=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
723
- emoji=$(get_caste_emoji "$ant_name")
724
- status_icon="✅"
725
- [[ "$status" == "failed" ]] && status_icon="❌"
726
- [[ "$status" == "blocked" ]] && status_icon="🚫"
727
- echo "[$ts] $status_icon $emoji $ant_name: $status${summary:+ - $summary}" >> "$DATA_DIR/activity.log"
728
- # Update spawn tree
729
- echo "$ts_full|$ant_name|$status|$summary" >> "$DATA_DIR/spawn-tree.txt"
730
- # Return emoji-formatted result for display
731
- json_ok "\"$status_icon $emoji $ant_name: ${summary:-$status}\""
732
- ;;
733
- spawn-can-spawn)
734
- # Check if spawning is allowed at given depth
735
- # Usage: spawn-can-spawn <depth>
736
- # Returns: {can_spawn: bool, depth: N, max_spawns: N, current_total: N}
737
- depth="${1:-1}"
738
-
739
- # Depth limits: 1→4 spawns, 2→2 spawns, 3+→0 spawns
740
- if [[ $depth -eq 1 ]]; then
741
- max_for_depth=4
742
- elif [[ $depth -eq 2 ]]; then
743
- max_for_depth=2
744
- else
745
- max_for_depth=0
746
- fi
747
-
748
- # Count current spawns in this session (from spawn-tree.txt)
749
- current=0
750
- if [[ -f "$DATA_DIR/spawn-tree.txt" ]]; then
751
- current=$(grep -c "|spawned$" "$DATA_DIR/spawn-tree.txt" 2>/dev/null || echo 0)
752
- fi
753
-
754
- # Global cap of 10 workers per phase
755
- global_cap=10
756
-
757
- # Can spawn if: depth < 3 AND under global cap
758
- if [[ $depth -lt 3 && $current -lt $global_cap ]]; then
759
- can="true"
760
- else
761
- can="false"
762
- fi
763
-
764
- json_ok "{\"can_spawn\":$can,\"depth\":$depth,\"max_spawns\":$max_for_depth,\"current_total\":$current,\"global_cap\":$global_cap}"
765
- ;;
766
- spawn-get-depth)
767
- # Return depth for a given ant name by tracing spawn tree
768
- # Usage: spawn-get-depth <ant_name>
769
- # Queen = depth 0, Queen's spawns = depth 1, their spawns = depth 2, etc.
770
- ant_name="${1:-Queen}"
771
-
772
- if [[ "$ant_name" == "Queen" ]]; then
773
- json_ok '{"ant":"Queen","depth":0}'
774
- exit 0
775
- fi
776
-
777
- # Check if spawn tree exists
778
- if [[ ! -f "$DATA_DIR/spawn-tree.txt" ]]; then
779
- json_ok "{\"ant\":\"$ant_name\",\"depth\":1,\"found\":false}"
780
- exit 0
781
- fi
782
-
783
- # Check if ant exists in spawn tree (gracefully handle missing ants)
784
- if ! grep -q "|$ant_name|" "$DATA_DIR/spawn-tree.txt" 2>/dev/null; then
785
- json_ok "{\"ant\":\"$ant_name\",\"depth\":1,\"found\":false}"
786
- exit 0
787
- fi
788
-
789
- # Find the spawn record for this ant and trace parents
790
- depth=1
791
- current_ant="$ant_name"
792
-
793
- # Find who spawned this ant (look for lines with |spawned)
794
- while true; do
795
- # Format: timestamp|parent|caste|child_name|task|spawned
796
- parent=$(grep "|$current_ant|" "$DATA_DIR/spawn-tree.txt" 2>/dev/null | grep "|spawned$" | head -1 | cut -d'|' -f2 || echo "")
797
-
798
- if [[ -z "$parent" || "$parent" == "Queen" ]]; then
799
- break
800
- fi
801
-
802
- depth=$((depth + 1))
803
- current_ant="$parent"
804
-
805
- # Safety limit
806
- if [[ $depth -gt 5 ]]; then
807
- break
808
- fi
809
- done
810
-
811
- json_ok "{\"ant\":\"$ant_name\",\"depth\":$depth,\"found\":true}"
812
- ;;
813
- update-progress)
814
- # Usage: update-progress <percent> <message> [phase] [total_phases]
815
- percent="${1:-0}"
816
- message="${2:-Working...}"
817
- phase="${3:-1}"
818
- total="${4:-1}"
819
- mkdir -p "$DATA_DIR"
820
-
821
- # Calculate bar width (30 chars)
822
- bar_width=30
823
- filled=$((percent * bar_width / 100))
824
- empty=$((bar_width - filled))
825
-
826
- # Build progress bar with ASCII
827
- bar=""
828
- for ((i=0; i<filled; i++)); do bar+="█"; done
829
- for ((i=0; i<empty; i++)); do bar+="░"; done
830
-
831
- # Spinner frames for animation
832
- spinners=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
833
- spin_idx=$(($(date +%s) % 10))
834
- spinner="${spinners[$spin_idx]}"
835
-
836
- # Status indicator
837
- if [[ $percent -ge 100 ]]; then
838
- status_icon="✅"
839
- elif [[ $percent -ge 50 ]]; then
840
- status_icon="🔨"
841
- else
842
- status_icon="$spinner"
843
- fi
844
-
845
- # Write progress file
846
- cat > "$DATA_DIR/watch-progress.txt" << EOF
847
- .-.
848
- (o o) AETHER COLONY
849
- | O | Progress
850
- \`-\`
851
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
852
-
853
- Phase: $phase / $total
854
-
855
- [$bar] $percent%
856
-
857
- $status_icon $message
858
-
859
- Target: 95% confidence
860
-
861
- EOF
862
- json_ok "{\"percent\":$percent,\"phase\":$phase}"
863
- ;;
864
- error-flag-pattern)
865
- # Usage: error-flag-pattern <pattern_name> <description> [severity]
866
- # Tracks recurring error patterns across sessions for colony learning
867
- pattern_name="${1:-}"
868
- description="${2:-}"
869
- severity="${3:-warning}"
870
- [[ -z "$pattern_name" || -z "$description" ]] && json_err "$E_VALIDATION_FAILED" "Usage: error-flag-pattern <pattern_name> <description> [severity]"
871
-
872
- patterns_file="$DATA_DIR/error-patterns.json"
873
- mkdir -p "$DATA_DIR"
874
-
875
- if [[ ! -f "$patterns_file" ]]; then
876
- echo '{"patterns":[],"version":1}' > "$patterns_file"
877
- fi
878
-
879
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
880
- project_name=$(basename "$PWD")
881
-
882
- # Check if pattern already exists
883
- existing=$(jq --arg name "$pattern_name" '.patterns[] | select(.name == $name)' "$patterns_file" 2>/dev/null)
884
-
885
- if [[ -n "$existing" ]]; then
886
- # Update existing pattern - increment count
887
- updated=$(jq --arg name "$pattern_name" --arg ts "$ts" --arg proj "$project_name" '
888
- .patterns = [.patterns[] | if .name == $name then
889
- .occurrences += 1 |
890
- .last_seen = $ts |
891
- .projects = ((.projects + [$proj]) | unique)
892
- else . end]
893
- ' "$patterns_file") || json_err "$E_JSON_INVALID" "Failed to update pattern"
894
- echo "$updated" > "$patterns_file"
895
- count=$(echo "$updated" | jq --arg name "$pattern_name" '.patterns[] | select(.name == $name) | .occurrences')
896
- json_ok "{\"updated\":true,\"pattern\":\"$pattern_name\",\"occurrences\":$count}"
897
- else
898
- # Add new pattern
899
- updated=$(jq --arg name "$pattern_name" --arg desc "$description" --arg sev "$severity" --arg ts "$ts" --arg proj "$project_name" '
900
- .patterns += [{
901
- "name": $name,
902
- "description": $desc,
903
- "severity": $sev,
904
- "first_seen": $ts,
905
- "last_seen": $ts,
906
- "occurrences": 1,
907
- "projects": [$proj],
908
- "resolved": false
909
- }]
910
- ' "$patterns_file") || json_err "$E_JSON_INVALID" "Failed to add pattern"
911
- echo "$updated" > "$patterns_file"
912
- json_ok "{\"created\":true,\"pattern\":\"$pattern_name\"}"
913
- fi
914
- ;;
915
- error-patterns-check)
916
- # Check for known error patterns in a file or codebase
917
- # Returns patterns that should be avoided
918
- global_file="$DATA_DIR/error-patterns.json"
919
-
920
- if [[ ! -f "$global_file" ]]; then
921
- json_ok '{"patterns":[],"count":0}'
922
- exit 0
923
- fi
924
-
925
- # Return patterns with 2+ occurrences (recurring issues)
926
- json_ok "$(jq '{
927
- patterns: [.patterns[] | select(.occurrences >= 2 and .resolved == false)],
928
- count: ([.patterns[] | select(.occurrences >= 2 and .resolved == false)] | length)
929
- }' "$global_file")"
930
- ;;
931
- check-antipattern)
932
- # Usage: check-antipattern <file_path>
933
- # Returns JSON with critical issues and warnings
934
- file_path="${1:-}"
935
- [[ -z "$file_path" ]] && json_err "$E_VALIDATION_FAILED" "Usage: check-antipattern <file_path>"
936
- [[ ! -f "$file_path" ]] && json_ok '{"critical":[],"warnings":[],"clean":true}'
937
-
938
- criticals=()
939
- warnings=()
940
-
941
- # Detect file type
942
- ext="${file_path##*.}"
943
-
944
- case "$ext" in
945
- swift)
946
- # Swift didSet infinite recursion check
947
- if grep -n "didSet" "$file_path" 2>/dev/null | grep -q "self\."; then
948
- line=$(grep -n "didSet" "$file_path" | grep "self\." | head -1 | cut -d: -f1)
949
- criticals+=("{\"pattern\":\"didSet-recursion\",\"file\":\"$file_path\",\"line\":$line,\"message\":\"Potential didSet infinite recursion - self assignment in didSet\"}")
950
- fi
951
- ;;
952
- ts|tsx|js|jsx)
953
- # TypeScript any type check
954
- if grep -nE '\bany\b' "$file_path" 2>/dev/null | grep -qv "//.*any"; then
955
- count=$(grep -cE '\bany\b' "$file_path" 2>/dev/null || echo "0")
956
- warnings+=("{\"pattern\":\"typescript-any\",\"file\":\"$file_path\",\"count\":$count,\"message\":\"Found $count uses of 'any' type\"}")
957
- fi
958
- # Console.log in production code (not in test files)
959
- if [[ ! "$file_path" =~ \.test\. && ! "$file_path" =~ \.spec\. ]]; then
960
- if grep -n "console\.log" "$file_path" 2>/dev/null | grep -qv "//"; then
961
- count=$(grep -c "console\.log" "$file_path" 2>/dev/null || echo "0")
962
- warnings+=("{\"pattern\":\"console-log\",\"file\":\"$file_path\",\"count\":$count,\"message\":\"Found $count console.log statements\"}")
963
- fi
964
- fi
965
- ;;
966
- py)
967
- # Python bare except
968
- if grep -n "except:" "$file_path" 2>/dev/null | grep -qv "#"; then
969
- line=$(grep -n "except:" "$file_path" | head -1 | cut -d: -f1)
970
- warnings+=("{\"pattern\":\"bare-except\",\"file\":\"$file_path\",\"line\":$line,\"message\":\"Bare except clause - specify exception type\"}")
971
- fi
972
- ;;
973
- esac
974
-
975
- # Common patterns across all languages
976
- # Exposed secrets check (critical)
977
- if grep -nE "(api_key|apikey|secret|password|token)\s*=\s*['\"][^'\"]+['\"]" "$file_path" 2>/dev/null | grep -qvi "example\|test\|mock\|fake"; then
978
- line=$(grep -nE "(api_key|apikey|secret|password|token)\s*=\s*['\"]" "$file_path" | head -1 | cut -d: -f1)
979
- criticals+=("{\"pattern\":\"exposed-secret\",\"file\":\"$file_path\",\"line\":${line:-0},\"message\":\"Potential hardcoded secret or credential\"}")
980
- fi
981
-
982
- # TODO/FIXME check (warning)
983
- if grep -nE "(TODO|FIXME|XXX|HACK)" "$file_path" 2>/dev/null | head -1 | grep -q .; then
984
- count=$(grep -cE "(TODO|FIXME|XXX|HACK)" "$file_path" 2>/dev/null || echo "0")
985
- warnings+=("{\"pattern\":\"todo-comment\",\"file\":\"$file_path\",\"count\":$count,\"message\":\"Found $count TODO/FIXME comments\"}")
986
- fi
987
-
988
- # Build result JSON
989
- crit_json="[]"
990
- warn_json="[]"
991
- if [[ ${#criticals[@]} -gt 0 ]]; then
992
- crit_json=$(printf '%s\n' "${criticals[@]}" | jq -s '.')
993
- fi
994
- if [[ ${#warnings[@]} -gt 0 ]]; then
995
- warn_json=$(printf '%s\n' "${warnings[@]}" | jq -s '.')
996
- fi
997
-
998
- clean="true"
999
- [[ ${#criticals[@]} -gt 0 || ${#warnings[@]} -gt 0 ]] && clean="false"
1000
-
1001
- json_ok "{\"critical\":$crit_json,\"warnings\":$warn_json,\"clean\":$clean}"
1002
- ;;
1003
- signature-scan)
1004
- # Scan a file for a signature pattern
1005
- # Usage: signature-scan <target_file> <signature_name>
1006
- # Returns matching signature details as JSON if found, empty result if no match
1007
- # Exit code 0 if no match, 1 if match found
1008
- target_file="${1:-}"
1009
- signature_name="${2:-}"
1010
- [[ -z "$target_file" || -z "$signature_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: signature-scan <target_file> <signature_name>"
1011
-
1012
- # Handle missing target file gracefully
1013
- if [[ ! -f "$target_file" ]]; then
1014
- json_ok '{"found":false,"signature":null}'
1015
- exit 0
1016
- fi
1017
-
1018
- # Read signature details from signatures.json
1019
- signatures_file="$DATA_DIR/signatures.json"
1020
- if [[ ! -f "$signatures_file" ]]; then
1021
- json_ok '{"found":false,"signature":null}'
1022
- exit 0
1023
- fi
1024
-
1025
- # Extract signature details using jq
1026
- signature_data=$(jq --arg name "$signature_name" '.signatures[] | select(.name == $name)' "$signatures_file" 2>/dev/null)
1027
-
1028
- if [[ -z "$signature_data" ]]; then
1029
- # Signature not found in storage
1030
- json_ok '{"found":false,"signature":null}'
1031
- exit 0
1032
- fi
1033
-
1034
- # Extract pattern and confidence threshold
1035
- pattern_string=$(echo "$signature_data" | jq -r '.pattern_string // empty')
1036
- confidence_threshold=$(echo "$signature_data" | jq -r '.confidence_threshold // 0.8')
1037
-
1038
- if [[ -z "$pattern_string" || "$pattern_string" == "null" ]]; then
1039
- json_ok '{"found":false,"signature":null}'
1040
- exit 0
1041
- fi
1042
-
1043
- # Use grep to search for the pattern in target file
1044
- if grep -q -- "$pattern_string" "$target_file" 2>/dev/null; then
1045
- # Match found - return signature details with match info
1046
- match_count=$(grep -c -- "$pattern_string" "$target_file" 2>/dev/null || echo "1")
1047
- json_ok "{\"found\":true,\"signature\":$signature_data,\"match_count\":$match_count}"
1048
- exit 1
1049
- else
1050
- # No match
1051
- json_ok '{"found":false,"signature":null}'
1052
- exit 0
1053
- fi
1054
- ;;
1055
- signature-match)
1056
- # Scan a directory for files matching high-confidence signatures
1057
- # Usage: signature-match <directory> [file_pattern]
1058
- # Returns results per file showing which signatures matched
1059
- target_dir="${1:-}"
1060
- file_pattern="${2:-}"
1061
- # Set default pattern if not provided - avoid zsh brace expansion quirk by setting it explicitly
1062
- if [[ -z "$file_pattern" ]]; then
1063
- file_pattern="*"
1064
- fi
1065
- [[ -z "$target_dir" ]] && json_err "$E_VALIDATION_FAILED" "Usage: signature-match <directory> [file_pattern]"
1066
-
1067
- # Validate directory exists
1068
- [[ ! -d "$target_dir" ]] && json_err "$E_FILE_NOT_FOUND" "Directory not found: $target_dir"
1069
-
1070
- # Path to signatures file
1071
- signatures_file="$DATA_DIR/signatures.json"
1072
- [[ ! -f "$signatures_file" ]] && json_err "$E_FILE_NOT_FOUND" "Signatures file not found"
1073
-
1074
- # Read high-confidence signatures (confidence >= 0.7) using jq -c for compact single-line output
1075
- high_conf_signatures=$(jq -c '.signatures[] | select(.confidence_threshold >= 0.7)' "$signatures_file" 2>/dev/null)
1076
-
1077
- # Check if any high-confidence signatures exist
1078
- sig_count=$(echo "$high_conf_signatures" | grep -c '{' || echo 0)
1079
- if [[ "$sig_count" -eq 0 ]]; then
1080
- json_ok '{"files_scanned":0,"matches":{},"signatures_checked":0}'
1081
- exit 0
1082
- fi
1083
-
1084
- # Find all files to scan
1085
- declare -a files=()
1086
- if [[ -n "$file_pattern" ]]; then
1087
- # User specified pattern - use it directly
1088
- while IFS= read -r -d '' file; do
1089
- files+=("$file")
1090
- done < <(find "$target_dir" -type f -name "$file_pattern" -print0 2>/dev/null || true)
1091
- else
1092
- # Default: match common code file types
1093
- while IFS= read -r -d '' file; do
1094
- files+=("$file")
1095
- done < <(find "$target_dir" -type f \( -name "*.js" -o -name "*.ts" -o -name "*.py" -o -name "*.sh" -o -name "*.txt" -o -name "*.md" \) -print0 2>/dev/null || true)
1096
- fi
1097
-
1098
- file_count=${#files[@]}
1099
-
1100
- # If no files found, return empty result
1101
- if [[ "$file_count" -eq 0 ]]; then
1102
- json_ok "{\"files_scanned\":0,\"matches\":{},\"signatures_checked\":$sig_count}"
1103
- exit 0
1104
- fi
1105
-
1106
- # Collect matches per file - process each file (bash 3.2 compatible: build JSON directly)
1107
- matched_files="{}"
1108
-
1109
- # Read signatures into array first (avoid subshell issues)
1110
- sig_array=""
1111
- while IFS= read -r sig_entry; do
1112
- [[ -z "$sig_entry" ]] && continue
1113
- sig_array="${sig_array}${sig_entry}"$'\n'
1114
- done <<< "$high_conf_signatures"
1115
-
1116
- for file in "${files[@]}"; do
1117
- # For each file, check each signature - use process subst to avoid subshell
1118
- file_key=$(basename "$file")
1119
- matches_for_file="[]"
1120
-
1121
- while IFS= read -r sig_entry; do
1122
- [[ -z "$sig_entry" ]] && continue
1123
- sig_name=$(echo "$sig_entry" | jq -r '.name')
1124
- sig_pattern=$(echo "$sig_entry" | jq -r '.pattern_string')
1125
- sig_conf=$(echo "$sig_entry" | jq -r '.confidence_threshold')
1126
- sig_desc=$(echo "$sig_entry" | jq -r '.description')
1127
-
1128
- # Skip if pattern is null/empty
1129
- [[ -z "$sig_pattern" || "$sig_pattern" == "null" ]] && continue
1130
-
1131
- # Check if pattern matches in file using grep
1132
- if grep -q -- "$sig_pattern" "$file" 2>/dev/null; then
1133
- match_count=$(grep -c -- "$sig_pattern" "$file" 2>/dev/null || echo "1")
1134
-
1135
- # Add to results
1136
- matches_for_file=$(echo "$matches_for_file" | jq --arg n "$sig_name" --arg d "$sig_desc" --argjson c "$sig_conf" --argjson m "$match_count" \
1137
- '. += [{"name":$n,"description":$d,"confidence_threshold":$c,"match_count":$m}]')
1138
- fi
1139
- done < <(echo "$high_conf_signatures" | jq -c '.' 2>/dev/null || true)
1140
-
1141
- # If any signatures matched, add to results
1142
- sig_result_count=$(echo "$matches_for_file" | jq 'length')
1143
- if [[ "$sig_result_count" -gt 0 ]]; then
1144
- temp_result=$(mktemp)
1145
- echo "$matched_files" | jq --arg k "$file_key" --argjson v "$matches_for_file" '. + {($k): $v}' > "$temp_result"
1146
- matched_files=$(cat "$temp_result")
1147
- rm -f "$temp_result"
1148
- fi
1149
- done
1150
-
1151
- json_ok "{\"files_scanned\":$file_count,\"matches\":$matched_files,\"signatures_checked\":$sig_count}"
1152
- ;;
1153
- flag-add)
1154
- # Add a project-specific flag (blocker, issue, or note)
1155
- # Usage: flag-add <type> <title> <description> [source] [phase]
1156
- # Types: blocker (critical, blocks advancement), issue (high, warning), note (low, info)
1157
- type="${1:-issue}"
1158
- title="${2:-}"
1159
- desc="${3:-}"
1160
- source="${4:-manual}"
1161
- phase="${5:-null}"
1162
- [[ -z "$title" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-add <type> <title> <description> [source] [phase]"
1163
-
1164
- mkdir -p "$DATA_DIR"
1165
- flags_file="$DATA_DIR/flags.json"
1166
-
1167
- if [[ ! -f "$flags_file" ]]; then
1168
- echo '{"version":1,"flags":[]}' > "$flags_file"
1169
- fi
1170
-
1171
- id="flag_$(date -u +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
1172
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1173
-
1174
- # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
1175
- lock_acquired=false
1176
- if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
1177
- json_warn "W_DEGRADED" "File locking disabled - proceeding without lock: $(type _feature_reason &>/dev/null && _feature_reason file_locking || echo 'unknown')"
1178
- else
1179
- acquire_lock "$flags_file" || {
1180
- if type json_err &>/dev/null; then
1181
- json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
1182
- else
1183
- echo '{"ok":false,"error":"Failed to acquire lock on flags.json"}' >&2
1184
- exit 1
1185
- fi
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
1190
- fi
1191
-
1192
- # Map type to severity
1193
- case "$type" in
1194
- blocker) severity="critical" ;;
1195
- issue) severity="high" ;;
1196
- note) severity="low" ;;
1197
- *) severity="medium" ;;
1198
- esac
1199
-
1200
- # Handle phase as number or null
1201
- if [[ "$phase" =~ ^[0-9]+$ ]]; then
1202
- phase_jq="$phase"
1203
- else
1204
- phase_jq="null"
1205
- fi
1206
-
1207
- updated=$(jq --arg id "$id" --arg type "$type" --arg sev "$severity" \
1208
- --arg title "$title" --arg desc "$desc" --arg source "$source" \
1209
- --argjson phase "$phase_jq" --arg ts "$ts" '
1210
- .flags += [{
1211
- id: $id,
1212
- type: $type,
1213
- severity: $sev,
1214
- title: $title,
1215
- description: $desc,
1216
- source: $source,
1217
- phase: $phase,
1218
- created_at: $ts,
1219
- acknowledged_at: null,
1220
- resolved_at: null,
1221
- resolution: null,
1222
- auto_resolve_on: (if $type == "blocker" and ($source | test("chaos") | not) then "build_pass" else null end)
1223
- }]
1224
- ' "$flags_file") || { json_err "$E_JSON_INVALID" "Failed to add flag"; }
1225
-
1226
- atomic_write "$flags_file" "$updated"
1227
- # Lock released by trap on exit (BUG-002 fix)
1228
- trap - EXIT
1229
- json_ok "{\"id\":\"$id\",\"type\":\"$type\",\"severity\":\"$severity\"}"
1230
- ;;
1231
- flag-check-blockers)
1232
- # Count unresolved blockers for the current phase
1233
- # Usage: flag-check-blockers [phase]
1234
- phase="${1:-}"
1235
- flags_file="$DATA_DIR/flags.json"
1236
-
1237
- if [[ ! -f "$flags_file" ]]; then
1238
- json_ok '{"blockers":0,"issues":0,"notes":0}'
1239
- exit 0
1240
- fi
1241
-
1242
- if [[ -n "$phase" && "$phase" =~ ^[0-9]+$ ]]; then
1243
- # Filter by phase
1244
- result=$(jq --argjson phase "$phase" '{
1245
- blockers: [.flags[] | select(.type == "blocker" and .resolved_at == null and (.phase == $phase or .phase == null))] | length,
1246
- issues: [.flags[] | select(.type == "issue" and .resolved_at == null and (.phase == $phase or .phase == null))] | length,
1247
- notes: [.flags[] | select(.type == "note" and .resolved_at == null and (.phase == $phase or .phase == null))] | length
1248
- }' "$flags_file")
1249
- else
1250
- # All unresolved
1251
- result=$(jq '{
1252
- blockers: [.flags[] | select(.type == "blocker" and .resolved_at == null)] | length,
1253
- issues: [.flags[] | select(.type == "issue" and .resolved_at == null)] | length,
1254
- notes: [.flags[] | select(.type == "note" and .resolved_at == null)] | length
1255
- }' "$flags_file")
1256
- fi
1257
-
1258
- json_ok "$result"
1259
- ;;
1260
- flag-resolve)
1261
- # Resolve a flag with optional resolution message
1262
- # Usage: flag-resolve <flag_id> [resolution_message]
1263
- flag_id="${1:-}"
1264
- resolution="${2:-Resolved}"
1265
- [[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-resolve <flag_id> [resolution_message]"
1266
-
1267
- flags_file="$DATA_DIR/flags.json"
1268
- [[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
1269
-
1270
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1271
-
1272
- # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
1273
- if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
1274
- json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
1275
- else
1276
- acquire_lock "$flags_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
1277
- fi
1278
-
1279
- updated=$(jq --arg id "$flag_id" --arg res "$resolution" --arg ts "$ts" '
1280
- .flags = [.flags[] | if .id == $id then
1281
- .resolved_at = $ts |
1282
- .resolution = $res
1283
- else . end]
1284
- ' "$flags_file") || {
1285
- if type feature_enabled &>/dev/null && feature_enabled "file_locking"; then
1286
- release_lock "$flags_file"
1287
- fi
1288
- json_err "$E_JSON_INVALID" "Failed to resolve flag"
1289
- }
1290
-
1291
- atomic_write "$flags_file" "$updated"
1292
- release_lock "$flags_file"
1293
- json_ok "{\"resolved\":\"$flag_id\"}"
1294
- ;;
1295
- flag-acknowledge)
1296
- # Acknowledge a flag (issue continues but noted)
1297
- # Usage: flag-acknowledge <flag_id>
1298
- flag_id="${1:-}"
1299
- [[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-acknowledge <flag_id>"
1300
-
1301
- flags_file="$DATA_DIR/flags.json"
1302
- [[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
1303
-
1304
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1305
-
1306
- # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
1307
- if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
1308
- json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
1309
- else
1310
- acquire_lock "$flags_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
1311
- fi
1312
-
1313
- updated=$(jq --arg id "$flag_id" --arg ts "$ts" '
1314
- .flags = [.flags[] | if .id == $id then
1315
- .acknowledged_at = $ts
1316
- else . end]
1317
- ' "$flags_file") || {
1318
- if type feature_enabled &>/dev/null && feature_enabled "file_locking"; then
1319
- release_lock "$flags_file"
1320
- fi
1321
- json_err "$E_JSON_INVALID" "Failed to acknowledge flag"
1322
- }
1323
-
1324
- atomic_write "$flags_file" "$updated"
1325
- release_lock "$flags_file"
1326
- json_ok "{\"acknowledged\":\"$flag_id\"}"
1327
- ;;
1328
- flag-list)
1329
- # List flags, optionally filtered
1330
- # Usage: flag-list [--all] [--type blocker|issue|note] [--phase N]
1331
- flags_file="$DATA_DIR/flags.json"
1332
- show_all="false"
1333
- filter_type=""
1334
- filter_phase=""
1335
-
1336
- while [[ $# -gt 0 ]]; do
1337
- case "$1" in
1338
- --all) show_all="true"; shift ;;
1339
- --type) filter_type="$2"; shift 2 ;;
1340
- --phase) filter_phase="$2"; shift 2 ;;
1341
- *) shift ;;
1342
- esac
1343
- done
1344
-
1345
- if [[ ! -f "$flags_file" ]]; then
1346
- json_ok '{"flags":[],"count":0}'
1347
- exit 0
1348
- fi
1349
-
1350
- # Build jq filter
1351
- jq_filter='.flags'
1352
-
1353
- if [[ "$show_all" != "true" ]]; then
1354
- jq_filter+=' | [.[] | select(.resolved_at == null)]'
1355
- fi
1356
-
1357
- if [[ -n "$filter_type" ]]; then
1358
- jq_filter+=" | [.[] | select(.type == \"$filter_type\")]"
1359
- fi
1360
-
1361
- if [[ -n "$filter_phase" && "$filter_phase" =~ ^[0-9]+$ ]]; then
1362
- jq_filter+=" | [.[] | select(.phase == $filter_phase or .phase == null)]"
1363
- fi
1364
-
1365
- result=$(jq "{flags: ($jq_filter), count: ($jq_filter | length)}" "$flags_file")
1366
- json_ok "$result"
1367
- ;;
1368
- flag-auto-resolve)
1369
- # Auto-resolve flags based on trigger (e.g., build_pass)
1370
- # Usage: flag-auto-resolve <trigger>
1371
- trigger="${1:-build_pass}"
1372
- flags_file="$DATA_DIR/flags.json"
1373
-
1374
- if [[ ! -f "$flags_file" ]]; then json_ok '{"resolved":0}'; exit 0; fi
1375
-
1376
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1377
-
1378
- # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
1379
- lock_acquired=false
1380
- if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
1381
- json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
1382
- else
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
1387
- fi
1388
-
1389
- # Count how many will be resolved
1390
- count=$(jq --arg trigger "$trigger" '
1391
- [.flags[] | select(.auto_resolve_on == $trigger and .resolved_at == null)] | length
1392
- ' "$flags_file") || {
1393
- json_err "$E_JSON_INVALID" "Failed to count flags for auto-resolve"
1394
- }
1395
-
1396
- # Resolve them
1397
- updated=$(jq --arg trigger "$trigger" --arg ts "$ts" '
1398
- .flags = [.flags[] | if .auto_resolve_on == $trigger and .resolved_at == null then
1399
- .resolved_at = $ts |
1400
- .resolution = "Auto-resolved on " + $trigger
1401
- else . end]
1402
- ' "$flags_file") || {
1403
- json_err "$E_JSON_INVALID" "Failed to auto-resolve flags"
1404
- }
1405
-
1406
- atomic_write "$flags_file" "$updated"
1407
- # Lock released by trap on exit (BUG-005/BUG-011 fix)
1408
- if [[ "$lock_acquired" == "true" ]]; then
1409
- trap - EXIT
1410
- fi
1411
- json_ok "{\"resolved\":$count,\"trigger\":\"$trigger\"}"
1412
- ;;
1413
- generate-ant-name)
1414
- caste="${1:-builder}"
1415
- # Caste-specific prefixes for personality
1416
- case "$caste" in
1417
- builder) prefixes=("Chip" "Hammer" "Forge" "Mason" "Brick" "Anvil" "Weld" "Bolt") ;;
1418
- watcher) prefixes=("Vigil" "Sentinel" "Guard" "Keen" "Sharp" "Hawk" "Watch" "Alert") ;;
1419
- scout) prefixes=("Swift" "Dash" "Ranger" "Track" "Seek" "Path" "Roam" "Quest") ;;
1420
- colonizer) prefixes=("Pioneer" "Map" "Chart" "Venture" "Explore" "Compass" "Atlas" "Trek") ;;
1421
- architect) prefixes=("Blueprint" "Draft" "Design" "Plan" "Schema" "Frame" "Sketch" "Model") ;;
1422
- prime) prefixes=("Prime" "Alpha" "Lead" "Chief" "First" "Core" "Apex" "Crown") ;;
1423
- chaos) prefixes=("Probe" "Stress" "Shake" "Twist" "Snap" "Breach" "Surge" "Jolt") ;;
1424
- archaeologist) prefixes=("Relic" "Fossil" "Dig" "Shard" "Epoch" "Strata" "Lore" "Glyph") ;;
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") ;;
1437
- *) prefixes=("Ant" "Worker" "Drone" "Toiler" "Marcher" "Runner" "Carrier" "Helper") ;;
1438
- esac
1439
- # Pick random prefix and add random number
1440
- idx=$((RANDOM % ${#prefixes[@]}))
1441
- prefix="${prefixes[$idx]}"
1442
- num=$((RANDOM % 99 + 1))
1443
- name="${prefix}-${num}"
1444
- json_ok "\"$name\""
1445
- ;;
1446
-
1447
- # ============================================
1448
- # SWARM UTILITIES (ant:swarm support)
1449
- # ============================================
1450
-
1451
- autofix-checkpoint)
1452
- # Create checkpoint before applying auto-fix
1453
- # Usage: autofix-checkpoint [label]
1454
- # Returns: {type: "stash"|"commit"|"none", ref: "..."}
1455
- # IMPORTANT: Only stash Aether-related files, never touch user work
1456
- if git rev-parse --git-dir >/dev/null 2>&1; then
1457
- # Check if there are changes to Aether-managed files only
1458
- # Target directories that Aether is allowed to modify
1459
- target_dirs=".aether .claude/commands/ant .claude/commands/st .opencode runtime bin"
1460
- has_changes=false
1461
-
1462
- for dir in $target_dirs; do
1463
- if [[ -d "$dir" ]] && [[ -n "$(git status --porcelain "$dir" 2>/dev/null)" ]]; then
1464
- has_changes=true
1465
- break
1466
- fi
1467
- done
1468
-
1469
- if [[ "$has_changes" == "true" ]]; then
1470
- label="${1:-autofix-$(date +%s)}"
1471
- stash_name="aether-checkpoint: $label"
1472
- # Only stash Aether-managed directories, never touch user files
1473
- if git stash push -m "$stash_name" -- $target_dirs >/dev/null 2>&1; then
1474
- json_ok "{\"type\":\"stash\",\"ref\":\"$stash_name\"}"
1475
- else
1476
- # Stash failed (possibly due to conflicts), record commit hash
1477
- hash=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
1478
- json_ok "{\"type\":\"commit\",\"ref\":\"$hash\"}"
1479
- fi
1480
- else
1481
- # No changes in Aether-managed directories, just record commit hash
1482
- hash=$(git rev-parse HEAD 2>/dev/null || echo "unknown")
1483
- json_ok "{\"type\":\"commit\",\"ref\":\"$hash\"}"
1484
- fi
1485
- else
1486
- json_ok '{"type":"none","ref":null}'
1487
- fi
1488
- ;;
1489
-
1490
- autofix-rollback)
1491
- # Rollback from checkpoint if fix failed
1492
- # Usage: autofix-rollback <type> <ref>
1493
- # Returns: {rolled_back: bool, method: "stash"|"reset"|"none"}
1494
- ref_type="${1:-none}"
1495
- ref="${2:-}"
1496
-
1497
- case "$ref_type" in
1498
- stash)
1499
- # Find and pop the stash
1500
- stash_ref=$(git stash list 2>/dev/null | grep "$ref" | head -1 | cut -d: -f1 || echo "")
1501
- if [[ -n "$stash_ref" ]]; then
1502
- if git stash pop "$stash_ref" >/dev/null 2>&1; then
1503
- json_ok '{"rolled_back":true,"method":"stash"}'
1504
- else
1505
- json_ok '{"rolled_back":false,"method":"stash","error":"stash pop failed"}'
1506
- fi
1507
- else
1508
- json_ok '{"rolled_back":false,"method":"stash","error":"stash not found"}'
1509
- fi
1510
- ;;
1511
- commit)
1512
- # Reset to the commit
1513
- if [[ -n "$ref" && "$ref" != "unknown" ]]; then
1514
- if git reset --hard "$ref" >/dev/null 2>&1; then
1515
- json_ok '{"rolled_back":true,"method":"reset"}'
1516
- else
1517
- json_ok '{"rolled_back":false,"method":"reset","error":"reset failed"}'
1518
- fi
1519
- else
1520
- json_ok '{"rolled_back":false,"method":"reset","error":"invalid ref"}'
1521
- fi
1522
- ;;
1523
- none|*)
1524
- json_ok '{"rolled_back":false,"method":"none"}'
1525
- ;;
1526
- esac
1527
- ;;
1528
-
1529
- spawn-can-spawn-swarm)
1530
- # Check if swarm can spawn more scouts (separate from phase workers)
1531
- # Usage: spawn-can-spawn-swarm <swarm_id>
1532
- # Swarm has its own cap of 6 (4 scouts + 2 sub-scouts max)
1533
- swarm_id="${1:-swarm}"
1534
- swarm_cap=6
1535
-
1536
- current=0
1537
- if [[ -f "$DATA_DIR/spawn-tree.txt" ]]; then
1538
- current=$(grep -c "|swarm:$swarm_id$" "$DATA_DIR/spawn-tree.txt" 2>/dev/null || echo 0)
1539
- fi
1540
-
1541
- if [[ $current -lt $swarm_cap ]]; then
1542
- can="true"
1543
- remaining=$((swarm_cap - current))
1544
- else
1545
- can="false"
1546
- remaining=0
1547
- fi
1548
-
1549
- json_ok "{\"can_spawn\":$can,\"current\":$current,\"cap\":$swarm_cap,\"remaining\":$remaining,\"swarm_id\":\"$swarm_id\"}"
1550
- ;;
1551
-
1552
- swarm-findings-init)
1553
- # Initialize swarm findings file
1554
- # Usage: swarm-findings-init <swarm_id>
1555
- swarm_id="${1:-swarm-$(date +%s)}"
1556
- findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
1557
-
1558
- mkdir -p "$DATA_DIR"
1559
- cat > "$findings_file" <<EOF
1560
- {
1561
- "swarm_id": "$swarm_id",
1562
- "created_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
1563
- "status": "active",
1564
- "findings": [],
1565
- "solution": null
1566
- }
1567
- EOF
1568
- json_ok "{\"swarm_id\":\"$swarm_id\",\"file\":\"$findings_file\"}"
1569
- ;;
1570
-
1571
- swarm-findings-add)
1572
- # Add a finding from a scout
1573
- # Usage: swarm-findings-add <swarm_id> <scout_type> <confidence> <finding_json>
1574
- swarm_id="${1:-}"
1575
- scout_type="${2:-}"
1576
- confidence="${3:-0.5}"
1577
- finding="${4:-}"
1578
-
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>"
1580
-
1581
- findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
1582
- [[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
1583
-
1584
- ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
1585
-
1586
- # Add finding to array
1587
- updated=$(jq --arg scout "$scout_type" --arg conf "$confidence" --arg ts "$ts" --argjson finding "$finding" '
1588
- .findings += [{
1589
- "scout": $scout,
1590
- "confidence": ($conf | tonumber),
1591
- "timestamp": $ts,
1592
- "finding": $finding
1593
- }]
1594
- ' "$findings_file")
1595
-
1596
- echo "$updated" > "$findings_file"
1597
- count=$(echo "$updated" | jq '.findings | length')
1598
- json_ok "{\"added\":true,\"scout\":\"$scout_type\",\"total_findings\":$count}"
1599
- ;;
1600
-
1601
- swarm-findings-read)
1602
- # Read all findings for a swarm
1603
- # Usage: swarm-findings-read <swarm_id>
1604
- swarm_id="${1:-}"
1605
- [[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-read <swarm_id>"
1606
-
1607
- findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
1608
- [[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
1609
-
1610
- json_ok "$(cat "$findings_file")"
1611
- ;;
1612
-
1613
- swarm-solution-set)
1614
- # Set the chosen solution for a swarm
1615
- # Usage: swarm-solution-set <swarm_id> <solution_json>
1616
- swarm_id="${1:-}"
1617
- solution="${2:-}"
1618
-
1619
- [[ -z "$swarm_id" || -z "$solution" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-solution-set <swarm_id> <solution_json>"
1620
-
1621
- findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
1622
- [[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
1623
-
1624
- ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
1625
-
1626
- updated=$(jq --argjson solution "$solution" --arg ts "$ts" '
1627
- .solution = $solution |
1628
- .status = "resolved" |
1629
- .resolved_at = $ts
1630
- ' "$findings_file")
1631
-
1632
- echo "$updated" > "$findings_file"
1633
- json_ok "{\"solution_set\":true,\"swarm_id\":\"$swarm_id\"}"
1634
- ;;
1635
-
1636
- swarm-cleanup)
1637
- # Clean up swarm files after completion
1638
- # Usage: swarm-cleanup <swarm_id> [--archive]
1639
- swarm_id="${1:-}"
1640
- archive="${2:-}"
1641
-
1642
- [[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-cleanup <swarm_id> [--archive]"
1643
-
1644
- findings_file="$DATA_DIR/swarm-findings-$swarm_id.json"
1645
-
1646
- if [[ -f "$findings_file" ]]; then
1647
- if [[ "$archive" == "--archive" ]]; then
1648
- mkdir -p "$DATA_DIR/swarm-archive"
1649
- mv "$findings_file" "$DATA_DIR/swarm-archive/"
1650
- json_ok "{\"archived\":true,\"swarm_id\":\"$swarm_id\"}"
1651
- else
1652
- rm -f "$findings_file"
1653
- json_ok "{\"deleted\":true,\"swarm_id\":\"$swarm_id\"}"
1654
- fi
1655
- else
1656
- json_ok "{\"not_found\":true,\"swarm_id\":\"$swarm_id\"}"
1657
- fi
1658
- ;;
1659
-
1660
- grave-add)
1661
- # Record a grave marker when a builder fails at a file
1662
- # 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]"
1664
- [[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
1665
- file="$1"
1666
- ant_name="$2"
1667
- task_id="$3"
1668
- phase="$4"
1669
- failure_summary="$5"
1670
- func="${6:-null}"
1671
- line="${7:-null}"
1672
- id="grave_$(date -u +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
1673
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1674
- if [[ "$phase" =~ ^[0-9]+$ ]]; then
1675
- phase_jq="$phase"
1676
- else
1677
- phase_jq="null"
1678
- fi
1679
- if [[ "$func" == "null" ]]; then
1680
- func_jq="null"
1681
- else
1682
- func_jq="\"$func\""
1683
- fi
1684
- if [[ "$line" =~ ^[0-9]+$ ]]; then
1685
- line_jq="$line"
1686
- else
1687
- line_jq="null"
1688
- fi
1689
- updated=$(jq --arg id "$id" --arg file "$file" --arg ant "$ant_name" --arg tid "$task_id" \
1690
- --argjson phase "$phase_jq" --arg summary "$failure_summary" \
1691
- --argjson func "$func_jq" --argjson line "$line_jq" --arg ts "$ts" '
1692
- (.graveyards // []) as $graves |
1693
- . + {graveyards: ($graves + [{
1694
- id: $id,
1695
- file: $file,
1696
- ant_name: $ant,
1697
- task_id: $tid,
1698
- phase: $phase,
1699
- failure_summary: $summary,
1700
- function: $func,
1701
- line: $line,
1702
- timestamp: $ts
1703
- }])} |
1704
- if (.graveyards | length) > 30 then .graveyards = .graveyards[-30:] else . end
1705
- ' "$DATA_DIR/COLONY_STATE.json") || json_err "$E_JSON_INVALID" "Failed to update COLONY_STATE.json"
1706
- atomic_write "$DATA_DIR/COLONY_STATE.json" "$updated"
1707
- json_ok "\"$id\""
1708
- ;;
1709
-
1710
- grave-check)
1711
- # Query for grave markers near a file path
1712
- # Usage: grave-check <file_path>
1713
- # Read-only, never modifies state
1714
- [[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: grave-check <file_path>"
1715
- [[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
1716
- check_file="$1"
1717
- check_dir=$(dirname "$check_file")
1718
- json_ok "$(jq --arg file "$check_file" --arg dir "$check_dir" '
1719
- (.graveyards // []) as $graves |
1720
- ($graves | map(select(.file == $file))) as $exact |
1721
- ($graves | map(select((.file | split("/")[:-1] | join("/")) == $dir))) as $dir_matches |
1722
- ($exact | length) as $exact_count |
1723
- ($dir_matches | length) as $dir_count |
1724
- (if $exact_count > 0 then "high"
1725
- elif $dir_count >= 2 then "high"
1726
- elif $dir_count == 1 then "low"
1727
- else "none" end) as $caution |
1728
- {graves: $dir_matches, count: $dir_count, exact_matches: $exact_count, caution_level: $caution}
1729
- ' "$DATA_DIR/COLONY_STATE.json")"
1730
- ;;
1731
-
1732
- # ============================================
1733
- # GIT COMMIT UTILITIES
1734
- # ============================================
1735
-
1736
- generate-commit-message)
1737
- # Generate an intelligent commit message from colony context
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, ...}
1741
-
1742
- msg_type="${1:-milestone}"
1743
- phase_id="${2:-0}"
1744
- phase_name="${3:-unknown}"
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")
1747
-
1748
- # Count changed files
1749
- files_changed=0
1750
- if git rev-parse --git-dir >/dev/null 2>&1; then
1751
- files_changed=$(git diff --stat --cached HEAD 2>/dev/null | tail -1 | grep -oE '[0-9]+ file' | grep -oE '[0-9]+' || echo "0")
1752
- if [[ "$files_changed" == "0" ]]; then
1753
- files_changed=$(git status --porcelain 2>/dev/null | wc -l | tr -d ' ')
1754
- fi
1755
- fi
1756
-
1757
- case "$msg_type" in
1758
- milestone)
1759
- # Format: aether-milestone: phase N complete -- <name>
1760
- if [[ -n "$summary" ]]; then
1761
- message="aether-milestone: phase ${phase_id} complete -- ${summary}"
1762
- else
1763
- message="aether-milestone: phase ${phase_id} complete -- ${phase_name}"
1764
- fi
1765
- body="All verification gates passed. User confirmed runtime behavior."
1766
- ;;
1767
- pause)
1768
- message="aether-checkpoint: session pause -- phase ${phase_id} in progress"
1769
- body="Colony paused mid-session. Handoff document saved."
1770
- ;;
1771
- fix)
1772
- if [[ -n "$summary" ]]; then
1773
- message="fix: ${summary}"
1774
- else
1775
- message="fix: resolve issue in phase ${phase_id}"
1776
- fi
1777
- body="Swarm-verified fix applied and tested."
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
- ;;
1806
- *)
1807
- message="aether-checkpoint: phase ${phase_id}"
1808
- body=""
1809
- ;;
1810
- esac
1811
-
1812
- # Enforce 72-char limit on subject line (truncate if needed)
1813
- if [[ ${#message} -gt 72 ]]; then
1814
- message="${message:0:69}..."
1815
- fi
1816
-
1817
- json_ok "{\"message\":\"$message\",\"body\":\"$body\",\"files_changed\":$files_changed}"
1818
- ;;
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
-
1846
- # ============================================
1847
- # REGISTRY & UPDATE UTILITIES
1848
- # ============================================
1849
-
1850
- version-check)
1851
- # Compare local .aether/version.json vs ~/.aether/version.json
1852
- # Outputs a notice string if versions differ, empty if matched or missing
1853
- local_version_file="$AETHER_ROOT/.aether/version.json"
1854
- hub_version_file="$HOME/.aether/version.json"
1855
-
1856
- # Silent exit if either file is missing
1857
- if [[ ! -f "$local_version_file" || ! -f "$hub_version_file" ]]; then
1858
- json_ok '""'
1859
- exit 0
1860
- fi
1861
-
1862
- local_ver=$(jq -r '.version // "unknown"' "$local_version_file" 2>/dev/null || echo "unknown")
1863
- hub_ver=$(jq -r '.version // "unknown"' "$hub_version_file" 2>/dev/null || echo "unknown")
1864
-
1865
- if [[ "$local_ver" == "$hub_ver" ]]; then
1866
- json_ok '""'
1867
- else
1868
- printf -v msg 'Update available: %s to %s (run /ant:update)' "$local_ver" "$hub_ver"
1869
- json_ok "$msg"
1870
- fi
1871
- ;;
1872
-
1873
- registry-add)
1874
- # Add or update a repo entry in ~/.aether/registry.json
1875
- # Usage: registry-add <repo_path> <version>
1876
- repo_path="${1:-}"
1877
- repo_version="${2:-}"
1878
- [[ -z "$repo_path" || -z "$repo_version" ]] && json_err "$E_VALIDATION_FAILED" "Usage: registry-add <repo_path> <version>"
1879
-
1880
- registry_file="$HOME/.aether/registry.json"
1881
- mkdir -p "$HOME/.aether"
1882
-
1883
- if [[ ! -f "$registry_file" ]]; then
1884
- echo '{"schema_version":1,"repos":[]}' > "$registry_file"
1885
- fi
1886
-
1887
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1888
-
1889
- # Check if repo already exists in registry
1890
- existing=$(jq --arg path "$repo_path" '.repos[] | select(.path == $path)' "$registry_file" 2>/dev/null)
1891
-
1892
- if [[ -n "$existing" ]]; then
1893
- # Update existing entry
1894
- updated=$(jq --arg path "$repo_path" --arg ver "$repo_version" --arg ts "$ts" '
1895
- .repos = [.repos[] | if .path == $path then
1896
- .version = $ver |
1897
- .updated_at = $ts
1898
- else . end]
1899
- ' "$registry_file") || json_err "$E_JSON_INVALID" "Failed to update registry"
1900
- else
1901
- # Add new entry
1902
- updated=$(jq --arg path "$repo_path" --arg ver "$repo_version" --arg ts "$ts" '
1903
- .repos += [{
1904
- "path": $path,
1905
- "version": $ver,
1906
- "registered_at": $ts,
1907
- "updated_at": $ts
1908
- }]
1909
- ' "$registry_file") || json_err "$E_JSON_INVALID" "Failed to update registry"
1910
- fi
1911
-
1912
- echo "$updated" > "$registry_file"
1913
- json_ok "{\"registered\":true,\"path\":\"$repo_path\",\"version\":\"$repo_version\"}"
1914
- ;;
1915
-
1916
- bootstrap-system)
1917
- # Copy system files from ~/.aether/system/ into local .aether/
1918
- # Uses explicit allowlist — never touches colony data
1919
- hub_system="$HOME/.aether/system"
1920
- local_aether="$AETHER_ROOT/.aether"
1921
-
1922
- [[ ! -d "$hub_system" ]] && json_err "$E_HUB_NOT_FOUND" "Hub system directory not found: $hub_system"
1923
-
1924
- # Allowlist of system files to copy (relative to system/)
1925
- allowlist=(
1926
- "aether-utils.sh"
1927
- "coding-standards.md"
1928
- "debugging.md"
1929
- "DISCIPLINES.md"
1930
- "learning.md"
1931
- "planning.md"
1932
- "QUEEN_ANT_ARCHITECTURE.md"
1933
- "tdd.md"
1934
- "verification-loop.md"
1935
- "verification.md"
1936
- "workers.md"
1937
- "docs/constraints.md"
1938
- "docs/pathogen-schema-example.json"
1939
- "docs/pathogen-schema.md"
1940
- "docs/pheromones.md"
1941
- "docs/progressive-disclosure.md"
1942
- "utils/atomic-write.sh"
1943
- "utils/colorize-log.sh"
1944
- "utils/file-lock.sh"
1945
- "utils/watch-spawn-tree.sh"
1946
- )
1947
-
1948
- copied=0
1949
- for file in "${allowlist[@]}"; do
1950
- src="$hub_system/$file"
1951
- dest="$local_aether/$file"
1952
- if [[ -f "$src" ]]; then
1953
- mkdir -p "$(dirname "$dest")"
1954
- cp "$src" "$dest"
1955
- # Preserve executable bit for shell scripts
1956
- if [[ "$file" == *.sh ]]; then
1957
- chmod 755 "$dest"
1958
- fi
1959
- copied=$((copied + 1))
1960
- fi
1961
- done
1962
-
1963
- json_ok "{\"copied\":$copied,\"total\":${#allowlist[@]}}"
1964
- ;;
1965
-
1966
- load-state)
1967
- source "$SCRIPT_DIR/utils/state-loader.sh" 2>/dev/null || {
1968
- json_err "$E_FILE_NOT_FOUND" "state-loader.sh not found"
1969
- exit 1
1970
- }
1971
- load_colony_state
1972
- if [[ $? -eq 0 ]]; then
1973
- # Output success with handoff info if detected
1974
- if [[ "$HANDOFF_DETECTED" == "true" ]]; then
1975
- json_ok "{\"loaded\":true,\"handoff_detected\":true,\"handoff_summary\":\"$(get_handoff_summary)\"}"
1976
- else
1977
- json_ok '{"loaded":true}'
1978
- fi
1979
- fi
1980
- # Note: load_colony_state handles its own error output
1981
- ;;
1982
-
1983
- unload-state)
1984
- source "$SCRIPT_DIR/utils/state-loader.sh" 2>/dev/null || {
1985
- json_err "$E_FILE_NOT_FOUND" "state-loader.sh not found"
1986
- exit 1
1987
- }
1988
- unload_colony_state
1989
- json_ok '{"unloaded":true}'
1990
- ;;
1991
-
1992
- spawn-tree-load)
1993
- source "$SCRIPT_DIR/utils/spawn-tree.sh" 2>/dev/null || {
1994
- json_err "$E_FILE_NOT_FOUND" "spawn-tree.sh not found"
1995
- exit 1
1996
- }
1997
- tree_json=$(reconstruct_tree_json)
1998
- json_ok "$tree_json"
1999
- ;;
2000
-
2001
- spawn-tree-active)
2002
- source "$SCRIPT_DIR/utils/spawn-tree.sh" 2>/dev/null || {
2003
- json_err "$E_FILE_NOT_FOUND" "spawn-tree.sh not found"
2004
- exit 1
2005
- }
2006
- active=$(get_active_spawns)
2007
- json_ok "$active"
2008
- ;;
2009
-
2010
- spawn-tree-depth)
2011
- ant_name="${1:-}"
2012
- [[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: spawn-tree-depth <ant_name>"
2013
- source "$SCRIPT_DIR/utils/spawn-tree.sh" 2>/dev/null || {
2014
- json_err "$E_FILE_NOT_FOUND" "spawn-tree.sh not found"
2015
- exit 1
2016
- }
2017
- depth=$(get_spawn_depth "$ant_name")
2018
- json_ok "$depth"
2019
- ;;
2020
-
2021
- # --- Model Profile Commands ---
2022
- model-profile)
2023
- action="${1:-get}"
2024
- case "$action" in
2025
- get)
2026
- caste="${2:-}"
2027
- [[ -z "$caste" ]] && json_err "$E_VALIDATION_FAILED" "Usage: model-profile get <caste>"
2028
-
2029
- profile_file="$AETHER_ROOT/.aether/model-profiles.yaml"
2030
- if [[ ! -f "$profile_file" ]]; then
2031
- json_ok '{"model":"kimi-k2.5","source":"default","caste":"'$caste'"}'
2032
- exit 0
2033
- fi
2034
-
2035
- # Extract model for caste using awk (bash-compatible YAML parsing)
2036
- model=$(awk '/^worker_models:/{found=1; next} found && /^[^ ]/{exit} found && /^ '$caste':/{print $2; exit}' "$profile_file" 2>/dev/null)
2037
-
2038
- [[ -z "$model" ]] && model="kimi-k2.5"
2039
- json_ok '{"model":"'$model'","source":"profile","caste":"'$caste'"}'
2040
- ;;
2041
-
2042
- list)
2043
- profile_file="$AETHER_ROOT/.aether/model-profiles.yaml"
2044
- if [[ ! -f "$profile_file" ]]; then
2045
- json_ok '{"models":{},"source":"default"}'
2046
- exit 0
2047
- fi
2048
-
2049
- # Extract all caste:model pairs as JSON
2050
- # Lines look like: " prime: glm-5 # Complex coordination..."
2051
- models=$(awk '/^worker_models:/{found=1; next} found && /^[^ ]/{exit} found && /^ [a-z_]+:/{gsub(/:/,""); printf "\"%s\":\"%s\",", $1, $2}' "$profile_file" 2>/dev/null)
2052
- # Remove trailing comma
2053
- models="${models%,}"
2054
-
2055
- json_ok '{"models":{'$models'},"source":"profile"}'
2056
- ;;
2057
-
2058
- verify)
2059
- profile_file="$AETHER_ROOT/.aether/model-profiles.yaml"
2060
- [[ ! -f "$profile_file" ]] && json_err "$E_FILE_NOT_FOUND" "Profile not found" '{"file":"model-profiles.yaml"}'
2061
-
2062
- # Check proxy health
2063
- proxy_health=$(curl -s -o /dev/null -w "%{http_code}" http://localhost:4000/health 2>/dev/null || echo "000")
2064
- proxy_status=$([[ "$proxy_health" == "200" ]] && echo "healthy" || echo "unhealthy")
2065
-
2066
- # Count castes
2067
- caste_count=$(awk '/^worker_models:/{found=1; next} found && /^[^ ]/{exit} found && /^ [a-z_]+:/{count++} END{print count+0}' "$profile_file" 2>/dev/null)
2068
-
2069
- json_ok '{"profile_exists":true,"caste_count":'$caste_count',"proxy_status":"'$proxy_status'","proxy_endpoint":"http://localhost:4000"}'
2070
- ;;
2071
-
2072
- select)
2073
- # Usage: model-profile select <caste> <task_description> [cli_override]
2074
- # Returns: JSON with model and source
2075
- caste="$2"
2076
- task_description="$3"
2077
- cli_override="${4:-}"
2078
-
2079
- [[ -z "$caste" ]] && json_err "$E_VALIDATION_FAILED" "Usage: model-profile select <caste> <task_description> [cli_override]"
2080
-
2081
- # Create a temporary Node.js script to call the library
2082
- node_script=$(cat << 'NODESCRIPT'
2083
- const { loadModelProfiles, selectModelForTask } = require('./bin/lib/model-profiles');
2084
- const caste = process.argv[2];
2085
- const taskDescription = process.argv[3];
2086
- const cliOverride = process.argv[4] || null;
2087
-
2088
- try {
2089
- const profiles = loadModelProfiles('.');
2090
- const result = selectModelForTask(profiles, caste, taskDescription, cliOverride);
2091
- console.log(JSON.stringify({ ok: true, result }));
2092
- } catch (error) {
2093
- console.log(JSON.stringify({ ok: false, error: error.message }));
2094
- process.exit(1);
2095
- }
2096
- NODESCRIPT
2097
- )
2098
-
2099
- result=$(echo "$node_script" | node - "$caste" "$task_description" "$cli_override")
2100
- echo "$result"
2101
- ;;
2102
-
2103
- validate)
2104
- # Usage: model-profile validate <model_name>
2105
- # Returns: JSON with valid boolean
2106
- model_name="$2"
2107
-
2108
- [[ -z "$model_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: model-profile validate <model_name>"
2109
-
2110
- node_script=$(cat << 'NODESCRIPT'
2111
- const { loadModelProfiles, validateModel } = require('./bin/lib/model-profiles');
2112
- const modelName = process.argv[2];
2113
-
2114
- try {
2115
- const profiles = loadModelProfiles('.');
2116
- const validation = validateModel(profiles, modelName);
2117
- console.log(JSON.stringify({ ok: true, result: validation }));
2118
- } catch (error) {
2119
- console.log(JSON.stringify({ ok: false, error: error.message }));
2120
- }
2121
- NODESCRIPT
2122
- )
2123
-
2124
- result=$(echo "$node_script" | node - "$model_name")
2125
- echo "$result"
2126
- ;;
2127
-
2128
- *)
2129
- echo "Usage: model-profile <command> [args]"
2130
- echo ""
2131
- echo "Commands:"
2132
- echo " get <caste> Get model for caste"
2133
- echo " set <caste> <model> Set user override"
2134
- echo " reset <caste> Reset user override"
2135
- echo " list List all assignments"
2136
- echo " select <caste> <task> [model] Select model with task routing"
2137
- echo " validate <model> Validate model name"
2138
- json_err "$E_VALIDATION_FAILED" "Usage: model-profile get <caste>|list|verify|select|validate"
2139
- ;;
2140
- esac
2141
- ;;
2142
-
2143
- model-get)
2144
- # Shortcut: model-get <caste>
2145
- caste="${1:-}"
2146
- [[ -z "$caste" ]] && json_err "$E_VALIDATION_FAILED" "Usage: model-get <caste>"
2147
-
2148
- # Delegate to model-profile get
2149
- exec bash "$0" model-profile get "$caste"
2150
- ;;
2151
-
2152
- model-list)
2153
- # Shortcut: list all models
2154
- exec bash "$0" model-profile list
2155
- ;;
2156
-
2157
- # ============================================
2158
- # CHAMBER UTILITIES (colony lifecycle)
2159
- # ============================================
2160
-
2161
- chamber-create)
2162
- # Create a new chamber (entomb a colony)
2163
- # Usage: chamber-create <chamber_dir> <state_file> <goal> <phases_completed> <total_phases> <milestone> <version> <decisions_json> <learnings_json>
2164
- [[ $# -ge 9 ]] || json_err "$E_VALIDATION_FAILED" "Usage: chamber-create <chamber_dir> <state_file> <goal> <phases_completed> <total_phases> <milestone> <version> <decisions_json> <learnings_json>"
2165
-
2166
- # Check if chamber-utils.sh is available
2167
- if ! type chamber_create &>/dev/null; then
2168
- json_err "$E_FILE_NOT_FOUND" "chamber-utils.sh not loaded"
2169
- fi
2170
-
2171
- chamber_create "$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" "$9"
2172
- ;;
2173
-
2174
- chamber-verify)
2175
- # Verify chamber integrity
2176
- # Usage: chamber-verify <chamber_dir>
2177
- [[ $# -ge 1 ]] || json_err "$E_VALIDATION_FAILED" "Usage: chamber-verify <chamber_dir>"
2178
-
2179
- if ! type chamber_verify &>/dev/null; then
2180
- json_err "$E_FILE_NOT_FOUND" "chamber-utils.sh not loaded"
2181
- fi
2182
-
2183
- chamber_verify "$1"
2184
- ;;
2185
-
2186
- chamber-list)
2187
- # List all chambers
2188
- # Usage: chamber-list [chambers_root]
2189
- chambers_root="${1:-$AETHER_ROOT/.aether/chambers}"
2190
-
2191
- if ! type chamber_list &>/dev/null; then
2192
- json_err "$E_FILE_NOT_FOUND" "chamber-utils.sh not loaded"
2193
- fi
2194
-
2195
- chamber_list "$chambers_root"
2196
- ;;
2197
-
2198
- milestone-detect)
2199
- # Detect colony milestone from state
2200
- # Usage: milestone-detect
2201
- # Returns: {ok: true, milestone: "...", version: "...", phases_completed: N, total_phases: N, progress_percent: N}
2202
-
2203
- [[ -f "$DATA_DIR/COLONY_STATE.json" ]] || json_err "$E_FILE_NOT_FOUND" "COLONY_STATE.json not found" '{"file":"COLONY_STATE.json"}'
2204
-
2205
- # Extract and compute milestone data using jq
2206
- result=$(jq '
2207
- # Extract key data
2208
- (.plan.phases // []) as $phases |
2209
- (.errors.records // []) as $errors |
2210
- (.milestone // null) as $stored_milestone |
2211
-
2212
- # Count completed phases
2213
- ([$phases[] | select(.status == "completed")] | length) as $completed_count |
2214
- ($phases | length) as $total_phases |
2215
-
2216
- # Check for critical errors
2217
- ([$errors[] | select(.severity == "critical")] | length) as $critical_count |
2218
-
2219
- # Determine milestone based on state
2220
- if $critical_count > 0 then
2221
- "Failed Mound"
2222
- elif $total_phases > 0 and $completed_count == $total_phases then
2223
- if $stored_milestone == "Crowned Anthill" then
2224
- "Crowned Anthill"
2225
- else
2226
- "Sealed Chambers"
2227
- end
2228
- elif $completed_count >= 5 then
2229
- "Ventilated Nest"
2230
- elif $completed_count >= 3 then
2231
- "Brood Stable"
2232
- elif $completed_count >= 1 then
2233
- "Open Chambers"
2234
- else
2235
- "First Mound"
2236
- end as $milestone |
2237
-
2238
- # Compute version: major = floor(total_phases / 10), minor = total_phases % 10, patch = completed_count
2239
- ($total_phases / 10 | floor) as $major |
2240
- ($total_phases % 10) as $minor |
2241
- $completed_count as $patch |
2242
- "v\($major).\($minor).\($patch)" as $version |
2243
-
2244
- # Calculate progress percentage
2245
- (if $total_phases > 0 then ($completed_count * 100 / $total_phases | round) else 0 end) as $progress |
2246
-
2247
- # Return result
2248
- {
2249
- ok: true,
2250
- milestone: $milestone,
2251
- version: $version,
2252
- phases_completed: $completed_count,
2253
- total_phases: $total_phases,
2254
- progress_percent: $progress
2255
- }
2256
- ' "$DATA_DIR/COLONY_STATE.json")
2257
-
2258
- echo "$result"
2259
- ;;
2260
-
2261
- # ============================================
2262
- # SWARM ACTIVITY TRACKING (colony visualization)
2263
- # ============================================
2264
-
2265
- swarm-activity-log)
2266
- # Log an activity entry for swarm visualization
2267
- # Usage: swarm-activity-log <ant_name> <action> <details>
2268
- ant_name="${1:-}"
2269
- action="${2:-}"
2270
- details="${3:-}"
2271
- [[ -z "$ant_name" || -z "$action" || -z "$details" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-activity-log <ant_name> <action> <details>"
2272
-
2273
- mkdir -p "$DATA_DIR"
2274
- log_file="$DATA_DIR/swarm-activity.log"
2275
- ts=$(date -u +"%H:%M:%S")
2276
- echo "[$ts] $ant_name: $action $details" >> "$log_file"
2277
- json_ok '"logged"'
2278
- ;;
2279
-
2280
- swarm-display-init)
2281
- # Initialize swarm display state file
2282
- # Usage: swarm-display-init <swarm_id>
2283
- swarm_id="${1:-swarm-$(date +%s)}"
2284
- mkdir -p "$DATA_DIR"
2285
-
2286
- display_file="$DATA_DIR/swarm-display.json"
2287
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
2288
-
2289
- atomic_write "$display_file" "{
2290
- \"swarm_id\": \"$swarm_id\",
2291
- \"timestamp\": \"$ts\",
2292
- \"active_ants\": [],
2293
- \"summary\": { \"total_active\": 0, \"by_caste\": {}, \"by_zone\": {} },
2294
- \"chambers\": {
2295
- \"fungus_garden\": {\"activity\": 0, \"icon\": \"🍄\"},
2296
- \"nursery\": {\"activity\": 0, \"icon\": \"🥚\"},
2297
- \"refuse_pile\": {\"activity\": 0, \"icon\": \"🗑️\"},
2298
- \"throne_room\": {\"activity\": 0, \"icon\": \"👑\"},
2299
- \"foraging_trail\": {\"activity\": 0, \"icon\": \"🌿\"}
2300
- }
2301
- }"
2302
- json_ok "{\"swarm_id\":\"$swarm_id\",\"initialized\":true}"
2303
- ;;
2304
-
2305
- swarm-display-update)
2306
- # Update ant activity in swarm display
2307
- # Usage: swarm-display-update <ant_name> <caste> <ant_status> <task> [parent] [tools_json] [tokens] [chamber] [progress]
2308
- ant_name="${1:-}"
2309
- caste="${2:-}"
2310
- ant_status="${3:-}"
2311
- task="${4:-}"
2312
- parent="${5:-}"
2313
- tools_json="${6:-}"
2314
- [[ -z "$tools_json" ]] && tools_json="{}"
2315
- tokens="${7:-0}"
2316
- chamber="${8:-}"
2317
- progress="${9:-0}"
2318
-
2319
- [[ -z "$ant_name" || -z "$caste" || -z "$ant_status" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-display-update <ant_name> <caste> <ant_status> <task> [parent] [tools_json] [tokens] [chamber] [progress]"
2320
-
2321
- display_file="$DATA_DIR/swarm-display.json"
2322
-
2323
- # Initialize if doesn't exist
2324
- if [[ ! -f "$display_file" ]]; then
2325
- bash "$0" swarm-display-init "default-swarm" >/dev/null 2>&1
2326
- fi
2327
-
2328
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
2329
-
2330
- # Read current display and update using jq
2331
- updated=$(jq --arg ant "$ant_name" --arg caste "$caste" --arg ant_status "$ant_status" \
2332
- --arg task "$task" --arg parent "$parent" --argjson tools "$tools_json" \
2333
- --argjson tokens "$tokens" --arg ts "$ts" --arg chamber "$chamber" --argjson progress "$progress" '
2334
- # Find existing ant or create new entry
2335
- (.active_ants | map(select(.name == $ant)) | length) as $exists |
2336
- # Get old chamber if ant exists
2337
- (if $exists > 0 then
2338
- (.active_ants[] | select(.name == $ant) | .chamber // "")
2339
- else
2340
- ""
2341
- end) as $old_chamber |
2342
- # Determine new chamber
2343
- (if $chamber != "" then $chamber else $old_chamber end) as $new_chamber |
2344
- if $exists > 0 then
2345
- # Update existing ant
2346
- .active_ants = [.active_ants[] | if .name == $ant then
2347
- . + {
2348
- caste: $caste,
2349
- status: $ant_status,
2350
- task: $task,
2351
- parent: (if $parent != "" then $parent else .parent end),
2352
- tools: (if $tools != {} then $tools else .tools end),
2353
- tokens: (.tokens + $tokens),
2354
- chamber: (if $chamber != "" then $chamber else (.chamber // null) end),
2355
- progress: (if $progress > 0 then $progress else (.progress // 0) end),
2356
- updated_at: $ts
2357
- }
2358
- else . end]
2359
- else
2360
- # Add new ant
2361
- .active_ants += [{
2362
- name: $ant,
2363
- caste: $caste,
2364
- status: $ant_status,
2365
- task: $task,
2366
- parent: (if $parent != "" then $parent else null end),
2367
- tools: (if $tools != {} then $tools else {read:0,grep:0,edit:0,bash:0} end),
2368
- tokens: $tokens,
2369
- chamber: (if $chamber != "" then $chamber else null end),
2370
- progress: $progress,
2371
- started_at: $ts,
2372
- updated_at: $ts
2373
- }]
2374
- end |
2375
- # Recalculate summary
2376
- .summary.total_active = (.active_ants | length) |
2377
- .summary.by_caste = (.active_ants | group_by(.caste) | map({key: .[0].caste, value: length}) | from_entries) |
2378
- .summary.by_zone = (.active_ants | group_by(.status) | map({key: .[0].status, value: length}) | from_entries) |
2379
- # Update chamber activity counts
2380
- # Decrement old chamber if changed
2381
- (if $old_chamber != "" and $old_chamber != $new_chamber and has("chambers") and (.chambers | has($old_chamber)) then
2382
- .chambers[$old_chamber].activity = [(.chambers[$old_chamber].activity // 1) - 1, 0] | max
2383
- else
2384
- .
2385
- end) |
2386
- # Increment new chamber
2387
- (if $new_chamber != "" and has("chambers") and (.chambers | has($new_chamber)) then
2388
- .chambers[$new_chamber].activity = (.chambers[$new_chamber].activity // 0) + 1
2389
- else
2390
- .
2391
- end)
2392
- ' "$display_file") || json_err "$E_JSON_INVALID" "Failed to update swarm display"
2393
-
2394
- atomic_write "$display_file" "$updated"
2395
-
2396
- # Get emoji for response
2397
- emoji=$(get_caste_emoji "$caste")
2398
- json_ok "{\"updated\":true,\"ant\":\"$ant_name\",\"caste\":\"$caste\",\"emoji\":\"$emoji\",\"chamber\":\"$chamber\",\"progress\":$progress}"
2399
- ;;
2400
-
2401
- swarm-display-get)
2402
- # Get current swarm display state
2403
- # Usage: swarm-display-get
2404
- display_file="$DATA_DIR/swarm-display.json"
2405
-
2406
- if [[ ! -f "$display_file" ]]; then
2407
- json_ok '{"swarm_id":null,"active_ants":[],"summary":{"total_active":0,"by_caste":{},"by_zone":{}},"chambers":{}}'
2408
- else
2409
- json_ok "$(cat "$display_file")"
2410
- fi
2411
- ;;
2412
-
2413
- swarm-display-render)
2414
- # Render the swarm display to terminal
2415
- # Usage: swarm-display-render [swarm_id]
2416
- swarm_id="${1:-default-swarm}"
2417
-
2418
- display_script="$SCRIPT_DIR/utils/swarm-display.sh"
2419
-
2420
- if [[ -f "$display_script" ]]; then
2421
- # Execute the display script
2422
- bash "$display_script" "$swarm_id" 2>/dev/null || true
2423
- json_ok '{"rendered":true}'
2424
- else
2425
- json_err "$E_FILE_NOT_FOUND" "Display script not found: $display_script"
2426
- fi
2427
- ;;
2428
-
2429
- swarm-display-inline)
2430
- # Inline swarm display for Claude Code (no loop, no clear)
2431
- # Usage: swarm-display-inline [swarm_id]
2432
- swarm_id="${1:-default-swarm}"
2433
- display_file="$DATA_DIR/swarm-display.json"
2434
-
2435
- # ANSI colors
2436
- BLUE='\033[34m'
2437
- GREEN='\033[32m'
2438
- YELLOW='\033[33m'
2439
- RED='\033[31m'
2440
- MAGENTA='\033[35m'
2441
- BOLD='\033[1m'
2442
- DIM='\033[2m'
2443
- RESET='\033[0m'
2444
-
2445
- # Caste colors
2446
- get_caste_color() {
2447
- case "$1" in
2448
- builder) echo "$BLUE" ;;
2449
- watcher) echo "$GREEN" ;;
2450
- scout) echo "$YELLOW" ;;
2451
- chaos) echo "$RED" ;;
2452
- prime) echo "$MAGENTA" ;;
2453
- oracle) echo "$MAGENTA" ;;
2454
- route_setter) echo "$MAGENTA" ;;
2455
- *) echo "$RESET" ;;
2456
- esac
2457
- }
2458
-
2459
- # Caste emojis with ant
2460
- get_caste_emoji() {
2461
- case "$1" in
2462
- builder) echo "🔨🐜" ;;
2463
- watcher) echo "👁️🐜" ;;
2464
- scout) echo "🔍🐜" ;;
2465
- chaos) echo "🎲🐜" ;;
2466
- prime) echo "👑🐜" ;;
2467
- oracle) echo "🔮🐜" ;;
2468
- route_setter) echo "🧭🐜" ;;
2469
- archaeologist) echo "🏺🐜" ;;
2470
- chronicler) echo "📝🐜" ;;
2471
- gatekeeper) echo "📦🐜" ;;
2472
- guardian) echo "🛡️🐜" ;;
2473
- includer) echo "♿🐜" ;;
2474
- keeper) echo "📚🐜" ;;
2475
- measurer) echo "⚡🐜" ;;
2476
- probe) echo "🧪🐜" ;;
2477
- sage) echo "📜🐜" ;;
2478
- tracker) echo "🐛🐜" ;;
2479
- weaver) echo "🔄🐜" ;;
2480
- colonizer) echo "🌱🐜" ;;
2481
- dreamer) echo "💭🐜" ;;
2482
- *) echo "🐜" ;;
2483
- esac
2484
- }
2485
-
2486
- # Status phrases
2487
- get_status_phrase() {
2488
- case "$1" in
2489
- builder) echo "excavating..." ;;
2490
- watcher) echo "observing..." ;;
2491
- scout) echo "exploring..." ;;
2492
- chaos) echo "testing..." ;;
2493
- prime) echo "coordinating..." ;;
2494
- oracle) echo "researching..." ;;
2495
- route_setter) echo "planning..." ;;
2496
- *) echo "working..." ;;
2497
- esac
2498
- }
2499
-
2500
- # Excavation phrase based on progress
2501
- get_excavation_phrase() {
2502
- local progress="${1:-0}"
2503
- if [[ "$progress" -lt 25 ]]; then
2504
- echo "🚧 Starting excavation..."
2505
- elif [[ "$progress" -lt 50 ]]; then
2506
- echo "⛏️ Digging deeper..."
2507
- elif [[ "$progress" -lt 75 ]]; then
2508
- echo "🪨 Moving earth..."
2509
- elif [[ "$progress" -lt 100 ]]; then
2510
- echo "🏗️ Almost there..."
2511
- else
2512
- echo "✅ Excavation complete!"
2513
- fi
2514
- }
2515
-
2516
- # Format tools: "📖5 🔍3 ✏️2 ⚡1"
2517
- format_tools() {
2518
- local read="${1:-0}"
2519
- local grep="${2:-0}"
2520
- local edit="${3:-0}"
2521
- local bash="${4:-0}"
2522
- local result=""
2523
- [[ "$read" -gt 0 ]] && result="${result}📖${read} "
2524
- [[ "$grep" -gt 0 ]] && result="${result}🔍${grep} "
2525
- [[ "$edit" -gt 0 ]] && result="${result}✏️${edit} "
2526
- [[ "$bash" -gt 0 ]] && result="${result}⚡${bash}"
2527
- echo "$result"
2528
- }
2529
-
2530
- # Render progress bar (green when working)
2531
- render_progress_bar() {
2532
- local percent="${1:-0}"
2533
- local width="${2:-20}"
2534
- [[ "$percent" -lt 0 ]] && percent=0
2535
- [[ "$percent" -gt 100 ]] && percent=100
2536
- local filled=$((percent * width / 100))
2537
- local empty=$((width - filled))
2538
- local bar=""
2539
- for ((i=0; i<filled; i++)); do bar+="█"; done
2540
- for ((i=0; i<empty; i++)); do bar+="░"; done
2541
- echo -e "${GREEN}[$bar]${RESET} ${percent}%"
2542
- }
2543
-
2544
- # Format duration
2545
- format_duration() {
2546
- local seconds="${1:-0}"
2547
- if [[ "$seconds" -lt 60 ]]; then
2548
- echo "${seconds}s"
2549
- else
2550
- local mins=$((seconds / 60))
2551
- local secs=$((seconds % 60))
2552
- echo "${mins}m${secs}s"
2553
- fi
2554
- }
2555
-
2556
- # Check for display file
2557
- if [[ ! -f "$display_file" ]]; then
2558
- echo -e "${DIM}🐜 No active swarm data${RESET}"
2559
- json_ok '{"displayed":false,"reason":"no_data"}'
2560
- exit 0
2561
- fi
2562
-
2563
- # Check for jq
2564
- if ! command -v jq >/dev/null 2>&1; then
2565
- echo -e "${DIM}🐜 Swarm active (jq not available for details)${RESET}"
2566
- json_ok '{"displayed":true,"warning":"jq_missing"}'
2567
- exit 0
2568
- fi
2569
-
2570
- # Read swarm data
2571
- total_active=$(jq -r '.summary.total_active // 0' "$display_file" 2>/dev/null || echo "0")
2572
-
2573
- if [[ "$total_active" -eq 0 ]]; then
2574
- echo -e "${DIM}🐜 Colony idle${RESET}"
2575
- json_ok '{"displayed":true,"ants":0}'
2576
- exit 0
2577
- fi
2578
-
2579
- # Render header with ant logo
2580
- echo ""
2581
- cat << 'ANTLOGO'
2582
-
2583
-
2584
- ▁▐▖ ▁
2585
- ▗▇▇███▆▇▃▅████▆▇▆▅▟██▛▇
2586
- ▝▜▅▛██████████████▜▅██
2587
- ▁▂▀▇▆██▙▜██████████▛▟███▛▁▃▁
2588
- ▕▂▁▉▅████▙▞██████▜█▚▟████▅▊ ▐
2589
- ▗▁▐█▀▜████▛▃▝▁████▍▘▟▜████▛▀█▂ ▖
2590
- ▁▎▝█▁▝▍▆▜████▊▐▀▏▀▍▂▂▝▀▕▀▌█████▀▅▐▚ █▏▁▁
2591
- ▂▚▃▇▙█▟████▛▏ ▝▜▐▛▀▍▛▘ ▕█████▆▊▐▂▃▞▂▔
2592
- ▚▔█▛██████▙▟▍▜▍▜▃▃▖▟▛▐██████▛▛▜▔▔▞
2593
- ▋▖▍▊▖██████▇▃▁▝██▘▝▃████▜█▜ ▋▐▐▗
2594
- ▍▌▇█▅▂▜██████████████████▉▃▄▋▖ ▝
2595
- ▁▎▍▁▜▟███▀▀▜████████████▛▀▀███▆▂ ▁▁
2596
- ██ ▆▇▌▁▕▚▅▆███▛████████▜███▆▄▞▁▁▐▅▎ █▉
2597
- ▆█████▛▃▟█▀████████████████▛█▙▙▜▉▟▛▜█▌▗
2598
- ▅▆▋ ▁▁▁▔▕▁▁▁▇█████▛▀▀▀▁▜▇▇▁▁▁▁▁▁▁▁ ▐▊▗
2599
- ▗▆▃▃▃▔███▖▔██▀▀▝▀██▀▍█▛▁▐█▏█▛▀▀▏█▛▀▜█▆▃▃▆▖
2600
- ▝▗▖ ▟█▟█▙ █▛▀▘ █▊ ▕█▛▀▜█▏█▛▀▘ █▋▆█▛ ▗▖
2601
- ▘ ▘ ▟▛ ▝▀▘▀▀▀▀▘ ▀▀▂▂█▙▂▐▀▏▀▀▀▀▘▀▘ ▝▀▅▂▝ ▕▏
2602
- ▕▕ ▃▗▄▔▗▄▄▗▗▗▔▄▄▄▄▗▄▄▗▔▃▃▃▗▄▂▄▃▗▄▂▖▖ ▏▁
2603
- ▝▘▏ ▔▔ ▁▔▁▔▔▁▔▔▔▔▔▔▔▁▁ ▔▔ ▔▔▔▔
2604
- ▀ ▀▝▘▀▀▔▘▘▀▝▕▀▀▝▝▀▔▀ ▀▔▘
2605
- ▘ ▗▅▁▝▚▃▀▆▟██▙▆▝▃ ▘ ▁▗▌
2606
- ▔▀▔▝ ▔▀▟▜▛▛▀▔ ▀
2607
-
2608
-
2609
- ANTLOGO
2610
- echo -e "${BOLD}AETHER COLONY :: Colony Activity${RESET}"
2611
- echo -e "${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
2612
- echo ""
2613
-
2614
- # Render each active ant (limit to 5)
2615
- jq -r '.active_ants[0:5][] | "\(.name)|\(.caste)|\(.status // "")|\(.task // "")|\(.tools.read // 0)|\(.tools.grep // 0)|\(.tools.edit // 0)|\(.tools.bash // 0)|\(.tokens // 0)|\(.started_at // "")|\(.parent // "Queen")|\(.progress // 0)"' "$display_file" 2>/dev/null | while IFS='|' read -r ant_name ant_caste ant_status ant_task read_ct grep_ct edit_ct bash_ct tokens started_at parent progress; do
2616
- color=$(get_caste_color "$ant_caste")
2617
- emoji=$(get_caste_emoji "$ant_caste")
2618
- phrase=$(get_status_phrase "$ant_caste")
2619
-
2620
- # Format tools
2621
- tools_str=$(format_tools "$read_ct" "$grep_ct" "$edit_ct" "$bash_ct")
2622
-
2623
- # Truncate task if too long
2624
- display_task="$ant_task"
2625
- [[ ${#display_task} -gt 35 ]] && display_task="${display_task:0:32}..."
2626
-
2627
- # Calculate elapsed time
2628
- elapsed_str=""
2629
- started_ts="${started_at:-}"
2630
- if [[ -n "$started_ts" ]] && [[ "$started_ts" != "null" ]]; then
2631
- started_ts=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$started_ts" +%s 2>/dev/null)
2632
- if [[ -z "$started_ts" ]] || [[ "$started_ts" == "null" ]]; then
2633
- started_ts=$(date -d "$started_ts" +%s 2>/dev/null) || started_ts=0
2634
- fi
2635
- now_ts=$(date +%s)
2636
- elapsed=0
2637
- if [[ -n "$started_ts" ]] && [[ "$started_ts" -gt 0 ]] 2>/dev/null; then
2638
- elapsed=$((now_ts - started_ts))
2639
- fi
2640
- if [[ ${elapsed:-0} -gt 0 ]]; then
2641
- elapsed_str="($(format_duration $elapsed))"
2642
- fi
2643
- fi
2644
-
2645
- # Token indicator
2646
- token_str=""
2647
- if [[ -n "$tokens" ]] && [[ "$tokens" -gt 0 ]]; then
2648
- token_str="🍯${tokens}"
2649
- fi
2650
-
2651
- # Output ant line: "🐜 Builder: excavating... Implement auth 📖5 🔍3 (2m3s) 🍯1250"
2652
- echo -e "${color}${emoji} ${BOLD}${ant_name}${RESET}${color}: ${phrase}${RESET} ${display_task}"
2653
- echo -e " ${tools_str} ${DIM}${elapsed_str}${RESET} ${token_str}"
2654
-
2655
- # Show progress bar if progress > 0
2656
- if [[ -n "$progress" ]] && [[ "$progress" -gt 0 ]]; then
2657
- progress_bar=$(render_progress_bar "$progress" 15)
2658
- excavation_phrase=$(get_excavation_phrase "$progress")
2659
- echo -e " ${DIM}${progress_bar}${RESET}"
2660
- echo -e " ${DIM}${excavation_phrase}${RESET}"
2661
- fi
2662
-
2663
- echo ""
2664
- done
2665
-
2666
- # Chamber activity map
2667
- echo -e "${DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
2668
- echo ""
2669
- echo -e "${BOLD}Chamber Activity:${RESET}"
2670
-
2671
- # Show active chambers with fire intensity
2672
- has_chamber_activity=0
2673
- jq -r '.chambers | to_entries[] | "\(.key)|\(.value.activity)|\(.value.icon)"' "$display_file" 2>/dev/null | \
2674
- while IFS='|' read -r chamber activity icon; do
2675
- if [[ -n "$activity" ]] && [[ "$activity" -gt 0 ]]; then
2676
- has_chamber_activity=1
2677
- if [[ "$activity" -ge 5 ]]; then
2678
- fires="🔥🔥🔥"
2679
- elif [[ "$activity" -ge 3 ]]; then
2680
- fires="🔥🔥"
2681
- else
2682
- fires="🔥"
2683
- fi
2684
- chamber_name="${chamber//_/ }"
2685
- echo -e " ${icon} ${chamber_name} ${fires} (${activity} ants)"
2686
- fi
2687
- done
2688
-
2689
- if [[ "$has_chamber_activity" -eq 0 ]]; then
2690
- echo -e "${DIM} (no chamber activity)${RESET}"
2691
- fi
2692
-
2693
- # Summary
2694
- echo ""
2695
- echo -e "${DIM}${total_active} forager$([[ "$total_active" -eq 1 ]] || echo "s") excavating...${RESET}"
2696
-
2697
- json_ok "{\"displayed\":true,\"ants\":$total_active}"
2698
- ;;
2699
-
2700
- swarm-timing-start)
2701
- # Record start time for an ant
2702
- # Usage: swarm-timing-start <ant_name>
2703
- ant_name="${1:-}"
2704
- [[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-start <ant_name>"
2705
-
2706
- mkdir -p "$DATA_DIR"
2707
- timing_file="$DATA_DIR/timing.log"
2708
- ts=$(date +%s)
2709
- ts_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
2710
-
2711
- # Remove any existing entry for this ant and append new one
2712
- if [[ -f "$timing_file" ]]; then
2713
- grep -v "^$ant_name|" "$timing_file" > "${timing_file}.tmp" 2>/dev/null || true
2714
- mv "${timing_file}.tmp" "$timing_file"
2715
- fi
2716
- echo "$ant_name|$ts|$ts_iso" >> "$timing_file"
2717
-
2718
- json_ok "{\"ant\":\"$ant_name\",\"started_at\":\"$ts_iso\",\"timestamp\":$ts}"
2719
- ;;
2720
-
2721
- swarm-timing-get)
2722
- # Get elapsed time for an ant
2723
- # Usage: swarm-timing-get <ant_name>
2724
- ant_name="${1:-}"
2725
- [[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-get <ant_name>"
2726
-
2727
- timing_file="$DATA_DIR/timing.log"
2728
-
2729
- if [[ ! -f "$timing_file" ]] || ! grep -q "^$ant_name|" "$timing_file" 2>/dev/null; then
2730
- json_ok "{\"ant\":\"$ant_name\",\"started_at\":null,\"elapsed_seconds\":0,\"elapsed_formatted\":\"00:00\"}"
2731
- exit 0
2732
- fi
2733
-
2734
- # Read start time
2735
- start_line=$(grep "^$ant_name|" "$timing_file" | tail -1)
2736
- start_ts=$(echo "$start_line" | cut -d'|' -f2)
2737
- start_iso=$(echo "$start_line" | cut -d'|' -f3)
2738
-
2739
- now=$(date +%s)
2740
- elapsed=$((now - start_ts))
2741
-
2742
- # Format as MM:SS
2743
- mins=$((elapsed / 60))
2744
- secs=$((elapsed % 60))
2745
- formatted=$(printf "%02d:%02d" $mins $secs)
2746
-
2747
- json_ok "{\"ant\":\"$ant_name\",\"started_at\":\"$start_iso\",\"elapsed_seconds\":$elapsed,\"elapsed_formatted\":\"$formatted\"}"
2748
- ;;
2749
-
2750
- swarm-timing-eta)
2751
- # Calculate ETA based on progress percentage
2752
- # Usage: swarm-timing-eta <ant_name> <percent_complete>
2753
- ant_name="${1:-}"
2754
- percent="${2:-0}"
2755
- [[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-eta <ant_name> <percent_complete>"
2756
-
2757
- # Validate percent is a number
2758
- if ! [[ "$percent" =~ ^[0-9]+$ ]]; then
2759
- percent=0
2760
- fi
2761
-
2762
- # Clamp percent to 0-100
2763
- if [[ $percent -lt 0 ]]; then
2764
- percent=0
2765
- elif [[ $percent -gt 100 ]]; then
2766
- percent=100
2767
- fi
2768
-
2769
- timing_file="$DATA_DIR/timing.log"
2770
-
2771
- if [[ ! -f "$timing_file" ]] || ! grep -q "^$ant_name|" "$timing_file" 2>/dev/null; then
2772
- json_ok "{\"ant\":\"$ant_name\",\"percent\":$percent,\"eta_seconds\":null,\"eta_formatted\":\"--:--\"}"
2773
- exit 0
2774
- fi
2775
-
2776
- # Read start time
2777
- start_ts=$(grep "^$ant_name|" "$timing_file" | tail -1 | cut -d'|' -f2)
2778
- now=$(date +%s)
2779
- elapsed=$((now - start_ts))
2780
-
2781
- # Calculate ETA
2782
- if [[ $percent -le 0 ]]; then
2783
- eta_seconds=null
2784
- eta_formatted="--:--"
2785
- elif [[ $percent -ge 100 ]]; then
2786
- eta_seconds=0
2787
- eta_formatted="00:00"
2788
- else
2789
- # ETA = (elapsed / percent) * (100 - percent)
2790
- eta_seconds=$(( (elapsed * (100 - percent)) / percent ))
2791
- mins=$((eta_seconds / 60))
2792
- secs=$((eta_seconds % 60))
2793
- eta_formatted=$(printf "%02d:%02d" $mins $secs)
2794
- fi
2795
-
2796
- json_ok "{\"ant\":\"$ant_name\",\"percent\":$percent,\"eta_seconds\":$eta_seconds,\"eta_formatted\":\"$eta_formatted\"}"
2797
- ;;
2798
-
2799
- # ============================================
2800
- # VIEW STATE MANAGEMENT (collapsible views)
2801
- # ============================================
2802
-
2803
- view-state-init)
2804
- # Initialize view state file with default structure
2805
- # Usage: view-state-init
2806
- mkdir -p "$DATA_DIR"
2807
- view_state_file="$DATA_DIR/view-state.json"
2808
-
2809
- if [[ ! -f "$view_state_file" ]]; then
2810
- atomic_write "$view_state_file" '{
2811
- "version": "1.0",
2812
- "swarm_display": {
2813
- "expanded": [],
2814
- "collapsed": [],
2815
- "default_expand_depth": 2
2816
- },
2817
- "tunnel_view": {
2818
- "expanded": [],
2819
- "collapsed": ["__depth_3_plus__"],
2820
- "default_expand_depth": 2,
2821
- "show_completed": true
2822
- }
2823
- }'
2824
- json_ok '{"initialized":true,"file":"view-state.json"}'
2825
- else
2826
- json_ok '{"initialized":false,"file":"view-state.json","exists":true}'
2827
- fi
2828
- ;;
2829
-
2830
- view-state-get)
2831
- # Get view state or specific key
2832
- # Usage: view-state-get [view_name] [key]
2833
- view_name="${1:-}"
2834
- key="${2:-}"
2835
- view_state_file="$DATA_DIR/view-state.json"
2836
-
2837
- if [[ ! -f "$view_state_file" ]]; then
2838
- # Auto-initialize if not exists
2839
- bash "$0" view-state-init >/dev/null 2>&1
2840
- fi
2841
-
2842
- if [[ -z "$view_name" ]]; then
2843
- # Return entire state
2844
- json_ok "$(cat "$view_state_file")"
2845
- elif [[ -z "$key" ]]; then
2846
- # Return specific view
2847
- json_ok "$(jq ".${view_name} // {}" "$view_state_file")"
2848
- else
2849
- # Return specific key from view
2850
- json_ok "$(jq ".${view_name}.${key} // null" "$view_state_file")"
2851
- fi
2852
- ;;
2853
-
2854
- view-state-set)
2855
- # Set a specific key in a view
2856
- # Usage: view-state-set <view_name> <key> <value>
2857
- view_name="${1:-}"
2858
- key="${2:-}"
2859
- value="${3:-}"
2860
- [[ -z "$view_name" || -z "$key" ]] && json_err "$E_VALIDATION_FAILED" "Usage: view-state-set <view_name> <key> <value>"
2861
-
2862
- view_state_file="$DATA_DIR/view-state.json"
2863
-
2864
- if [[ ! -f "$view_state_file" ]]; then
2865
- bash "$0" view-state-init >/dev/null 2>&1
2866
- fi
2867
-
2868
- # Determine if value is JSON or string
2869
- if [[ "$value" =~ ^\[.*\]$ ]] || [[ "$value" =~ ^\{.*\}$ ]] || [[ "$value" =~ ^(true|false|null|[0-9]+)$ ]]; then
2870
- # Value appears to be JSON - use as-is
2871
- updated=$(jq --arg view "$view_name" --arg key "$key" --argjson val "$value" '
2872
- .[$view][$key] = $val
2873
- ' "$view_state_file") || json_err "$E_JSON_INVALID" "Failed to update view state"
2874
- else
2875
- # Treat as string
2876
- updated=$(jq --arg view "$view_name" --arg key "$key" --arg val "$value" '
2877
- .[$view][$key] = $val
2878
- ' "$view_state_file") || json_err "$E_JSON_INVALID" "Failed to update view state"
2879
- fi
2880
-
2881
- atomic_write "$view_state_file" "$updated"
2882
- json_ok "$(echo "$updated" | jq ".${view_name}")"
2883
- ;;
2884
-
2885
- view-state-toggle)
2886
- # Toggle item between expanded and collapsed
2887
- # Usage: view-state-toggle <view_name> <item>
2888
- view_name="${1:-}"
2889
- item="${2:-}"
2890
- [[ -z "$view_name" || -z "$item" ]] && json_err "$E_VALIDATION_FAILED" "Usage: view-state-toggle <view_name> <item>"
2891
-
2892
- view_state_file="$DATA_DIR/view-state.json"
2893
-
2894
- if [[ ! -f "$view_state_file" ]]; then
2895
- bash "$0" view-state-init >/dev/null 2>&1
2896
- fi
2897
-
2898
- # Check current state
2899
- is_expanded=$(jq --arg view "$view_name" --arg item "$item" '
2900
- .[$view].expanded | contains([$item])
2901
- ' "$view_state_file")
2902
-
2903
- if [[ "$is_expanded" == "true" ]]; then
2904
- # Move from expanded to collapsed
2905
- updated=$(jq --arg view "$view_name" --arg item "$item" '
2906
- .[$view].expanded -= [$item] |
2907
- .[$view].collapsed += [$item]
2908
- ' "$view_state_file")
2909
- new_state="collapsed"
2910
- else
2911
- # Move from collapsed to expanded
2912
- updated=$(jq --arg view "$view_name" --arg item "$item" '
2913
- .[$view].collapsed -= [$item] |
2914
- .[$view].expanded += [$item]
2915
- ' "$view_state_file")
2916
- new_state="expanded"
2917
- fi
2918
-
2919
- atomic_write "$view_state_file" "$updated"
2920
- json_ok "{\"item\":\"$item\",\"state\":\"$new_state\",\"view\":\"$view_name\"}"
2921
- ;;
2922
-
2923
- view-state-expand)
2924
- # Explicitly expand an item
2925
- # Usage: view-state-expand <view_name> <item>
2926
- view_name="${1:-}"
2927
- item="${2:-}"
2928
- [[ -z "$view_name" || -z "$item" ]] && json_err "$E_VALIDATION_FAILED" "Usage: view-state-expand <view_name> <item>"
2929
-
2930
- view_state_file="$DATA_DIR/view-state.json"
2931
-
2932
- if [[ ! -f "$view_state_file" ]]; then
2933
- bash "$0" view-state-init >/dev/null 2>&1
2934
- fi
2935
-
2936
- updated=$(jq --arg view "$view_name" --arg item "$item" '
2937
- .[$view].collapsed -= [$item] |
2938
- .[$view].expanded += [$item]
2939
- ' "$view_state_file") || json_err "$E_JSON_INVALID" "Failed to update view state"
2940
-
2941
- atomic_write "$view_state_file" "$updated"
2942
- json_ok "{\"item\":\"$item\",\"state\":\"expanded\",\"view\":\"$view_name\"}"
2943
- ;;
2944
-
2945
- view-state-collapse)
2946
- # Explicitly collapse an item
2947
- # Usage: view-state-collapse <view_name> <item>
2948
- view_name="${1:-}"
2949
- item="${2:-}"
2950
- [[ -z "$view_name" || -z "$item" ]] && json_err "$E_VALIDATION_FAILED" "Usage: view-state-collapse <view_name> <item>"
2951
-
2952
- view_state_file="$DATA_DIR/view-state.json"
2953
-
2954
- if [[ ! -f "$view_state_file" ]]; then
2955
- bash "$0" view-state-init >/dev/null 2>&1
2956
- fi
2957
-
2958
- updated=$(jq --arg view "$view_name" --arg item "$item" '
2959
- .[$view].expanded -= [$item] |
2960
- .[$view].collapsed += [$item]
2961
- ' "$view_state_file") || json_err "$E_JSON_INVALID" "Failed to update view state"
2962
-
2963
- atomic_write "$view_state_file" "$updated"
2964
- json_ok "{\"item\":\"$item\",\"state\":\"collapsed\",\"view\":\"$view_name\"}"
2965
- ;;
2966
-
2967
- queen-init)
2968
- # Initialize QUEEN.md from template
2969
- # Creates .aether/QUEEN.md from template if missing
2970
- queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
2971
-
2972
- # Check multiple locations for template
2973
- # Order: dev (runtime/) -> npm install (hub) -> legacy
2974
- template_file=""
2975
- for path in \
2976
- "$AETHER_ROOT/runtime/templates/QUEEN.md.template" \
2977
- "$HOME/.aether/templates/QUEEN.md.template" \
2978
- "$AETHER_ROOT/.aether/templates/QUEEN.md.template"; do
2979
- if [[ -f "$path" ]]; then
2980
- template_file="$path"
2981
- break
2982
- fi
2983
- done
2984
-
2985
- # Ensure docs directory exists
2986
- mkdir -p "$AETHER_ROOT/.aether/docs"
2987
-
2988
- # Check if QUEEN.md already exists and has content
2989
- if [[ -f "$queen_file" ]] && [[ -s "$queen_file" ]]; then
2990
- json_ok '{"created":false,"path":".aether/docs/QUEEN.md","reason":"already_exists"}'
2991
- exit 0
2992
- fi
2993
-
2994
- # Check if template was found
2995
- if [[ -z "$template_file" ]]; then
2996
- 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"]}'
2997
- exit 1
2998
- fi
2999
-
3000
- # Create QUEEN.md from template with timestamp substitution
3001
- timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
3002
- sed -e "s/{TIMESTAMP}/$timestamp/g" "$template_file" > "$queen_file"
3003
-
3004
- if [[ -f "$queen_file" ]]; then
3005
- json_ok "{\"created\":true,\"path\":\".aether/docs/QUEEN.md\",\"source\":\"$template_file\"}"
3006
- else
3007
- json_err "$E_FILE_NOT_FOUND" "Failed to create QUEEN.md" '{"path":".aether/docs/QUEEN.md"}'
3008
- exit 1
3009
- fi
3010
- ;;
3011
-
3012
- queen-read)
3013
- # Read QUEEN.md and return wisdom as JSON for worker priming
3014
- # Extracts METADATA block and sections for colony guidance
3015
- queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
3016
-
3017
- # Check if QUEEN.md exists
3018
- if [[ ! -f "$queen_file" ]]; then
3019
- json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"path":".aether/docs/QUEEN.md"}'
3020
- exit 1
3021
- fi
3022
-
3023
- # Extract METADATA JSON block (between <!-- METADATA and -->)
3024
- metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_file" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
3025
-
3026
- # If no metadata found, return empty structure
3027
- if [[ -z "$metadata" ]]; then
3028
- metadata='{"version":"unknown","last_evolved":null,"colonies_contributed":[],"promotion_thresholds":{},"stats":{}}'
3029
- fi
3030
-
3031
- # Extract sections content for worker priming
3032
- # Use awk to parse markdown sections - remove header line and trailing section header
3033
- philosophies=$(awk '/^## 📜 Philosophies$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
3034
- patterns=$(awk '/^## 🧭 Patterns$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
3035
- redirects=$(awk '/^## ⚠️ Redirects$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
3036
- stack_wisdom=$(awk '/^## 🔧 Stack Wisdom$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
3037
- decrees=$(awk '/^## 🏛️ Decrees$/,/^## /' "$queen_file" | tail -n +2 | sed '$d' | sed '/^$/d' | jq -Rs '.')
3038
-
3039
- # Build JSON output
3040
- result=$(jq -n \
3041
- --argjson meta "$metadata" \
3042
- --arg philosophies "$philosophies" \
3043
- --arg patterns "$patterns" \
3044
- --arg redirects "$redirects" \
3045
- --arg stack_wisdom "$stack_wisdom" \
3046
- --arg decrees "$decrees" \
3047
- '{
3048
- metadata: $meta,
3049
- wisdom: {
3050
- philosophies: $philosophies,
3051
- patterns: $patterns,
3052
- redirects: $redirects,
3053
- stack_wisdom: $stack_wisdom,
3054
- decrees: $decrees
3055
- },
3056
- priming: {
3057
- has_philosophies: ($philosophies | length) > 0 and $philosophies != "*No philosophies recorded yet.*\n",
3058
- has_patterns: ($patterns | length) > 0 and $patterns != "*No patterns recorded yet.*\n",
3059
- has_redirects: ($redirects | length) > 0 and $redirects != "*No redirects recorded yet.*\n",
3060
- has_stack_wisdom: ($stack_wisdom | length) > 0 and $stack_wisdom != "*No stack wisdom recorded yet.*\n",
3061
- has_decrees: ($decrees | length) > 0 and $decrees != "*No decrees recorded yet.*\n"
3062
- }
3063
- }')
3064
-
3065
- json_ok "$result"
3066
- ;;
3067
-
3068
- queen-promote)
3069
- # Promote a learning to QUEEN.md wisdom
3070
- # Usage: queen-promote <type> <content> <colony_name>
3071
- # Types: philosophy, pattern, redirect, stack, decree
3072
- wisdom_type="${1:-}"
3073
- content="${2:-}"
3074
- colony_name="${3:-}"
3075
-
3076
- # Validate required arguments
3077
- [[ -z "$wisdom_type" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"type"}'
3078
- [[ -z "$content" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"content"}'
3079
- [[ -z "$colony_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"colony_name"}'
3080
-
3081
- # Validate type
3082
- valid_types=("philosophy" "pattern" "redirect" "stack" "decree")
3083
- type_valid=false
3084
- for vt in "${valid_types[@]}"; do
3085
- [[ "$wisdom_type" == "$vt" ]] && type_valid=true && break
3086
- done
3087
- [[ "$type_valid" == "false" ]] && json_err "$E_VALIDATION_FAILED" "Invalid type: $wisdom_type" '{"valid_types":["philosophy","pattern","redirect","stack","decree"]}'
3088
-
3089
- queen_file="$AETHER_ROOT/.aether/docs/QUEEN.md"
3090
-
3091
- # Check if QUEEN.md exists
3092
- if [[ ! -f "$queen_file" ]]; then
3093
- json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"path":".aether/docs/QUEEN.md"}'
3094
- exit 1
3095
- fi
3096
-
3097
- # Extract METADATA to get promotion thresholds
3098
- metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_file" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
3099
-
3100
- # Get threshold for this type (default: philosophy=5, pattern=3, redirect=2, stack=1, decree=0)
3101
- threshold=$(echo "$metadata" | jq -r ".promotion_thresholds.${wisdom_type} // null")
3102
- if [[ "$threshold" == "null" ]]; then
3103
- case "$wisdom_type" in
3104
- philosophy) threshold=5 ;;
3105
- pattern) threshold=3 ;;
3106
- redirect) threshold=2 ;;
3107
- stack) threshold=1 ;;
3108
- decree) threshold=0 ;;
3109
- *) threshold=1 ;;
3110
- esac
3111
- fi
3112
-
3113
- # For decrees, always promote immediately (threshold 0)
3114
- # For other types, we assume validation count is passed or threshold is met
3115
- # In a real implementation, this would check a validation counter
3116
- # For now, we append if threshold allows (decrees always, others need external validation)
3117
-
3118
- ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
3119
-
3120
- # Map type to section header and emoji
3121
- case "$wisdom_type" in
3122
- philosophy) section_header="## 📜 Philosophies" ;;
3123
- pattern) section_header="## 🧭 Patterns" ;;
3124
- redirect) section_header="## ⚠️ Redirects" ;;
3125
- stack) section_header="## 🔧 Stack Wisdom" ;;
3126
- decree) section_header="## 🏛️ Decrees" ;;
3127
- esac
3128
-
3129
- # Build the new entry
3130
- entry="- **${colony_name}** (${ts}): ${content}"
3131
-
3132
- # Create temp file for atomic write
3133
- tmp_file="${queen_file}.tmp.$$"
3134
-
3135
- # Find line numbers for section boundaries
3136
- section_line=$(grep -n "^${section_header}$" "$queen_file" | head -1 | cut -d: -f1)
3137
- next_section_line=$(tail -n +$((section_line + 1)) "$queen_file" | grep -n "^## " | head -1 | cut -d: -f1)
3138
- if [[ -n "$next_section_line" ]]; then
3139
- section_end=$((section_line + next_section_line - 1))
3140
- else
3141
- section_end=$(wc -l < "$queen_file")
3142
- fi
3143
-
3144
- # Check if section has placeholder (grep returns 1 when no matches, handle with || true)
3145
- has_placeholder=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -c "No.*recorded yet" || true)
3146
- has_placeholder=${has_placeholder:-0}
3147
-
3148
- if [[ "$has_placeholder" -gt 0 ]]; then
3149
- # Replace placeholder with entry - only within the target section
3150
- # Find the specific line number of the placeholder within the section
3151
- placeholder_line=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -n "^\\*No .* recorded yet" | head -1 | cut -d: -f1)
3152
- if [[ -n "$placeholder_line" ]]; then
3153
- actual_line=$((section_line + placeholder_line - 1))
3154
- sed "${actual_line}c\\
3155
- ${entry}" "$queen_file" > "$tmp_file"
3156
- else
3157
- # Fallback: insert after section header
3158
- sed "${section_line}a\\
3159
- ${entry}" "$queen_file" > "$tmp_file"
3160
- fi
3161
- else
3162
- # Insert entry after the description paragraph (after the second empty line in section)
3163
- # The structure is: header, blank, description, blank, [entries...]
3164
- # We want to insert after the blank line following the description
3165
- empty_lines=$(sed -n "$((section_line + 1)),${section_end}p" "$queen_file" | grep -n "^$" | cut -d: -f1)
3166
- # Get the second empty line (after description)
3167
- insert_line=$(echo "$empty_lines" | sed -n '2p')
3168
- if [[ -n "$insert_line" ]]; then
3169
- insert_line=$((section_line + insert_line))
3170
- else
3171
- # Fallback: use first empty line
3172
- insert_line=$(echo "$empty_lines" | head -1)
3173
- if [[ -n "$insert_line" ]]; then
3174
- insert_line=$((section_line + insert_line))
3175
- else
3176
- insert_line=$((section_line + 1))
3177
- fi
3178
- fi
3179
- # Insert the entry after the found line
3180
- sed "${insert_line}a\\
3181
- ${entry}" "$queen_file" > "$tmp_file"
3182
- fi
3183
-
3184
- # Update Evolution Log in temp file
3185
- ev_entry="| ${ts} | ${colony_name} | promoted_${wisdom_type} | Added: ${content:0:50}... |"
3186
- # Find the line after the separator in Evolution Log table
3187
- ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1)
3188
-
3189
- # Use awk for cross-platform insertion
3190
- 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"
3191
-
3192
- # Update METADATA stats in temp file
3193
- # Map wisdom_type to stat key (irregular plurals handled)
3194
- case "$wisdom_type" in
3195
- stack) stat_key="total_stack_entries" ;;
3196
- philosophy) stat_key="total_philosophies" ;;
3197
- *) stat_key="total_${wisdom_type}s" ;;
3198
- esac
3199
- # Read current count from temp file (which has the latest state)
3200
- current_count=$(grep "\"${stat_key}\":" "$tmp_file" 2>/dev/null | grep -o '[0-9]*' | head -1 || true)
3201
- current_count=${current_count:-0}
3202
- new_count=$((current_count + 1))
3203
-
3204
- # Update last_evolved using awk
3205
- awk -v ts="$ts" '/"last_evolved":/ { gsub(/"last_evolved": "[^"]*"/, "\"last_evolved\": \"" ts "\""); } {print}' "$tmp_file" > "${tmp_file}.meta" && mv "${tmp_file}.meta" "$tmp_file"
3206
-
3207
- # Update stats count using awk
3208
- awk -v type="$stat_key" -v count="$new_count" '{
3209
- gsub("\"" type "\": [0-9]*", "\"" type "\": " count)
3210
- print
3211
- }' "$tmp_file" > "${tmp_file}.stats" && mv "${tmp_file}.stats" "$tmp_file"
3212
-
3213
- # Add colony to colonies_contributed if not present
3214
- if ! grep -q "\"${colony_name}\"" "$tmp_file"; then
3215
- # Add to colonies_contributed array using awk - handle empty and non-empty arrays
3216
- awk -v colony="$colony_name" '
3217
- /"colonies_contributed": \[\]/ {
3218
- gsub(/"colonies_contributed": \[\]/, "\"colonies_contributed\": [\"" colony "\"]")
3219
- print
3220
- next
3221
- }
3222
- /"colonies_contributed": \[/ && !/\]/ {
3223
- # Multi-line array, add at next closing bracket
3224
- print
3225
- next
3226
- }
3227
- /"colonies_contributed": \[/ {
3228
- # Single-line array with elements
3229
- gsub(/\]$/, "\"" colony "\", ]")
3230
- print
3231
- next
3232
- }
3233
- { print }
3234
- ' "$tmp_file" > "${tmp_file}.col" && mv "${tmp_file}.col" "$tmp_file"
3235
- fi
3236
-
3237
- # Atomic move
3238
- mv "$tmp_file" "$queen_file"
3239
-
3240
- json_ok "{\"promoted\":true,\"type\":\"$wisdom_type\",\"colony\":\"$colony_name\",\"timestamp\":\"$ts\",\"threshold\":$threshold,\"new_count\":$new_count}"
3241
- ;;
3242
-
3243
- survey-load)
3244
- phase_type="${1:-}"
3245
- survey_dir=".aether/data/survey"
3246
-
3247
- if [[ ! -d "$survey_dir" ]]; then
3248
- json_err "$E_FILE_NOT_FOUND" "No survey found"
3249
- fi
3250
-
3251
- docs=""
3252
- case "$phase_type" in
3253
- *frontend*|*component*|*UI*|*page*|*button*)
3254
- docs="DISCIPLINES.md,CHAMBERS.md"
3255
- ;;
3256
- *API*|*endpoint*|*backend*|*route*)
3257
- docs="BLUEPRINT.md,DISCIPLINES.md"
3258
- ;;
3259
- *database*|*schema*|*model*|*migration*)
3260
- docs="BLUEPRINT.md,PROVISIONS.md"
3261
- ;;
3262
- *test*|*spec*|*coverage*)
3263
- docs="SENTINEL-PROTOCOLS.md,DISCIPLINES.md"
3264
- ;;
3265
- *integration*|*external*|*client*)
3266
- docs="TRAILS.md,PROVISIONS.md"
3267
- ;;
3268
- *refactor*|*cleanup*|*debt*)
3269
- docs="PATHOGENS.md,BLUEPRINT.md"
3270
- ;;
3271
- *setup*|*config*|*initialize*)
3272
- docs="PROVISIONS.md,CHAMBERS.md"
3273
- ;;
3274
- *)
3275
- docs="PROVISIONS.md,BLUEPRINT.md"
3276
- ;;
3277
- esac
3278
-
3279
- json_ok "{\"ok\":true,\"docs\":\"$docs\",\"dir\":\"$survey_dir\"}"
3280
- ;;
3281
-
3282
- survey-verify)
3283
- survey_dir=".aether/data/survey"
3284
- required="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
3285
- missing=""
3286
- counts=""
3287
-
3288
- for doc in $required; do
3289
- if [[ ! -f "$survey_dir/$doc" ]]; then
3290
- missing="$missing $doc"
3291
- else
3292
- lines=$(wc -l < "$survey_dir/$doc" | tr -d ' ')
3293
- counts="$counts $doc:$lines"
3294
- fi
3295
- done
3296
-
3297
- if [[ -n "$missing" ]]; then
3298
- json_err "$E_FILE_NOT_FOUND" "Missing survey documents" "{\"missing\":\"$missing\"}"
3299
- fi
3300
-
3301
- json_ok "{\"ok\":true,\"counts\":\"$counts\"}"
3302
- ;;
3303
-
3304
- checkpoint-check)
3305
- allowlist_file="$DATA_DIR/checkpoint-allowlist.json"
3306
-
3307
- if [[ ! -f "$allowlist_file" ]]; then
3308
- json_err "$E_FILE_NOT_FOUND" "Allowlist not found" "{\"path\":\"$allowlist_file\"}"
3309
- fi
3310
-
3311
- # Get dirty files from git (staged or unstaged)
3312
- dirty_files=$(git status --porcelain 2>/dev/null | awk '{print $2}' || true)
3313
-
3314
- if [[ -z "$dirty_files" ]]; then
3315
- json_ok '{"ok":true,"system_files":[],"user_files":[],"has_user_files":false}'
3316
- exit 0
3317
- fi
3318
-
3319
- # Temporary files for building JSON
3320
- system_files_tmp=$(mktemp)
3321
- user_files_tmp=$(mktemp)
3322
-
3323
- # Check each file against allowlist patterns
3324
- for file in $dirty_files; do
3325
- is_system=false
3326
-
3327
- # Check against system file patterns
3328
- if [[ "$file" == ".aether/aether-utils.sh" ]]; then
3329
- is_system=true
3330
- elif [[ "$file" == ".aether/workers.md" ]]; then
3331
- is_system=true
3332
- elif [[ "$file" == .aether/docs/*.md ]]; then
3333
- is_system=true
3334
- elif [[ "$file" == .claude/commands/ant/*.md ]] || [[ "$file" == .claude/commands/ant/**/*.md ]]; then
3335
- is_system=true
3336
- elif [[ "$file" == .claude/commands/st/*.md ]] || [[ "$file" == .claude/commands/st/**/*.md ]]; then
3337
- is_system=true
3338
- elif [[ "$file" == .opencode/commands/ant/*.md ]] || [[ "$file" == .opencode/commands/ant/**/*.md ]]; then
3339
- is_system=true
3340
- elif [[ "$file" == .opencode/agents/*.md ]] || [[ "$file" == .opencode/agents/**/*.md ]]; then
3341
- is_system=true
3342
- elif [[ "$file" == runtime/* ]]; then
3343
- is_system=true
3344
- elif [[ "$file" == bin/* ]]; then
3345
- is_system=true
3346
- fi
3347
-
3348
- if [[ "$is_system" == "true" ]]; then
3349
- echo "$file" >> "$system_files_tmp"
3350
- else
3351
- echo "$file" >> "$user_files_tmp"
3352
- fi
3353
- done
3354
-
3355
- # Build JSON using jq if available, otherwise use simple format
3356
- if command -v jq >/dev/null 2>&1; then
3357
- result=$(jq -n \
3358
- --argjson system "$(jq -R . < "$system_files_tmp" 2>/dev/null | jq -s .)" \
3359
- --argjson user "$(jq -R . < "$user_files_tmp" 2>/dev/null | jq -s .)" \
3360
- '{ok: true, system_files: $system, user_files: $user, has_user_files: ($user | length > 0)}')
3361
- else
3362
- # Fallback without jq - simple output
3363
- system_count=$(wc -l < "$system_files_tmp" 2>/dev/null | tr -d ' ' || echo "0")
3364
- user_count=$(wc -l < "$user_files_tmp" 2>/dev/null | tr -d ' ' || echo "0")
3365
- has_user=false
3366
- [[ "$user_count" -gt 0 ]] && has_user=true
3367
- result="{\"ok\":true,\"system_files\":[],\"user_files\":[],\"has_user_files\":$has_user}"
3368
- fi
3369
-
3370
- rm -f "$system_files_tmp" "$user_files_tmp"
3371
- echo "$result"
3372
- exit 0
3373
- ;;
3374
-
3375
- normalize-args)
3376
- # Normalize arguments from Claude Code ($ARGUMENTS) or OpenCode ($@)
3377
- # Usage: bash .aether/aether-utils.sh normalize-args [args...]
3378
- # Or: eval "$(bash .aether/aether-utils.sh normalize-args)"
3379
- #
3380
- # Claude Code passes args in $ARGUMENTS variable
3381
- # OpenCode passes args in $@ (positional parameters)
3382
- # This command outputs the normalized arguments as a single string
3383
-
3384
- normalized=""
3385
-
3386
- # Try Claude Code style first ($ARGUMENTS environment variable)
3387
- if [ -n "${ARGUMENTS:-}" ]; then
3388
- normalized="$ARGUMENTS"
3389
- # Fall back to OpenCode style ($@ positional params)
3390
- elif [ $# -gt 0 ]; then
3391
- # Preserve arguments with spaces by quoting
3392
- for arg in "$@"; do
3393
- if [[ "$arg" == *" "* ]] || [[ "$arg" == *"\t"* ]] || [[ "$arg" == *"\n"* ]]; then
3394
- # Quote arguments containing whitespace
3395
- normalized="$normalized \"$arg\""
3396
- else
3397
- normalized="$normalized $arg"
3398
- fi
3399
- done
3400
- # Trim leading space
3401
- normalized="${normalized# }"
3402
- fi
3403
-
3404
- # Output normalized arguments
3405
- echo "$normalized"
3406
- exit 0
3407
- ;;
3408
-
3409
- # Backward compatibility wrappers for session commands
3410
- survey-verify-fresh)
3411
- # Backward compatibility: delegate to session-verify-fresh --command survey
3412
- # Usage: bash .aether/aether-utils.sh survey-verify-fresh [--force] <survey_start_unixtime>
3413
-
3414
- force_mode=""
3415
- survey_start_time=""
3416
-
3417
- # Parse arguments
3418
- for arg in "$@"; do
3419
- if [[ "$arg" == "--force" ]]; then
3420
- force_mode="--force"
3421
- elif [[ "$arg" =~ ^[0-9]+$ ]]; then
3422
- survey_start_time="$arg"
3423
- fi
3424
- done
3425
-
3426
- # Delegate to generic command
3427
- if [[ -n "$force_mode" ]]; then
3428
- $0 session-verify-fresh --command survey --force "$survey_start_time"
3429
- else
3430
- $0 session-verify-fresh --command survey "$survey_start_time"
3431
- fi
3432
- ;;
3433
-
3434
- survey-clear)
3435
- # Backward compatibility: delegate to session-clear --command survey
3436
- # Usage: bash .aether/aether-utils.sh survey-clear [--dry-run]
3437
-
3438
- dry_run=""
3439
-
3440
- # Parse arguments
3441
- for arg in "$@"; do
3442
- if [[ "$arg" == "--dry-run" ]]; then
3443
- dry_run="--dry-run"
3444
- fi
3445
- done
3446
-
3447
- # Delegate to generic command
3448
- if [[ "$dry_run" == "--dry-run" ]]; then
3449
- $0 session-clear --command survey --dry-run
3450
- else
3451
- $0 session-clear --command survey
3452
- fi
3453
- ;;
3454
-
3455
- session-verify-fresh)
3456
- # Generic session freshness verification
3457
- # Usage: bash .aether/aether-utils.sh session-verify-fresh --command <name> [--force] <session_start_unixtime>
3458
- # Returns: JSON with pass/fail status and file details
3459
-
3460
- # Parse arguments
3461
- command_name=""
3462
- force_mode=""
3463
- session_start_time=""
3464
-
3465
- while [[ $# -gt 0 ]]; do
3466
- case "$1" in
3467
- --command) command_name="$2"; shift 2 ;;
3468
- --force) force_mode="--force"; shift ;;
3469
- *) session_start_time="$1"; shift ;;
3470
- esac
3471
- done
3472
-
3473
- # Validate command name
3474
- [[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-verify-fresh --command <name> [--force] <session_start>"
3475
-
3476
- # Map command to directory and files (using env var override pattern)
3477
- case "$command_name" in
3478
- survey)
3479
- session_dir="${SURVEY_DIR:-.aether/data/survey}"
3480
- required_docs="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
3481
- ;;
3482
- oracle)
3483
- session_dir="${ORACLE_DIR:-.aether/oracle}"
3484
- required_docs="progress.md research.json"
3485
- ;;
3486
- watch)
3487
- session_dir="${WATCH_DIR:-.aether/data}"
3488
- required_docs="watch-status.txt watch-progress.txt"
3489
- ;;
3490
- swarm)
3491
- session_dir="${SWARM_DIR:-.aether/data/swarm}"
3492
- required_docs="findings.json"
3493
- ;;
3494
- init)
3495
- session_dir="${INIT_DIR:-.aether/data}"
3496
- required_docs="COLONY_STATE.json constraints.json"
3497
- ;;
3498
- seal|entomb)
3499
- session_dir="${ARCHIVE_DIR:-.aether/data/archive}"
3500
- required_docs="manifest.json"
3501
- ;;
3502
- *)
3503
- json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name" '{"commands":["survey","oracle","watch","swarm","init","seal","entomb"]}'
3504
- ;;
3505
- esac
3506
-
3507
- # Initialize result arrays
3508
- fresh_docs=""
3509
- stale_docs=""
3510
- missing_docs=""
3511
- total_lines=0
3512
-
3513
- for doc in $required_docs; do
3514
- doc_path="$session_dir/$doc"
3515
-
3516
- if [[ ! -f "$doc_path" ]]; then
3517
- missing_docs="${missing_docs:+$missing_docs }$doc"
3518
- continue
3519
- fi
3520
-
3521
- # Get line count
3522
- lines=$(wc -l < "$doc_path" 2>/dev/null | tr -d ' ' || echo "0")
3523
- total_lines=$((total_lines + lines))
3524
-
3525
- # In force mode, accept any existing file
3526
- if [[ "$force_mode" == "--force" ]]; then
3527
- fresh_docs="${fresh_docs:+$fresh_docs }$doc"
3528
- continue
3529
- fi
3530
-
3531
- # Check timestamp if session_start_time provided
3532
- if [[ -n "$session_start_time" ]]; then
3533
- # Cross-platform stat: macOS uses -f %m, Linux uses -c %Y
3534
- file_mtime=$(stat -f %m "$doc_path" 2>/dev/null || stat -c %Y "$doc_path" 2>/dev/null || echo "0")
3535
-
3536
- if [[ "$file_mtime" -ge "$session_start_time" ]]; then
3537
- fresh_docs="${fresh_docs:+$fresh_docs }$doc"
3538
- else
3539
- stale_docs="${stale_docs:+$stale_docs }$doc"
3540
- fi
3541
- else
3542
- # No start time provided - accept existing file (backward compatible)
3543
- fresh_docs="${fresh_docs:+$fresh_docs }$doc"
3544
- fi
3545
- done
3546
-
3547
- # Determine pass/fail
3548
- # pass = true if: no stale files (fresh files can coexist with missing files)
3549
- # missing files are ok - they will be created during the session
3550
- pass=false
3551
- if [[ "$force_mode" == "--force" ]] || [[ -z "$stale_docs" ]]; then
3552
- pass=true
3553
- fi
3554
-
3555
- # Build JSON response
3556
- fresh_json=""
3557
- for item in $fresh_docs; do fresh_json="$fresh_json\"$item\","; done
3558
- fresh_json="[${fresh_json%,}]"
3559
-
3560
- stale_json=""
3561
- for item in $stale_docs; do stale_json="$stale_json\"$item\","; done
3562
- stale_json="[${stale_json%,}]"
3563
-
3564
- missing_json=""
3565
- for item in $missing_docs; do missing_json="$missing_json\"$item\","; done
3566
- missing_json="[${missing_json%,}]"
3567
-
3568
- echo "{\"ok\":$pass,\"command\":\"$command_name\",\"fresh\":$fresh_json,\"stale\":$stale_json,\"missing\":$missing_json,\"total_lines\":$total_lines}"
3569
- exit 0
3570
- ;;
3571
-
3572
- session-clear)
3573
- # Generic session file clearing
3574
- # Usage: bash .aether/aether-utils.sh session-clear --command <name> [--dry-run]
3575
-
3576
- # Parse arguments
3577
- command_name=""
3578
- dry_run=""
3579
-
3580
- while [[ $# -gt 0 ]]; do
3581
- case "$1" in
3582
- --command) command_name="$2"; shift 2 ;;
3583
- --dry-run) dry_run="--dry-run"; shift ;;
3584
- *) shift ;;
3585
- esac
3586
- done
3587
-
3588
- [[ -z "$command_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: session-clear --command <name> [--dry-run]"
3589
-
3590
- # Map command to directory and files
3591
- case "$command_name" in
3592
- survey)
3593
- session_dir="${SURVEY_DIR:-.aether/data/survey}"
3594
- files="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
3595
- ;;
3596
- oracle)
3597
- session_dir="${ORACLE_DIR:-.aether/oracle}"
3598
- files="progress.md research.json .stop"
3599
- # Also clear discoveries subdirectory
3600
- subdir_files="discoveries/*"
3601
- ;;
3602
- watch)
3603
- session_dir="${WATCH_DIR:-.aether/data}"
3604
- files="watch-status.txt watch-progress.txt"
3605
- ;;
3606
- swarm)
3607
- session_dir="${SWARM_DIR:-.aether/data/swarm}"
3608
- files="findings.json display.json timing.json"
3609
- ;;
3610
- init)
3611
- # Init clear is destructive - blocked for auto-clear
3612
- json_err "$E_VALIDATION_FAILED" "Command 'init' is protected and cannot be auto-cleared. Use manual removal of COLONY_STATE.json if absolutely necessary."
3613
- ;;
3614
- seal|entomb)
3615
- # Archive operations should never be auto-cleared
3616
- json_err "$E_VALIDATION_FAILED" "Command '$command_name' is protected and cannot be auto-cleared. Archives and chambers must be managed manually."
3617
- ;;
3618
- *)
3619
- json_err "$E_VALIDATION_FAILED" "Unknown command: $command_name"
3620
- ;;
3621
- esac
3622
-
3623
- cleared=""
3624
- errors=""
3625
-
3626
- if [[ -d "$session_dir" && -n "$files" ]]; then
3627
- for doc in $files; do
3628
- doc_path="$session_dir/$doc"
3629
- if [[ -f "$doc_path" ]]; then
3630
- if [[ "$dry_run" == "--dry-run" ]]; then
3631
- cleared="$cleared $doc"
3632
- else
3633
- if rm -f "$doc_path" 2>/dev/null; then
3634
- cleared="$cleared $doc"
3635
- else
3636
- errors="$errors $doc"
3637
- fi
3638
- fi
3639
- fi
3640
- done
3641
-
3642
- # Handle oracle discoveries subdirectory
3643
- if [[ "$command_name" == "oracle" && -d "$session_dir/discoveries" ]]; then
3644
- if [[ "$dry_run" == "--dry-run" ]]; then
3645
- cleared="$cleared discoveries/"
3646
- else
3647
- rm -rf "$session_dir/discoveries" 2>/dev/null && cleared="$cleared discoveries/" || errors="$errors discoveries/"
3648
- fi
3649
- fi
3650
- fi
3651
-
3652
- json_ok "{\"command\":\"$command_name\",\"cleared\":\"${cleared// /}\",\"errors\":\"${errors// /}\",\"dry_run\":$([[ "$dry_run" == "--dry-run" ]] && echo "true" || echo "false")}"
3653
- ;;
3654
-
3655
- pheromone-export)
3656
- # Export pheromones to eternal XML format
3657
- # Usage: pheromone-export [input_json] [output_xml]
3658
- # input_json: Path to pheromones.json (default: .aether/data/pheromones.json)
3659
- # output_xml: Path to output XML (default: ~/.aether/eternal/pheromones.xml)
3660
-
3661
- input_json="${1:-.aether/data/pheromones.json}"
3662
- output_xml="${2:-$HOME/.aether/eternal/pheromones.xml}"
3663
- schema_file="${3:-$SCRIPT_DIR/schemas/pheromone.xsd}"
3664
-
3665
- # Ensure xml-utils.sh is sourced
3666
- if ! type pheromone-export &>/dev/null; then
3667
- [[ -f "$SCRIPT_DIR/utils/xml-utils.sh" ]] && source "$SCRIPT_DIR/utils/xml-utils.sh"
3668
- fi
3669
-
3670
- if type pheromone-export &>/dev/null; then
3671
- pheromone-export "$input_json" "$output_xml" "$schema_file"
3672
- else
3673
- json_err "$E_DEPENDENCY_MISSING" "xml-utils.sh not available for pheromone export"
3674
- fi
3675
- ;;
3676
-
3677
- # ============================================================================
3678
- # Session Continuity Commands
3679
- # ============================================================================
3680
-
3681
- session-init)
3682
- # Initialize a new session tracking file
3683
- # Usage: session-init [session_id] [goal]
3684
- session_id="${2:-$(date +%s)_$(openssl rand -hex 4 2>/dev/null || echo $$)}"
3685
- goal="${3:-}"
3686
-
3687
- session_file="$DATA_DIR/session.json"
3688
-
3689
- cat > "$session_file" << EOF
3690
- {
3691
- "session_id": "$session_id",
3692
- "started_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
3693
- "last_command": null,
3694
- "last_command_at": null,
3695
- "colony_goal": "$goal",
3696
- "current_phase": 0,
3697
- "current_milestone": "First Mound",
3698
- "suggested_next": "/ant:plan",
3699
- "context_cleared": false,
3700
- "resumed_at": null,
3701
- "active_todos": [],
3702
- "summary": "Session initialized"
3703
- }
3704
- EOF
3705
- json_ok "{\"session_id\":\"$session_id\",\"goal\":\"$goal\",\"file\":\"$session_file\"}"
3706
- ;;
3707
-
3708
- session-update)
3709
- # Update session with latest activity
3710
- # Usage: session-update <command> [suggested_next] [summary]
3711
- cmd_run="${2:-}"
3712
- suggested="${3:-}"
3713
- summary="${4:-}"
3714
-
3715
- session_file="$DATA_DIR/session.json"
3716
-
3717
- if [[ ! -f "$session_file" ]]; then
3718
- # Auto-initialize if doesn't exist
3719
- bash "$0" session-init "auto_$(date +%s)" ""
3720
- fi
3721
-
3722
- # Read current session
3723
- current_session=$(cat "$session_file" 2>/dev/null || echo '{}')
3724
-
3725
- # Extract current values for preservation
3726
- current_goal=$(echo "$current_session" | jq -r '.colony_goal // empty')
3727
- current_phase=$(echo "$current_session" | jq -r '.current_phase // 0')
3728
- current_milestone=$(echo "$current_session" | jq -r '.current_milestone // "First Mound"')
3729
-
3730
- # Get top 3 TODOs if TO-DOs.md exists
3731
- todos="[]"
3732
- if [[ -f "TO-DOs.md" ]]; then
3733
- todos=$(grep "^### " TO-DOs.md 2>/dev/null | head -3 | sed 's/^### //' | jq -R . | jq -s .)
3734
- fi
3735
-
3736
- # Get colony state if exists
3737
- if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
3738
- current_goal=$(jq -r '.goal // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_goal")
3739
- current_phase=$(jq -r '.current_phase // 0' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_phase")
3740
- current_milestone=$(jq -r '.milestone // "First Mound"' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "$current_milestone")
3741
- fi
3742
-
3743
- # Build updated session
3744
- echo "$current_session" | jq --arg cmd "$cmd_run" \
3745
- --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
3746
- --arg suggested "$suggested" \
3747
- --arg summary "$summary" \
3748
- --arg goal "$current_goal" \
3749
- --argjson phase "$current_phase" \
3750
- --arg milestone "$current_milestone" \
3751
- --argjson todos "$todos" \
3752
- '.last_command = $cmd |
3753
- .last_command_at = $ts |
3754
- .suggested_next = $suggested |
3755
- .summary = $summary |
3756
- .colony_goal = $goal |
3757
- .current_phase = $phase |
3758
- .current_milestone = $milestone |
3759
- .active_todos = $todos' > "$session_file"
3760
-
3761
- json_ok "{\"updated\":true,\"command\":\"$cmd_run\"}"
3762
- ;;
3763
-
3764
- session-read)
3765
- # Read and return current session state
3766
- session_file="$DATA_DIR/session.json"
3767
-
3768
- if [[ ! -f "$session_file" ]]; then
3769
- json_ok "{\"exists\":false,\"session\":null}"
3770
- exit 0
3771
- fi
3772
-
3773
- session_data=$(cat "$session_file" 2>/dev/null || echo '{}')
3774
-
3775
- # Check if stale (> 24 hours)
3776
- last_cmd_ts="" is_stale="" age_hours=""
3777
- last_cmd_ts=$(echo "$session_data" | jq -r '.last_command_at // .started_at // empty')
3778
- if [[ -n "$last_cmd_ts" ]]; then
3779
- last_epoch=0 now_epoch=0
3780
- last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null || echo 0)
3781
- now_epoch=$(date +%s)
3782
- age_hours=$(( (now_epoch - last_epoch) / 3600 ))
3783
- [[ $age_hours -gt 24 ]] && is_stale=true || is_stale=false
3784
- else
3785
- is_stale="false"
3786
- age_hours="unknown"
3787
- fi
3788
-
3789
- json_ok "{\"exists\":true,\"is_stale\":$is_stale,\"age_hours\":$age_hours,\"session\":$session_data}"
3790
- ;;
3791
-
3792
- session-is-stale)
3793
- # Check if session is stale (returns true/false)
3794
- session_file="$DATA_DIR/session.json"
3795
-
3796
- if [[ ! -f "$session_file" ]]; then
3797
- echo "true"
3798
- exit 0
3799
- fi
3800
-
3801
- last_cmd_ts=$(jq -r '.last_command_at // .started_at // empty' "$session_file" 2>/dev/null)
3802
-
3803
- if [[ -z "$last_cmd_ts" ]]; then
3804
- echo "true"
3805
- exit 0
3806
- fi
3807
-
3808
- last_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$last_cmd_ts" +%s 2>/dev/null || echo 0)
3809
- now_epoch=$(date +%s)
3810
- age_hours=$(( (now_epoch - last_epoch) / 3600 ))
3811
-
3812
- [[ $age_hours -gt 24 ]] && echo "true" || echo "false"
3813
- ;;
3814
-
3815
- session-clear)
3816
- # Mark session as cleared (preserves file but marks context_cleared)
3817
- preserve="${2:-false}"
3818
- session_file="$DATA_DIR/session.json"
3819
-
3820
- if [[ -f "$session_file" ]]; then
3821
- if [[ "$preserve" == "true" ]]; then
3822
- # Just mark as cleared
3823
- jq '.context_cleared = true' "$session_file" > "$session_file.tmp" && mv "$session_file.tmp" "$session_file"
3824
- json_ok "{\"cleared\":true,\"preserved\":true}"
3825
- else
3826
- # Remove file entirely
3827
- rm -f "$session_file"
3828
- json_ok "{\"cleared\":true,\"preserved\":false}"
3829
- fi
3830
- else
3831
- json_ok "{\"cleared\":false,\"reason\":\"no_session_exists\"}"
3832
- fi
3833
- ;;
3834
-
3835
- session-mark-resumed)
3836
- # Mark session as resumed
3837
- session_file="$DATA_DIR/session.json"
3838
-
3839
- if [[ -f "$session_file" ]]; then
3840
- jq --arg ts "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
3841
- '.resumed_at = $ts | .context_cleared = false' "$session_file" > "$session_file.tmp" && mv "$session_file.tmp" "$session_file"
3842
- json_ok "{\"resumed\":true,\"timestamp\":\"$(date -u +"%Y-%m-%dT%H:%M:%SZ")\"}"
3843
- else
3844
- json_err "$E_RESOURCE_NOT_FOUND" "No session to mark as resumed"
3845
- fi
3846
- ;;
3847
-
3848
- session-summary)
3849
- # Get human-readable session summary
3850
- session_file="$DATA_DIR/session.json"
3851
-
3852
- if [[ ! -f "$session_file" ]]; then
3853
- echo "No active session found."
3854
- exit 0
3855
- fi
3856
-
3857
- goal=$(jq -r '.colony_goal // "No goal set"' "$session_file")
3858
- phase=$(jq -r '.current_phase // 0' "$session_file")
3859
- milestone=$(jq -r '.current_milestone // "First Mound"' "$session_file")
3860
- last_cmd=$(jq -r '.last_command // "None"' "$session_file")
3861
- last_at=$(jq -r '.last_command_at // "Unknown"' "$session_file")
3862
- suggested=$(jq -r '.suggested_next // "None"' "$session_file")
3863
- cleared=$(jq -r '.context_cleared // false' "$session_file")
3864
-
3865
- echo "📋 Session Summary"
3866
- echo "=================="
3867
- echo "Goal: $goal"
3868
- [[ "$phase" != "0" ]] && echo "Phase: $phase"
3869
- echo "Milestone: $milestone"
3870
- echo "Last Command: $last_cmd"
3871
- echo "Last Active: $last_at"
3872
- [[ "$suggested" != "None" ]] && echo "Suggested Next: $suggested"
3873
- [[ "$cleared" == "true" ]] && echo "Status: Context was cleared"
3874
- ;;
3875
-
3876
- *)
3877
- json_err "$E_VALIDATION_FAILED" "Unknown command: $cmd"
3878
- ;;
3879
- esac