aether-colony 5.0.0 → 5.2.1

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 (317) hide show
  1. package/.aether/aether-utils.sh +3226 -3345
  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 +442 -0
  30. package/.aether/commands/continue.yaml +1484 -0
  31. package/.aether/commands/council.yaml +509 -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 +502 -0
  44. package/.aether/commands/insert-phase.yaml +102 -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 +1364 -0
  57. package/.aether/commands/preferences.yaml +63 -0
  58. package/.aether/commands/quick.yaml +104 -0
  59. package/.aether/commands/redirect.yaml +123 -0
  60. package/.aether/commands/resume-colony.yaml +375 -0
  61. package/.aether/commands/resume.yaml +407 -0
  62. package/.aether/commands/run.yaml +229 -0
  63. package/.aether/commands/seal.yaml +1214 -0
  64. package/.aether/commands/skill-create.yaml +337 -0
  65. package/.aether/commands/status.yaml +408 -0
  66. package/.aether/commands/swarm.yaml +352 -0
  67. package/.aether/commands/tunnels.yaml +814 -0
  68. package/.aether/commands/update.yaml +131 -0
  69. package/.aether/commands/verify-castes.yaml +159 -0
  70. package/.aether/commands/watch.yaml +454 -0
  71. package/.aether/docs/INCIDENT_TEMPLATE.md +32 -0
  72. package/.aether/docs/QUEEN-SYSTEM.md +11 -11
  73. package/.aether/docs/README.md +32 -2
  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 +1683 -0
  78. package/.aether/docs/command-playbooks/build-prep.md +284 -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 +1725 -0
  84. package/.aether/docs/command-playbooks/continue-gates.md +686 -0
  85. package/.aether/docs/command-playbooks/continue-verify.md +407 -0
  86. package/.aether/docs/context-continuity.md +84 -0
  87. package/.aether/docs/disciplines/DISCIPLINES.md +9 -7
  88. package/.aether/docs/error-codes.md +1 -1
  89. package/.aether/docs/known-issues.md +34 -173
  90. package/.aether/docs/pheromones.md +86 -6
  91. package/.aether/docs/plans/pheromone-display-plan.md +257 -0
  92. package/.aether/docs/queen-commands.md +10 -9
  93. package/.aether/docs/source-of-truth-map.md +132 -0
  94. package/.aether/docs/xml-utilities.md +47 -0
  95. package/.aether/rules/aether-colony.md +23 -13
  96. package/.aether/scripts/incident-test-add.sh +47 -0
  97. package/.aether/scripts/weekly-audit.sh +79 -0
  98. package/.aether/skills/.index.json +649 -0
  99. package/.aether/skills/colony/.manifest.json +16 -0
  100. package/.aether/skills/colony/build-discipline/SKILL.md +78 -0
  101. package/.aether/skills/colony/colony-interaction/SKILL.md +56 -0
  102. package/.aether/skills/colony/colony-lifecycle/SKILL.md +77 -0
  103. package/.aether/skills/colony/colony-visuals/SKILL.md +112 -0
  104. package/.aether/skills/colony/context-management/SKILL.md +80 -0
  105. package/.aether/skills/colony/error-presentation/SKILL.md +99 -0
  106. package/.aether/skills/colony/pheromone-protocol/SKILL.md +79 -0
  107. package/.aether/skills/colony/pheromone-visibility/SKILL.md +81 -0
  108. package/.aether/skills/colony/state-safety/SKILL.md +84 -0
  109. package/.aether/skills/colony/worker-priming/SKILL.md +82 -0
  110. package/.aether/skills/domain/.manifest.json +24 -0
  111. package/.aether/skills/domain/README.md +33 -0
  112. package/.aether/skills/domain/django/SKILL.md +49 -0
  113. package/.aether/skills/domain/docker/SKILL.md +52 -0
  114. package/.aether/skills/domain/golang/SKILL.md +52 -0
  115. package/.aether/skills/domain/graphql/SKILL.md +51 -0
  116. package/.aether/skills/domain/html-css/SKILL.md +48 -0
  117. package/.aether/skills/domain/nextjs/SKILL.md +45 -0
  118. package/.aether/skills/domain/nodejs/SKILL.md +53 -0
  119. package/.aether/skills/domain/postgresql/SKILL.md +53 -0
  120. package/.aether/skills/domain/prisma/SKILL.md +59 -0
  121. package/.aether/skills/domain/python/SKILL.md +50 -0
  122. package/.aether/skills/domain/rails/SKILL.md +52 -0
  123. package/.aether/skills/domain/react/SKILL.md +45 -0
  124. package/.aether/skills/domain/rest-api/SKILL.md +58 -0
  125. package/.aether/skills/domain/svelte/SKILL.md +47 -0
  126. package/.aether/skills/domain/tailwind/SKILL.md +45 -0
  127. package/.aether/skills/domain/testing/SKILL.md +53 -0
  128. package/.aether/skills/domain/typescript/SKILL.md +58 -0
  129. package/.aether/skills/domain/vue/SKILL.md +49 -0
  130. package/.aether/templates/QUEEN.md.template +23 -41
  131. package/.aether/templates/colony-state-reset.jq.template +1 -0
  132. package/.aether/templates/colony-state.template.json +4 -0
  133. package/.aether/templates/learning-observations.template.json +6 -0
  134. package/.aether/templates/midden.template.json +13 -0
  135. package/.aether/templates/pheromones.template.json +6 -0
  136. package/.aether/templates/session.template.json +9 -0
  137. package/.aether/utils/atomic-write.sh +63 -17
  138. package/.aether/utils/chamber-utils.sh +145 -2
  139. package/.aether/utils/council.sh +425 -0
  140. package/.aether/utils/emoji-audit.sh +166 -0
  141. package/.aether/utils/error-handler.sh +21 -7
  142. package/.aether/utils/file-lock.sh +182 -27
  143. package/.aether/utils/flag.sh +278 -0
  144. package/.aether/utils/hive.sh +572 -0
  145. package/.aether/utils/immune.sh +508 -0
  146. package/.aether/utils/learning.sh +1928 -0
  147. package/.aether/utils/midden.sh +520 -0
  148. package/.aether/utils/oracle/oracle.md +168 -0
  149. package/.aether/utils/oracle/oracle.sh +1023 -0
  150. package/.aether/utils/pheromone.sh +2029 -0
  151. package/.aether/utils/queen.sh +1710 -0
  152. package/.aether/utils/scan.sh +860 -0
  153. package/.aether/utils/semantic-cli.sh +10 -8
  154. package/.aether/utils/session.sh +816 -0
  155. package/.aether/utils/skills.sh +509 -0
  156. package/.aether/utils/spawn-tree.sh +103 -271
  157. package/.aether/utils/spawn.sh +260 -0
  158. package/.aether/utils/state-api.sh +389 -0
  159. package/.aether/utils/state-loader.sh +8 -6
  160. package/.aether/utils/suggest.sh +611 -0
  161. package/.aether/utils/swarm-display.sh +10 -1
  162. package/.aether/utils/swarm.sh +1004 -0
  163. package/.aether/utils/watch-spawn-tree.sh +11 -2
  164. package/.aether/utils/xml-compose.sh +2 -2
  165. package/.aether/utils/xml-convert.sh +9 -5
  166. package/.aether/utils/xml-core.sh +5 -9
  167. package/.aether/utils/xml-query.sh +4 -4
  168. package/.aether/workers.md +86 -67
  169. package/.claude/agents/ant/aether-ambassador.md +2 -1
  170. package/.claude/agents/ant/aether-archaeologist.md +6 -1
  171. package/.claude/agents/ant/aether-architect.md +236 -0
  172. package/.claude/agents/ant/aether-auditor.md +6 -1
  173. package/.claude/agents/ant/aether-builder.md +38 -1
  174. package/.claude/agents/ant/aether-chaos.md +2 -1
  175. package/.claude/agents/ant/aether-chronicler.md +1 -0
  176. package/.claude/agents/ant/aether-gatekeeper.md +6 -1
  177. package/.claude/agents/ant/aether-includer.md +1 -0
  178. package/.claude/agents/ant/aether-keeper.md +1 -0
  179. package/.claude/agents/ant/aether-measurer.md +6 -1
  180. package/.claude/agents/ant/aether-oracle.md +237 -0
  181. package/.claude/agents/ant/aether-probe.md +2 -1
  182. package/.claude/agents/ant/aether-queen.md +6 -1
  183. package/.claude/agents/ant/aether-route-setter.md +6 -1
  184. package/.claude/agents/ant/aether-sage.md +68 -3
  185. package/.claude/agents/ant/aether-scout.md +38 -1
  186. package/.claude/agents/ant/aether-surveyor-disciplines.md +2 -1
  187. package/.claude/agents/ant/aether-surveyor-nest.md +2 -1
  188. package/.claude/agents/ant/aether-surveyor-pathogens.md +2 -1
  189. package/.claude/agents/ant/aether-surveyor-provisions.md +2 -1
  190. package/.claude/agents/ant/aether-tracker.md +6 -1
  191. package/.claude/agents/ant/aether-watcher.md +37 -1
  192. package/.claude/agents/ant/aether-weaver.md +2 -1
  193. package/.claude/commands/ant/archaeology.md +1 -8
  194. package/.claude/commands/ant/build.md +43 -1159
  195. package/.claude/commands/ant/chaos.md +1 -14
  196. package/.claude/commands/ant/colonize.md +3 -14
  197. package/.claude/commands/ant/continue.md +40 -1026
  198. package/.claude/commands/ant/council.md +213 -15
  199. package/.claude/commands/ant/data-clean.md +81 -0
  200. package/.claude/commands/ant/dream.md +12 -9
  201. package/.claude/commands/ant/entomb.md +62 -87
  202. package/.claude/commands/ant/export-signals.md +57 -0
  203. package/.claude/commands/ant/feedback.md +18 -0
  204. package/.claude/commands/ant/flag.md +12 -0
  205. package/.claude/commands/ant/flags.md +22 -8
  206. package/.claude/commands/ant/focus.md +18 -0
  207. package/.claude/commands/ant/help.md +40 -8
  208. package/.claude/commands/ant/history.md +3 -0
  209. package/.claude/commands/ant/import-signals.md +71 -0
  210. package/.claude/commands/ant/init.md +349 -191
  211. package/.claude/commands/ant/insert-phase.md +105 -0
  212. package/.claude/commands/ant/interpret.md +11 -0
  213. package/.claude/commands/ant/lay-eggs.md +167 -158
  214. package/.claude/commands/ant/maturity.md +22 -11
  215. package/.claude/commands/ant/memory-details.md +77 -0
  216. package/.claude/commands/ant/migrate-state.md +6 -0
  217. package/.claude/commands/ant/oracle.md +317 -62
  218. package/.claude/commands/ant/organize.md +10 -5
  219. package/.claude/commands/ant/patrol.md +620 -0
  220. package/.claude/commands/ant/pause-colony.md +8 -22
  221. package/.claude/commands/ant/phase.md +26 -37
  222. package/.claude/commands/ant/pheromones.md +156 -0
  223. package/.claude/commands/ant/plan.md +199 -50
  224. package/.claude/commands/ant/preferences.md +65 -0
  225. package/.claude/commands/ant/quick.md +100 -0
  226. package/.claude/commands/ant/redirect.md +18 -0
  227. package/.claude/commands/ant/resume-colony.md +37 -22
  228. package/.claude/commands/ant/resume.md +60 -7
  229. package/.claude/commands/ant/run.md +231 -0
  230. package/.claude/commands/ant/seal.md +506 -78
  231. package/.claude/commands/ant/skill-create.md +286 -0
  232. package/.claude/commands/ant/status.md +171 -1
  233. package/.claude/commands/ant/swarm.md +11 -23
  234. package/.claude/commands/ant/tunnels.md +1 -0
  235. package/.claude/commands/ant/update.md +58 -135
  236. package/.claude/commands/ant/verify-castes.md +90 -42
  237. package/.claude/commands/ant/watch.md +1 -0
  238. package/.opencode/agents/aether-ambassador.md +1 -1
  239. package/.opencode/agents/aether-architect.md +133 -0
  240. package/.opencode/agents/aether-builder.md +3 -3
  241. package/.opencode/agents/aether-oracle.md +137 -0
  242. package/.opencode/agents/aether-queen.md +1 -1
  243. package/.opencode/agents/aether-route-setter.md +1 -1
  244. package/.opencode/agents/aether-scout.md +1 -1
  245. package/.opencode/agents/aether-surveyor-disciplines.md +6 -1
  246. package/.opencode/agents/aether-surveyor-nest.md +6 -1
  247. package/.opencode/agents/aether-surveyor-pathogens.md +6 -1
  248. package/.opencode/agents/aether-surveyor-provisions.md +6 -1
  249. package/.opencode/agents/aether-tracker.md +1 -1
  250. package/.opencode/agents/aether-watcher.md +1 -1
  251. package/.opencode/agents/aether-weaver.md +1 -1
  252. package/.opencode/commands/ant/archaeology.md +7 -14
  253. package/.opencode/commands/ant/build.md +54 -88
  254. package/.opencode/commands/ant/chaos.md +7 -24
  255. package/.opencode/commands/ant/colonize.md +10 -17
  256. package/.opencode/commands/ant/continue.md +595 -66
  257. package/.opencode/commands/ant/council.md +150 -18
  258. package/.opencode/commands/ant/data-clean.md +77 -0
  259. package/.opencode/commands/ant/dream.md +15 -17
  260. package/.opencode/commands/ant/entomb.md +28 -18
  261. package/.opencode/commands/ant/export-signals.md +54 -0
  262. package/.opencode/commands/ant/feedback.md +24 -5
  263. package/.opencode/commands/ant/flag.md +16 -4
  264. package/.opencode/commands/ant/flags.md +24 -10
  265. package/.opencode/commands/ant/focus.md +22 -5
  266. package/.opencode/commands/ant/help.md +41 -8
  267. package/.opencode/commands/ant/history.md +9 -0
  268. package/.opencode/commands/ant/import-signals.md +68 -0
  269. package/.opencode/commands/ant/init.md +396 -154
  270. package/.opencode/commands/ant/insert-phase.md +111 -0
  271. package/.opencode/commands/ant/interpret.md +16 -0
  272. package/.opencode/commands/ant/lay-eggs.md +184 -112
  273. package/.opencode/commands/ant/maturity.md +18 -2
  274. package/.opencode/commands/ant/memory-details.md +83 -0
  275. package/.opencode/commands/ant/migrate-state.md +12 -0
  276. package/.opencode/commands/ant/oracle.md +322 -67
  277. package/.opencode/commands/ant/organize.md +14 -12
  278. package/.opencode/commands/ant/patrol.md +626 -0
  279. package/.opencode/commands/ant/pause-colony.md +12 -29
  280. package/.opencode/commands/ant/phase.md +30 -40
  281. package/.opencode/commands/ant/pheromones.md +162 -0
  282. package/.opencode/commands/ant/plan.md +210 -57
  283. package/.opencode/commands/ant/preferences.md +71 -0
  284. package/.opencode/commands/ant/quick.md +91 -0
  285. package/.opencode/commands/ant/redirect.md +22 -5
  286. package/.opencode/commands/ant/resume-colony.md +41 -29
  287. package/.opencode/commands/ant/resume.md +80 -20
  288. package/.opencode/commands/ant/run.md +237 -0
  289. package/.opencode/commands/ant/seal.md +230 -25
  290. package/.opencode/commands/ant/skill-create.md +63 -0
  291. package/.opencode/commands/ant/status.md +125 -30
  292. package/.opencode/commands/ant/swarm.md +3 -345
  293. package/.opencode/commands/ant/tunnels.md +3 -9
  294. package/.opencode/commands/ant/update.md +63 -127
  295. package/.opencode/commands/ant/verify-castes.md +96 -42
  296. package/.opencode/commands/ant/watch.md +7 -0
  297. package/CHANGELOG.md +368 -1
  298. package/README.md +195 -324
  299. package/bin/cli.js +236 -429
  300. package/bin/generate-commands.js +186 -0
  301. package/bin/generate-commands.sh +128 -89
  302. package/bin/lib/spawn-logger.js +0 -15
  303. package/bin/lib/update-transaction.js +285 -35
  304. package/bin/npx-install.js +178 -0
  305. package/bin/validate-package.sh +85 -3
  306. package/package.json +16 -4
  307. package/.aether/CONTEXT.md +0 -160
  308. package/.aether/docs/QUEEN.md +0 -84
  309. package/.aether/exchange/colony-registry.xml +0 -11
  310. package/.aether/exchange/pheromones.xml +0 -87
  311. package/.aether/exchange/queen-wisdom.xml +0 -14
  312. package/.aether/model-profiles.yaml +0 -100
  313. package/.aether/utils/spawn-with-model.sh +0 -56
  314. package/bin/lib/model-profiles.js +0 -445
  315. package/bin/lib/model-verify.js +0 -288
  316. package/bin/lib/proxy-health.js +0 -253
  317. package/bin/lib/telemetry.js +0 -441
