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,6 @@
1
+ {
2
+ "_template": "learning-observations",
3
+ "_version": "1.0",
4
+ "_instructions": "Write to .aether/data/learning-observations.json. No substitution needed. Remove underscore-prefixed keys.",
5
+ "observations": []
6
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "_template": "midden",
3
+ "_version": "1.0",
4
+ "_instructions": "Write to .aether/data/midden/midden.json. No substitution needed. Remove underscore-prefixed keys.",
5
+ "version": "1.0.0",
6
+ "signals": [],
7
+ "spawn_metrics": {
8
+ "total_spawned": 0,
9
+ "completed": 0,
10
+ "failed": 0,
11
+ "efficiency_pct": 0
12
+ }
13
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "_template": "pheromones",
3
+ "_version": "1.0",
4
+ "_instructions": "Write to .aether/data/pheromones.json. No substitution needed. Remove underscore-prefixed keys.",
5
+ "signals": []
6
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "_template": "session",
3
+ "_version": "1.0",
4
+ "_instructions": "Write to .aether/data/session.json. Replace __SESSION_ID__, __GOAL__, __TIMESTAMP__. Remove underscore-prefixed keys.",
5
+ "session_id": "__SESSION_ID__",
6
+ "colony_goal": "__GOAL__",
7
+ "started_at": "__TIMESTAMP__",
8
+ "last_activity": "__TIMESTAMP__"
9
+ }
@@ -25,11 +25,13 @@ if [ ! -f "$_AETHER_UTILS_DIR/file-lock.sh" ]; then
25
25
  fi
26
26
  source "$_AETHER_UTILS_DIR/file-lock.sh"
27
27
 
28
- # Aether root detection - use git root if available, otherwise use current directory
29
- if git rev-parse --show-toplevel >/dev/null 2>&1; then
30
- AETHER_ROOT="$(git rev-parse --show-toplevel)"
31
- else
32
- AETHER_ROOT="$(pwd)"
28
+ # Aether root detection - respect existing AETHER_ROOT, or use git root, or use current directory
29
+ if [[ -z "${AETHER_ROOT:-}" ]]; then
30
+ if git rev-parse --show-toplevel >/dev/null 2>&1; then
31
+ AETHER_ROOT="$(git rev-parse --show-toplevel)"
32
+ else
33
+ AETHER_ROOT="$(pwd)"
34
+ fi
33
35
  fi
34
36
 
35
37
  TEMP_DIR="$AETHER_ROOT/.aether/temp"
@@ -41,9 +43,44 @@ mkdir -p "$TEMP_DIR" "$BACKUP_DIR"
41
43
  # Number of backups to keep
42
44
  MAX_BACKUPS=3
43
45
 
