aether-colony 5.0.0 → 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 (312) hide show
  1. package/.aether/aether-utils.sh +3150 -3349
  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 +11 -11
  72. package/.aether/docs/README.md +32 -2
  73. package/.aether/docs/command-playbooks/README.md +23 -0
  74. package/.aether/docs/command-playbooks/build-complete.md +349 -0
  75. package/.aether/docs/command-playbooks/build-context.md +282 -0
  76. package/.aether/docs/command-playbooks/build-full.md +1682 -0
  77. package/.aether/docs/command-playbooks/build-prep.md +283 -0
  78. package/.aether/docs/command-playbooks/build-verify.md +405 -0
  79. package/.aether/docs/command-playbooks/build-wave.md +749 -0
  80. package/.aether/docs/command-playbooks/continue-advance.md +524 -0
  81. package/.aether/docs/command-playbooks/continue-finalize.md +447 -0
  82. package/.aether/docs/command-playbooks/continue-full.md +1724 -0
  83. package/.aether/docs/command-playbooks/continue-gates.md +686 -0
  84. package/.aether/docs/command-playbooks/continue-verify.md +406 -0
  85. package/.aether/docs/context-continuity.md +84 -0
  86. package/.aether/docs/disciplines/DISCIPLINES.md +9 -7
  87. package/.aether/docs/error-codes.md +1 -1
  88. package/.aether/docs/known-issues.md +34 -173
  89. package/.aether/docs/pheromones.md +86 -6
  90. package/.aether/docs/plans/pheromone-display-plan.md +257 -0
  91. package/.aether/docs/queen-commands.md +10 -9
  92. package/.aether/docs/source-of-truth-map.md +132 -0
  93. package/.aether/docs/xml-utilities.md +47 -0
  94. package/.aether/rules/aether-colony.md +23 -13
  95. package/.aether/scripts/incident-test-add.sh +47 -0
  96. package/.aether/scripts/weekly-audit.sh +79 -0
  97. package/.aether/skills/.index.json +649 -0
  98. package/.aether/skills/colony/.manifest.json +16 -0
  99. package/.aether/skills/colony/build-discipline/SKILL.md +78 -0
  100. package/.aether/skills/colony/colony-interaction/SKILL.md +56 -0
  101. package/.aether/skills/colony/colony-lifecycle/SKILL.md +77 -0
  102. package/.aether/skills/colony/colony-visuals/SKILL.md +112 -0
  103. package/.aether/skills/colony/context-management/SKILL.md +80 -0
  104. package/.aether/skills/colony/error-presentation/SKILL.md +99 -0
  105. package/.aether/skills/colony/pheromone-protocol/SKILL.md +79 -0
  106. package/.aether/skills/colony/pheromone-visibility/SKILL.md +81 -0
  107. package/.aether/skills/colony/state-safety/SKILL.md +84 -0
  108. package/.aether/skills/colony/worker-priming/SKILL.md +82 -0
  109. package/.aether/skills/domain/.manifest.json +24 -0
  110. package/.aether/skills/domain/README.md +33 -0
  111. package/.aether/skills/domain/django/SKILL.md +49 -0
  112. package/.aether/skills/domain/docker/SKILL.md +52 -0
  113. package/.aether/skills/domain/golang/SKILL.md +52 -0
  114. package/.aether/skills/domain/graphql/SKILL.md +51 -0
  115. package/.aether/skills/domain/html-css/SKILL.md +48 -0
  116. package/.aether/skills/domain/nextjs/SKILL.md +45 -0
  117. package/.aether/skills/domain/nodejs/SKILL.md +53 -0
  118. package/.aether/skills/domain/postgresql/SKILL.md +53 -0
  119. package/.aether/skills/domain/prisma/SKILL.md +59 -0
  120. package/.aether/skills/domain/python/SKILL.md +50 -0
  121. package/.aether/skills/domain/rails/SKILL.md +52 -0
  122. package/.aether/skills/domain/react/SKILL.md +45 -0
  123. package/.aether/skills/domain/rest-api/SKILL.md +58 -0
  124. package/.aether/skills/domain/svelte/SKILL.md +47 -0
  125. package/.aether/skills/domain/tailwind/SKILL.md +45 -0
  126. package/.aether/skills/domain/testing/SKILL.md +53 -0
  127. package/.aether/skills/domain/typescript/SKILL.md +58 -0
  128. package/.aether/skills/domain/vue/SKILL.md +49 -0
  129. package/.aether/templates/QUEEN.md.template +23 -41
  130. package/.aether/templates/colony-state-reset.jq.template +1 -0
  131. package/.aether/templates/colony-state.template.json +4 -0
  132. package/.aether/templates/learning-observations.template.json +6 -0
  133. package/.aether/templates/midden.template.json +13 -0
  134. package/.aether/templates/pheromones.template.json +6 -0
  135. package/.aether/templates/session.template.json +9 -0
  136. package/.aether/utils/atomic-write.sh +63 -17
  137. package/.aether/utils/chamber-utils.sh +145 -2
  138. package/.aether/utils/emoji-audit.sh +166 -0
  139. package/.aether/utils/error-handler.sh +21 -7
  140. package/.aether/utils/file-lock.sh +182 -27
  141. package/.aether/utils/flag.sh +267 -0
  142. package/.aether/utils/hive.sh +572 -0
  143. package/.aether/utils/learning.sh +1928 -0
  144. package/.aether/utils/midden.sh +342 -0
  145. package/.aether/utils/oracle/oracle.md +168 -0
  146. package/.aether/utils/oracle/oracle.sh +1023 -0
  147. package/.aether/utils/pheromone.sh +2029 -0
  148. package/.aether/utils/queen.sh +1698 -0
  149. package/.aether/utils/scan.sh +860 -0
  150. package/.aether/utils/semantic-cli.sh +10 -8
  151. package/.aether/utils/session.sh +552 -0
  152. package/.aether/utils/skills.sh +509 -0
  153. package/.aether/utils/spawn-tree.sh +103 -271
  154. package/.aether/utils/spawn.sh +260 -0
  155. package/.aether/utils/state-api.sh +199 -0
  156. package/.aether/utils/state-loader.sh +8 -6
  157. package/.aether/utils/suggest.sh +611 -0
  158. package/.aether/utils/swarm-display.sh +10 -1
  159. package/.aether/utils/swarm.sh +1004 -0
  160. package/.aether/utils/watch-spawn-tree.sh +11 -2
  161. package/.aether/utils/xml-compose.sh +2 -2
  162. package/.aether/utils/xml-convert.sh +9 -5
  163. package/.aether/utils/xml-core.sh +5 -9
  164. package/.aether/utils/xml-query.sh +4 -4
  165. package/.aether/workers.md +86 -67
  166. package/.claude/agents/ant/aether-ambassador.md +2 -1
  167. package/.claude/agents/ant/aether-archaeologist.md +6 -1
  168. package/.claude/agents/ant/aether-architect.md +236 -0
  169. package/.claude/agents/ant/aether-auditor.md +6 -1
  170. package/.claude/agents/ant/aether-builder.md +38 -1
  171. package/.claude/agents/ant/aether-chaos.md +2 -1
  172. package/.claude/agents/ant/aether-chronicler.md +1 -0
  173. package/.claude/agents/ant/aether-gatekeeper.md +6 -1
  174. package/.claude/agents/ant/aether-includer.md +1 -0
  175. package/.claude/agents/ant/aether-keeper.md +1 -0
  176. package/.claude/agents/ant/aether-measurer.md +6 -1
  177. package/.claude/agents/ant/aether-oracle.md +237 -0
  178. package/.claude/agents/ant/aether-probe.md +2 -1
  179. package/.claude/agents/ant/aether-queen.md +6 -1
  180. package/.claude/agents/ant/aether-route-setter.md +6 -1
  181. package/.claude/agents/ant/aether-sage.md +68 -3
  182. package/.claude/agents/ant/aether-scout.md +38 -1
  183. package/.claude/agents/ant/aether-surveyor-disciplines.md +2 -1
  184. package/.claude/agents/ant/aether-surveyor-nest.md +2 -1
  185. package/.claude/agents/ant/aether-surveyor-pathogens.md +2 -1
  186. package/.claude/agents/ant/aether-surveyor-provisions.md +2 -1
  187. package/.claude/agents/ant/aether-tracker.md +6 -1
  188. package/.claude/agents/ant/aether-watcher.md +37 -1
  189. package/.claude/agents/ant/aether-weaver.md +2 -1
  190. package/.claude/commands/ant/archaeology.md +1 -8
  191. package/.claude/commands/ant/build.md +43 -1159
  192. package/.claude/commands/ant/chaos.md +1 -14
  193. package/.claude/commands/ant/colonize.md +1 -14
  194. package/.claude/commands/ant/continue.md +40 -1026
  195. package/.claude/commands/ant/council.md +9 -16
  196. package/.claude/commands/ant/data-clean.md +81 -0
  197. package/.claude/commands/ant/dream.md +12 -9
  198. package/.claude/commands/ant/entomb.md +62 -87
  199. package/.claude/commands/ant/export-signals.md +57 -0
  200. package/.claude/commands/ant/feedback.md +18 -0
  201. package/.claude/commands/ant/flag.md +12 -0
  202. package/.claude/commands/ant/flags.md +22 -8
  203. package/.claude/commands/ant/focus.md +18 -0
  204. package/.claude/commands/ant/help.md +40 -8
  205. package/.claude/commands/ant/history.md +3 -0
  206. package/.claude/commands/ant/import-signals.md +71 -0
  207. package/.claude/commands/ant/init.md +316 -191
  208. package/.claude/commands/ant/insert-phase.md +101 -0
  209. package/.claude/commands/ant/interpret.md +11 -0
  210. package/.claude/commands/ant/lay-eggs.md +167 -158
  211. package/.claude/commands/ant/maturity.md +22 -11
  212. package/.claude/commands/ant/memory-details.md +77 -0
  213. package/.claude/commands/ant/migrate-state.md +6 -0
  214. package/.claude/commands/ant/oracle.md +317 -62
  215. package/.claude/commands/ant/organize.md +10 -5
  216. package/.claude/commands/ant/patrol.md +620 -0
  217. package/.claude/commands/ant/pause-colony.md +8 -22
  218. package/.claude/commands/ant/phase.md +26 -37
  219. package/.claude/commands/ant/pheromones.md +156 -0
  220. package/.claude/commands/ant/plan.md +175 -52
  221. package/.claude/commands/ant/preferences.md +65 -0
  222. package/.claude/commands/ant/redirect.md +18 -0
  223. package/.claude/commands/ant/resume-colony.md +34 -20
  224. package/.claude/commands/ant/resume.md +51 -7
  225. package/.claude/commands/ant/run.md +195 -0
  226. package/.claude/commands/ant/seal.md +497 -78
  227. package/.claude/commands/ant/skill-create.md +286 -0
  228. package/.claude/commands/ant/status.md +127 -1
  229. package/.claude/commands/ant/swarm.md +11 -23
  230. package/.claude/commands/ant/tunnels.md +1 -0
  231. package/.claude/commands/ant/update.md +58 -135
  232. package/.claude/commands/ant/verify-castes.md +90 -42
  233. package/.claude/commands/ant/watch.md +1 -0
  234. package/.opencode/agents/aether-ambassador.md +1 -1
  235. package/.opencode/agents/aether-architect.md +133 -0
  236. package/.opencode/agents/aether-builder.md +3 -3
  237. package/.opencode/agents/aether-oracle.md +137 -0
  238. package/.opencode/agents/aether-queen.md +1 -1
  239. package/.opencode/agents/aether-route-setter.md +1 -1
  240. package/.opencode/agents/aether-scout.md +1 -1
  241. package/.opencode/agents/aether-surveyor-disciplines.md +6 -1
  242. package/.opencode/agents/aether-surveyor-nest.md +6 -1
  243. package/.opencode/agents/aether-surveyor-pathogens.md +6 -1
  244. package/.opencode/agents/aether-surveyor-provisions.md +6 -1
  245. package/.opencode/agents/aether-tracker.md +1 -1
  246. package/.opencode/agents/aether-watcher.md +1 -1
  247. package/.opencode/agents/aether-weaver.md +1 -1
  248. package/.opencode/commands/ant/archaeology.md +7 -14
  249. package/.opencode/commands/ant/build.md +54 -88
  250. package/.opencode/commands/ant/chaos.md +7 -24
  251. package/.opencode/commands/ant/colonize.md +8 -17
  252. package/.opencode/commands/ant/continue.md +595 -66
  253. package/.opencode/commands/ant/council.md +11 -22
  254. package/.opencode/commands/ant/data-clean.md +77 -0
  255. package/.opencode/commands/ant/dream.md +15 -17
  256. package/.opencode/commands/ant/entomb.md +28 -18
  257. package/.opencode/commands/ant/export-signals.md +54 -0
  258. package/.opencode/commands/ant/feedback.md +24 -5
  259. package/.opencode/commands/ant/flag.md +16 -4
  260. package/.opencode/commands/ant/flags.md +24 -10
  261. package/.opencode/commands/ant/focus.md +22 -5
  262. package/.opencode/commands/ant/help.md +41 -8
  263. package/.opencode/commands/ant/history.md +9 -0
  264. package/.opencode/commands/ant/import-signals.md +68 -0
  265. package/.opencode/commands/ant/init.md +365 -156
  266. package/.opencode/commands/ant/insert-phase.md +107 -0
  267. package/.opencode/commands/ant/interpret.md +16 -0
  268. package/.opencode/commands/ant/lay-eggs.md +184 -112
  269. package/.opencode/commands/ant/maturity.md +18 -2
  270. package/.opencode/commands/ant/memory-details.md +83 -0
  271. package/.opencode/commands/ant/migrate-state.md +12 -0
  272. package/.opencode/commands/ant/oracle.md +322 -67
  273. package/.opencode/commands/ant/organize.md +14 -12
  274. package/.opencode/commands/ant/patrol.md +626 -0
  275. package/.opencode/commands/ant/pause-colony.md +12 -29
  276. package/.opencode/commands/ant/phase.md +30 -40
  277. package/.opencode/commands/ant/pheromones.md +162 -0
  278. package/.opencode/commands/ant/plan.md +184 -56
  279. package/.opencode/commands/ant/preferences.md +71 -0
  280. package/.opencode/commands/ant/redirect.md +22 -5
  281. package/.opencode/commands/ant/resume-colony.md +38 -27
  282. package/.opencode/commands/ant/resume.md +71 -20
  283. package/.opencode/commands/ant/run.md +201 -0
  284. package/.opencode/commands/ant/seal.md +230 -25
  285. package/.opencode/commands/ant/skill-create.md +63 -0
  286. package/.opencode/commands/ant/status.md +124 -31
  287. package/.opencode/commands/ant/swarm.md +3 -345
  288. package/.opencode/commands/ant/tunnels.md +3 -9
  289. package/.opencode/commands/ant/update.md +63 -127
  290. package/.opencode/commands/ant/verify-castes.md +96 -42
  291. package/.opencode/commands/ant/watch.md +7 -0
  292. package/CHANGELOG.md +278 -1
  293. package/README.md +188 -340
  294. package/bin/cli.js +236 -429
  295. package/bin/generate-commands.js +186 -0
  296. package/bin/generate-commands.sh +128 -89
  297. package/bin/lib/spawn-logger.js +0 -15
  298. package/bin/lib/update-transaction.js +285 -35
  299. package/bin/npx-install.js +178 -0
  300. package/bin/validate-package.sh +85 -3
  301. package/package.json +7 -3
  302. package/.aether/CONTEXT.md +0 -160
  303. package/.aether/docs/QUEEN.md +0 -84
  304. package/.aether/exchange/colony-registry.xml +0 -11
  305. package/.aether/exchange/pheromones.xml +0 -87
  306. package/.aether/exchange/queen-wisdom.xml +0 -14
  307. package/.aether/model-profiles.yaml +0 -100
  308. package/.aether/utils/spawn-with-model.sh +0 -56
  309. package/bin/lib/model-profiles.js +0 -445
  310. package/bin/lib/model-verify.js +0 -288
  311. package/bin/lib/proxy-health.js +0 -253
  312. package/bin/lib/telemetry.js +0 -441
