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,1710 @@
1
+ #!/usr/bin/env bash
2
+ # Queen utility functions -- extracted from aether-utils.sh
3
+ # Provides: _queen_init, _queen_read, _queen_thresholds, _queen_promote
4
+ # Also includes: _extract_wisdom_sections (helper used only by _queen_read)
5
+ # Note: Uses get_wisdom_threshold() and get_wisdom_thresholds_json() which remain in the main file
6
+
7
+ # ============================================================================
8
+ # _extract_wisdom_sections
9
+ # Helper function to extract wisdom sections from a QUEEN.md file
10
+ # Uses line number approach to avoid macOS awk range issues
11
+ # Usage: _extract_wisdom_sections <file_path>
12
+ # Returns: JSON object with wisdom sections
13
+ # ============================================================================
14
+ _extract_wisdom_sections() {
15
+ local file="$1"
16
+
17
+ # Format detection: check for v2 header "## Build Learnings"
18
+ # If present -> v2 format (4 sections). Otherwise -> v1 format (6 emoji sections, mapped).
19
+ if grep -q '^## Build Learnings$' "$file" 2>/dev/null; then
20
+ # === V2 FORMAT (4 clean sections) ===
21
+ local uprefs_line=$(awk '/^## User Preferences$/ {print NR; exit}' "$file")
22
+ local cpat_line=$(awk '/^## Codebase Patterns$/ {print NR; exit}' "$file")
23
+ local blearn_line=$(awk '/^## Build Learnings$/ {print NR; exit}' "$file")
24
+ local inst_line=$(awk '/^## Instincts$/ {print NR; exit}' "$file")
25
+ local evo_line=$(awk '/^## Evolution Log$/ {print NR; exit}' "$file")
26
+
27
+ local user_prefs codebase_patterns build_learnings instincts
28
+
29
+ # User Preferences: between uprefs_line and next section
30
+ local uprefs_end="${cpat_line:-${blearn_line:-${inst_line:-${evo_line:-999999}}}}"
31
+ if [[ -n "$uprefs_line" ]]; then
32
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
33
+ user_prefs=$(awk -v s="$uprefs_line" -v e="$uprefs_end" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | sed '/^---$/d' | jq -Rs '.' 2>/dev/null || echo '""')
34
+ else user_prefs='""'; fi
35
+
36
+ # Codebase Patterns: between cpat_line and next section
37
+ local cpat_end="${blearn_line:-${inst_line:-${evo_line:-999999}}}"
38
+ if [[ -n "$cpat_line" ]]; then
39
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
40
+ codebase_patterns=$(awk -v s="$cpat_line" -v e="$cpat_end" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | sed '/^---$/d' | jq -Rs '.' 2>/dev/null || echo '""')
41
+ else codebase_patterns='""'; fi
42
+
43
+ # Build Learnings: between blearn_line and next section
44
+ local blearn_end="${inst_line:-${evo_line:-999999}}"
45
+ if [[ -n "$blearn_line" ]]; then
46
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
47
+ build_learnings=$(awk -v s="$blearn_line" -v e="$blearn_end" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | sed '/^---$/d' | jq -Rs '.' 2>/dev/null || echo '""')
48
+ else build_learnings='""'; fi
49
+
50
+ # Instincts: between inst_line and evo_line (or end)
51
+ if [[ -n "$inst_line" ]]; then
52
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
53
+ instincts=$(awk -v s="$inst_line" -v e="${evo_line:-999999}" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | sed '/^---$/d' | jq -Rs '.' 2>/dev/null || echo '""')
54
+ else instincts='""'; fi
55
+
56
+ # Output v2 JSON
57
+ echo "{\"user_prefs\":$user_prefs,\"codebase_patterns\":$codebase_patterns,\"build_learnings\":$build_learnings,\"instincts\":$instincts}"
58
+
59
+ else
60
+ # === V1 FORMAT (6 emoji sections, mapped to v2 keys) ===
61
+ local p_line=$(awk '/^## ..? ?Philosophies$/ {print NR; exit}' "$file")
62
+ local pat_line=$(awk '/^## ..? ?Patterns$/ {print NR; exit}' "$file")
63
+ local red_line=$(awk '/^## ..? ?Redirects$/ {print NR; exit}' "$file")
64
+ local stack_line=$(awk '/^## ..? ?Stack Wisdom$/ {print NR; exit}' "$file")
65
+ local dec_line=$(awk '/^## ..? ?Decrees$/ {print NR; exit}' "$file")
66
+ local prefs_line=$(awk '/^## ..? ?User Preferences$/ {print NR; exit}' "$file")
67
+ local evo_line=$(awk '/^## ..? ?Evolution Log$/ {print NR; exit}' "$file")
68
+
69
+ local philosophies patterns redirects stack_wisdom decrees user_prefs
70
+
71
+ if [[ -n "$p_line" && -n "$pat_line" ]]; then
72
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
73
+ philosophies=$(awk -v s="$p_line" -v e="$pat_line" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | jq -Rs '.' 2>/dev/null || echo '""')
74
+ else philosophies='""'; fi
75
+
76
+ if [[ -n "$pat_line" && -n "$red_line" ]]; then
77
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
78
+ patterns=$(awk -v s="$pat_line" -v e="$red_line" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | jq -Rs '.' 2>/dev/null || echo '""')
79
+ else patterns='""'; fi
80
+
81
+ if [[ -n "$red_line" && -n "$stack_line" ]]; then
82
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
83
+ redirects=$(awk -v s="$red_line" -v e="$stack_line" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | jq -Rs '.' 2>/dev/null || echo '""')
84
+ else redirects='""'; fi
85
+
86
+ if [[ -n "$stack_line" && -n "$dec_line" ]]; then
87
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
88
+ stack_wisdom=$(awk -v s="$stack_line" -v e="$dec_line" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | jq -Rs '.' 2>/dev/null || echo '""')
89
+ else stack_wisdom='""'; fi
90
+
91
+ local dec_end="${prefs_line:-${evo_line:-999999}}"
92
+ if [[ -n "$dec_line" ]]; then
93
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
94
+ decrees=$(awk -v s="$dec_line" -v e="$dec_end" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | jq -Rs '.' 2>/dev/null || echo '""')
95
+ else decrees='""'; fi
96
+
97
+ if [[ -n "$prefs_line" ]]; then
98
+ # SUPPRESS:OK -- read-default: text escaping returns fallback on empty input
99
+ user_prefs=$(awk -v s="$prefs_line" -v e="${evo_line:-999999}" 'NR > s && NR < e {print}' "$file" | sed '/^$/d' | jq -Rs '.' 2>/dev/null || echo '""')
100
+ else user_prefs='""'; fi
101
+
102
+ # Map v1 sections to v2 keys:
103
+ # Philosophies + Patterns + Redirects + Stack Wisdom -> codebase_patterns
104
+ # Decrees + old User Preferences -> user_prefs
105
+ # build_learnings and instincts -> empty for v1 files
106
+ local combined_codebase
107
+ combined_codebase=$(jq -n \
108
+ --arg phil "$philosophies" \
109
+ --arg pat "$patterns" \
110
+ --arg red "$redirects" \
111
+ --arg stack "$stack_wisdom" \
112
+ '[$phil, $pat, $red, $stack] | map(select(. != "" and . != null)) | join("\n")' 2>/dev/null || echo '""')
113
+
114
+ local combined_uprefs
115
+ combined_uprefs=$(jq -n \
116
+ --arg dec "$decrees" \
117
+ --arg up "$user_prefs" \
118
+ '[$dec, $up] | map(select(. != "" and . != null)) | join("\n")' 2>/dev/null || echo '""')
119
+
120
+ echo "{\"user_prefs\":$combined_uprefs,\"codebase_patterns\":$combined_codebase,\"build_learnings\":\"\",\"instincts\":\"\"}"
121
+ fi
122
+ }
123
+
124
+ # ============================================================================
125
+ # _queen_init
126
+ # Initialize QUEEN.md from template
127
+ # Creates .aether/QUEEN.md from template if missing
128
+ # Usage: Called via dispatcher as "queen-init"
129
+ # ============================================================================
130
+ _queen_init() {
131
+ local queen_file template_file timestamp path
132
+ queen_file="$AETHER_ROOT/.aether/QUEEN.md"
133
+
134
+ # Check multiple locations for template
135
+ # Order: hub (system/) -> dev (.aether/) -> repo local -> legacy
136
+ template_file=""
137
+ for path in \
138
+ "$HOME/.aether/system/templates/QUEEN.md.template" \
139
+ "$AETHER_ROOT/.aether/templates/QUEEN.md.template" \
140
+ "$HOME/.aether/templates/QUEEN.md.template"; do
141
+ if [[ -f "$path" ]]; then
142
+ template_file="$path"
143
+ break
144
+ fi
145
+ done
146
+
147
+ # Ensure .aether directory exists
148
+ mkdir -p "$AETHER_ROOT/.aether"
149
+
150
+ # Check if QUEEN.md already exists and has content
151
+ if [[ -f "$queen_file" ]] && [[ -s "$queen_file" ]]; then
152
+ json_ok '{"created":false,"path":".aether/QUEEN.md","reason":"already_exists"}'
153
+ exit 0
154
+ fi
155
+
156
+ # Check if template was found
157
+ if [[ -z "$template_file" ]]; then
158
+ json_err "$E_FILE_NOT_FOUND" \
159
+ "Template not found. Run: npm install -g aether && aether install to restore it." \
160
+ '{"templates_checked":["~/.aether/system/templates/QUEEN.md.template",".aether/templates/QUEEN.md.template","~/.aether/templates/QUEEN.md.template"]}'
161
+ exit 1
162
+ fi
163
+
164
+ # Create QUEEN.md from template with timestamp substitution
165
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
166
+ sed -e "s/{TIMESTAMP}/$timestamp/g" "$template_file" > "$queen_file"
167
+
168
+ if [[ -f "$queen_file" ]]; then
169
+ json_ok "$(jq -n --arg source "$template_file" '{created: true, path: ".aether/QUEEN.md", source: $source}')"
170
+ else
171
+ json_err "$E_FILE_NOT_FOUND" "Failed to create QUEEN.md" '{"path":".aether/QUEEN.md"}'
172
+ exit 1
173
+ fi
174
+ }
175
+
176
+ # ============================================================================
177
+ # _queen_read
178
+ # Read QUEEN.md and return wisdom as JSON for worker priming
179
+ # Supports two-level loading: global (~/.aether/QUEEN.md) first, then local (.aether/QUEEN.md)
180
+ # Local wisdom extends global - entries are combined per category
181
+ # Usage: Called via dispatcher as "queen-read"
182
+ # ============================================================================
183
+ _queen_read() {
184
+ local queen_global queen_local has_global has_local global_wisdom local_wisdom combined metadata
185
+ local user_prefs codebase_patterns build_learnings instincts result
186
+ queen_global="$HOME/.aether/QUEEN.md"
187
+ queen_local="$AETHER_ROOT/.aether/QUEEN.md"
188
+
189
+ # Track which files exist
190
+ has_global=false
191
+ has_local=false
192
+
193
+ # Check for global QUEEN.md
194
+ if [[ -f "$queen_global" ]]; then
195
+ has_global=true
196
+ fi
197
+
198
+ # Check for local QUEEN.md
199
+ if [[ -f "$queen_local" ]]; then
200
+ has_local=true
201
+ fi
202
+
203
+ # FAIL HARD if no QUEEN.md found at all
204
+ if [[ "$has_global" == "false" && "$has_local" == "false" ]]; then
205
+ json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"global_path":"~/.aether/QUEEN.md","local_path":".aether/QUEEN.md"}'
206
+ exit 1
207
+ fi
208
+
209
+ # Extract wisdom from global (if exists) -- _extract_wisdom_sections returns v2 keys
210
+ global_wisdom='{"user_prefs":"","codebase_patterns":"","build_learnings":"","instincts":""}'
211
+ if [[ "$has_global" == "true" ]]; then
212
+ global_wisdom=$(_extract_wisdom_sections "$queen_global")
213
+ fi
214
+
215
+ # Extract wisdom from local (if exists)
216
+ local_wisdom='{"user_prefs":"","codebase_patterns":"","build_learnings":"","instincts":""}'
217
+ if [[ "$has_local" == "true" ]]; then
218
+ local_wisdom=$(_extract_wisdom_sections "$queen_local")
219
+ fi
220
+
221
+ # Combine wisdom: local extends global - content appended (v2 keys)
222
+ combined=$(jq -n \
223
+ --argjson global "$global_wisdom" \
224
+ --argjson local "$local_wisdom" \
225
+ '
226
+ def combine(a; b):
227
+ if a == "" or a == null then b
228
+ elif b == "" or b == null then a
229
+ else a + "\n" + b
230
+ end;
231
+
232
+ {
233
+ user_prefs: combine($global.user_prefs; $local.user_prefs),
234
+ codebase_patterns: combine($global.codebase_patterns; $local.codebase_patterns),
235
+ build_learnings: combine($global.build_learnings; $local.build_learnings),
236
+ instincts: combine($global.instincts; $local.instincts)
237
+ }
238
+ ')
239
+
240
+ # Get metadata from local (preferred) or global
241
+ metadata='{"version":"unknown","last_evolved":null,"source":"none"}'
242
+ if [[ "$has_local" == "true" ]]; then
243
+ metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_local" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
244
+ elif [[ "$has_global" == "true" ]]; then
245
+ metadata=$(sed -n '/<!-- METADATA/,/-->/p' "$queen_global" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
246
+ fi
247
+
248
+ # If no metadata found, return empty structure
249
+ if [[ -z "$metadata" ]]; then
250
+ metadata='{"version":"unknown","last_evolved":null,"source":"none","stats":{}}'
251
+ fi
252
+
253
+ # Gate 1: Validate metadata is parseable JSON BEFORE using as --argjson
254
+ if ! echo "$metadata" | jq -e . >/dev/null 2>&1; then # SUPPRESS:OK -- validation: testing JSON validity
255
+ json_err "$E_JSON_INVALID" \
256
+ "QUEEN.md has a malformed METADATA block — the JSON between <!-- METADATA and --> is invalid. Try: fix the JSON in .aether/QUEEN.md or run queen-init to reset."
257
+ fi
258
+
259
+ # Extract individual combined wisdom values (v2 keys)
260
+ user_prefs=$(echo "$combined" | jq -r '.user_prefs')
261
+ codebase_patterns=$(echo "$combined" | jq -r '.codebase_patterns')
262
+ build_learnings=$(echo "$combined" | jq -r '.build_learnings')
263
+ instincts=$(echo "$combined" | jq -r '.instincts')
264
+
265
+ # Build JSON output (v2 keys)
266
+ # Pass shell-level file existence flags to jq (these are authoritative, not $meta.source)
267
+ local hg_json="false"
268
+ local hl_json="false"
269
+ [[ "$has_global" == "true" ]] && hg_json="true"
270
+ [[ "$has_local" == "true" ]] && hl_json="true"
271
+
272
+ result=$(jq -n \
273
+ --argjson meta "$metadata" \
274
+ --arg user_prefs "$user_prefs" \
275
+ --arg codebase_patterns "$codebase_patterns" \
276
+ --arg build_learnings "$build_learnings" \
277
+ --arg instincts "$instincts" \
278
+ --argjson has_g "$hg_json" \
279
+ --argjson has_l "$hl_json" \
280
+ '{
281
+ metadata: $meta,
282
+ wisdom: {
283
+ user_prefs: $user_prefs,
284
+ codebase_patterns: $codebase_patterns,
285
+ build_learnings: $build_learnings,
286
+ instincts: $instincts
287
+ },
288
+ priming: {
289
+ has_user_prefs: ([$user_prefs | split("\n")[] | select(startswith("- "))] | length) > 0,
290
+ has_codebase_patterns: ([$codebase_patterns | split("\n")[] | select(startswith("- "))] | length) > 0,
291
+ has_build_learnings: ([$build_learnings | split("\n")[] | select(startswith("- ") or startswith("#"))] | length) > 0,
292
+ has_instincts: ([$instincts | split("\n")[] | select(startswith("- "))] | length) > 0
293
+ },
294
+ sources: {
295
+ has_global: $has_g,
296
+ has_local: $has_l
297
+ }
298
+ }')
299
+
300
+ # Gate 2: Validate assembled result before returning
301
+ if [[ -z "$result" ]] || ! echo "$result" | jq -e . >/dev/null 2>&1; then # SUPPRESS:OK -- validation: testing JSON validity
302
+ json_err "$E_JSON_INVALID" \
303
+ "Couldn't assemble queen-read output. QUEEN.md may have formatting issues. Try: run queen-init to reset."
304
+ fi
305
+ json_ok "$result"
306
+ }
307
+
308
+ # ============================================================================
309
+ # _queen_thresholds
310
+ # Return proposal and auto-promotion thresholds for each wisdom type
311
+ # Usage: Called via dispatcher as "queen-thresholds"
312
+ # Note: Uses get_wisdom_thresholds_json() which remains in the main file
313
+ # ============================================================================
314
+ _queen_thresholds() {
315
+ json_ok "$(get_wisdom_thresholds_json)"
316
+ }
317
+
318
+ # ============================================================================
319
+ # _queen_promote
320
+ # Promote a learning to QUEEN.md wisdom
321
+ # Usage: Called via dispatcher as "queen-promote <type> <content> <colony_name>"
322
+ # Types: philosophy, pattern, redirect, stack, decree, failure
323
+ # Note: Uses get_wisdom_threshold() which remains in the main file
324
+ # ============================================================================
325
+ _queen_promote() {
326
+ local wisdom_type content colony_name valid_types type_valid vt queen_file threshold
327
+ local observations_file content_hash observation_data obs_count obs_colonies
328
+ local ts entry tmp_file section_header section_line next_section_line section_end
329
+ local has_placeholder entry_prefix ev_entry ev_separator current_count new_count stat_key
330
+ # Usage: queen-promote <type> <content> <colony_name>
331
+ # Types: philosophy, pattern, redirect, stack, decree
332
+ wisdom_type="${1:-}"
333
+ content="${2:-}"
334
+ colony_name="${3:-}"
335
+
336
+ # Validate required arguments
337
+ [[ -z "$wisdom_type" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"type"}'
338
+ [[ -z "$content" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"content"}'
339
+ [[ -z "$colony_name" ]] && json_err "$E_VALIDATION_FAILED" "Usage: queen-promote <type> <content> <colony_name>" '{"missing":"colony_name"}'
340
+
341
+ # Validate type (failure observations map to pattern when promoted)
342
+ valid_types=("philosophy" "pattern" "redirect" "stack" "decree" "failure")
343
+ type_valid=false
344
+ for vt in "${valid_types[@]}"; do
345
+ [[ "$wisdom_type" == "$vt" ]] && type_valid=true && break
346
+ done
347
+ [[ "$type_valid" == "false" ]] && json_err "$E_VALIDATION_FAILED" "Invalid type: $wisdom_type" '{"valid_types":["philosophy","pattern","redirect","stack","decree","failure"]}'
348
+
349
+ queen_file="$AETHER_ROOT/.aether/QUEEN.md"
350
+
351
+ # Check if QUEEN.md exists
352
+ if [[ ! -f "$queen_file" ]]; then
353
+ json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found" '{"path":".aether/QUEEN.md"}'
354
+ exit 1
355
+ fi
356
+
357
+ # Thresholds come from the shared command policy to keep promotion behavior consistent.
358
+ threshold=$(get_wisdom_threshold "$wisdom_type" "propose")
359
+
360
+ # QUEEN-04: Check threshold against learning-observations.json
361
+ # For decrees, always promote immediately (threshold 0)
362
+ # For other types, verify observation count meets threshold
363
+ observations_file="$COLONY_DATA_DIR/learning-observations.json"
364
+ content_hash="sha256:$(echo -n "$content" | sha256sum | cut -d' ' -f1)"
365
+
366
+ if [[ "$wisdom_type" != "decree" ]] && [[ -f "$observations_file" ]]; then
367
+ # Check if this content has been observed enough times
368
+ # SUPPRESS:OK -- read-default: query may return empty
369
+ observation_data=$(jq -r --arg hash "$content_hash" '.observations[] | select(.content_hash == $hash) | {count: .observation_count, colonies: .colonies}' "$observations_file" 2>/dev/null || echo '{}')
370
+
371
+ if [[ -n "$observation_data" ]] && [[ "$observation_data" != '{}' ]]; then
372
+ obs_count=$(echo "$observation_data" | jq -r '.count // 0')
373
+ obs_colonies=$(echo "$observation_data" | jq -r '.colonies // []')
374
+
375
+ if [[ "$obs_count" -lt "$threshold" ]]; then
376
+ json_err "$E_VALIDATION_FAILED" "Threshold not met: $obs_count/$threshold observations" "{\"observation_count\":$obs_count,\"threshold\":$threshold,\"content_hash\":\"$content_hash\"}"
377
+ fi
378
+ else
379
+ # No observations found for this content
380
+ json_err "$E_VALIDATION_FAILED" "No observations found for this content" "{\"threshold\":$threshold,\"content_hash\":\"$content_hash\"}"
381
+ fi
382
+ fi
383
+
384
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
385
+
386
+ # Map type to section header (v2 format)
387
+ # Old types map to new sections; detect QUEEN.md format first
388
+ local is_v2=false
389
+ if grep -q '^## Build Learnings$' "$queen_file" 2>/dev/null; then
390
+ is_v2=true
391
+ fi
392
+
393
+ if [[ "$is_v2" == "true" ]]; then
394
+ # V2 format: map old types to new clean section headers
395
+ case "$wisdom_type" in
396
+ philosophy) section_header="## Codebase Patterns" ; entry_prefix="[general] " ;;
397
+ pattern|failure) section_header="## Codebase Patterns" ; entry_prefix="" ;;
398
+ redirect) section_header="## Codebase Patterns" ; entry_prefix="AVOID: " ;;
399
+ stack) section_header="## Codebase Patterns" ; entry_prefix="[repo] " ;;
400
+ decree) section_header="## User Preferences" ; entry_prefix="" ;;
401
+ esac
402
+ else
403
+ # V1 format: use original emoji headers
404
+ case "$wisdom_type" in
405
+ philosophy) section_header="## 📜 Philosophies" ; entry_prefix="" ;;
406
+ pattern|failure) section_header="## 🧭 Patterns" ; entry_prefix="" ;;
407
+ redirect) section_header="## ⚠️ Redirects" ; entry_prefix="" ;;
408
+ stack) section_header="## 🔧 Stack Wisdom" ; entry_prefix="" ;;
409
+ decree) section_header="## 🏛️ Decrees" ; entry_prefix="" ;;
410
+ esac
411
+ fi
412
+
413
+ # Build the new entry
414
+ entry="- ${entry_prefix}**${colony_name}** (${ts}): ${content}"
415
+
416
+ # Create temp file for atomic write
417
+ tmp_file="${queen_file}.tmp.$$"
418
+
419
+ # Trap-based cleanup for intermediate temp files on exit/interrupt
420
+ # Compose with _aether_exit_cleanup to preserve lock/temp cleanup
421
+ # SUPPRESS:OK -- cleanup: files may not exist yet
422
+ trap 'rm -f "${tmp_file}" "${tmp_file}".*; _aether_exit_cleanup 2>/dev/null || true' EXIT TERM INT HUP
423
+
424
+ # Find line numbers for section boundaries
425
+ section_line=$(grep -n "^${section_header}$" "$queen_file" | head -1 | cut -d: -f1)
426
+ next_section_line=$(tail -n +$((section_line + 1)) "$queen_file" | grep -n "^## " | head -1 | cut -d: -f1)
427
+ if [[ -n "$next_section_line" ]]; then
428
+ section_end=$((section_line + next_section_line - 1))
429
+ else
430
+ section_end=$(wc -l < "$queen_file")
431
+ fi
432
+
433
+ # SUPPRESS:OK -- read-default: operation returns fallback on failure
434
+ # Check if section has placeholder (grep returns 1 when no matches, handle with || true)
435
+ # SUPPRESS:OK -- existence-test: grep returns 1 when no matches
436
+ has_placeholder=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -c "No.*recorded yet" || true)
437
+ has_placeholder=${has_placeholder:-0}
438
+
439
+ if [[ "$has_placeholder" -gt 0 ]]; then
440
+ # Replace placeholder with entry - only within the target section
441
+ # Find the specific line number of the placeholder within the section
442
+ placeholder_line=$(sed -n "${section_line},${section_end}p" "$queen_file" | grep -n "^\\*No .* recorded yet" | head -1 | cut -d: -f1)
443
+ if [[ -n "$placeholder_line" ]]; then
444
+ actual_line=$((section_line + placeholder_line - 1))
445
+ # Use head/tail instead of sed c-command for newline safety (sed c\ breaks on multi-line content on macOS)
446
+ {
447
+ head -n $((actual_line - 1)) "$queen_file"
448
+ echo "$entry"
449
+ tail -n +$((actual_line + 1)) "$queen_file"
450
+ } > "$tmp_file"
451
+ else
452
+ # Fallback: insert after section header using head/tail
453
+ {
454
+ head -n "$section_line" "$queen_file"
455
+ echo "$entry"
456
+ tail -n +$((section_line + 1)) "$queen_file"
457
+ } > "$tmp_file"
458
+ fi
459
+ else
460
+ # Insert entry after the description paragraph (after the second empty line in section)
461
+ # The structure is: header, blank, description, blank, [entries...]
462
+ # We want to insert after the blank line following the description
463
+ empty_lines=$(sed -n "$((section_line + 1)),${section_end}p" "$queen_file" | grep -n "^$" | cut -d: -f1)
464
+ # Get the second empty line (after description)
465
+ insert_line=$(echo "$empty_lines" | sed -n '2p')
466
+ if [[ -n "$insert_line" ]]; then
467
+ insert_line=$((section_line + insert_line))
468
+ else
469
+ # Fallback: use first empty line
470
+ insert_line=$(echo "$empty_lines" | head -1)
471
+ if [[ -n "$insert_line" ]]; then
472
+ insert_line=$((section_line + insert_line))
473
+ else
474
+ insert_line=$((section_line + 1))
475
+ fi
476
+ fi
477
+ # Insert the entry after the found line using head/tail for newline safety
478
+ {
479
+ head -n "$insert_line" "$queen_file"
480
+ echo "$entry"
481
+ tail -n +$((insert_line + 1)) "$queen_file"
482
+ } > "$tmp_file"
483
+ fi
484
+
485
+ # Update Evolution Log in temp file
486
+ ev_entry="| ${ts} | ${colony_name} | promoted_${wisdom_type} | Added: ${content:0:50}... |"
487
+ # Find the line after the separator in Evolution Log table
488
+ ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
489
+
490
+ # Use awk for cross-platform insertion (only if separator found)
491
+ # Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
492
+ if [[ -n "$ev_separator" ]]; then
493
+ AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
494
+ fi
495
+
496
+ # Update METADATA stats in temp file
497
+ # Map wisdom_type to stat key -- detect v2 vs v1 METADATA format
498
+ if [[ "$is_v2" == "true" ]]; then
499
+ # V2 stats keys
500
+ case "$wisdom_type" in
501
+ philosophy|pattern|failure|redirect|stack) stat_key="total_codebase_patterns" ;;
502
+ decree) stat_key="total_user_prefs" ;;
503
+ *) stat_key="total_codebase_patterns" ;;
504
+ esac
505
+ else
506
+ # V1 stats keys (irregular plurals handled)
507
+ case "$wisdom_type" in
508
+ stack) stat_key="total_stack_entries" ;;
509
+ philosophy) stat_key="total_philosophies" ;;
510
+ *) stat_key="total_${wisdom_type}s" ;;
511
+ esac
512
+ fi
513
+ # Read current count from temp file (which has the latest state)
514
+ current_count=$(grep "\"${stat_key}\":" "$tmp_file" 2>/dev/null | grep -o '[0-9]*' | head -1 || true) # SUPPRESS:OK -- read-default: file may not exist
515
+ current_count=${current_count:-0}
516
+ new_count=$((current_count + 1))
517
+
518
+ # Update last_evolved using awk
519
+ awk -v ts="$ts" '/"last_evolved":/ { gsub(/"last_evolved": "[^"]*"/, "\"last_evolved\": \"" ts "\""); } {print}' "$tmp_file" > "${tmp_file}.meta" && mv "${tmp_file}.meta" "$tmp_file"
520
+
521
+ # Update stats count using awk
522
+ awk -v type="$stat_key" -v count="$new_count" '{
523
+ gsub("\"" type "\": [0-9]*", "\"" type "\": " count)
524
+ print
525
+ }' "$tmp_file" > "${tmp_file}.stats" && mv "${tmp_file}.stats" "$tmp_file"
526
+
527
+ # META-02: Update evolution_log in METADATA JSON
528
+ # Add entry with timestamp, action, wisdom_type, content_hash
529
+ ev_log_entry="{\"timestamp\": \"$ts\", \"action\": \"promote\", \"wisdom_type\": \"$wisdom_type\", \"content_hash\": \"$content_hash\", \"colony\": \"$colony_name\"}"
530
+
531
+ # Check if evolution_log exists in metadata, add if not
532
+ # Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
533
+ if ! grep -q '"evolution_log"' "$tmp_file"; then
534
+ # Add evolution_log array after stats
535
+ AETHER_EV_LOG_ENTRY="$ev_log_entry" awk '
536
+ /"stats": \{/ {
537
+ print
538
+ # Read until closing brace of stats
539
+ while (getline > 0) {
540
+ print
541
+ if (/\}/) break
542
+ }
543
+ # Add comma and evolution_log
544
+ print ","
545
+ print " \"evolution_log\": [" ENVIRON["AETHER_EV_LOG_ENTRY"] "]"
546
+ next
547
+ }
548
+ { print }
549
+ ' "$tmp_file" > "${tmp_file}.evlog" && mv "${tmp_file}.evlog" "$tmp_file"
550
+ else
551
+ # Append to existing evolution_log array
552
+ AETHER_EV_LOG_ENTRY="$ev_log_entry" awk '
553
+ /"evolution_log": \[/ {
554
+ # Check if array is empty or has items
555
+ if (/\]/) {
556
+ # Empty array - replace with entry
557
+ gsub(/"evolution_log": \[\]/, "\"evolution_log\": [" ENVIRON["AETHER_EV_LOG_ENTRY"] "]")
558
+ } else {
559
+ # Has items - need to add before closing bracket
560
+ # For now, just print and handle in next iteration
561
+ }
562
+ print
563
+ next
564
+ }
565
+ # Handle multi-line evolution_log arrays
566
+ /"evolution_log": \[/ && !/\]/ {
567
+ print
568
+ getline
569
+ if (/\]/) {
570
+ # Was empty, now add entry
571
+ print ENVIRON["AETHER_EV_LOG_ENTRY"]
572
+ print "]"
573
+ } else {
574
+ # Has items, add comma and entry before closing
575
+ print
576
+ while (getline > 0) {
577
+ if (/^\s*\]/) {
578
+ print ","
579
+ print ENVIRON["AETHER_EV_LOG_ENTRY"]
580
+ print "]"
581
+ break
582
+ }
583
+ print
584
+ }
585
+ }
586
+ next
587
+ }
588
+ { print }
589
+ ' "$tmp_file" > "${tmp_file}.evlog" && mv "${tmp_file}.evlog" "$tmp_file"
590
+ fi
591
+
592
+ # META-04: Update colonies_contributed mapping in METADATA JSON
593
+ # This maps content_hash to array of colonies that contributed
594
+ # Get colonies from observations file if available
595
+ colonies_json="[]"
596
+ if [[ -f "$observations_file" ]]; then
597
+ # SUPPRESS:OK -- read-default: query may return empty
598
+ colonies_from_obs=$(jq -r --arg hash "$content_hash" '.observations[] | select(.content_hash == $hash) | .colonies // [] | @json' "$observations_file" 2>/dev/null || echo '[]')
599
+ if [[ -n "$colonies_from_obs" ]] && [[ "$colonies_from_obs" != "null" ]]; then
600
+ colonies_json="$colonies_from_obs"
601
+ fi
602
+ fi
603
+
604
+ # Add colonies_contributed object if not present
605
+ if ! grep -q '"colonies_contributed"' "$tmp_file"; then
606
+ # Add after evolution_log or stats
607
+ awk -v hash="$content_hash" -v colonies="$colonies_json" '
608
+ /"evolution_log": / {
609
+ print
610
+ # Skip to end of evolution_log array
611
+ brace_count = 1
612
+ while (getline > 0) {
613
+ print
614
+ if (/\[/) brace_count++
615
+ if (/\]/) brace_count--
616
+ if (brace_count == 0) break
617
+ }
618
+ print ","
619
+ print " \"colonies_contributed\": {"
620
+ print " \"" hash "\": " colonies
621
+ print " }"
622
+ next
623
+ }
624
+ { print }
625
+ ' "$tmp_file" > "${tmp_file}.colmap" && mv "${tmp_file}.colmap" "$tmp_file"
626
+ else
627
+ # Update existing colonies_contributed - add/update entry for this hash
628
+ # Use jq for reliable JSON manipulation
629
+ meta_section=$(sed -n '/<!-- METADATA/,/-->/p' "$tmp_file" | sed '1d;$d' | tr -d '\n' | sed 's/^[[:space:]]*//')
630
+ if [[ -n "$meta_section" ]]; then
631
+ # SUPPRESS:OK -- read-default: returns fallback on failure
632
+ updated_meta=$(echo "$meta_section" | jq --arg hash "$content_hash" --argjson cols "$colonies_json" '.colonies_contributed[$hash] = $cols' 2>/dev/null || echo "$meta_section")
633
+ # Replace metadata section using head/tail to handle multi-line content safely
634
+ # awk -v cannot handle embedded newlines in variable values (C-escape interpretation)
635
+ local meta_start_line meta_end_line
636
+ meta_start_line=$(grep -n "^<!-- METADATA$" "$tmp_file" | head -1 | cut -d: -f1)
637
+ meta_end_line=$(grep -n "^-->$" "$tmp_file" | head -1 | cut -d: -f1)
638
+ if [[ -n "$meta_start_line" && -n "$meta_end_line" ]]; then
639
+ {
640
+ head -n $((meta_start_line - 1)) "$tmp_file"
641
+ printf '<!-- METADATA\n%s\n-->\n' "$updated_meta"
642
+ tail -n +$((meta_end_line + 1)) "$tmp_file"
643
+ } > "${tmp_file}.metaupd" && mv "${tmp_file}.metaupd" "$tmp_file"
644
+ fi
645
+ fi
646
+ fi
647
+
648
+ # Add colony to colonies_contributed array (legacy) if not present
649
+ if ! grep -q "\"${colony_name}\"" "$tmp_file"; then
650
+ # Add to colonies_contributed array using awk - handle empty and non-empty arrays
651
+ awk -v colony="$colony_name" '
652
+ /"colonies_contributed": \[\]/ {
653
+ gsub(/"colonies_contributed": \[\]/, "\"colonies_contributed\": [\"" colony "\"]")
654
+ print
655
+ next
656
+ }
657
+ /"colonies_contributed": \[/ && !/\]/ {
658
+ # Multi-line array, add at next closing bracket
659
+ print
660
+ next
661
+ }
662
+ /"colonies_contributed": \[/ {
663
+ # Single-line array with elements
664
+ gsub(/\]$/, "\"" colony "\", ]")
665
+ print
666
+ next
667
+ }
668
+ { print }
669
+ ' "$tmp_file" > "${tmp_file}.col" && mv "${tmp_file}.col" "$tmp_file"
670
+ fi
671
+
672
+ # Restore default cleanup trap before final move (file becomes permanent)
673
+ trap '_aether_exit_cleanup 2>/dev/null || true' EXIT TERM INT HUP
674
+
675
+ # Safety guard: never overwrite QUEEN.md with empty content
676
+ if [[ ! -s "$tmp_file" ]]; then
677
+ rm -f "$tmp_file"
678
+ json_err "$E_INTERNAL" "queen-promote produced empty output — aborting to protect QUEEN.md"
679
+ return 1
680
+ fi
681
+
682
+ # Atomic move
683
+ mv "$tmp_file" "$queen_file"
684
+
685
+ json_ok "$(jq -n --arg type "$wisdom_type" --arg colony "$colony_name" --arg ts "$ts" --argjson threshold "$threshold" --argjson new_count "$new_count" --arg hash "$content_hash" '{promoted: true, type: $type, colony: $colony, timestamp: $ts, threshold: $threshold, new_count: $new_count, content_hash: $hash}')"
686
+ }
687
+
688
+ # ============================================================================
689
+ # _queen_write_learnings
690
+ # Write build learnings directly to QUEEN.md Build Learnings section
691
+ # Bypasses observation thresholds -- every build writes learnings
692
+ # Usage: queen-write-learnings <phase_id> <phase_name> <learnings_json>
693
+ # learnings_json: [{"claim":"what happened","tag":"repo|general","evidence":"..."}]
694
+ # ============================================================================
695
+ _queen_write_learnings() {
696
+ local phase_id="${1:-}"
697
+ local phase_name="${2:-}"
698
+ local learnings_json="${3:-}"
699
+
700
+ # Validate required arguments
701
+ [[ -z "$phase_id" ]] && { json_err "$E_VALIDATION_FAILED" "Usage: queen-write-learnings <phase_id> <phase_name> <learnings_json>" '{"missing":"phase_id"}'; return 1; }
702
+ [[ -z "$phase_name" ]] && { json_err "$E_VALIDATION_FAILED" "Usage: queen-write-learnings <phase_id> <phase_name> <learnings_json>" '{"missing":"phase_name"}'; return 1; }
703
+ [[ -z "$learnings_json" ]] && { json_err "$E_VALIDATION_FAILED" "Usage: queen-write-learnings <phase_id> <phase_name> <learnings_json>" '{"missing":"learnings_json"}'; return 1; }
704
+
705
+ # Validate learnings_json is valid JSON array
706
+ if ! echo "$learnings_json" | jq -e 'type == "array"' >/dev/null 2>&1; then
707
+ json_err "$E_JSON_INVALID" "learnings_json must be a JSON array" '{"received":"not_array"}'
708
+ return 1
709
+ fi
710
+
711
+ local queen_file="$AETHER_ROOT/.aether/QUEEN.md"
712
+
713
+ if [[ ! -f "$queen_file" ]]; then
714
+ json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found. Run queen-init first." '{"path":".aether/QUEEN.md"}'
715
+ return 1
716
+ fi
717
+
718
+ local ts
719
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
720
+ local date_short
721
+ date_short=$(date -u +"%Y-%m-%d")
722
+
723
+ # Build entries from learnings_json
724
+ local written=0
725
+ local entries=""
726
+ local subsection_header="### Phase ${phase_id}: ${phase_name}"
727
+
728
+ while IFS= read -r encoded_entry; do
729
+ [[ -z "$encoded_entry" ]] && continue
730
+ local claim tag evidence
731
+ claim=$(echo "$encoded_entry" | base64 -d 2>/dev/null | jq -r '.claim // ""' 2>/dev/null) || continue
732
+ tag=$(echo "$encoded_entry" | base64 -d 2>/dev/null | jq -r '.tag // "repo"' 2>/dev/null) || tag="repo"
733
+ evidence=$(echo "$encoded_entry" | base64 -d 2>/dev/null | jq -r '.evidence // ""' 2>/dev/null) || evidence=""
734
+
735
+ [[ -z "$claim" ]] && continue
736
+
737
+ # Dedup check: skip if claim already in QUEEN.md
738
+ if grep -Fq -- "$claim" "$queen_file" 2>/dev/null; then
739
+ continue
740
+ fi
741
+
742
+ local entry_line="- [${tag}] ${claim} -- *Phase ${phase_id} (${phase_name})* (${date_short})"
743
+ entries="${entries}${entry_line}"$'\n'
744
+ written=$((written + 1))
745
+ done < <(echo "$learnings_json" | jq -r '.[] | @base64')
746
+
747
+ # If nothing to write, return early
748
+ if [[ "$written" -eq 0 ]]; then
749
+ json_ok "$(jq -n --arg phase "$phase_id" --arg ts "$ts" \
750
+ '{written: 0, phase: $phase, timestamp: $ts, reason: "all_duplicates_or_empty"}')"
751
+ return 0
752
+ fi
753
+
754
+ # Create temp file for atomic write
755
+ local tmp_file="${queen_file}.tmp.$$"
756
+ cp "$queen_file" "$tmp_file"
757
+
758
+ # Find Build Learnings section
759
+ local section_line
760
+ section_line=$(grep -n '^## Build Learnings$' "$tmp_file" | head -1 | cut -d: -f1)
761
+
762
+ if [[ -z "$section_line" ]]; then
763
+ rm -f "$tmp_file"
764
+ json_err "$E_VALIDATION_FAILED" "Build Learnings section not found in QUEEN.md. Is this a v2 format file?" '{"section":"Build Learnings"}'
765
+ return 1
766
+ fi
767
+
768
+ # Find end of section (next ## header or end of file)
769
+ local next_section_line
770
+ next_section_line=$(tail -n +$((section_line + 1)) "$tmp_file" | grep -n "^## " | head -1 | cut -d: -f1)
771
+ local section_end
772
+ if [[ -n "$next_section_line" ]]; then
773
+ section_end=$((section_line + next_section_line - 1))
774
+ else
775
+ section_end=$(wc -l < "$tmp_file")
776
+ fi
777
+
778
+ # Remove placeholder if present
779
+ # SUPPRESS:OK -- existence-test: grep returns 1 when no matches
780
+ local has_placeholder
781
+ has_placeholder=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -c "No build learnings recorded yet" || true)
782
+ has_placeholder=${has_placeholder:-0}
783
+
784
+ if [[ "$has_placeholder" -gt 0 ]]; then
785
+ local placeholder_line
786
+ placeholder_line=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -n "^\\*No build learnings recorded yet" | head -1 | cut -d: -f1)
787
+ if [[ -n "$placeholder_line" ]]; then
788
+ local actual_line=$((section_line + placeholder_line - 1))
789
+ sed -i.bak "${actual_line}d" "$tmp_file" && rm -f "${tmp_file}.bak"
790
+ section_end=$((section_end - 1))
791
+ fi
792
+ fi
793
+
794
+ # Check if subsection header already exists
795
+ local has_subsection
796
+ has_subsection=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -c "^### Phase ${phase_id}:" || true)
797
+ has_subsection=${has_subsection:-0}
798
+
799
+ if [[ "$has_subsection" -gt 0 ]]; then
800
+ # Find the subsection header line and append entries after it
801
+ local sub_line
802
+ sub_line=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -n "^### Phase ${phase_id}:" | head -1 | cut -d: -f1)
803
+ local actual_sub_line=$((section_line + sub_line - 1))
804
+ # Find end of subsection (next ### or next ## or --- or end of section)
805
+ local sub_end_rel
806
+ sub_end_rel=$(tail -n +$((actual_sub_line + 1)) "$tmp_file" | grep -n "^###\|^## \|^---$" | head -1 | cut -d: -f1)
807
+ local insert_at
808
+ if [[ -n "$sub_end_rel" ]]; then
809
+ insert_at=$((actual_sub_line + sub_end_rel - 1))
810
+ else
811
+ insert_at=$section_end
812
+ fi
813
+ # Insert entries before the boundary
814
+ local temp_entries="${tmp_file}.entries"
815
+ {
816
+ head -n "$insert_at" "$tmp_file"
817
+ printf '%s' "$entries"
818
+ tail -n +$((insert_at + 1)) "$tmp_file"
819
+ } > "$temp_entries" && mv "$temp_entries" "$tmp_file"
820
+ else
821
+ # Create new subsection at end of Build Learnings section (before --- or next ##)
822
+ # Find last content line in section (skip trailing --- and blanks)
823
+ local insert_at
824
+ # Insert before the separator (---) that ends the section, or before next ## header
825
+ local sep_line
826
+ sep_line=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -n "^---$" | tail -1 | cut -d: -f1)
827
+ if [[ -n "$sep_line" ]]; then
828
+ insert_at=$((section_line + sep_line - 2))
829
+ else
830
+ insert_at=$section_end
831
+ fi
832
+
833
+ local temp_entries="${tmp_file}.entries"
834
+ {
835
+ head -n "$insert_at" "$tmp_file"
836
+ echo ""
837
+ echo "$subsection_header"
838
+ printf '%s' "$entries"
839
+ tail -n +$((insert_at + 1)) "$tmp_file"
840
+ } > "$temp_entries" && mv "$temp_entries" "$tmp_file"
841
+ fi
842
+
843
+ # Update Evolution Log
844
+ local ev_entry="| ${ts} | phase-${phase_id} | build_learnings | Added ${written} learnings from Phase ${phase_id}: ${phase_name} |"
845
+ local ev_separator
846
+ ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
847
+ if [[ -n "$ev_separator" ]]; then
848
+ # Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
849
+ AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
850
+ fi
851
+
852
+ # Update METADATA stats: increment total_build_learnings
853
+ local current_count
854
+ current_count=$(grep '"total_build_learnings":' "$tmp_file" 2>/dev/null | grep -o '[0-9]*' | head -1 || true)
855
+ current_count=${current_count:-0}
856
+ local new_count=$((current_count + written))
857
+ awk -v count="$new_count" '{
858
+ gsub(/"total_build_learnings": [0-9]*/, "\"total_build_learnings\": " count)
859
+ print
860
+ }' "$tmp_file" > "${tmp_file}.stats" && mv "${tmp_file}.stats" "$tmp_file"
861
+
862
+ # Update last_evolved
863
+ awk -v ts="$ts" '/"last_evolved":/ { gsub(/"last_evolved": "[^"]*"/, "\"last_evolved\": \"" ts "\""); } {print}' "$tmp_file" > "${tmp_file}.meta" && mv "${tmp_file}.meta" "$tmp_file"
864
+
865
+ # Safety guard: never overwrite QUEEN.md with empty content
866
+ if [[ ! -s "$tmp_file" ]]; then
867
+ rm -f "$tmp_file"
868
+ json_err "$E_INTERNAL" "queen-write-learnings produced empty output — aborting to protect QUEEN.md"
869
+ return 1
870
+ fi
871
+
872
+ # Atomic move
873
+ mv "$tmp_file" "$queen_file"
874
+
875
+ json_ok "$(jq -n --argjson written "$written" --arg phase "$phase_id" --arg ts "$ts" \
876
+ '{written: $written, phase: $phase, timestamp: $ts}')"
877
+ }
878
+
879
+ # ============================================================================
880
+ # _queen_promote_instinct
881
+ # Promote a high-confidence instinct to QUEEN.md Instincts section
882
+ # Usage: queen-promote-instinct <trigger> <action> [confidence] [domain]
883
+ # ============================================================================
884
+ _queen_promote_instinct() {
885
+ local trigger="${1:-}"
886
+ local action="${2:-}"
887
+ local confidence="${3:-0.8}"
888
+ local domain="${4:-workflow}"
889
+
890
+ # Validate required arguments
891
+ [[ -z "$trigger" ]] && { json_err "$E_VALIDATION_FAILED" "Usage: queen-promote-instinct <trigger> <action> [confidence] [domain]" '{"missing":"trigger"}'; return 1; }
892
+ [[ -z "$action" ]] && { json_err "$E_VALIDATION_FAILED" "Usage: queen-promote-instinct <trigger> <action> [confidence] [domain]" '{"missing":"action"}'; return 1; }
893
+
894
+ local queen_file="$AETHER_ROOT/.aether/QUEEN.md"
895
+
896
+ if [[ ! -f "$queen_file" ]]; then
897
+ json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found. Run queen-init first." '{"path":".aether/QUEEN.md"}'
898
+ return 1
899
+ fi
900
+
901
+ # Dedup check: skip if action already in QUEEN.md
902
+ if grep -Fq -- "$action" "$queen_file" 2>/dev/null; then
903
+ json_ok '{"promoted":false,"written":0,"reason":"duplicate"}'
904
+ return 0
905
+ fi
906
+
907
+ local ts
908
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
909
+
910
+ # Build entry
911
+ local entry="- [instinct] **${domain}** (${confidence}): When ${trigger}, then ${action}"
912
+
913
+ # Create temp file for atomic write
914
+ local tmp_file="${queen_file}.tmp.$$"
915
+ cp "$queen_file" "$tmp_file"
916
+
917
+ # Find Instincts section
918
+ local section_line
919
+ section_line=$(grep -n '^## Instincts$' "$tmp_file" | head -1 | cut -d: -f1)
920
+
921
+ if [[ -z "$section_line" ]]; then
922
+ rm -f "$tmp_file"
923
+ json_err "$E_VALIDATION_FAILED" "Instincts section not found in QUEEN.md. Is this a v2 format file?" '{"section":"Instincts"}'
924
+ return 1
925
+ fi
926
+
927
+ # Find end of section
928
+ local next_section_line
929
+ next_section_line=$(tail -n +$((section_line + 1)) "$tmp_file" | grep -n "^## " | head -1 | cut -d: -f1)
930
+ local section_end
931
+ if [[ -n "$next_section_line" ]]; then
932
+ section_end=$((section_line + next_section_line - 1))
933
+ else
934
+ section_end=$(wc -l < "$tmp_file")
935
+ fi
936
+
937
+ # Check for placeholder and replace or append
938
+ # SUPPRESS:OK -- existence-test: grep returns 1 when no matches
939
+ local has_placeholder
940
+ has_placeholder=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -c "No instincts recorded yet" || true)
941
+ has_placeholder=${has_placeholder:-0}
942
+
943
+ if [[ "$has_placeholder" -gt 0 ]]; then
944
+ local placeholder_line
945
+ placeholder_line=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -n "^\\*No instincts recorded yet" | head -1 | cut -d: -f1)
946
+ if [[ -n "$placeholder_line" ]]; then
947
+ local actual_line=$((section_line + placeholder_line - 1))
948
+ # Use head/tail instead of sed c-command for newline safety
949
+ {
950
+ head -n $((actual_line - 1)) "$tmp_file"
951
+ echo "$entry"
952
+ tail -n +$((actual_line + 1)) "$tmp_file"
953
+ } > "${tmp_file}.rep" && mv "${tmp_file}.rep" "$tmp_file"
954
+ fi
955
+ else
956
+ # Append entry before section separator (---) or at end
957
+ local sep_line
958
+ sep_line=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -n "^---$" | tail -1 | cut -d: -f1)
959
+ local insert_at
960
+ if [[ -n "$sep_line" ]]; then
961
+ insert_at=$((section_line + sep_line - 2))
962
+ else
963
+ insert_at=$section_end
964
+ fi
965
+
966
+ local temp_entries="${tmp_file}.entries"
967
+ {
968
+ head -n "$insert_at" "$tmp_file"
969
+ echo "$entry"
970
+ tail -n +$((insert_at + 1)) "$tmp_file"
971
+ } > "$temp_entries" && mv "$temp_entries" "$tmp_file"
972
+ fi
973
+
974
+ # Update Evolution Log
975
+ local ev_entry="| ${ts} | instinct | promoted_instinct | ${domain}: ${action:0:50}... |"
976
+ local ev_separator
977
+ ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
978
+ if [[ -n "$ev_separator" ]]; then
979
+ # Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
980
+ AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
981
+ fi
982
+
983
+ # Update METADATA stats: increment total_instincts
984
+ local current_count
985
+ current_count=$(grep '"total_instincts":' "$tmp_file" 2>/dev/null | grep -o '[0-9]*' | head -1 || true)
986
+ current_count=${current_count:-0}
987
+ local new_count=$((current_count + 1))
988
+ awk -v count="$new_count" '{
989
+ gsub(/"total_instincts": [0-9]*/, "\"total_instincts\": " count)
990
+ print
991
+ }' "$tmp_file" > "${tmp_file}.stats" && mv "${tmp_file}.stats" "$tmp_file"
992
+
993
+ # Update last_evolved
994
+ awk -v ts="$ts" '/"last_evolved":/ { gsub(/"last_evolved": "[^"]*"/, "\"last_evolved\": \"" ts "\""); } {print}' "$tmp_file" > "${tmp_file}.meta" && mv "${tmp_file}.meta" "$tmp_file"
995
+
996
+ # Safety guard: never overwrite QUEEN.md with empty content
997
+ if [[ ! -s "$tmp_file" ]]; then
998
+ rm -f "$tmp_file"
999
+ json_err "$E_INTERNAL" "queen-promote-instinct produced empty output — aborting to protect QUEEN.md"
1000
+ return 1
1001
+ fi
1002
+
1003
+ # Atomic move
1004
+ mv "$tmp_file" "$queen_file"
1005
+
1006
+ json_ok "$(jq -n --arg domain "$domain" --argjson confidence "$confidence" --arg ts "$ts" \
1007
+ '{promoted: true, written: 1, domain: $domain, confidence: $confidence, timestamp: $ts}')"
1008
+ }
1009
+
1010
+ # ============================================================================
1011
+ # _queen_seed_from_hive
1012
+ # Seed QUEEN.md Codebase Patterns section from cross-colony hive wisdom
1013
+ # Usage: queen-seed-from-hive [--domain <csv>] [--limit <N>]
1014
+ # Writes [hive]-tagged entries to Codebase Patterns. NON-BLOCKING.
1015
+ # ============================================================================
1016
+ _queen_seed_from_hive() {
1017
+ local qs_domain=""
1018
+ local qs_limit="5"
1019
+
1020
+ while [[ $# -gt 0 ]]; do
1021
+ case "$1" in
1022
+ --domain) qs_domain="${2:-}"; shift 2 ;;
1023
+ --limit) qs_limit="${2:-5}"; shift 2 ;;
1024
+ *) shift ;;
1025
+ esac
1026
+ done
1027
+
1028
+ local queen_file="$AETHER_ROOT/.aether/QUEEN.md"
1029
+ [[ -f "$queen_file" ]] || { json_ok '{"seeded":0,"reason":"no_queen_md"}'; return 0; }
1030
+
1031
+ # Read hive wisdom with domain filter
1032
+ local hive_args=(hive-read --limit "$qs_limit" --min-confidence 0.5 --format json)
1033
+ [[ -n "$qs_domain" ]] && hive_args+=(--domain "$qs_domain")
1034
+ local hive_result
1035
+ hive_result=$(bash "$0" "${hive_args[@]}" 2>/dev/null) || { json_ok '{"seeded":0,"reason":"hive_read_failed"}'; return 0; }
1036
+
1037
+ local entry_count
1038
+ entry_count=$(echo "$hive_result" | jq -r '.result.total_matched // 0' 2>/dev/null)
1039
+ [[ "$entry_count" -eq 0 ]] && { json_ok '{"seeded":0,"reason":"no_matching_wisdom"}'; return 0; }
1040
+
1041
+ # Build entries for QUEEN.md Codebase Patterns section
1042
+ local entries=""
1043
+ local seeded=0
1044
+ while IFS= read -r encoded; do
1045
+ [[ -z "$encoded" ]] && continue
1046
+ local text confidence
1047
+ text=$(echo "$encoded" | base64 -d | jq -r '.text // empty')
1048
+ confidence=$(echo "$encoded" | base64 -d | jq -r '.confidence // 0')
1049
+ [[ -z "$text" ]] && continue
1050
+
1051
+ # Dedup: skip if already in QUEEN.md
1052
+ if grep -Fq -- "$text" "$queen_file" 2>/dev/null; then continue; fi
1053
+
1054
+ entries="${entries}- [hive] ${text} (cross-colony, confidence: ${confidence})"$'\n'
1055
+ seeded=$((seeded + 1))
1056
+ done < <(echo "$hive_result" | jq -r '.result.entries[] | @base64')
1057
+
1058
+ [[ "$seeded" -eq 0 ]] && { json_ok '{"seeded":0,"reason":"all_duplicates"}'; return 0; }
1059
+
1060
+ # Write to Codebase Patterns section (reuse placeholder removal pattern from _queen_write_learnings)
1061
+ local tmp_file="${queen_file}.tmp.$$"
1062
+ cp "$queen_file" "$tmp_file"
1063
+
1064
+ local section_line
1065
+ section_line=$(grep -n '^## Codebase Patterns$' "$tmp_file" | head -1 | cut -d: -f1)
1066
+
1067
+ if [[ -z "$section_line" ]]; then
1068
+ rm -f "$tmp_file"
1069
+ json_ok '{"seeded":0,"reason":"no_codebase_patterns_section"}'
1070
+ return 0
1071
+ fi
1072
+
1073
+ # Find end of section (next ## header or end of file)
1074
+ local next_section_line
1075
+ next_section_line=$(tail -n +$((section_line + 1)) "$tmp_file" | grep -n "^## " | head -1 | cut -d: -f1)
1076
+ local section_end
1077
+ if [[ -n "$next_section_line" ]]; then
1078
+ section_end=$((section_line + next_section_line - 1))
1079
+ else
1080
+ section_end=$(wc -l < "$tmp_file")
1081
+ fi
1082
+
1083
+ # Remove placeholder if present
1084
+ # SUPPRESS:OK -- existence-test: grep returns 1 when no matches
1085
+ local has_placeholder
1086
+ has_placeholder=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -c "No codebase patterns recorded yet" || true)
1087
+ has_placeholder=${has_placeholder:-0}
1088
+
1089
+ if [[ "$has_placeholder" -gt 0 ]]; then
1090
+ local placeholder_line
1091
+ placeholder_line=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -n "^\\*No codebase patterns recorded yet" | head -1 | cut -d: -f1)
1092
+ if [[ -n "$placeholder_line" ]]; then
1093
+ local actual_line=$((section_line + placeholder_line - 1))
1094
+ sed -i.bak "${actual_line}d" "$tmp_file" && rm -f "${tmp_file}.bak"
1095
+ section_end=$((section_end - 1))
1096
+ fi
1097
+ fi
1098
+
1099
+ # Insert entries before the separator (---) that ends the section, or at end
1100
+ local sep_line
1101
+ sep_line=$(sed -n "${section_line},${section_end}p" "$tmp_file" | grep -n "^---$" | tail -1 | cut -d: -f1)
1102
+ local insert_at
1103
+ if [[ -n "$sep_line" ]]; then
1104
+ insert_at=$((section_line + sep_line - 2))
1105
+ else
1106
+ insert_at=$section_end
1107
+ fi
1108
+
1109
+ local temp_entries="${tmp_file}.entries"
1110
+ {
1111
+ head -n "$insert_at" "$tmp_file"
1112
+ printf '%s' "$entries"
1113
+ tail -n +$((insert_at + 1)) "$tmp_file"
1114
+ } > "$temp_entries" && mv "$temp_entries" "$tmp_file"
1115
+
1116
+ # Update Evolution Log with seed event
1117
+ local ts
1118
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1119
+ local ev_entry="| ${ts} | hive | seed | Seeded ${seeded} cross-colony patterns from hive |"
1120
+ local ev_separator
1121
+ ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
1122
+ if [[ -n "$ev_separator" ]]; then
1123
+ # Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
1124
+ AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
1125
+ fi
1126
+
1127
+ # Update METADATA stats: increment total_codebase_patterns
1128
+ local current_count
1129
+ current_count=$(grep '"total_codebase_patterns":' "$tmp_file" 2>/dev/null | grep -o '[0-9]*' | head -1 || true)
1130
+ current_count=${current_count:-0}
1131
+ local new_count=$((current_count + seeded))
1132
+ awk -v count="$new_count" '{
1133
+ gsub(/"total_codebase_patterns": [0-9]*/, "\"total_codebase_patterns\": " count)
1134
+ print
1135
+ }' "$tmp_file" > "${tmp_file}.stats" && mv "${tmp_file}.stats" "$tmp_file"
1136
+
1137
+ # Update last_evolved
1138
+ awk -v ts="$ts" '/"last_evolved":/ { gsub(/"last_evolved": "[^"]*"/, "\"last_evolved\": \"" ts "\""); } {print}' "$tmp_file" > "${tmp_file}.meta" && mv "${tmp_file}.meta" "$tmp_file"
1139
+
1140
+ # Safety guard: never overwrite QUEEN.md with empty content
1141
+ if [[ ! -s "$tmp_file" ]]; then
1142
+ rm -f "$tmp_file"
1143
+ json_err "$E_INTERNAL" "queen-seed-from-hive produced empty output — aborting to protect QUEEN.md"
1144
+ return 1
1145
+ fi
1146
+
1147
+ # Atomic move
1148
+ mv "$tmp_file" "$queen_file"
1149
+
1150
+ json_ok "$(jq -n --argjson seeded "$seeded" '{seeded: $seeded}')"
1151
+ }
1152
+
1153
+ # ============================================================================
1154
+ # _domain_detect
1155
+ # Auto-detect repo domain tags from file/directory presence
1156
+ # Usage: domain-detect
1157
+ # Returns: {"tags":"node,typescript,..."}
1158
+ # ============================================================================
1159
+ _domain_detect() {
1160
+ local tags=""
1161
+ local root="${AETHER_ROOT:-.}"
1162
+
1163
+ [[ -f "$root/package.json" ]] && tags="${tags:+$tags,}node"
1164
+ [[ -f "$root/tsconfig.json" ]] && tags="${tags:+$tags,}typescript"
1165
+ [[ -f "$root/Cargo.toml" ]] && tags="${tags:+$tags,}rust"
1166
+ [[ -f "$root/go.mod" ]] && tags="${tags:+$tags,}go"
1167
+ [[ -f "$root/requirements.txt" || -f "$root/pyproject.toml" ]] && tags="${tags:+$tags,}python"
1168
+ [[ -d "$root/wp-content" || -f "$root/wp-config.php" ]] && tags="${tags:+$tags,}wordpress"
1169
+ [[ -f "$root/Gemfile" ]] && tags="${tags:+$tags,}ruby"
1170
+ [[ -f "$root/.aether/aether-utils.sh" ]] && tags="${tags:+$tags,}aether"
1171
+ [[ -d "$root/.next" || -f "$root/next.config.js" || -f "$root/next.config.ts" ]] && tags="${tags:+$tags,}nextjs"
1172
+ [[ -f "$root/docker-compose.yml" || -f "$root/Dockerfile" ]] && tags="${tags:+$tags,}docker"
1173
+
1174
+ json_ok "$(jq -n --arg tags "$tags" '{tags: $tags}')"
1175
+ }
1176
+
1177
+ # ============================================================================
1178
+ # _queen_migrate
1179
+ # Convert a v1 QUEEN.md (emoji headers) to v2 (clean headers) format
1180
+ # Usage: queen-migrate [--target hub|local]
1181
+ # --target hub -> migrates $HOME/.aether/QUEEN.md
1182
+ # --target local -> migrates $AETHER_ROOT/.aether/QUEEN.md (default)
1183
+ # If already v2 format, prints message and exits 0.
1184
+ # ============================================================================
1185
+ _queen_migrate() {
1186
+ local qm_target="local"
1187
+
1188
+ while [[ $# -gt 0 ]]; do
1189
+ case "$1" in
1190
+ --target) qm_target="${2:-local}"; shift 2 ;;
1191
+ *) shift ;;
1192
+ esac
1193
+ done
1194
+
1195
+ local qm_file
1196
+ if [[ "$qm_target" == "hub" ]]; then
1197
+ qm_file="$HOME/.aether/QUEEN.md"
1198
+ else
1199
+ qm_file="$AETHER_ROOT/.aether/QUEEN.md"
1200
+ fi
1201
+
1202
+ if [[ ! -f "$qm_file" ]]; then
1203
+ json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found at $qm_file" '{"target":"'"$qm_target"'"}'
1204
+ exit 1
1205
+ fi
1206
+
1207
+ # Check if already v2 format
1208
+ if grep -q '^## Build Learnings$' "$qm_file" 2>/dev/null; then # SUPPRESS:OK -- existence-test: format detection
1209
+ json_ok '{"migrated":false,"reason":"Already v2 format"}'
1210
+ return 0
1211
+ fi
1212
+
1213
+ # Extract content from v1 format using _extract_wisdom_sections (maps v1 -> v2 keys)
1214
+ local qm_wisdom
1215
+ qm_wisdom=$(_extract_wisdom_sections "$qm_file")
1216
+
1217
+ # Extract real entries from each section
1218
+ # v1 _extract_wisdom_sections produces double-encoded JSON strings, so we need
1219
+ # to unwrap them: jq -r removes outer quotes, then sed strips remaining inner quotes
1220
+ local qm_uprefs qm_codebase qm_learnings qm_instincts
1221
+ qm_uprefs=$(echo "$qm_wisdom" | jq -r '.user_prefs // ""' 2>/dev/null | sed 's/^"//;s/"$//' | sed 's/\\n/\n/g') # SUPPRESS:OK -- read-default: may be empty
1222
+ qm_uprefs=$(echo "$qm_uprefs" | grep -E '^- ' || true) # SUPPRESS:OK -- grep returns 1 on no matches
1223
+ qm_codebase=$(echo "$qm_wisdom" | jq -r '.codebase_patterns // ""' 2>/dev/null | sed 's/^"//;s/"$//' | sed 's/\\n/\n/g') # SUPPRESS:OK -- read-default: may be empty
1224
+ qm_codebase=$(echo "$qm_codebase" | grep -E '^- ' || true) # SUPPRESS:OK -- grep returns 1 on no matches
1225
+ qm_learnings=$(echo "$qm_wisdom" | jq -r '.build_learnings // ""' 2>/dev/null | sed 's/^"//;s/"$//' | sed 's/\\n/\n/g') # SUPPRESS:OK -- read-default: may be empty
1226
+ qm_learnings=$(echo "$qm_learnings" | grep -E '^- ' || true) # SUPPRESS:OK -- grep returns 1 on no matches
1227
+ qm_instincts=$(echo "$qm_wisdom" | jq -r '.instincts // ""' 2>/dev/null | sed 's/^"//;s/"$//' | sed 's/\\n/\n/g') # SUPPRESS:OK -- read-default: may be empty
1228
+ qm_instincts=$(echo "$qm_instincts" | grep -E '^- ' || true) # SUPPRESS:OK -- grep returns 1 on no matches
1229
+
1230
+ local qm_ts
1231
+ qm_ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1232
+
1233
+ # Build fresh v2 QUEEN.md
1234
+ local qm_tmp="${qm_file}.migrate.$$"
1235
+ cat > "$qm_tmp" << MIGRATEEOF
1236
+ # QUEEN.md -- Colony Wisdom
1237
+
1238
+ > Last evolved: $qm_ts
1239
+ > Wisdom version: 2.0.0
1240
+
1241
+ ---
1242
+
1243
+ ## User Preferences
1244
+
1245
+ Communication style, expertise level, and decision-making patterns observed from the user (the Queen). These shape how the colony communicates and what it prioritizes.
1246
+
1247
+ MIGRATEEOF
1248
+
1249
+ if [[ -n "$qm_uprefs" ]]; then
1250
+ echo "$qm_uprefs" >> "$qm_tmp"
1251
+ else
1252
+ echo "*No user preferences recorded yet.*" >> "$qm_tmp"
1253
+ fi
1254
+
1255
+ cat >> "$qm_tmp" << 'MIGRATEEOF'
1256
+
1257
+ ---
1258
+
1259
+ ## Codebase Patterns
1260
+
1261
+ Validated approaches that work in this codebase, and anti-patterns to avoid. Includes architecture conventions, naming patterns, error handling style, and technology-specific insights.
1262
+
1263
+ MIGRATEEOF
1264
+
1265
+ if [[ -n "$qm_codebase" ]]; then
1266
+ echo "$qm_codebase" >> "$qm_tmp"
1267
+ else
1268
+ echo "*No codebase patterns recorded yet.*" >> "$qm_tmp"
1269
+ fi
1270
+
1271
+ cat >> "$qm_tmp" << 'MIGRATEEOF'
1272
+
1273
+ ---
1274
+
1275
+ ## Build Learnings
1276
+
1277
+ What worked and what failed during builds. Captures the full picture of colony experience -- successes, failures, and adjustments.
1278
+
1279
+ MIGRATEEOF
1280
+
1281
+ if [[ -n "$qm_learnings" ]]; then
1282
+ echo "$qm_learnings" >> "$qm_tmp"
1283
+ else
1284
+ echo "*No build learnings recorded yet.*" >> "$qm_tmp"
1285
+ fi
1286
+
1287
+ cat >> "$qm_tmp" << 'MIGRATEEOF'
1288
+
1289
+ ---
1290
+
1291
+ ## Instincts
1292
+
1293
+ High-confidence behavioral patterns that have been validated through repeated colony work. Auto-promoted when confidence reaches 0.8 or higher.
1294
+
1295
+ MIGRATEEOF
1296
+
1297
+ if [[ -n "$qm_instincts" ]]; then
1298
+ echo "$qm_instincts" >> "$qm_tmp"
1299
+ else
1300
+ echo "*No instincts recorded yet.*" >> "$qm_tmp"
1301
+ fi
1302
+
1303
+ cat >> "$qm_tmp" << MIGRATEEOF
1304
+
1305
+ ---
1306
+
1307
+ ## Evolution Log
1308
+
1309
+ | Date | Source | Type | Details |
1310
+ |------|--------|------|---------|
1311
+ | $qm_ts | system | migration | Migrated from v1 to v2 format |
1312
+
1313
+ ---
1314
+
1315
+ <!-- METADATA {"version":"2.0.0","wisdom_version":"2.0","last_evolved":"$qm_ts","colonies_contributed":[],"stats":{"total_user_prefs":0,"total_codebase_patterns":0,"total_build_learnings":0,"total_instincts":0}} -->
1316
+ MIGRATEEOF
1317
+
1318
+ # Atomic move
1319
+ mv "$qm_tmp" "$qm_file"
1320
+
1321
+ json_ok "$(jq -n --arg target "$qm_target" '{migrated: true, target: $target, format: "v2"}')"
1322
+ }
1323
+
1324
+ # ============================================================================
1325
+ # _colony_name()
1326
+ # Derive human-readable colony name from repo context
1327
+ # Fallback chain: COLONY_STATE.json -> package.json -> directory basename
1328
+ # Usage: colony-name
1329
+ # Returns: {"ok":true,"result":{"name":"Aether Colony","source":"colony_state"}}
1330
+ # ============================================================================
1331
+ _colony_name() {
1332
+ local name=""
1333
+ local source="directory"
1334
+
1335
+ # 1. Check COLONY_STATE.json for pre-set colony_name
1336
+ if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
1337
+ local preset
1338
+ preset=$(jq -r '.colony_name // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "")
1339
+ if [[ -n "$preset" ]]; then
1340
+ name="$preset"
1341
+ source="colony_state"
1342
+ fi
1343
+ fi
1344
+
1345
+ # 2. Fall back to package.json name
1346
+ if [[ -z "$name" ]] && [[ -f "$AETHER_ROOT/package.json" ]]; then
1347
+ local pkg_name
1348
+ pkg_name=$(jq -r '.name // empty' "$AETHER_ROOT/package.json" 2>/dev/null || echo "")
1349
+ if [[ -n "$pkg_name" ]]; then
1350
+ # Strip @scope/ prefix
1351
+ name="${pkg_name#@*/}"
1352
+ source="package_json"
1353
+ fi
1354
+ fi
1355
+
1356
+ # 3. Fall back to directory basename
1357
+ if [[ -z "$name" ]]; then
1358
+ name="$(basename "$AETHER_ROOT")"
1359
+ source="directory"
1360
+ fi
1361
+
1362
+ # Convert kebab-case to title case
1363
+ name=$(echo "$name" | sed 's/-/ /g' | awk '{for(i=1;i<=NF;i++) $i=toupper(substr($i,1,1)) substr($i,2)};1')
1364
+
1365
+ json_ok "$(jq -n --arg name "$name" --arg source "$source" '{name: $name, source: $source}')"
1366
+ }
1367
+
1368
+ # ============================================================================
1369
+ # _colony_depth()
1370
+ # Read or write colony depth setting (controls agent spawn thoroughness)
1371
+ # Values: light, standard (default), deep, full
1372
+ # NOTE: This is "colony depth" (build thoroughness), NOT "spawn depth"
1373
+ # (_spawn_get_depth which controls sub-worker nesting levels 1/2/3).
1374
+ # Usage: colony-depth [get|set <value>]
1375
+ # Returns: {"ok":true,"result":{"depth":"standard","source":"..."}}
1376
+ # ============================================================================
1377
+ _colony_depth() {
1378
+ local action="${1:-get}"
1379
+
1380
+ case "$action" in
1381
+ get)
1382
+ local depth
1383
+ depth=$(jq -r '.colony_depth // "standard"' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null || echo "standard")
1384
+ local source="default"
1385
+ if [[ "$(jq -r '.colony_depth // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null)" != "" ]]; then
1386
+ source="colony_state"
1387
+ fi
1388
+ json_ok "$(jq -n --arg depth "$depth" --arg source "$source" '{depth: $depth, source: $source}')"
1389
+ ;;
1390
+ set)
1391
+ local new_depth="${2:-}"
1392
+ case "$new_depth" in
1393
+ light|standard|deep|full)
1394
+ local tmp="${DATA_DIR}/COLONY_STATE.json.tmp.$$"
1395
+ jq --arg d "$new_depth" '.colony_depth = $d' "$DATA_DIR/COLONY_STATE.json" > "$tmp" && mv "$tmp" "$DATA_DIR/COLONY_STATE.json"
1396
+ json_ok "$(jq -n --arg depth "$new_depth" '{depth: $depth, updated: true}')"
1397
+ ;;
1398
+ *)
1399
+ json_err "$E_VALIDATION_FAILED" "Invalid depth. Use: light, standard, deep, full" "$(jq -n --arg got "$new_depth" '{got: $got}')"
1400
+ return 1
1401
+ ;;
1402
+ esac
1403
+ ;;
1404
+ *)
1405
+ json_err "$E_VALIDATION_FAILED" "Usage: colony-depth [get|set <value>]" "{}"
1406
+ return 1
1407
+ ;;
1408
+ esac
1409
+ }
1410
+
1411
+ # ============================================================================
1412
+ # _queen_write_charter()
1413
+ # Write or update colony charter content in QUEEN.md using [charter] tags
1414
+ # Writes intent+vision to User Preferences, governance+goals to Codebase Patterns
1415
+ # Handles re-init: removes existing [charter] entries before writing new ones
1416
+ # Usage: charter-write --intent "text" --vision "text" --governance "text" --goals "text" [--colony-name "name"]
1417
+ # Returns: {"ok":true,"result":{"written":N,"updated":bool,"colony_name":"...","timestamp":"..."}}
1418
+ # ============================================================================
1419
+ _queen_write_charter() {
1420
+ local cw_intent=""
1421
+ local cw_vision=""
1422
+ local cw_governance=""
1423
+ local cw_goals=""
1424
+ local cw_colony_name=""
1425
+
1426
+ # Parse arguments
1427
+ while [[ $# -gt 0 ]]; do
1428
+ case "$1" in
1429
+ --intent) cw_intent="${2:-}"; shift 2 ;;
1430
+ --vision) cw_vision="${2:-}"; shift 2 ;;
1431
+ --governance) cw_governance="${2:-}"; shift 2 ;;
1432
+ --goals) cw_goals="${2:-}"; shift 2 ;;
1433
+ --colony-name) cw_colony_name="${2:-}"; shift 2 ;;
1434
+ *) shift ;;
1435
+ esac
1436
+ done
1437
+
1438
+ # At least one content field must be provided
1439
+ if [[ -z "$cw_intent" && -z "$cw_vision" && -z "$cw_governance" && -z "$cw_goals" ]]; then
1440
+ json_err "$E_VALIDATION_FAILED" "Usage: charter-write --intent <text> --vision <text> --governance <text> --goals <text> [--colony-name <name>]" '{"missing":"all_fields"}'
1441
+ return 1
1442
+ fi
1443
+
1444
+ local queen_file="$AETHER_ROOT/.aether/QUEEN.md"
1445
+ if [[ ! -f "$queen_file" ]]; then
1446
+ json_err "$E_FILE_NOT_FOUND" "QUEEN.md not found. Run queen-init first." '{"path":".aether/QUEEN.md"}'
1447
+ return 1
1448
+ fi
1449
+
1450
+ local ts
1451
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
1452
+
1453
+ # Derive colony name if not provided
1454
+ if [[ -z "$cw_colony_name" ]]; then
1455
+ local cn_result
1456
+ cn_result=$(bash "$0" colony-name 2>/dev/null) || true
1457
+ cw_colony_name=$(echo "$cn_result" | jq -r '.result.name // ""' 2>/dev/null) || true
1458
+ fi
1459
+
1460
+ # Cap each content field at 200 characters
1461
+ _cap_200() {
1462
+ local val="$1"
1463
+ if [[ ${#val} -gt 200 ]]; then
1464
+ echo "${val:0:197}..."
1465
+ else
1466
+ echo "$val"
1467
+ fi
1468
+ }
1469
+ cw_intent=$(_cap_200 "$cw_intent")
1470
+ cw_vision=$(_cap_200 "$cw_vision")
1471
+ cw_governance=$(_cap_200 "$cw_governance")
1472
+ cw_goals=$(_cap_200 "$cw_goals")
1473
+
1474
+ # Auto-set colony_name in COLONY_STATE.json if not already set
1475
+ if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
1476
+ local current_name
1477
+ current_name=$(jq -r '.colony_name // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null) || true
1478
+ if [[ -z "$current_name" && -n "$cw_colony_name" ]]; then
1479
+ local tmp_state="${DATA_DIR}/COLONY_STATE.json.tmp.$$"
1480
+ jq --arg cn "$cw_colony_name" '.colony_name = $cn' "$DATA_DIR/COLONY_STATE.json" > "$tmp_state" && mv "$tmp_state" "$DATA_DIR/COLONY_STATE.json"
1481
+ fi
1482
+ fi
1483
+
1484
+ # Create temp file for atomic write
1485
+ local tmp_file="${queen_file}.tmp.$$"
1486
+ cp "$queen_file" "$tmp_file"
1487
+
1488
+ # Count existing charter entries before removal (to detect re-init)
1489
+ local existing_charter_count
1490
+ existing_charter_count=$(grep -c '^- \[charter\] ' "$tmp_file" 2>/dev/null || true)
1491
+ existing_charter_count=${existing_charter_count:-0}
1492
+ local is_update="false"
1493
+ if [[ "$existing_charter_count" -gt 0 ]]; then
1494
+ is_update="true"
1495
+ fi
1496
+
1497
+ # Remove ALL existing charter entries (re-init safety -- CHARTER-03)
1498
+ sed -i.bak '/^- \[charter\] /d' "$tmp_file" && rm -f "${tmp_file}.bak"
1499
+
1500
+ # Helper: insert entries into a QUEEN.md section
1501
+ # Usage: _insert_section_entries <tmp_file> <section_name> <placeholder_text> <entries>
1502
+ _insert_section_entries() {
1503
+ local is_file="$1"
1504
+ local is_section="$2"
1505
+ local is_placeholder="$3"
1506
+ local is_entries="$4"
1507
+ local is_tmp="${is_file}.insert"
1508
+
1509
+ local is_section_line
1510
+ is_section_line=$(grep -n "^## ${is_section}\$" "$is_file" | head -1 | cut -d: -f1)
1511
+
1512
+ if [[ -z "$is_section_line" ]]; then
1513
+ return 1
1514
+ fi
1515
+
1516
+ # Find end of section (next ## header or end of file)
1517
+ local is_next_section
1518
+ is_next_section=$(tail -n +$((is_section_line + 1)) "$is_file" | grep -n "^## " | head -1 | cut -d: -f1)
1519
+ local is_section_end
1520
+ if [[ -n "$is_next_section" ]]; then
1521
+ is_section_end=$((is_section_line + is_next_section - 1))
1522
+ else
1523
+ is_section_end=$(wc -l < "$is_file")
1524
+ fi
1525
+
1526
+ # Remove placeholder if present
1527
+ # SUPPRESS:OK -- existence-test: grep returns 1 when no matches
1528
+ local is_has_placeholder
1529
+ is_has_placeholder=$(sed -n "${is_section_line},${is_section_end}p" "$is_file" | grep -c "No .* recorded yet" || true)
1530
+ is_has_placeholder=${is_has_placeholder:-0}
1531
+
1532
+ if [[ "$is_has_placeholder" -gt 0 ]]; then
1533
+ local is_pl_line
1534
+ is_pl_line=$(sed -n "${is_section_line},${is_section_end}p" "$is_file" | grep -n "^\\*No .* recorded yet" | head -1 | cut -d: -f1)
1535
+ if [[ -n "$is_pl_line" ]]; then
1536
+ local is_actual_line=$((is_section_line + is_pl_line - 1))
1537
+ # Replace placeholder with entries
1538
+ {
1539
+ head -n $((is_actual_line - 1)) "$is_file"
1540
+ printf '%s\n' "$is_entries"
1541
+ tail -n +$((is_actual_line + 1)) "$is_file"
1542
+ } > "$is_tmp" && mv "$is_tmp" "$is_file"
1543
+ return 0
1544
+ fi
1545
+ fi
1546
+
1547
+ # No placeholder -- insert before section separator (---) or at section end
1548
+ local is_sep_line
1549
+ is_sep_line=$(sed -n "${is_section_line},${is_section_end}p" "$is_file" | grep -n "^---$" | tail -1 | cut -d: -f1)
1550
+ local is_insert_at
1551
+ if [[ -n "$is_sep_line" ]]; then
1552
+ is_insert_at=$((is_section_line + is_sep_line - 2))
1553
+ else
1554
+ is_insert_at=$is_section_end
1555
+ fi
1556
+
1557
+ {
1558
+ head -n "$is_insert_at" "$is_file"
1559
+ printf '%s\n' "$is_entries"
1560
+ tail -n +$((is_insert_at + 1)) "$is_file"
1561
+ } > "$is_tmp" && mv "$is_tmp" "$is_file"
1562
+ return 0
1563
+ }
1564
+
1565
+ # Build User Preferences entries (intent + vision)
1566
+ local up_entries=""
1567
+ local written=0
1568
+ local up_failed=0
1569
+ local cp_failed=0
1570
+ if [[ -n "$cw_intent" ]]; then
1571
+ up_entries="${up_entries}- [charter] **Intent**: ${cw_intent} (Colony: ${cw_colony_name})"
1572
+ written=$((written + 1))
1573
+ fi
1574
+ if [[ -n "$cw_vision" ]]; then
1575
+ up_entries="${up_entries}"$'\n'"- [charter] **Vision**: ${cw_vision} (Colony: ${cw_colony_name})"
1576
+ written=$((written + 1))
1577
+ fi
1578
+
1579
+ if [[ -n "$up_entries" ]]; then
1580
+ if ! _insert_section_entries "$tmp_file" "User Preferences" "user preferences" "$up_entries"; then
1581
+ up_failed=1
1582
+ echo "[charter-write] WARNING: '## User Preferences' section not found in QUEEN.md; entries dropped" >&2
1583
+ fi
1584
+ fi
1585
+
1586
+ # Build Codebase Patterns entries (governance + goals)
1587
+ local cp_entries=""
1588
+ if [[ -n "$cw_governance" ]]; then
1589
+ cp_entries="${cp_entries}- [charter] **Governance**: ${cw_governance} (Colony: ${cw_colony_name})"
1590
+ written=$((written + 1))
1591
+ fi
1592
+ if [[ -n "$cw_goals" ]]; then
1593
+ cp_entries="${cp_entries}"$'\n'"- [charter] **Goal**: ${cw_goals} (Colony: ${cw_colony_name})"
1594
+ written=$((written + 1))
1595
+ fi
1596
+
1597
+ if [[ -n "$cp_entries" ]]; then
1598
+ if ! _insert_section_entries "$tmp_file" "Codebase Patterns" "codebase patterns" "$cp_entries"; then
1599
+ cp_failed=1
1600
+ echo "[charter-write] WARNING: '## Codebase Patterns' section not found in QUEEN.md; entries dropped" >&2
1601
+ fi
1602
+ fi
1603
+
1604
+ # Update Evolution Log
1605
+ local ev_type="charter_initialized"
1606
+ local ev_details="Colony charter created for ${cw_colony_name}"
1607
+ if [[ "$is_update" == "true" ]]; then
1608
+ ev_type="charter_updated"
1609
+ ev_details="Colony charter updated for ${cw_colony_name}"
1610
+ fi
1611
+ local ev_entry="| ${ts} | system | ${ev_type} | ${ev_details} |"
1612
+ local ev_separator
1613
+ ev_separator=$(grep -n "^|------|" "$tmp_file" | tail -1 | cut -d: -f1 || true)
1614
+ if [[ -n "$ev_separator" ]]; then
1615
+ # Use ENVIRON instead of -v for user content to avoid C-escape interpretation (\n, \t, \\)
1616
+ AETHER_EV_ENTRY="$ev_entry" awk -v line="$ev_separator" 'NR==line{print; print ENVIRON["AETHER_EV_ENTRY"]; next}1' "$tmp_file" > "${tmp_file}.ev" && mv "${tmp_file}.ev" "$tmp_file"
1617
+ fi
1618
+
1619
+ # Update METADATA stats -- count non-charter list items in each section, add charter entries
1620
+ local up_section_line
1621
+ up_section_line=$(grep -n '^## User Preferences$' "$tmp_file" | head -1 | cut -d: -f1 || true)
1622
+
1623
+ # Count charter entries written to User Preferences
1624
+ local up_charter_written=0
1625
+ [[ -n "$cw_intent" ]] && up_charter_written=$((up_charter_written + 1))
1626
+ [[ -n "$cw_vision" ]] && up_charter_written=$((up_charter_written + 1))
1627
+
1628
+ local up_total=0
1629
+ if [[ -n "$up_section_line" ]]; then
1630
+ local up_next_section
1631
+ up_next_section=$(tail -n +$((up_section_line + 1)) "$tmp_file" | grep -n "^## " | head -1 | cut -d: -f1)
1632
+ local up_section_end
1633
+ if [[ -n "$up_next_section" ]]; then
1634
+ up_section_end=$((up_section_line + up_next_section - 1))
1635
+ else
1636
+ up_section_end=$(wc -l < "$tmp_file")
1637
+ fi
1638
+
1639
+ # Count non-charter list items in User Preferences
1640
+ local up_non_charter
1641
+ # Count all list items, then subtract charter ones
1642
+ local up_all_items
1643
+ up_all_items=$(sed -n "${up_section_line},${up_section_end}p" "$tmp_file" | grep -c '^- ' || true)
1644
+ up_all_items=${up_all_items:-0}
1645
+ local up_charter_items
1646
+ up_charter_items=$(sed -n "${up_section_line},${up_section_end}p" "$tmp_file" | grep -c '^- \[charter\] ' || true)
1647
+ up_charter_items=${up_charter_items:-0}
1648
+ up_non_charter=$((up_all_items - up_charter_items))
1649
+ up_total=$((up_non_charter + up_charter_written))
1650
+ fi
1651
+
1652
+ # Count for Codebase Patterns
1653
+ local cp_section_line
1654
+ cp_section_line=$(grep -n '^## Codebase Patterns$' "$tmp_file" | head -1 | cut -d: -f1 || true)
1655
+
1656
+ # Count charter entries written to Codebase Patterns
1657
+ local cp_charter_written=0
1658
+ [[ -n "$cw_governance" ]] && cp_charter_written=$((cp_charter_written + 1))
1659
+ [[ -n "$cw_goals" ]] && cp_charter_written=$((cp_charter_written + 1))
1660
+
1661
+ local cp_total=0
1662
+ if [[ -n "$cp_section_line" ]]; then
1663
+ local cp_next_section
1664
+ cp_next_section=$(tail -n +$((cp_section_line + 1)) "$tmp_file" | grep -n "^## " | head -1 | cut -d: -f1)
1665
+ local cp_section_end
1666
+ if [[ -n "$cp_next_section" ]]; then
1667
+ cp_section_end=$((cp_section_line + cp_next_section - 1))
1668
+ else
1669
+ cp_section_end=$(wc -l < "$tmp_file")
1670
+ fi
1671
+ local cp_all_items
1672
+ cp_all_items=$(sed -n "${cp_section_line},${cp_section_end}p" "$tmp_file" | grep -c '^- ' || true)
1673
+ cp_all_items=${cp_all_items:-0}
1674
+ local cp_charter_items
1675
+ cp_charter_items=$(sed -n "${cp_section_line},${cp_section_end}p" "$tmp_file" | grep -c '^- \[charter\] ' || true)
1676
+ cp_charter_items=${cp_charter_items:-0}
1677
+ local cp_non_charter=$((cp_all_items - cp_charter_items))
1678
+ cp_total=$((cp_non_charter + cp_charter_written))
1679
+ fi
1680
+
1681
+ # Update METADATA stats
1682
+ awk -v up="$up_total" -v cp="$cp_total" '{
1683
+ gsub(/"total_user_prefs": [0-9]*/, "\"total_user_prefs\": " up)
1684
+ gsub(/"total_codebase_patterns": [0-9]*/, "\"total_codebase_patterns\": " cp)
1685
+ print
1686
+ }' "$tmp_file" > "${tmp_file}.stats" && mv "${tmp_file}.stats" "$tmp_file"
1687
+
1688
+ # Update last_evolved
1689
+ awk -v ts="$ts" '/"last_evolved":/ { gsub(/"last_evolved": "[^"]*"/, "\"last_evolved\": \"" ts "\""); } {print}' "$tmp_file" > "${tmp_file}.meta" && mv "${tmp_file}.meta" "$tmp_file"
1690
+
1691
+ # Safety guard: never overwrite QUEEN.md with empty content
1692
+ if [[ ! -s "$tmp_file" ]]; then
1693
+ rm -f "$tmp_file"
1694
+ json_err "$E_INTERNAL" "queen-write-charter produced empty output — aborting to protect QUEEN.md"
1695
+ return 1
1696
+ fi
1697
+
1698
+ # Atomic move
1699
+ mv "$tmp_file" "$queen_file"
1700
+
1701
+ local sections_failed=$((up_failed + cp_failed))
1702
+ if [[ "$written" -eq 0 && "$sections_failed" -gt 0 ]]; then
1703
+ json_err "$E_VALIDATION_FAILED" "charter-write: no entries written; all target sections missing from QUEEN.md" \
1704
+ "{\"written\":${written},\"sections_failed\":${sections_failed},\"colony_name\":\"${cw_colony_name}\",\"timestamp\":\"${ts}\"}"
1705
+ return 1
1706
+ fi
1707
+ json_ok "$(jq -n --argjson written "$written" --argjson updated "$is_update" \
1708
+ --arg colony_name "$cw_colony_name" --arg ts "$ts" --argjson sections_failed "$sections_failed" \
1709
+ '{written: $written, updated: $updated, colony_name: $colony_name, timestamp: $ts, sections_failed: $sections_failed}')"
1710
+ }