aether-colony 3.1.17 → 5.1.0

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