@@ -0,0 +1,425 @@
1
+ #!/bin/bash
2
+ # Council deliberation module — Advocate/Challenger/Sage model with spawn budget guards
3
+ # Provides: _council_deliberate, _council_advocate, _council_challenger, _council_sage,
4
+ # _council_history, _council_budget_check
5
+ #
6
+ # These functions are sourced by aether-utils.sh at startup.
7
+ # All shared infrastructure (json_ok, json_err, atomic_write, acquire_lock,
8
+ # release_lock, LOCK_DIR, COLONY_DATA_DIR, error constants) is available.
9
+ # _spawn_can_spawn is available from spawn.sh (sourced before this module).
10
+
11
+ # ---------------------------------------------------------------------------
12
+ # Internal helpers
13
+ # ---------------------------------------------------------------------------
14
+
15
+ _council_data_dir() {
16
+ echo "$COLONY_DATA_DIR/council"
17
+ }
18
+
19
+ _council_deliberations_file() {
20
+ echo "$COLONY_DATA_DIR/council/deliberations.json"
21
+ }
22
+
23
+ _council_ensure_file() {
24
+ local cf
25
+ cf="$(_council_deliberations_file)"
26
+ mkdir -p "$(_council_data_dir)"
27
+ if [[ ! -f "$cf" ]]; then
28
+ printf '%s\n' '{"version":"1.0","deliberations":[]}' > "$cf"
29
+ fi
30
+ }
31
+
32
+ # ---------------------------------------------------------------------------
33
+ # _council_deliberate
34
+ # Usage: council-deliberate --proposal <text> [--budget N] [--depth light|standard|deep]
35
+ # ---------------------------------------------------------------------------
36
+ _council_deliberate() {
37
+ local cd_proposal=""
38
+ local cd_budget="3"
39
+ local cd_depth="standard"
40
+
41
+ while [[ $# -gt 0 ]]; do
42
+ case "$1" in
43
+ --proposal) cd_proposal="${2:-}"; shift 2 ;;
44
+ --budget) cd_budget="${2:-3}"; shift 2 ;;
45
+ --depth) cd_depth="${2:-standard}"; shift 2 ;;
46
+ *) shift ;;
47
+ esac
48
+ done
49
+
50
+ if [[ -z "$cd_proposal" ]]; then
51
+ json_err "$E_VALIDATION_FAILED" "council-deliberate requires --proposal"
52
+ return
53
+ fi
54
+
55
+ if ! [[ "$cd_budget" =~ ^[0-9]+$ ]]; then
56
+ json_err "$E_VALIDATION_FAILED" "council-deliberate --budget must be a positive integer"
57
+ return
58
+ fi
59
+
60
+ local cd_ts
61
+ cd_ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
62
+ local cd_unix
63
+ cd_unix=$(date -u +%s)
64
+ local cd_id="delib_${cd_unix}"
65
+
66
+ _council_ensure_file
67
+
68
+ local cd_file
69
+ cd_file="$(_council_deliberations_file)"
70
+
71
+ acquire_lock "$cd_file" 2>/dev/null || true
72
+ # shellcheck disable=SC2064
73
+ trap "release_lock '$cd_file' 2>/dev/null || true" EXIT
74
+
75
+ local cd_updated
76
+ cd_updated=$(jq \
77
+ --arg id "$cd_id" \
78
+ --arg proposal "$cd_proposal" \
79
+ --arg ts "$cd_ts" \
80
+ --argjson budget "$cd_budget" \
81
+ --arg depth "$cd_depth" \
82
+ '.deliberations += [{
83
+ "id": $id,
84
+ "proposal": $proposal,
85
+ "advocate": null,
86
+ "challenger": null,
87
+ "sage": null,
88
+ "budget": $budget,
89
+ "depth": $depth,
90
+ "created_at": $ts,
91
+ "status": "pending"
92
+ }]' "$cd_file") || {
93
+ release_lock "$cd_file" 2>/dev/null || true
94
+ trap - EXIT
95
+ json_err "$E_UNKNOWN" "Failed to write deliberation"
96
+ return
97
+ }
98
+
99
+ atomic_write "$cd_file" "$cd_updated" || {
100
+ release_lock "$cd_file" 2>/dev/null || true
101
+ trap - EXIT
102
+ json_err "$E_UNKNOWN" "Failed to persist deliberation"
103
+ return
104
+ }
105
+
106
+ release_lock "$cd_file" 2>/dev/null || true
107
+ trap - EXIT
108
+
109
+ json_ok "$(jq -n \
110
+ --arg id "$cd_id" \
111
+ --arg proposal "$cd_proposal" \
112
+ --argjson budget "$cd_budget" \
113
+ '{"id":$id,"proposal":$proposal,"status":"pending","budget":$budget}')"
114
+ }
115
+
116
+ # ---------------------------------------------------------------------------
117
+ # _council_advocate
118
+ # Usage: council-advocate --deliberation-id <id> --argument <text>
119
+ # ---------------------------------------------------------------------------
120
+ _council_advocate() {
121
+ local ca_id=""
122
+ local ca_argument=""
123
+
124
+ while [[ $# -gt 0 ]]; do
125
+ case "$1" in
126
+ --deliberation-id) ca_id="${2:-}"; shift 2 ;;
127
+ --argument) ca_argument="${2:-}"; shift 2 ;;
128
+ *) shift ;;
129
+ esac
130
+ done
131
+
132
+ if [[ -z "$ca_id" ]]; then
133
+ json_err "$E_VALIDATION_FAILED" "council-advocate requires --deliberation-id"
134
+ return
135
+ fi
136
+
137
+ if [[ -z "$ca_argument" ]]; then
138
+ json_err "$E_VALIDATION_FAILED" "council-advocate requires --argument"
139
+ return
140
+ fi
141
+
142
+ local ca_file
143
+ ca_file="$(_council_deliberations_file)"
144
+
145
+ if [[ ! -f "$ca_file" ]]; then
146
+ json_err "$E_VALIDATION_FAILED" "No deliberations found; run council-deliberate first"
147
+ return
148
+ fi
149
+
150
+ local ca_exists
151
+ ca_exists=$(jq -r --arg id "$ca_id" '.deliberations[] | select(.id == $id) | .id' "$ca_file" 2>/dev/null || echo "")
152
+ if [[ -z "$ca_exists" ]]; then
153
+ json_err "$E_VALIDATION_FAILED" "Deliberation not found: $ca_id"
154
+ return
155
+ fi
156
+
157
+ acquire_lock "$ca_file" 2>/dev/null || true
158
+ # shellcheck disable=SC2064
159
+ trap "release_lock '$ca_file' 2>/dev/null || true" EXIT
160
+
161
+ local ca_updated
162
+ ca_updated=$(jq \
163
+ --arg id "$ca_id" \
164
+ --arg arg "$ca_argument" \
165
+ '(.deliberations[] | select(.id == $id)).advocate = $arg
166
+ | (.deliberations[] | select(.id == $id)).status = "in_progress"' \
167
+ "$ca_file") || {
168
+ release_lock "$ca_file" 2>/dev/null || true
169
+ trap - EXIT
170
+ json_err "$E_UNKNOWN" "Failed to record advocate argument"
171
+ return
172
+ }
173
+
174
+ atomic_write "$ca_file" "$ca_updated" || {
175
+ release_lock "$ca_file" 2>/dev/null || true
176
+ trap - EXIT
177
+ json_err "$E_UNKNOWN" "Failed to persist advocate argument"
178
+ return
179
+ }
180
+
181
+ release_lock "$ca_file" 2>/dev/null || true
182
+ trap - EXIT
183
+
184
+ json_ok '{"role":"advocate","recorded":true}'
185
+ }
186
+
187
+ # ---------------------------------------------------------------------------
188
+ # _council_challenger
189
+ # Usage: council-challenger --deliberation-id <id> --argument <text>
190
+ # ---------------------------------------------------------------------------
191
+ _council_challenger() {
192
+ local cc_id=""
193
+ local cc_argument=""
194
+
195
+ while [[ $# -gt 0 ]]; do
196
+ case "$1" in
197
+ --deliberation-id) cc_id="${2:-}"; shift 2 ;;
198
+ --argument) cc_argument="${2:-}"; shift 2 ;;
199
+ *) shift ;;
200
+ esac
201
+ done
202
+
203
+ if [[ -z "$cc_id" ]]; then
204
+ json_err "$E_VALIDATION_FAILED" "council-challenger requires --deliberation-id"
205
+ return
206
+ fi
207
+
208
+ if [[ -z "$cc_argument" ]]; then
209
+ json_err "$E_VALIDATION_FAILED" "council-challenger requires --argument"
210
+ return
211
+ fi
212
+
213
+ local cc_file
214
+ cc_file="$(_council_deliberations_file)"
215
+
216
+ if [[ ! -f "$cc_file" ]]; then
217
+ json_err "$E_VALIDATION_FAILED" "No deliberations found; run council-deliberate first"
218
+ return
219
+ fi
220
+
221
+ local cc_exists
222
+ cc_exists=$(jq -r --arg id "$cc_id" '.deliberations[] | select(.id == $id) | .id' "$cc_file" 2>/dev/null || echo "")
223
+ if [[ -z "$cc_exists" ]]; then
224
+ json_err "$E_VALIDATION_FAILED" "Deliberation not found: $cc_id"
225
+ return
226
+ fi
227
+
228
+ acquire_lock "$cc_file" 2>/dev/null || true
229
+ # shellcheck disable=SC2064
230
+ trap "release_lock '$cc_file' 2>/dev/null || true" EXIT
231
+
232
+ local cc_updated
233
+ cc_updated=$(jq \
234
+ --arg id "$cc_id" \
235
+ --arg arg "$cc_argument" \
236
+ '(.deliberations[] | select(.id == $id)).challenger = $arg
237
+ | (.deliberations[] | select(.id == $id)).status = "in_progress"' \
238
+ "$cc_file") || {
239
+ release_lock "$cc_file" 2>/dev/null || true
240
+ trap - EXIT
241
+ json_err "$E_UNKNOWN" "Failed to record challenger argument"
242
+ return
243
+ }
244
+
245
+ atomic_write "$cc_file" "$cc_updated" || {
246
+ release_lock "$cc_file" 2>/dev/null || true
247
+ trap - EXIT
248
+ json_err "$E_UNKNOWN" "Failed to persist challenger argument"
249
+ return
250
+ }
251
+
252
+ release_lock "$cc_file" 2>/dev/null || true
253
+ trap - EXIT
254
+
255
+ json_ok '{"role":"challenger","recorded":true}'
256
+ }
257
+
258
+ # ---------------------------------------------------------------------------
259
+ # _council_sage
260
+ # Usage: council-sage --deliberation-id <id> --synthesis <text> --recommendation <text>
261
+ # ---------------------------------------------------------------------------
262
+ _council_sage() {
263
+ local cs_id=""
264
+ local cs_synthesis=""
265
+ local cs_recommendation=""
266
+
267
+ while [[ $# -gt 0 ]]; do
268
+ case "$1" in
269
+ --deliberation-id) cs_id="${2:-}"; shift 2 ;;
270
+ --synthesis) cs_synthesis="${2:-}"; shift 2 ;;
271
+ --recommendation) cs_recommendation="${2:-}"; shift 2 ;;
272
+ *) shift ;;
273
+ esac
274
+ done
275
+
276
+ if [[ -z "$cs_id" ]]; then
277
+ json_err "$E_VALIDATION_FAILED" "council-sage requires --deliberation-id"
278
+ return
279
+ fi
280
+
281
+ if [[ -z "$cs_synthesis" ]]; then
282
+ json_err "$E_VALIDATION_FAILED" "council-sage requires --synthesis"
283
+ return
284
+ fi
285
+
286
+ if [[ -z "$cs_recommendation" ]]; then
287
+ json_err "$E_VALIDATION_FAILED" "council-sage requires --recommendation"
288
+ return
289
+ fi
290
+
291
+ local cs_file
292
+ cs_file="$(_council_deliberations_file)"
293
+
294
+ if [[ ! -f "$cs_file" ]]; then
295
+ json_err "$E_VALIDATION_FAILED" "No deliberations found; run council-deliberate first"
296
+ return
297
+ fi
298
+
299
+ local cs_exists
300
+ cs_exists=$(jq -r --arg id "$cs_id" '.deliberations[] | select(.id == $id) | .id' "$cs_file" 2>/dev/null || echo "")
301
+ if [[ -z "$cs_exists" ]]; then
302
+ json_err "$E_VALIDATION_FAILED" "Deliberation not found: $cs_id"
303
+ return
304
+ fi
305
+
306
+ acquire_lock "$cs_file" 2>/dev/null || true
307
+ # shellcheck disable=SC2064
308
+ trap "release_lock '$cs_file' 2>/dev/null || true" EXIT
309
+
310
+ local cs_updated
311
+ cs_updated=$(jq \
312
+ --arg id "$cs_id" \
313
+ --arg synthesis "$cs_synthesis" \
314
+ --arg rec "$cs_recommendation" \
315
+ '(.deliberations[] | select(.id == $id)).sage = {"synthesis": $synthesis, "recommendation": $rec}
316
+ | (.deliberations[] | select(.id == $id)).status = "complete"' \
317
+ "$cs_file") || {
318
+ release_lock "$cs_file" 2>/dev/null || true
319
+ trap - EXIT
320
+ json_err "$E_UNKNOWN" "Failed to record sage synthesis"
321
+ return
322
+ }
323
+
324
+ atomic_write "$cs_file" "$cs_updated" || {
325
+ release_lock "$cs_file" 2>/dev/null || true
326
+ trap - EXIT
327
+ json_err "$E_UNKNOWN" "Failed to persist sage synthesis"
328
+ return
329
+ }
330
+
331
+ release_lock "$cs_file" 2>/dev/null || true
332
+ trap - EXIT
333
+
334
+ json_ok "$(jq -n \
335
+ --arg rec "$cs_recommendation" \
336
+ '{"role":"sage","recommendation":$rec,"deliberation_complete":true}')"
337
+ }
338
+
339
+ # ---------------------------------------------------------------------------
340
+ # _council_history
341
+ # Usage: council-history [--limit N]
342
+ # ---------------------------------------------------------------------------
343
+ _council_history() {
344
+ local ch_limit=""
345
+
346
+ while [[ $# -gt 0 ]]; do
347
+ case "$1" in
348
+ --limit) ch_limit="${2:-}"; shift 2 ;;
349
+ *) shift ;;
350
+ esac
351
+ done
352
+
353
+ local ch_file
354
+ ch_file="$(_council_deliberations_file)"
355
+
356
+ if [[ ! -f "$ch_file" ]]; then
357
+ json_ok '{"total":0,"deliberations":[]}'
358
+ return
359
+ fi
360
+
361
+ local ch_total
362
+ ch_total=$(jq '.deliberations | length' "$ch_file" 2>/dev/null || echo 0)
363
+
364
+ if [[ -n "$ch_limit" ]] && [[ "$ch_limit" =~ ^[0-9]+$ ]]; then
365
+ local ch_result
366
+ ch_result=$(jq \
367
+ --argjson limit "$ch_limit" \
368
+ --argjson total "$ch_total" \
369
+ '{"total":$total,"deliberations":(.deliberations | .[-($limit):])}' \
370
+ "$ch_file" 2>/dev/null) || ch_result='{"total":0,"deliberations":[]}'
371
+ json_ok "$ch_result"
372
+ else
373
+ local ch_result
374
+ ch_result=$(jq \
375
+ --argjson total "$ch_total" \
376
+ '{"total":$total,"deliberations":.deliberations}' \
377
+ "$ch_file" 2>/dev/null) || ch_result='{"total":0,"deliberations":[]}'
378
+ json_ok "$ch_result"
379
+ fi
380
+ }
381
+
382
+ # ---------------------------------------------------------------------------
383
+ # _council_budget_check
384
+ # Usage: council-budget-check [--budget N]
385
+ # ---------------------------------------------------------------------------
386
+ _council_budget_check() {
387
+ local cb_budget="3"
388
+
389
+ while [[ $# -gt 0 ]]; do
390
+ case "$1" in
391
+ --budget) cb_budget="${2:-3}"; shift 2 ;;
392
+ *) shift ;;
393
+ esac
394
+ done
395
+
396
+ if ! [[ "$cb_budget" =~ ^[0-9]+$ ]]; then
397
+ json_err "$E_VALIDATION_FAILED" "council-budget-check --budget must be a positive integer"
398
+ return
399
+ fi
400
+
401
+ # Delegate to spawn-can-spawn at depth 1
402
+ local cb_spawn_result
403
+ cb_spawn_result=$(_spawn_can_spawn 1 2>/dev/null || echo '{"can_spawn":false,"current_total":0,"global_cap":10}')
404
+
405
+ local cb_can
406
+ cb_can=$(echo "$cb_spawn_result" | jq -r '.result.can_spawn // .can_spawn // false' 2>/dev/null || echo "false")
407
+ local cb_current
408
+ cb_current=$(echo "$cb_spawn_result" | jq -r '.result.current_total // .current_total // 0' 2>/dev/null || echo 0)
409
+ local cb_cap
410
+ cb_cap=$(echo "$cb_spawn_result" | jq -r '.result.global_cap // .global_cap // 10' 2>/dev/null || echo 10)
411
+ local cb_remaining=$(( cb_cap - cb_current ))
412
+ [[ $cb_remaining -lt 0 ]] && cb_remaining=0
413
+
414
+ # allowed is true only if spawn is allowed AND remaining >= requested budget
415
+ local cb_allowed="false"
416
+ if [[ "$cb_can" == "true" ]] && [[ $cb_remaining -ge $cb_budget ]]; then
417
+ cb_allowed="true"
418
+ fi
419
+
420
+ json_ok "$(jq -n \
421
+ --argjson allowed "$cb_allowed" \
422
+ --argjson remaining "$cb_remaining" \
423
+ --argjson budget "$cb_budget" \
424
+ '{"allowed":$allowed,"remaining":$remaining,"budget":$budget}')"
425
+ }
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env bash
2
+ # emoji-audit.sh — Audit emoji usage across Aether command files
3
+ # Checks command files against the canonical emoji reference map defined in
4
+ # .aether/skills/colony/colony-visuals/SKILL.md
5
+ #
6
+ # Usage: bash emoji-audit.sh [repo_root]
7
+ #
8
+ # Output: JSON compatible with aether-utils.sh subcommand pattern:
9
+ # {"ok": true, "result": {"files_scanned": N, "total_emojis": N,
10
+ # "unmapped": [...], "unused": [...], "usage": {...}}}
11
+ #
12
+ # Compatible with bash 3.x (macOS system bash).
13
+ # Can be sourced by aether-utils.sh or run standalone.
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # _emoji_audit_main — perform the audit and print JSON result
17
+ # Uses Python3 for emoji extraction and map logic (handles multi-codepoint sequences)
18
+ # ---------------------------------------------------------------------------
19
+ _emoji_audit_main() {
20
+ local repo_root="${1:-.}"
21
+
22
+ if ! command -v python3 >/dev/null 2>&1; then
23
+ printf '{"ok":false,"error":"python3 is required but not found"}\n'
24
+ return 1
25
+ fi
26
+
27
+ python3 - "$repo_root" <<'PYEOF'
28
+ import sys
29
+ import os
30
+ import re
31
+ import json
32
+ import glob
33
+
34
+ repo_root = sys.argv[1] if len(sys.argv) > 1 else "."
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # Canonical emoji reference map — must match colony-visuals SKILL.md
38
+ # ---------------------------------------------------------------------------
39
+ EMOJI_REF_MAP = {
40
+ "\U0001f528": "Builder ant", # 🔨
41
+ "\U0001f441\ufe0f": "Watcher ant", # 👁️
42
+ "\U0001f3b2": "Chaos ant", # 🎲
43
+ "\U0001f50d": "Scout ant", # 🔍
44
+ "\U0001f3fa": "Archaeologist / Seal", # 🏺
45
+ "\U0001f52e": "Oracle ant", # 🔮
46
+ "\U0001f3db\ufe0f": "Architect ant", # 🏛️
47
+ "\U0001f50c": "Ambassador ant", # 🔌
48
+ "\U0001f4ca": "Measurer ant / Status", # 📊
49
+ "\U0001f9ea": "Probe / Tests", # 🧪
50
+ "\U0001f504": "Weaver / Refresh", # 🔄
51
+ "\U0001f4e6": "Gatekeeper / Package", # 📦
52
+ "\U0001f465": "Auditor", # 👥
53
+ "\U0001f6a9": "Flag / Blocker", # 🚩
54
+ "\U0001f4ad": "Dream", # 💭
55
+ "\U0001f95a": "Queen / Init", # 🥚
56
+ "\U0001f4cb": "Plan / List", # 📋
57
+ "\u2705": "Pass / Success", # ✅
58
+ "\u274c": "Fail / Error", # ❌
59
+ "\u26a0\ufe0f": "Warning", # ⚠️
60
+ "\u26d4": "Hard block", # ⛔
61
+ "\U0001f4be": "Save / Persist", # 💾
62
+ "\U0001f3af": "Focus signal", # 🎯
63
+ "\U0001f6ab": "Redirect signal", # 🚫
64
+ "\U0001f4ac": "Feedback signal", # 💬
65
+ }
66
+
67
+ # ---------------------------------------------------------------------------
68
+ # Scan command files
69
+ # ---------------------------------------------------------------------------
70
+ scan_dirs = [
71
+ os.path.join(repo_root, ".claude", "commands", "ant"),
72
+ os.path.join(repo_root, ".opencode", "commands", "ant"),
73
+ ]
74
+
75
+ scan_files = []
76
+ for d in scan_dirs:
77
+ if os.path.isdir(d):
78
+ scan_files.extend(glob.glob(os.path.join(d, "*.md")))
79
+
80
+ files_scanned = len(scan_files)
81
+
82
+ # ---------------------------------------------------------------------------
83
+ # Extract emoji sequences from combined content
84
+ # Matches base emoji + optional variation selectors, ZWJ sequences
85
+ # ---------------------------------------------------------------------------
86
+ EMOJI_PATTERN = re.compile(
87
+ r'[\U0001F300-\U0001F9FF\U00002600-\U000027BF\U00002702-\U000027B0'
88
+ r'\U0001FA00-\U0001FA9F\U0001FAA0-\U0001FAFF\U00002300-\U000023FF'
89
+ r'\U00002B00-\U00002BFF\U00003000-\U0000303F'
90
+ r'\U0001F600-\U0001F64F\U0001F680-\U0001F6FF'
91
+ r'\u2300-\u27BF\u2B00-\u2BFF\u2600-\u27FF'
92
+ r'\u2702-\u27B0\u2194-\u21AA\u231A-\u231B\u23E9-\u23F3\u23F8-\u23FA'
93
+ r'\u25AA-\u25FE\u2614-\u2615\u2648-\u2653\u267F\u2693\u26A0-\u26A1'
94
+ r'\u26AA-\u26AB\u26BD-\u26BE\u26C4-\u26C5\u26CE\u26D4\u26EA'
95
+ r'\u26F2-\u26F3\u26F5\u26FA\u26FD\u2702\u2705\u2708-\u270D'
96
+ r'\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733-\u2734\u2744'
97
+ r'\u2747\u274C\u274E\u2753-\u2755\u2757\u2763-\u2764\u2795-\u2797'
98
+ r'\u27A1\u27B0\u27BF\u2934-\u2935\u2B05-\u2B07\u2B1B-\u2B1C\u2B50'
99
+ r'\u2B55\u3030\u303D\u3297\u3299]'
100
+ r'[\uFE0E\uFE0F\u20D0-\u20FF\u200D\U0001F3FB-\U0001F3FF]*'
101
+ r'(?:\u200D[\U0001F300-\U0001FFFF\u2600-\u27BF][\uFE0E\uFE0F\u20D0-\u20FF]*)*',
102
+ re.UNICODE
103
+ )
104
+
105
+ found_emojis = set()
106
+ for filepath in scan_files:
107
+ try:
108
+ with open(filepath, "r", encoding="utf-8", errors="replace") as fh:
109
+ content = fh.read()
110
+ for m in EMOJI_PATTERN.finditer(content):
111
+ e = m.group(0)
112
+ if e.strip():
113
+ found_emojis.add(e)
114
+ except OSError:
115
+ pass
116
+
117
+ total_emojis = len(found_emojis)
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # Compute results
121
+ # ---------------------------------------------------------------------------
122
+ # Normalize ref map keys for lookup (strip variation selectors for comparison)
123
+ def normalize(s):
124
+ return s.replace("\ufe0f", "").replace("\ufe0e", "")
125
+
126
+ ref_normalized = {normalize(k): (k, v) for k, v in EMOJI_REF_MAP.items()}
127
+ found_normalized = {normalize(e): e for e in found_emojis}
128
+
129
+ # unmapped: found in files but not in reference map (by normalized form)
130
+ unmapped = sorted([
131
+ raw for norm, raw in found_normalized.items()
132
+ if norm not in ref_normalized
133
+ ])
134
+
135
+ # unused: in reference map but not found in files (by normalized form)
136
+ unused = sorted([
137
+ canonical for norm, (canonical, concept) in ref_normalized.items()
138
+ if norm not in found_normalized
139
+ ])
140
+
141
+ # usage: reference map entries found in files -> concept
142
+ usage = {}
143
+ for norm, (canonical, concept) in ref_normalized.items():
144
+ if norm in found_normalized:
145
+ usage[canonical] = concept
146
+
147
+ output = {
148
+ "ok": True,
149
+ "result": {
150
+ "files_scanned": files_scanned,
151
+ "total_emojis": total_emojis,
152
+ "unmapped": unmapped,
153
+ "unused": unused,
154
+ "usage": usage,
155
+ }
156
+ }
157
+ print(json.dumps(output))
158
+ PYEOF
159
+ }
160
+
161
+ # ---------------------------------------------------------------------------
162
+ # Entry point when run as a standalone script
163
+ # ---------------------------------------------------------------------------
164
+ if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
165
+ _emoji_audit_main "${1:-$(pwd)}"
166
+ fi
@@ -87,8 +87,8 @@ json_err() {
87
87
  "$code" "$escaped_message" "$details_json" "$recovery" "$timestamp" >&2
88
88
 
89
89
  # Log to activity.log (best effort)
90
- if [[ -n "${DATA_DIR:-}" ]]; then
91
- echo "[$timestamp] ERROR $code: $escaped_message" >> "$DATA_DIR/activity.log" 2>/dev/null || true
90
+ if [[ -n "${COLONY_DATA_DIR:-}" ]] && [[ "${AETHER_TESTING:-}" != "1" ]]; then
91
+ echo "[$timestamp] ERROR $code: $escaped_message" >> "$COLONY_DATA_DIR/activity.log" 2>/dev/null || true
92
92
  fi
93
93
 
94
94
  exit 1
@@ -111,8 +111,22 @@ json_warn() {
111
111
  "$code" "$escaped_message" "$timestamp"
112
112
 
113
113
  # Log to activity.log (best effort)
114
- if [[ -n "${DATA_DIR:-}" ]]; then
115
- echo "[$timestamp] WARN $code: $escaped_message" >> "$DATA_DIR/activity.log" 2>/dev/null || true
114
+ if [[ -n "${COLONY_DATA_DIR:-}" ]] && [[ "${AETHER_TESTING:-}" != "1" ]]; then
115
+ echo "[$timestamp] WARN $code: $escaped_message" >> "$COLONY_DATA_DIR/activity.log" 2>/dev/null || true
116
+ fi
117
+ }
118
+
119
+ # --- _aether_log_error function for surfaced errors ---
120
+ # Dual output: [error] prefix to stderr (screen) + timestamped entry to errors.log (file)
121
+ # Distinct from: json_err (structured JSON), json_warn (non-fatal JSON), ⚠ (recovery), [trimmed] (budget)
122
+ _aether_log_error() {
123
+ local message="$1"
124
+ local timestamp
125
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
126
+ echo "[error] $message" >&2
127
+ if [[ -n "${COLONY_DATA_DIR:-}" ]]; then
128
+ mkdir -p "$DATA_DIR" 2>/dev/null # SUPPRESS:OK -- idempotent: ensure dir exists
129
+ echo "[$timestamp] $message" >> "$COLONY_DATA_DIR/errors.log" 2>/dev/null # SUPPRESS:OK -- cleanup: log write is best-effort
116
130
  fi
117
131
  }
118
132
 
@@ -139,8 +153,8 @@ error_handler() {
139
153
  "$E_BASH_ERROR" "$details" "$(_recovery_default)" "$timestamp" >&2
140
154
 
141
155
  # Log to activity.log (best effort)
142
- if [[ -n "${DATA_DIR:-}" ]]; then
143
- echo "[$timestamp] ERROR $E_BASH_ERROR: Command failed at line $line_num (exit $exit_code)" >> "$DATA_DIR/activity.log" 2>/dev/null || true
156
+ if [[ -n "${COLONY_DATA_DIR:-}" ]] && [[ "${AETHER_TESTING:-}" != "1" ]]; then
157
+ echo "[$timestamp] ERROR $E_BASH_ERROR: Command failed at line $line_num (exit $exit_code)" >> "$COLONY_DATA_DIR/activity.log" 2>/dev/null || true
144
158
  fi
145
159
 
146
160
  exit 1
@@ -198,7 +212,7 @@ feature_log_degradation() {
198
212
  }
199
213
 
200
214
  # --- Export all functions and variables ---
201
- export -f json_err json_warn error_handler
215
+ export -f json_err json_warn _aether_log_error error_handler
202
216
  export -f feature_enable feature_disable feature_enabled feature_log_degradation
203
217
  export -f _get_recovery _recovery_hub_not_found _recovery_repo_not_init
204
218
  export -f _recovery_file_not_found _recovery_json_invalid _recovery_lock_failed