@@ -0,0 +1,1004 @@
1
+ #!/usr/bin/env bash
2
+ # Swarm utility functions -- extracted from aether-utils.sh
3
+ # Provides: _autofix_checkpoint, _autofix_rollback, _swarm_findings_init, _swarm_findings_add,
4
+ # _swarm_findings_read, _swarm_solution_set, _swarm_cleanup, _swarm_activity_log,
5
+ # _swarm_display_init, _swarm_display_update, _swarm_display_get, _swarm_display_render,
6
+ # _swarm_display_inline, _swarm_display_text, _swarm_timing_start, _swarm_timing_get,
7
+ # _swarm_timing_eta
8
+ # Note: Uses get_caste_emoji() which remains in the main file (shared helper)
9
+
10
+ # ============================================================================
11
+ # _autofix_checkpoint
12
+ # Create checkpoint before applying auto-fix
13
+ # Usage: _autofix_checkpoint [label]
14
+ # Returns: {type: "stash"|"commit"|"none", ref: "..."}
15
+ # IMPORTANT: Only stash Aether-related files, never touch user work
16
+ # ============================================================================
17
+ _autofix_checkpoint() {
18
+ if git rev-parse --git-dir >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: may not be a git repo
19
+ # Check if there are changes to Aether-managed files only
20
+ # Target directories that Aether is allowed to modify
21
+ target_dirs=".aether .claude/commands/ant .claude/commands/st .opencode bin"
22
+ has_changes=false
23
+
24
+ for dir in $target_dirs; do
25
+ if [[ -d "$dir" ]] && [[ -n "$(git status --porcelain "$dir" 2>/dev/null)" ]]; then # SUPPRESS:OK -- existence-test: may not be a git repo
26
+ has_changes=true
27
+ break
28
+ fi
29
+ done
30
+
31
+ if [[ "$has_changes" == "true" ]]; then
32
+ label="${1:-autofix-$(date +%s)}"
33
+ stash_name="aether-checkpoint: $label"
34
+ # Only stash Aether-managed directories, never touch user files
35
+ if git stash push -m "$stash_name" -- $target_dirs >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: stash operation may fail
36
+ json_ok "$(jq -n --arg ref "$stash_name" '{type: "stash", ref: $ref}')"
37
+ else
38
+ # Stash failed (possibly due to conflicts), record commit hash
39
+ hash=$(git rev-parse HEAD 2>/dev/null || echo "unknown") # SUPPRESS:OK -- read-default: may not have commits yet
40
+ json_ok "$(jq -n --arg ref "$hash" '{type: "commit", ref: $ref}')"
41
+ fi
42
+ else
43
+ # No changes in Aether-managed directories, just record commit hash
44
+ hash=$(git rev-parse HEAD 2>/dev/null || echo "unknown") # SUPPRESS:OK -- read-default: may not have commits yet
45
+ json_ok "$(jq -n --arg ref "$hash" '{type: "commit", ref: $ref}')"
46
+ fi
47
+ else
48
+ json_ok '{"type":"none","ref":null}'
49
+ fi
50
+ }
51
+
52
+ # ============================================================================
53
+ # _autofix_rollback
54
+ # Rollback from checkpoint if fix failed
55
+ # Usage: _autofix_rollback <type> <ref>
56
+ # Returns: {rolled_back: bool, method: "stash"|"reset"|"none"}
57
+ # ============================================================================
58
+ _autofix_rollback() {
59
+ ref_type="${1:-none}"
60
+ ref="${2:-}"
61
+
62
+ case "$ref_type" in
63
+ stash)
64
+ # Find and pop the stash
65
+ stash_ref=$(git stash list 2>/dev/null | grep "$ref" | head -1 | cut -d: -f1 || echo "") # SUPPRESS:OK -- existence-test: stash operation may fail
66
+ if [[ -n "$stash_ref" ]]; then
67
+ if git stash pop "$stash_ref" >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: stash operation may fail
68
+ json_ok '{"rolled_back":true,"method":"stash"}'
69
+ else
70
+ json_ok '{"rolled_back":false,"method":"stash","error":"stash pop failed"}'
71
+ fi
72
+ else
73
+ json_ok '{"rolled_back":false,"method":"stash","error":"stash not found"}'
74
+ fi
75
+ ;;
76
+ commit)
77
+ # Reset to the commit
78
+ if [[ -n "$ref" && "$ref" != "unknown" ]]; then
79
+ if git reset --hard "$ref" >/dev/null 2>&1; then # SUPPRESS:OK -- existence-test: reset may fail
80
+ json_ok '{"rolled_back":true,"method":"reset"}'
81
+ else
82
+ json_ok '{"rolled_back":false,"method":"reset","error":"reset failed"}'
83
+ fi
84
+ else
85
+ json_ok '{"rolled_back":false,"method":"reset","error":"invalid ref"}'
86
+ fi
87
+ ;;
88
+ none|*)
89
+ json_ok '{"rolled_back":false,"method":"none"}'
90
+ ;;
91
+ esac
92
+ }
93
+
94
+ # ============================================================================
95
+ # _swarm_findings_init
96
+ # Initialize swarm findings file
97
+ # Usage: _swarm_findings_init <swarm_id>
98
+ # ============================================================================
99
+ _swarm_findings_init() {
100
+ swarm_id="${1:-swarm-$(date +%s)}"
101
+ findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
102
+
103
+ mkdir -p "$COLONY_DATA_DIR"
104
+ # Build initial findings JSON safely via jq (swarm_id may contain JSON-special chars)
105
+ local init_json
106
+ init_json=$(jq -n --arg sid "$swarm_id" --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" '{
107
+ swarm_id: $sid,
108
+ created_at: $ts,
109
+ status: "active",
110
+ findings: [],
111
+ solution: null
112
+ }')
113
+ echo "$init_json" > "$findings_file"
114
+ json_ok "$(jq -n --arg swarm_id "$swarm_id" --arg file "$findings_file" \
115
+ '{swarm_id: $swarm_id, file: $file}')"
116
+ }
117
+
118
+ # ============================================================================
119
+ # _swarm_findings_add
120
+ # Add a finding from a scout
121
+ # Usage: _swarm_findings_add <swarm_id> <scout_type> <confidence> <finding_json>
122
+ # ============================================================================
123
+ _swarm_findings_add() {
124
+ swarm_id="${1:-}"
125
+ scout_type="${2:-}"
126
+ confidence="${3:-0.5}"
127
+ finding="${4:-}"
128
+
129
+ [[ -z "$swarm_id" || -z "$scout_type" || -z "$finding" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-add <swarm_id> <scout_type> <confidence> <finding_json>"
130
+
131
+ findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
132
+ [[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
133
+
134
+ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
135
+
136
+ # Add finding to array
137
+ updated=$(jq --arg scout "$scout_type" --arg conf "$confidence" --arg ts "$ts" --argjson finding "$finding" '
138
+ .findings += [{
139
+ "scout": $scout,
140
+ "confidence": ($conf | tonumber),
141
+ "timestamp": $ts,
142
+ "finding": $finding
143
+ }]
144
+ ' "$findings_file")
145
+
146
+ atomic_write "$findings_file" "$updated" || {
147
+ _aether_log_error "Could not save swarm findings"
148
+ json_err "$E_UNKNOWN" "Failed to write swarm findings file"
149
+ }
150
+ count=$(echo "$updated" | jq '.findings | length')
151
+ json_ok "$(jq -n --arg scout "$scout_type" --argjson total_findings "$count" \
152
+ '{added: true, scout: $scout, total_findings: $total_findings}')"
153
+ }
154
+
155
+ # ============================================================================
156
+ # _swarm_findings_read
157
+ # Read all findings for a swarm
158
+ # Usage: _swarm_findings_read <swarm_id>
159
+ # ============================================================================
160
+ _swarm_findings_read() {
161
+ swarm_id="${1:-}"
162
+ [[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-findings-read <swarm_id>"
163
+
164
+ findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
165
+ [[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
166
+
167
+ json_ok "$(cat "$findings_file")"
168
+ }
169
+
170
+ # ============================================================================
171
+ # _swarm_solution_set
172
+ # Set the chosen solution for a swarm
173
+ # Usage: _swarm_solution_set <swarm_id> <solution_json>
174
+ # ============================================================================
175
+ _swarm_solution_set() {
176
+ swarm_id="${1:-}"
177
+ solution="${2:-}"
178
+
179
+ [[ -z "$swarm_id" || -z "$solution" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-solution-set <swarm_id> <solution_json>"
180
+
181
+ findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
182
+ [[ ! -f "$findings_file" ]] && json_err "$E_FILE_NOT_FOUND" "Swarm findings file not found: $swarm_id"
183
+
184
+ ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
185
+
186
+ updated=$(jq --argjson solution "$solution" --arg ts "$ts" '
187
+ .solution = $solution |
188
+ .status = "resolved" |
189
+ .resolved_at = $ts
190
+ ' "$findings_file")
191
+
192
+ atomic_write "$findings_file" "$updated" || {
193
+ _aether_log_error "Could not save swarm solution"
194
+ json_err "$E_UNKNOWN" "Failed to write swarm findings file"
195
+ }
196
+ json_ok "$(jq -n --arg swarm_id "$swarm_id" '{solution_set: true, swarm_id: $swarm_id}')"
197
+ }
198
+
199
+ # ============================================================================
200
+ # _swarm_cleanup
201
+ # Clean up swarm files after completion
202
+ # Usage: _swarm_cleanup <swarm_id> [--archive]
203
+ # ============================================================================
204
+ _swarm_cleanup() {
205
+ swarm_id="${1:-}"
206
+ archive="${2:-}"
207
+
208
+ [[ -z "$swarm_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-cleanup <swarm_id> [--archive]"
209
+
210
+ findings_file="$COLONY_DATA_DIR/swarm-findings-$swarm_id.json"
211
+
212
+ if [[ -f "$findings_file" ]]; then
213
+ if [[ "$archive" == "--archive" ]]; then
214
+ mkdir -p "$COLONY_DATA_DIR/swarm-archive"
215
+ mv "$findings_file" "$COLONY_DATA_DIR/swarm-archive/"
216
+ json_ok "$(jq -n --arg swarm_id "$swarm_id" '{archived: true, swarm_id: $swarm_id}')"
217
+ else
218
+ rm -f "$findings_file"
219
+ json_ok "$(jq -n --arg swarm_id "$swarm_id" '{deleted: true, swarm_id: $swarm_id}')"
220
+ fi
221
+ else
222
+ json_ok "$(jq -n --arg swarm_id "$swarm_id" '{not_found: true, swarm_id: $swarm_id}')"
223
+ fi
224
+ }
225
+
226
+ # ============================================================================
227
+ # _swarm_activity_log
228
+ # Log an activity entry for swarm visualization
229
+ # Usage: _swarm_activity_log <ant_name> <action> <details>
230
+ # ============================================================================
231
+ _swarm_activity_log() {
232
+ ant_name="${1:-}"
233
+ action="${2:-}"
234
+ details="${3:-}"
235
+ [[ -z "$ant_name" || -z "$action" || -z "$details" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-activity-log <ant_name> <action> <details>"
236
+
237
+ mkdir -p "$COLONY_DATA_DIR"
238
+ log_file="$COLONY_DATA_DIR/swarm-activity.log"
239
+ ts=$(date -u +"%H:%M:%S")
240
+ echo "[$ts] $ant_name: $action $details" >> "$log_file"
241
+ json_ok '"logged"'
242
+ }
243
+
244
+ # ============================================================================
245
+ # _swarm_display_init
246
+ # Initialize swarm display state file
247
+ # Usage: _swarm_display_init <swarm_id>
248
+ # ============================================================================
249
+ _swarm_display_init() {
250
+ swarm_id="${1:-swarm-$(date +%s)}"
251
+ mkdir -p "$COLONY_DATA_DIR"
252
+
253
+ display_file="$COLONY_DATA_DIR/swarm-display.json"
254
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
255
+
256
+ # Build initial display JSON safely via jq (swarm_id may contain JSON-special chars)
257
+ local init_json
258
+ init_json=$(jq -n --arg sid "$swarm_id" --arg ts "$ts" \
259
+ --arg e1 "🍄" --arg e2 "🥚" --arg e3 "🗑️" --arg e4 "👑" --arg e5 "🌿" '{
260
+ swarm_id: $sid,
261
+ timestamp: $ts,
262
+ active_ants: [],
263
+ summary: { total_active: 0, by_caste: {}, by_zone: {} },
264
+ chambers: {
265
+ fungus_garden: {activity: 0, icon: $e1},
266
+ nursery: {activity: 0, icon: $e2},
267
+ refuse_pile: {activity: 0, icon: $e3},
268
+ throne_room: {activity: 0, icon: $e4},
269
+ foraging_trail: {activity: 0, icon: $e5}
270
+ }
271
+ }')
272
+ atomic_write "$display_file" "$init_json"
273
+ json_ok "$(jq -n --arg sid "$swarm_id" '{swarm_id: $sid, initialized: true}')"
274
+ }
275
+
276
+ # ============================================================================
277
+ # _swarm_display_update
278
+ # Update ant activity in swarm display
279
+ # Usage: _swarm_display_update <ant_name> <caste> <ant_status> <task> [parent] [tools_json] [tokens] [chamber] [progress]
280
+ # ============================================================================
281
+ _swarm_display_update() {
282
+ ant_name="${1:-}"
283
+ caste="${2:-}"
284
+ ant_status="${3:-}"
285
+ task="${4:-}"
286
+ parent="${5:-}"
287
+ tools_json="${6:-}"
288
+ [[ -z "$tools_json" ]] && tools_json="{}"
289
+ tokens="${7:-0}"
290
+ chamber="${8:-}"
291
+ progress="${9:-0}"
292
+
293
+ [[ -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]"
294
+
295
+ # Tolerate malformed argument ordering from LLM-generated commands.
296
+ # Common failure mode: tools_json omitted, so tokens/chamber/progress shift left.
297
+ tools_type=$(echo "$tools_json" | jq -r 'type' 2>/dev/null || echo "invalid") # SUPPRESS:OK -- read-default: returns fallback if missing
298
+ if [[ "$tools_type" != "object" ]]; then
299
+ if [[ "$tools_json" =~ ^[0-9]+$ ]] && [[ ! "$tokens" =~ ^[0-9]+$ ]] && [[ "$chamber" =~ ^[0-9]+$ ]]; then
300
+ progress="$chamber"
301
+ chamber="$tokens"
302
+ tokens="$tools_json"
303
+ fi
304
+ tools_json="{}"
305
+ fi
306
+
307
+ # Ensure numeric fields are always valid for --argjson.
308
+ [[ "$tokens" =~ ^-?[0-9]+$ ]] || tokens=0
309
+ [[ "$progress" =~ ^-?[0-9]+$ ]] || progress=0
310
+
311
+ display_file="$COLONY_DATA_DIR/swarm-display.json"
312
+
313
+ # Initialize if doesn't exist
314
+ if [[ ! -f "$display_file" ]]; then
315
+ bash "$0" swarm-display-init "default-swarm" >/dev/null 2>&1 || _aether_log_error "Could not initialize swarm display"
316
+ fi
317
+
318
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
319
+
320
+ # Read current display and update using jq
321
+ updated=$(jq --arg ant "$ant_name" --arg caste "$caste" --arg ant_status "$ant_status" \
322
+ --arg task "$task" --arg parent "$parent" --argjson tools "$tools_json" \
323
+ --argjson tokens "$tokens" --arg ts "$ts" --arg chamber "$chamber" --argjson progress "$progress" '
324
+ # Find existing ant or create new entry
325
+ (.active_ants | map(select(.name == $ant)) | length) as $exists |
326
+ # Get old chamber if ant exists
327
+ (if $exists > 0 then
328
+ (.active_ants[] | select(.name == $ant) | .chamber // "")
329
+ else
330
+ ""
331
+ end) as $old_chamber |
332
+ # Determine new chamber
333
+ (if $chamber != "" then $chamber else $old_chamber end) as $new_chamber |
334
+ if $exists > 0 then
335
+ # Update existing ant
336
+ .active_ants = [.active_ants[] | if .name == $ant then
337
+ . + {
338
+ caste: $caste,
339
+ status: $ant_status,
340
+ task: $task,
341
+ parent: (if $parent != "" then $parent else .parent end),
342
+ tools: (if $tools != {} then $tools else .tools end),
343
+ tokens: (.tokens + $tokens),
344
+ chamber: (if $chamber != "" then $chamber else (.chamber // null) end),
345
+ progress: (if $progress > 0 then $progress else (.progress // 0) end),
346
+ updated_at: $ts
347
+ }
348
+ else . end]
349
+ else
350
+ # Add new ant
351
+ .active_ants += [{
352
+ name: $ant,
353
+ caste: $caste,
354
+ status: $ant_status,
355
+ task: $task,
356
+ parent: (if $parent != "" then $parent else null end),
357
+ tools: (if $tools != {} then $tools else {read:0,grep:0,edit:0,bash:0} end),
358
+ tokens: $tokens,
359
+ chamber: (if $chamber != "" then $chamber else null end),
360
+ progress: $progress,
361
+ started_at: $ts,
362
+ updated_at: $ts
363
+ }]
364
+ end |
365
+ # Recalculate summary
366
+ .summary.total_active = (.active_ants | length) |
367
+ .summary.by_caste = (.active_ants | group_by(.caste) | map({key: .[0].caste, value: length}) | from_entries) |
368
+ .summary.by_zone = (.active_ants | group_by(.status) | map({key: .[0].status, value: length}) | from_entries) |
369
+ # Update chamber activity counts
370
+ # Decrement old chamber if changed
371
+ (if $old_chamber != "" and $old_chamber != $new_chamber and has("chambers") and (.chambers | has($old_chamber)) then
372
+ .chambers[$old_chamber].activity = ([(.chambers[$old_chamber].activity // 1) - 1, 0] | max)
373
+ else
374
+ .
375
+ end) |
376
+ # Increment new chamber
377
+ (if $new_chamber != "" and has("chambers") and (.chambers | has($new_chamber)) then
378
+ .chambers[$new_chamber].activity = (.chambers[$new_chamber].activity // 0) + 1
379
+ else
380
+ .
381
+ end)
382
+ ' "$display_file") || json_err "$E_JSON_INVALID" "Failed to update swarm display"
383
+
384
+ atomic_write "$display_file" "$updated"
385
+
386
+ # Get emoji for response
387
+ emoji=$(get_caste_emoji "$caste")
388
+ json_ok "$(jq -n --arg ant "$ant_name" --arg caste "$caste" --arg emoji "$emoji" \
389
+ --arg chamber "$chamber" --argjson progress "$progress" \
390
+ '{updated: true, ant: $ant, caste: $caste, emoji: $emoji, chamber: $chamber, progress: $progress}')"
391
+ }
392
+
393
+ # ============================================================================
394
+ # _swarm_display_get
395
+ # Get current swarm display state
396
+ # Usage: _swarm_display_get
397
+ # ============================================================================
398
+ _swarm_display_get() {
399
+ display_file="$COLONY_DATA_DIR/swarm-display.json"
400
+
401
+ if [[ ! -f "$display_file" ]]; then
402
+ json_ok '{"swarm_id":null,"active_ants":[],"summary":{"total_active":0,"by_caste":{},"by_zone":{}},"chambers":{}}'
403
+ else
404
+ json_ok "$(cat "$display_file")"
405
+ fi
406
+ }
407
+
408
+ # ============================================================================
409
+ # _swarm_display_render
410
+ # Render the swarm display to terminal
411
+ # Usage: _swarm_display_render [swarm_id]
412
+ # ============================================================================
413
+ _swarm_display_render() {
414
+ _deprecation_warning "swarm-display-render"
415
+ swarm_id="${1:-default-swarm}"
416
+
417
+ display_script="$SCRIPT_DIR/utils/swarm-display.sh"
418
+
419
+ if [[ -f "$display_script" ]]; then
420
+ # Execute the display script
421
+ bash "$display_script" "$swarm_id" 2>/dev/null || _aether_log_error "Could not run swarm display script"
422
+ json_ok '{"rendered":true}'
423
+ else
424
+ json_err "$E_FILE_NOT_FOUND" "Display script not found: $display_script"
425
+ fi
426
+ }
427
+
428
+ # ============================================================================
429
+ # Display helper functions (used by _swarm_display_inline)
430
+ # These are local helpers that were defined inside the swarm-display-inline case block
431
+ # ============================================================================
432
+
433
+ # Caste colors (ANSI)
434
+ _sw_get_caste_color() {
435
+ case "$1" in
436
+ builder) echo "$_SW_BLUE" ;;
437
+ watcher) echo "$_SW_GREEN" ;;
438
+ scout) echo "$_SW_YELLOW" ;;
439
+ chaos) echo "$_SW_RED" ;;
440
+ prime) echo "$_SW_MAGENTA" ;;
441
+ oracle) echo "$_SW_MAGENTA" ;;
442
+ route_setter) echo "$_SW_MAGENTA" ;;
443
+ *) echo "$_SW_RESET" ;;
444
+ esac
445
+ }
446
+
447
+ # Caste emojis with ant (local copy -- may differ from main file's get_caste_emoji)
448
+ _sw_get_caste_emoji() {
449
+ case "$1" in
450
+ builder) echo "🔨🐜" ;;
451
+ watcher) echo "👁️🐜" ;;
452
+ scout) echo "🔍🐜" ;;
453
+ chaos) echo "🎲🐜" ;;
454
+ prime) echo "👑🐜" ;;
455
+ oracle) echo "🔮🐜" ;;
456
+ route_setter) echo "🧭🐜" ;;
457
+ archaeologist) echo "🏺🐜" ;;
458
+ chronicler) echo "📝🐜" ;;
459
+ gatekeeper) echo "📦🐜" ;;
460
+ guardian) echo "🛡️🐜" ;;
461
+ includer) echo "♿🐜" ;;
462
+ keeper) echo "📚🐜" ;;
463
+ measurer) echo "⚡🐜" ;;
464
+ probe) echo "🧪🐜" ;;
465
+ sage) echo "📜🐜" ;;
466
+ tracker) echo "🐛🐜" ;;
467
+ weaver) echo "🔄🐜" ;;
468
+ colonizer) echo "🌱🐜" ;;
469
+ dreamer) echo "💭🐜" ;;
470
+ *) echo "🐜" ;;
471
+ esac
472
+ }
473
+
474
+ # Status phrases
475
+ _sw_get_status_phrase() {
476
+ case "$1" in
477
+ builder) echo "excavating..." ;;
478
+ watcher) echo "observing..." ;;
479
+ scout) echo "exploring..." ;;
480
+ chaos) echo "testing..." ;;
481
+ prime) echo "coordinating..." ;;
482
+ oracle) echo "researching..." ;;
483
+ route_setter) echo "planning..." ;;
484
+ *) echo "working..." ;;
485
+ esac
486
+ }
487
+
488
+ # Excavation phrase based on progress
489
+ _sw_get_excavation_phrase() {
490
+ local progress="${1:-0}"
491
+ if [[ "$progress" -lt 25 ]]; then
492
+ echo "🚧 Starting excavation..."
493
+ elif [[ "$progress" -lt 50 ]]; then
494
+ echo "⛏️ Digging deeper..."
495
+ elif [[ "$progress" -lt 75 ]]; then
496
+ echo "🪨 Moving earth..."
497
+ elif [[ "$progress" -lt 100 ]]; then
498
+ echo "🏗️ Almost there..."
499
+ else
500
+ echo "✅ Excavation complete!"
501
+ fi
502
+ }
503
+
504
+ # Format tools: "📖5 🔍3 ✏️2 ⚡1"
505
+ _sw_format_tools() {
506
+ local read="${1:-0}"
507
+ local grep="${2:-0}"
508
+ local edit="${3:-0}"
509
+ local bash="${4:-0}"
510
+ local result=""
511
+ [[ "$read" -gt 0 ]] && result="${result}📖${read} "
512
+ [[ "$grep" -gt 0 ]] && result="${result}🔍${grep} "
513
+ [[ "$edit" -gt 0 ]] && result="${result}✏️${edit} "
514
+ [[ "$bash" -gt 0 ]] && result="${result}⚡${bash}"
515
+ echo "$result"
516
+ }
517
+
518
+ # Render progress bar (green when working)
519
+ _sw_render_progress_bar() {
520
+ local percent="${1:-0}"
521
+ local width="${2:-20}"
522
+ [[ "$percent" -lt 0 ]] && percent=0
523
+ [[ "$percent" -gt 100 ]] && percent=100
524
+ local filled=$((percent * width / 100))
525
+ local empty=$((width - filled))
526
+ local bar=""
527
+ for ((i=0; i<filled; i++)); do bar+="█"; done
528
+ for ((i=0; i<empty; i++)); do bar+="░"; done
529
+ echo -e "${_SW_GREEN}[$bar]${_SW_RESET} ${percent}%"
530
+ }
531
+
532
+ # Format duration
533
+ _sw_format_duration() {
534
+ local seconds="${1:-0}"
535
+ if [[ "$seconds" -lt 60 ]]; then
536
+ echo "${seconds}s"
537
+ else
538
+ local mins=$((seconds / 60))
539
+ local secs=$((seconds % 60))
540
+ echo "${mins}m${secs}s"
541
+ fi
542
+ }
543
+
544
+ # ============================================================================
545
+ # _swarm_display_inline
546
+ # Inline swarm display for Claude Code (no loop, no clear)
547
+ # Usage: _swarm_display_inline [swarm_id]
548
+ # ============================================================================
549
+ _swarm_display_inline() {
550
+ _deprecation_warning "swarm-display-inline"
551
+ swarm_id="${1:-default-swarm}"
552
+ display_file="$COLONY_DATA_DIR/swarm-display.json"
553
+
554
+ # ANSI colors
555
+ _SW_BLUE='\033[34m'
556
+ _SW_GREEN='\033[32m'
557
+ _SW_YELLOW='\033[33m'
558
+ _SW_RED='\033[31m'
559
+ _SW_MAGENTA='\033[35m'
560
+ _SW_BOLD='\033[1m'
561
+ _SW_DIM='\033[2m'
562
+ _SW_RESET='\033[0m'
563
+
564
+ # Check for display file
565
+ if [[ ! -f "$display_file" ]]; then
566
+ echo -e "${_SW_DIM}🐜 No active swarm data${_SW_RESET}"
567
+ json_ok '{"displayed":false,"reason":"no_data"}'
568
+ exit 0
569
+ fi
570
+
571
+ # Check for jq
572
+ if ! command -v jq >/dev/null 2>&1; then
573
+ echo -e "${_SW_DIM}🐜 Swarm active (jq not available for details)${_SW_RESET}"
574
+ json_ok '{"displayed":true,"warning":"jq_missing"}'
575
+ exit 0
576
+ fi
577
+
578
+ # Read swarm data
579
+ total_active=$(jq -r '.summary.total_active // 0' "$display_file" 2>/dev/null || echo "0") # SUPPRESS:OK -- read-default: file may not exist yet
580
+
581
+ if [[ "$total_active" -eq 0 ]]; then
582
+ echo -e "${_SW_DIM}🐜 Colony idle${_SW_RESET}"
583
+ json_ok '{"displayed":true,"ants":0}'
584
+ exit 0
585
+ fi
586
+
587
+ # Render header with ant logo
588
+ echo ""
589
+ cat << 'ANTLOGO'
590
+
591
+
592
+ ▁▐▖ ▁
593
+ ▗▇▇███▆▇▃▅████▆▇▆▅▟██▛▇
594
+ ▝▜▅▛██████████████▜▅██
595
+ ▁▂▀▇▆██▙▜██████████▛▟███▛▁▃▁
596
+ ▕▂▁▉▅████▙▞██████▜█▚▟████▅▊ ▐
597
+ ▗▁▐█▀▜████▛▃▝▁████▍▘▟▜████▛▀█▂ ▖
598
+ ▁▎▝█▁▝▍▆▜████▊▐▀▏▀▍▂▂▝▀▕▀▌█████▀▅▐▚ █▏▁▁
599
+ ▂▚▃▇▙█▟████▛▏ ▝▜▐▛▀▍▛▘ ▕█████▆▊▐▂▃▞▂▔
600
+ ▚▔█▛██████▙▟▍▜▍▜▃▃▖▟▛▐██████▛▛▜▔▔▞
601
+ ▋▖▍▊▖██████▇▃▁▝██▘▝▃████▜█▜ ▋▐▐▗
602
+ ▍▌▇█▅▂▜██████████████████▉▃▄▋▖ ▝
603
+ ▁▎▍▁▜▟███▀▀▜████████████▛▀▀███▆▂ ▁▁
604
+ ██ ▆▇▌▁▕▚▅▆███▛████████▜███▆▄▞▁▁▐▅▎ █▉
605
+ ▆█████▛▃▟█▀████████████████▛█▙▙▜▉▟▛▜█▌▗
606
+ ▅▆▋ ▁▁▁▔▕▁▁▁▇█████▛▀▀▀▁▜▇▇▁▁▁▁▁▁▁▁ ▐▊▗
607
+ ▗▆▃▃▃▔███▖▔██▀▀▝▀██▀▍█▛▁▐█▏█▛▀▀▏█▛▀▜█▆▃▃▆▖
608
+ ▝▗▖ ▟█▟█▙ █▛▀▘ █▊ ▕█▛▀▜█▏█▛▀▘ █▋▆█▛ ▗▖
609
+ ▘ ▘ ▟▛ ▝▀▘▀▀▀▀▘ ▀▀▂▂█▙▂▐▀▏▀▀▀▀▘▀▘ ▝▀▅▂▝ ▕▏
610
+ ▕▕ ▃▗▄▔▗▄▄▗▗▗▔▄▄▄▄▗▄▄▗▔▃▃▃▗▄▂▄▃▗▄▂▖▖ ▏▁
611
+ ▝▘▏ ▔▔ ▁▔▁▔▔▁▔▔▔▔▔▔▔▁▁ ▔▔ ▔▔▔▔
612
+ ▀ ▀▝▘▀▀▔▘▘▀▝▕▀▀▝▝▀▔▀ ▀▔▘
613
+ ▘ ▗▅▁▝▚▃▀▆▟██▙▆▝▃ ▘ ▁▗▌
614
+ ▔▀▔▝ ▔▀▟▜▛▛▀▔ ▀
615
+
616
+
617
+ ANTLOGO
618
+ echo -e "${_SW_BOLD}AETHER COLONY :: Colony Activity${_SW_RESET}"
619
+ echo -e "${_SW_DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${_SW_RESET}"
620
+ echo ""
621
+
622
+ # Render each active ant (limit to 5)
623
+ # SUPPRESS:OK -- read-default: display file may not exist yet
624
+ 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
625
+ color=$(_sw_get_caste_color "$ant_caste")
626
+ emoji=$(_sw_get_caste_emoji "$ant_caste")
627
+ phrase=$(_sw_get_status_phrase "$ant_caste")
628
+
629
+ # Format tools
630
+ tools_str=$(_sw_format_tools "$read_ct" "$grep_ct" "$edit_ct" "$bash_ct")
631
+
632
+ # Truncate task if too long
633
+ display_task="$ant_task"
634
+ [[ ${#display_task} -gt 35 ]] && display_task="${display_task:0:32}..."
635
+
636
+ # Calculate elapsed time
637
+ elapsed_str=""
638
+ started_ts="${started_at:-}"
639
+ if [[ -n "$started_ts" ]] && [[ "$started_ts" != "null" ]]; then
640
+ started_ts=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$started_ts" +%s 2>/dev/null) # SUPPRESS:OK -- cross-platform: macOS date syntax
641
+ if [[ -z "$started_ts" ]] || [[ "$started_ts" == "null" ]]; then
642
+ started_ts=$(date -d "$started_ts" +%s 2>/dev/null) || started_ts=0 # SUPPRESS:OK -- cross-platform: Linux date syntax
643
+ fi
644
+ now_ts=$(date +%s)
645
+ elapsed=0
646
+ if [[ -n "$started_ts" ]] && [[ "$started_ts" -gt 0 ]] 2>/dev/null; then # SUPPRESS:OK -- existence-test: value may not be numeric
647
+ elapsed=$((now_ts - started_ts))
648
+ fi
649
+ if [[ ${elapsed:-0} -gt 0 ]]; then
650
+ elapsed_str="($(_sw_format_duration $elapsed))"
651
+ fi
652
+ fi
653
+
654
+ # Token indicator
655
+ token_str=""
656
+ if [[ -n "$tokens" ]] && [[ "$tokens" -gt 0 ]]; then
657
+ token_str="🍯${tokens}"
658
+ fi
659
+
660
+ # Output ant line: "🐜 Builder: excavating... Implement auth 📖5 🔍3 (2m3s) 🍯1250"
661
+ echo -e "${color}${emoji} ${_SW_BOLD}${ant_name}${_SW_RESET}${color}: ${phrase}${_SW_RESET} ${display_task}"
662
+ echo -e " ${tools_str} ${_SW_DIM}${elapsed_str}${_SW_RESET} ${token_str}"
663
+
664
+ # Show progress bar if progress > 0
665
+ if [[ -n "$progress" ]] && [[ "$progress" -gt 0 ]]; then
666
+ progress_bar=$(_sw_render_progress_bar "$progress" 15)
667
+ excavation_phrase=$(_sw_get_excavation_phrase "$progress")
668
+ echo -e " ${_SW_DIM}${progress_bar}${_SW_RESET}"
669
+ echo -e " ${_SW_DIM}${excavation_phrase}${_SW_RESET}"
670
+ fi
671
+
672
+ echo ""
673
+ done
674
+
675
+ # Chamber activity map
676
+ echo -e "${_SW_DIM}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${_SW_RESET}"
677
+ echo ""
678
+ echo -e "${_SW_BOLD}Chamber Activity:${_SW_RESET}"
679
+
680
+ # Show active chambers with fire intensity
681
+ has_chamber_activity=0
682
+ # SUPPRESS:OK -- read-default: returns fallback on failure
683
+ jq -r '.chambers | to_entries[] | "\(.key)|\(.value.activity)|\(.value.icon)"' "$display_file" 2>/dev/null | \
684
+ while IFS='|' read -r chamber activity icon; do
685
+ if [[ -n "$activity" ]] && [[ "$activity" -gt 0 ]]; then
686
+ has_chamber_activity=1
687
+ if [[ "$activity" -ge 5 ]]; then
688
+ fires="🔥🔥🔥"
689
+ elif [[ "$activity" -ge 3 ]]; then
690
+ fires="🔥🔥"
691
+ else
692
+ fires="🔥"
693
+ fi
694
+ chamber_name="${chamber//_/ }"
695
+ echo -e " ${icon} ${chamber_name} ${fires} (${activity} ants)"
696
+ fi
697
+ done
698
+
699
+ if [[ "$has_chamber_activity" -eq 0 ]]; then
700
+ echo -e "${_SW_DIM} (no chamber activity)${_SW_RESET}"
701
+ fi
702
+
703
+ # Summary
704
+ echo ""
705
+ echo -e "${_SW_DIM}${total_active} forager$([[ "$total_active" -eq 1 ]] || echo "s") excavating...${_SW_RESET}"
706
+
707
+ json_ok "{\"displayed\":true,\"ants\":$total_active}"
708
+ }
709
+
710
+ # ============================================================================
711
+ # Display helper functions (used by _swarm_display_text)
712
+ # These are local helpers that were defined inside the swarm-display-text case block
713
+ # ============================================================================
714
+
715
+ # Caste emoji lookup (text-only version)
716
+ _sw_get_emoji() {
717
+ case "$1" in
718
+ builder) echo "🔨🐜" ;;
719
+ watcher) echo "👁️🐜" ;;
720
+ scout) echo "🔍🐜" ;;
721
+ chaos) echo "🎲🐜" ;;
722
+ prime) echo "👑🐜" ;;
723
+ oracle) echo "🔮🐜" ;;
724
+ route_setter) echo "🧭🐜" ;;
725
+ archaeologist) echo "🏺🐜" ;;
726
+ surveyor) echo "📊🐜" ;;
727
+ *) echo "🐜" ;;
728
+ esac
729
+ }
730
+
731
+ # Format tool counts (only non-zero)
732
+ _sw_format_tools_text() {
733
+ local r="${1:-0}" g="${2:-0}" e="${3:-0}" b="${4:-0}"
734
+ local result=""
735
+ [[ "$r" -gt 0 ]] && result="${result}📖${r} "
736
+ [[ "$g" -gt 0 ]] && result="${result}🔍${g} "
737
+ [[ "$e" -gt 0 ]] && result="${result}✏️${e} "
738
+ [[ "$b" -gt 0 ]] && result="${result}⚡${b}"
739
+ echo "$result"
740
+ }
741
+
742
+ # Progress bar using block characters (no ANSI)
743
+ _sw_render_bar_text() {
744
+ local pct="${1:-0}" w="${2:-10}"
745
+ [[ "$pct" -lt 0 ]] && pct=0
746
+ [[ "$pct" -gt 100 ]] && pct=100
747
+ local filled=$((pct * w / 100))
748
+ local empty=$((w - filled))
749
+ local bar=""
750
+ for ((i=0; i<filled; i++)); do bar+="█"; done
751
+ for ((i=0; i<empty; i++)); do bar+="░"; done
752
+ echo "[$bar] ${pct}%"
753
+ }
754
+
755
+ # Helper: parse ISO-8601 timestamp to epoch (macOS + Linux)
756
+ _sw_iso_to_epoch_text() {
757
+ local iso="$1"
758
+ local epoch=""
759
+ epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$iso" +%s 2>/dev/null || true) # SUPPRESS:OK -- cross-platform: macOS date syntax
760
+ if [[ -z "$epoch" ]]; then
761
+ epoch=$(date -d "$iso" +%s 2>/dev/null || true) # SUPPRESS:OK -- cross-platform: Linux date syntax
762
+ fi
763
+ echo "${epoch:-0}"
764
+ }
765
+
766
+ # Helper: duration formatter (e.g., 45s, 3m12s)
767
+ _sw_format_duration_text() {
768
+ local seconds="${1:-0}"
769
+ if [[ "$seconds" -lt 60 ]]; then
770
+ echo "${seconds}s"
771
+ else
772
+ local mins=$((seconds / 60))
773
+ local secs=$((seconds % 60))
774
+ echo "${mins}m${secs}s"
775
+ fi
776
+ }
777
+
778
+ # Helper: compact number formatter (e.g., 1.2k, 2.4M)
779
+ _sw_format_compact_tokens() {
780
+ local n="${1:-0}"
781
+ if [[ "$n" -ge 1000000 ]]; then
782
+ awk -v n="$n" 'BEGIN { printf "%.1fM", n/1000000 }'
783
+ elif [[ "$n" -ge 1000 ]]; then
784
+ awk -v n="$n" 'BEGIN { printf "%.1fk", n/1000 }'
785
+ else
786
+ echo "$n"
787
+ fi
788
+ }
789
+
790
+ # ============================================================================
791
+ # _swarm_display_text
792
+ # Plain-text swarm display for Claude conversation (no ANSI codes)
793
+ # Usage: _swarm_display_text [swarm_id]
794
+ # ============================================================================
795
+ _swarm_display_text() {
796
+ swarm_id="${1:-default-swarm}"
797
+ display_file="$COLONY_DATA_DIR/swarm-display.json"
798
+
799
+ # Check for display file
800
+ if [[ ! -f "$display_file" ]]; then
801
+ echo "🐜 Colony idle"
802
+ json_ok '{"displayed":false,"reason":"no_data"}'
803
+ exit 0
804
+ fi
805
+
806
+ # Check for jq
807
+ if ! command -v jq >/dev/null 2>&1; then
808
+ echo "🐜 Swarm active (details unavailable)"
809
+ json_ok '{"displayed":true,"warning":"jq_missing"}'
810
+ exit 0
811
+ fi
812
+
813
+ # Read swarm data — handle both flat total_active and nested .summary.total_active
814
+ # SUPPRESS:OK -- read-default: query may return empty
815
+ total_active=$(jq -r '(.total_active // .summary.total_active // 0)' "$display_file" 2>/dev/null || echo "0")
816
+
817
+ if [[ "$total_active" -eq 0 ]]; then
818
+ echo "🐜 Colony idle"
819
+ json_ok '{"displayed":true,"ants":0}'
820
+ exit 0
821
+ fi
822
+
823
+ # Compact header
824
+ echo "🐜 COLONY ACTIVITY"
825
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
826
+
827
+ # SUPPRESS:OK -- read-default: query may return empty
828
+ total_tokens=$(jq -r '[.active_ants[]?.tokens // 0] | add // 0' "$display_file" 2>/dev/null || echo "0")
829
+ started_iso=$(jq -r '.timestamp // ""' "$display_file" 2>/dev/null || echo "") # SUPPRESS:OK -- read-default: file may not exist yet
830
+ elapsed_text="n/a"
831
+ if [[ -n "$started_iso" && "$started_iso" != "null" ]]; then
832
+ started_epoch=$(_sw_iso_to_epoch_text "$started_iso")
833
+ now_epoch=$(date +%s)
834
+ if [[ "$started_epoch" -gt 0 ]] 2>/dev/null; then # SUPPRESS:OK -- existence-test: value may not be numeric
835
+ total_elapsed=$((now_epoch - started_epoch))
836
+ [[ "$total_elapsed" -lt 0 ]] && total_elapsed=0
837
+ elapsed_text=$(_sw_format_duration_text "$total_elapsed")
838
+ fi
839
+ fi
840
+
841
+ # Render each ant (max 5)
842
+ # SUPPRESS:OK -- read-default: query may return empty
843
+ jq -r '.active_ants[0:5][] | "\(.name)|\(.caste)|\(.task // "")|\(.tools.read // 0)|\(.tools.grep // 0)|\(.tools.edit // 0)|\(.tools.bash // 0)|\(.progress // 0)|\(.tokens // 0)|\(.started_at // "")"' "$display_file" 2>/dev/null | while IFS='|' read -r name caste task r g e b progress tokens started_at; do
844
+ emoji=$(_sw_get_emoji "$caste")
845
+ tools=$(_sw_format_tools_text "$r" "$g" "$e" "$b")
846
+ bar=$(_sw_render_bar_text "${progress:-0}" 10)
847
+ token_str=""
848
+ elapsed_ant=""
849
+
850
+ # Truncate task to 25 chars
851
+ [[ ${#task} -gt 25 ]] && task="${task:0:22}..."
852
+
853
+ if [[ -n "$tokens" && "$tokens" -gt 0 ]] 2>/dev/null; then # SUPPRESS:OK -- existence-test: value may not be numeric
854
+ token_str="🍯$(_sw_format_compact_tokens "$tokens")"
855
+ fi
856
+
857
+ if [[ -n "$started_at" && "$started_at" != "null" ]]; then
858
+ ant_start_epoch=$(_sw_iso_to_epoch_text "$started_at")
859
+ now_epoch=$(date +%s)
860
+ if [[ "$ant_start_epoch" -gt 0 ]] 2>/dev/null; then # SUPPRESS:OK -- existence-test: value may not be numeric
861
+ ant_elapsed=$((now_epoch - ant_start_epoch))
862
+ [[ "$ant_elapsed" -lt 0 ]] && ant_elapsed=0
863
+ elapsed_ant="($(_sw_format_duration_text "$ant_elapsed"))"
864
+ fi
865
+ fi
866
+
867
+ echo "${emoji} ${name} ${bar} ${task}"
868
+ meta_line=""
869
+ [[ -n "$tools" ]] && meta_line="${meta_line}${tools} "
870
+ [[ -n "$elapsed_ant" ]] && meta_line="${meta_line}${elapsed_ant} "
871
+ [[ -n "$token_str" ]] && meta_line="${meta_line}${token_str}"
872
+ [[ -n "$meta_line" ]] && echo " ${meta_line}"
873
+ echo ""
874
+ done
875
+
876
+ # Overflow indicator
877
+ if [[ "$total_active" -gt 5 ]]; then
878
+ echo " +$((total_active - 5)) more ants..."
879
+ echo ""
880
+ fi
881
+
882
+ # Footer
883
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
884
+ echo "⏱️ Elapsed: ${elapsed_text} | 🍯 Total: $(_sw_format_compact_tokens "$total_tokens") | ${total_active} ants active"
885
+
886
+ json_ok "{\"displayed\":true,\"ants\":$total_active}"
887
+ }
888
+
889
+ # ============================================================================
890
+ # _swarm_timing_start
891
+ # Record start time for an ant
892
+ # Usage: _swarm_timing_start <ant_name>
893
+ # ============================================================================
894
+ _swarm_timing_start() {
895
+ ant_name="${1:-}"
896
+ [[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-start <ant_name>"
897
+
898
+ mkdir -p "$COLONY_DATA_DIR"
899
+ timing_file="$COLONY_DATA_DIR/timing.log"
900
+ ts=$(date +%s)
901
+ ts_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
902
+
903
+ # Remove any existing entry for this ant and append new one
904
+ if [[ -f "$timing_file" ]]; then
905
+ # -F: ant_name may contain regex metacharacters; ^ anchor dropped (ant names are unique per swarm, no substring collision)
906
+ grep -vF "$ant_name|" "$timing_file" > "${timing_file}.tmp" 2>/dev/null || true # SUPPRESS:OK -- read-default: file may not exist
907
+ mv "${timing_file}.tmp" "$timing_file"
908
+ fi
909
+ echo "$ant_name|$ts|$ts_iso" >> "$timing_file"
910
+
911
+ json_ok "$(jq -n --arg ant "$ant_name" --arg started_at "$ts_iso" --argjson timestamp "$ts" \
912
+ '{ant: $ant, started_at: $started_at, timestamp: $timestamp}')"
913
+ }
914
+
915
+ # ============================================================================
916
+ # _swarm_timing_get
917
+ # Get elapsed time for an ant
918
+ # Usage: _swarm_timing_get <ant_name>
919
+ # ============================================================================
920
+ _swarm_timing_get() {
921
+ ant_name="${1:-}"
922
+ [[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-get <ant_name>"
923
+
924
+ timing_file="$COLONY_DATA_DIR/timing.log"
925
+
926
+ # -F: ant_name may contain regex metacharacters; ^ anchor dropped (ant names are unique per swarm)
927
+ if [[ ! -f "$timing_file" ]] || ! grep -qF "$ant_name|" "$timing_file" 2>/dev/null; then # SUPPRESS:OK -- existence-test: file may not exist
928
+ json_ok "$(jq -n --arg ant "$ant_name" '{ant: $ant, started_at: null, elapsed_seconds: 0, elapsed_formatted: "00:00"}')"
929
+ exit 0
930
+ fi
931
+
932
+ # Read start time
933
+ start_line=$(grep -F "$ant_name|" "$timing_file" | tail -1)
934
+ start_ts=$(echo "$start_line" | cut -d'|' -f2)
935
+ start_iso=$(echo "$start_line" | cut -d'|' -f3)
936
+
937
+ now=$(date +%s)
938
+ elapsed=$((now - start_ts))
939
+
940
+ # Format as MM:SS
941
+ mins=$((elapsed / 60))
942
+ secs=$((elapsed % 60))
943
+ formatted=$(printf "%02d:%02d" $mins $secs)
944
+
945
+ json_ok "$(jq -n --arg ant "$ant_name" --arg started_at "$start_iso" \
946
+ --argjson elapsed "$elapsed" --arg formatted "$formatted" \
947
+ '{ant: $ant, started_at: $started_at, elapsed_seconds: $elapsed, elapsed_formatted: $formatted}')"
948
+ }
949
+
950
+ # ============================================================================
951
+ # _swarm_timing_eta
952
+ # Calculate ETA based on progress percentage
953
+ # Usage: _swarm_timing_eta <ant_name> <percent_complete>
954
+ # ============================================================================
955
+ _swarm_timing_eta() {
956
+ ant_name="${1:-}"
957
+ percent="${2:-0}"
958
+ [[ -z "$ant_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: swarm-timing-eta <ant_name> <percent_complete>"
959
+
960
+ # Validate percent is a number
961
+ if ! [[ "$percent" =~ ^[0-9]+$ ]]; then
962
+ percent=0
963
+ fi
964
+
965
+ # Clamp percent to 0-100
966
+ if [[ $percent -lt 0 ]]; then
967
+ percent=0
968
+ elif [[ $percent -gt 100 ]]; then
969
+ percent=100
970
+ fi
971
+
972
+ timing_file="$COLONY_DATA_DIR/timing.log"
973
+
974
+ # -F: ant_name may contain regex metacharacters; ^ anchor dropped (ant names are unique per swarm)
975
+ if [[ ! -f "$timing_file" ]] || ! grep -qF "$ant_name|" "$timing_file" 2>/dev/null; then # SUPPRESS:OK -- existence-test: file may not exist
976
+ json_ok "$(jq -n --arg ant "$ant_name" --argjson percent "$percent" \
977
+ '{ant: $ant, percent: $percent, eta_seconds: null, eta_formatted: "--:--"}')"
978
+ exit 0
979
+ fi
980
+
981
+ # Read start time
982
+ start_ts=$(grep -F "$ant_name|" "$timing_file" | tail -1 | cut -d'|' -f2)
983
+ now=$(date +%s)
984
+ elapsed=$((now - start_ts))
985
+
986
+ # Calculate ETA
987
+ if [[ $percent -le 0 ]]; then
988
+ eta_seconds=null
989
+ eta_formatted="--:--"
990
+ elif [[ $percent -ge 100 ]]; then
991
+ eta_seconds=0
992
+ eta_formatted="00:00"
993
+ else
994
+ # ETA = (elapsed / percent) * (100 - percent)
995
+ eta_seconds=$(( (elapsed * (100 - percent)) / percent ))
996
+ mins=$((eta_seconds / 60))
997
+ secs=$((eta_seconds % 60))
998
+ eta_formatted=$(printf "%02d:%02d" $mins $secs)
999
+ fi
1000
+
1001
+ json_ok "$(jq -n --arg ant "$ant_name" --argjson percent "$percent" \
1002
+ --argjson eta "$eta_seconds" --arg eta_fmt "$eta_formatted" \
1003
+ '{ant: $ant, percent: $percent, eta_seconds: $eta, eta_formatted: $eta_fmt}')"
1004
+ }