46
+ # Safety stats file for tracking data safety events (best-effort, never fails operations)
47
+ SAFETY_STATS_FILE="${AETHER_ROOT}/.aether/data/safety-stats.json"
48
+
49
+ # Increment a safety stats counter (best-effort, never fails the calling operation)
50
+ # Arguments: counter_name (e.g., "stale_locks_cleaned", "json_validation_rejects")
51
+ _safety_stats_increment() {
52
+ local counter_name="$1"
53
+ local stats_file="$SAFETY_STATS_FILE"
54
+ local now_iso
55
+ now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "unknown")
56
+
57
+ # Ensure data directory exists
58
+ mkdir -p "$(dirname "$stats_file")" 2>/dev/null || return 0
59
+
60
+ # Initialize if missing
61
+ if [[ ! -f "$stats_file" ]]; then
62
+ printf '{"stale_locks_cleaned":0,"json_validation_rejects":0,"last_updated":"%s"}\n' "$now_iso" > "$stats_file" 2>/dev/null || return 0
63
+ fi
64
+
65
+ # Increment counter (best-effort, don't fail on jq errors)
66
+ local updated
67
+ updated=$(jq --arg key "$counter_name" --arg ts "$now_iso" '
68
+ .[$key] = ((.[$key] // 0) + 1) |
69
+ .last_updated = $ts
70
+ ' "$stats_file" 2>/dev/null) || return 0
71
+
72
+ if [[ -n "$updated" ]]; then
73
+ printf '%s\n' "$updated" > "$stats_file" 2>/dev/null || return 0
74
+ fi
75
+ }
76
+
44
77
  # Atomic write: write content to file via temporary file
45
78
  # Arguments: target_file, content
46
79
  # Returns: 0 on success, 1 on failure
80
+ # NOTE: atomic_write does NOT interact with file locks. Lock management
81
+ # (acquire_lock/release_lock) is the CALLER's responsibility. If you need
82
+ # exclusive access, acquire the lock before calling atomic_write, and release
83
+ # it after (including on error paths).
47
84
  atomic_write() {
48
85
  local target_file="$1"
49
86
  local content="$2"
@@ -53,10 +90,10 @@ atomic_write() {
53
90
  mkdir -p "$target_dir"
54
91
 
55
92
  # Create unique temp file
56
- local temp_file="${TEMP_DIR}/$(basename "$target_file").$$.$(date +%s%N).tmp"
93
+ local temp_file="${TEMP_DIR}/$(basename "$target_file").$$.$( date +%s )_${RANDOM}.tmp"
57
94
 
58
95
  # Write content to temp file
59
- if ! echo "$content" > "$temp_file"; then
96
+ if ! printf '%s\n' "$content" > "$temp_file"; then
60
97
  echo "Failed to write to temp file: $temp_file"
61
98
  rm -f "$temp_file"
62
99
  return 1
@@ -67,11 +104,12 @@ atomic_write() {
67
104
  create_backup "$target_file"
68
105
  fi
69
106
 
70
- # Validate JSON if it's a JSON file
107
+ # Validate JSON if it's a JSON file (lock management is caller's responsibility)
71
108
  if [[ "$target_file" == *.json ]]; then
72
- if ! python3 -c "import json; json.load(open('$temp_file'))" 2>/dev/null; then
109
+ if ! jq empty "$temp_file" 2>/dev/null; then
73
110
  echo "Invalid JSON in temp file: $temp_file"
74
111
  rm -f "$temp_file"
112
+ _safety_stats_increment "json_validation_rejects" 2>/dev/null || true
75
113
  return 1
76
114
  fi
77
115
  fi
@@ -108,7 +146,7 @@ atomic_write_from_file() {
108
146
  mkdir -p "$target_dir"
109
147
 
110
148
  # Create unique temp file
111
- local temp_file="${TEMP_DIR}/$(basename "$target_file").$$.$(date +%s%N).tmp"
149
+ local temp_file="${TEMP_DIR}/$(basename "$target_file").$$.$( date +%s )_${RANDOM}.tmp"
112
150
 
113
151
  # Copy source to temp
114
152
  if ! cp "$source_file" "$temp_file"; then
@@ -122,11 +160,12 @@ atomic_write_from_file() {
122
160
  create_backup "$target_file"
123
161
  fi
124
162
 
125
- # Validate JSON if it's a JSON file
163
+ # Validate JSON if it's a JSON file (lock management is caller's responsibility)
126
164
  if [[ "$target_file" == *.json ]]; then
127
- if ! python3 -c "import json; json.load(open('$temp_file'))" 2>/dev/null; then
165
+ if ! jq empty "$temp_file" 2>/dev/null; then
128
166
  echo "Invalid JSON in temp file: $temp_file"
129
167
  rm -f "$temp_file"
168
+ _safety_stats_increment "json_validation_rejects" 2>/dev/null || true
130
169
  return 1
131
170
  fi
132
171
  fi
@@ -166,10 +205,17 @@ create_backup() {
166
205
  # Arguments: base_name
167
206
  rotate_backups() {
168
207
  local base_name="$1"
169
- local backups=$(ls -t "${BACKUP_DIR}/${base_name}".*.backup 2>/dev/null | wc -l)
170
208
 
171
- if [ "$backups" -gt "$MAX_BACKUPS" ]; then
172
- ls -t "${BACKUP_DIR}/${base_name}".*.backup | tail -n +$((MAX_BACKUPS + 1)) | xargs rm -f
209
+ # Use find with -print0 for safe handling of paths with spaces
210
+ local backup_count
211
+ backup_count=$(find "$BACKUP_DIR" -maxdepth 1 -name "${base_name}.*.backup" -type f 2>/dev/null | wc -l | tr -d ' ')
212
+
213
+ if [ "$backup_count" -gt "$MAX_BACKUPS" ]; then
214
+ # Delete oldest backups beyond MAX_BACKUPS using find for space-safe handling
215
+ find "$BACKUP_DIR" -maxdepth 1 -name "${base_name}.*.backup" -type f -print0 2>/dev/null \
216
+ | xargs -0 ls -t 2>/dev/null \
217
+ | tail -n +$((MAX_BACKUPS + 1)) \
218
+ | while IFS= read -r file; do rm -f "$file" 2>/dev/null || true; done
173
219
  fi
174
220
  }
175
221
 
@@ -209,9 +255,9 @@ list_backups() {
209
255
 
210
256
  # Cleanup temp files older than 1 hour
211
257
  cleanup_temp_files() {
212
- find "$TEMP_DIR" -name "*.tmp" -mtime +1/24 -delete 2>/dev/null || true
258
+ find "$TEMP_DIR" -name "*.tmp" -mmin +60 -delete 2>/dev/null || true
213
259
  }
214
260
 
215
261
  # Export functions
216
262
  export -f atomic_write atomic_write_from_file create_backup rotate_backups
217
- export -f restore_backup list_backups cleanup_temp_files
263
+ export -f restore_backup list_backups cleanup_temp_files _safety_stats_increment
@@ -17,7 +17,10 @@ CURRENT_LOCK=${CURRENT_LOCK:-""}
17
17
 
18
18
  # Get script directory for sourcing (preserve parent SCRIPT_DIR if set)
19
19
  __chamber_utils_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
- AETHER_ROOT="$(cd "$__chamber_utils_dir/../.." && pwd 2>/dev/null || echo "$__chamber_utils_dir/../..")"
20
+ # Respect existing AETHER_ROOT if already set
21
+ if [[ -z "${AETHER_ROOT:-}" ]]; then
22
+ AETHER_ROOT="$(cd "$__chamber_utils_dir/../.." && pwd 2>/dev/null || echo "$__chamber_utils_dir/../..")"
23
+ fi
21
24
 
22
25
  # Use parent SCRIPT_DIR if available, otherwise use local
23
26
  SCRIPT_DIR="${SCRIPT_DIR:-$__chamber_utils_dir}"
@@ -44,6 +47,7 @@ fi
44
47
  : "${E_FILE_NOT_FOUND:=E_FILE_NOT_FOUND}"
45
48
  : "${E_BASH_ERROR:=E_BASH_ERROR}"
46
49
  : "${E_JSON_INVALID:=E_JSON_INVALID}"
50
+ : "${E_FEATURE_UNAVAILABLE:=E_FEATURE_UNAVAILABLE}"
47
51
 
48
52
  # --- Chamber Functions ---
49
53
 
@@ -293,5 +297,144 @@ EOF
293
297
  json_ok "$sorted"
294
298
  }
295
299
 
300
+ # --- Colony Archive XML ---
301
+
302
+ # Export combined colony archive XML containing pheromones, wisdom, and registry
303
+ # Usage: _colony_archive_xml [output_file]
304
+ # Default output: .aether/exchange/colony-archive.xml
305
+ # Always filters to active-only pheromone signals
306
+ _colony_archive_xml() {
307
+ # Graceful degradation: check for xmllint
308
+ if ! command -v xmllint >/dev/null 2>&1; then
309
+ json_err "$E_FEATURE_UNAVAILABLE" "xmllint is not installed. Try: xcode-select --install on macOS."
310
+ fi
311
+
312
+ cax_output="${1:-$SCRIPT_DIR/exchange/colony-archive.xml}"
313
+ mkdir -p "$(dirname "$cax_output")"
314
+
315
+ # Step 1: Filter active-only pheromone signals to a temp file
316
+ cax_tmp_pheromones=$(mktemp)
317
+ if [[ -f "$COLONY_DATA_DIR/pheromones.json" ]]; then
318
+ jq '{
319
+ version: .version,
320
+ colony_id: .colony_id,
321
+ generated_at: .generated_at,
322
+ signals: [.signals[] | select(.active == true)]
323
+ }' "$COLONY_DATA_DIR/pheromones.json" > "$cax_tmp_pheromones" 2>/dev/null # SUPPRESS:OK -- read-default: file may not exist yet
324
+ else
325
+ printf '%s\n' '{"version":"1.0","colony_id":"unknown","generated_at":"","signals":[]}' > "$cax_tmp_pheromones"
326
+ fi
327
+
328
+ # Step 2: Export each section to temp XML files
329
+ cax_tmp_dir=$(mktemp -d)
330
+
331
+ # Pheromone section (using filtered active-only)
332
+ source "$SCRIPT_DIR/exchange/pheromone-xml.sh"
333
+ xml-pheromone-export "$cax_tmp_pheromones" "$cax_tmp_dir/pheromones.xml" 2>/dev/null || _aether_log_error "Could not export pheromones to XML"
334
+
335
+ # Wisdom section — reuse wisdom-export-xml fallback logic
336
+ source "$SCRIPT_DIR/exchange/wisdom-xml.sh"
337
+ cax_wisdom_input="$DATA_DIR/queen-wisdom.json"
338
+ if [[ ! -f "$cax_wisdom_input" ]]; then
339
+ # MIGRATE: direct COLONY_STATE.json access -- use _state_read_field instead
340
+ # Try extracting from COLONY_STATE.json memory field
341
+ if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
342
+ cax_wex_memory=$(jq '.memory // {}' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo '{}') # SUPPRESS:OK -- read-default: returns fallback if missing
343
+ if [[ "$cax_wex_memory" != "{}" && "$cax_wex_memory" != "null" ]]; then
344
+ cax_wex_created_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
345
+ cax_wisdom_input="$cax_tmp_dir/wisdom-input.json"
346
+ printf '%s\n' "{
347
+ \"version\": \"1.0.0\",
348
+ # SUPPRESS:OK -- read-default: query may return empty
349
+ \"metadata\": {\"created\": \"$cax_wex_created_at\", \"colony_id\": \"$(jq -r '.goal // \"unknown\"' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null)\"},
350
+ \"philosophies\": [],
351
+ # SUPPRESS:OK -- read-default: query may return empty
352
+ \"patterns\": $(echo "$cax_wex_memory" | jq '[.instincts // [] | .[] | {"id": (. | @base64), "content": ., "confidence": 0.7, "domain": "general", "source": "colony_memory"}]' 2>/dev/null || echo '[]')
353
+ }" > "$cax_wisdom_input"
354
+ fi
355
+ fi
356
+ fi
357
+ if [[ -f "$cax_wisdom_input" ]]; then
358
+ xml-wisdom-export "$cax_wisdom_input" "$cax_tmp_dir/wisdom.xml" 2>/dev/null || _aether_log_error "Could not export wisdom to XML"
359
+ fi
360
+
361
+ # Registry section — reuse registry-export-xml on-demand generation logic
362
+ source "$SCRIPT_DIR/exchange/registry-xml.sh"
363
+ cax_registry_input="$DATA_DIR/colony-registry.json"
364
+ if [[ ! -f "$cax_registry_input" ]]; then
365
+ cax_rex_chambers_dir="$AETHER_ROOT/.aether/chambers"
366
+ cax_rex_generated_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
367
+ cax_rex_colonies="[]"
368
+ if [[ -d "$cax_rex_chambers_dir" ]]; then
369
+ cax_rex_colonies=$(
370
+ for manifest in "$cax_rex_chambers_dir"/*/manifest.json; do
371
+ [[ -f "$manifest" ]] || continue
372
+ jq -c '{
373
+ id: (.colony_id // .goal // "unknown"),
374
+ name: (.goal // "Unnamed Colony"),
375
+ created_at: (.created_at // "unknown"),
376
+ sealed_at: (.sealed_at // null),
377
+ status: (if .sealed_at then "sealed" else "active" end),
378
+ chamber: input_filename
379
+ }' "$manifest" 2>/dev/null || true # SUPPRESS:OK -- cleanup: operation is best-effort
380
+ done | jq -s '.' 2>/dev/null || echo '[]' # SUPPRESS:OK -- read-default: returns fallback if missing
381
+ )
382
+ fi
383
+ cax_registry_input="$cax_tmp_dir/registry-input.json"
384
+ printf '%s\n' "{
385
+ \"version\": \"1.0.0\",
386
+ \"generated_at\": \"$cax_rex_generated_at\",
387
+ \"colonies\": $cax_rex_colonies
388
+ }" > "$cax_registry_input"
389
+ fi
390
+ xml-registry-export "$cax_registry_input" "$cax_tmp_dir/registry.xml" 2>/dev/null || _aether_log_error "Could not export registry to XML"
391
+
392
+ # Step 3: Build combined XML
393
+ # SUPPRESS:OK -- read-default: query may return empty
394
+ cax_colony_id=$(jq -r '.goal // "unknown"' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//')
395
+ [[ -z "$cax_colony_id" || "$cax_colony_id" == "unknown" ]] && cax_colony_id="unknown"
396
+ cax_sealed_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
397
+ cax_pheromone_count=$(jq '.signals | length' "$cax_tmp_pheromones" 2>/dev/null || echo 0) # SUPPRESS:OK -- read-default: file may not exist yet
398
+
399
+ {
400
+ printf '<?xml version="1.0" encoding="UTF-8"?>\n'
401
+ printf '<colony-archive\n'
402
+ printf ' xmlns="http://aether.colony/schemas/archive/1.0"\n'
403
+ printf ' colony_id="%s"\n' "$cax_colony_id"
404
+ printf ' sealed_at="%s"\n' "$cax_sealed_at"
405
+ printf ' version="1.0.0"\n'
406
+ printf ' pheromone_count="%s">\n' "$cax_pheromone_count"
407
+
408
+ # Append pheromone section (strip XML declaration)
409
+ if [[ -f "$cax_tmp_dir/pheromones.xml" ]]; then
410
+ sed '1{/^<?xml/d;}' "$cax_tmp_dir/pheromones.xml"
411
+ fi
412
+
413
+ # Append wisdom section (strip XML declaration)
414
+ if [[ -f "$cax_tmp_dir/wisdom.xml" ]]; then
415
+ sed '1{/^<?xml/d;}' "$cax_tmp_dir/wisdom.xml"
416
+ fi
417
+
418
+ # Append registry section (strip XML declaration)
419
+ if [[ -f "$cax_tmp_dir/registry.xml" ]]; then
420
+ sed '1{/^<?xml/d;}' "$cax_tmp_dir/registry.xml"
421
+ fi
422
+
423
+ printf '</colony-archive>\n'
424
+ } > "$cax_output"
425
+
426
+ # Step 4: Validate well-formedness
427
+ if xmllint --noout "$cax_output" 2>/dev/null; then # SUPPRESS:OK -- validation: testing XML validity
428
+ cax_valid=true
429
+ else
430
+ cax_valid=false
431
+ fi
432
+
433
+ # Step 5: Cleanup temp files
434
+ rm -rf "$cax_tmp_dir" "$cax_tmp_pheromones"
435
+
436
+ json_ok "$(jq -n --arg path "$cax_output" --argjson valid "$cax_valid" --arg colony_id "$cax_colony_id" --argjson pheromone_count "$cax_pheromone_count" '{path: $path, valid: $valid, colony_id: $colony_id, pheromone_count: $pheromone_count}')"
437
+ }
438
+
296
439
  # Export functions for use in other scripts
297
- export -f chamber_sanitize_goal chamber_compute_hash chamber_create chamber_verify chamber_list
440
+ export -f chamber_sanitize_goal chamber_compute_hash chamber_create chamber_verify chamber_list _colony_archive_xml
@@ -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:-}" ]]; 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:-}" ]]; 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:-}" ]]; 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