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
@@ -13,10 +13,13 @@ E_REPO_NOT_INITIALIZED="E_REPO_NOT_INITIALIZED"
13
13
  E_FILE_NOT_FOUND="E_FILE_NOT_FOUND"
14
14
  E_JSON_INVALID="E_JSON_INVALID"
15
15
  E_LOCK_FAILED="E_LOCK_FAILED"
16
+ E_LOCK_STALE="E_LOCK_STALE"
16
17
  E_GIT_ERROR="E_GIT_ERROR"
17
18
  E_VALIDATION_FAILED="E_VALIDATION_FAILED"
18
19
  E_FEATURE_UNAVAILABLE="E_FEATURE_UNAVAILABLE"
19
20
  E_BASH_ERROR="E_BASH_ERROR"
21
+ E_DEPENDENCY_MISSING="E_DEPENDENCY_MISSING"
22
+ E_RESOURCE_NOT_FOUND="E_RESOURCE_NOT_FOUND"
20
23
 
21
24
  # --- Recovery Suggestion Functions (internal, prefixed with _) ---
22
25
  _recovery_hub_not_found() { echo '"Run: aether install"'; }
@@ -24,8 +27,11 @@ _recovery_repo_not_init() { echo '"Run /ant:init in this repo first"'; }
24
27
  _recovery_file_not_found() { echo '"Check file path and permissions"'; }
25
28
  _recovery_json_invalid() { echo '"Validate JSON syntax"'; }
26
29
  _recovery_lock_failed() { echo '"Wait for other operations to complete"'; }
30
+ _recovery_lock_stale() { echo '"Remove the stale lock file manually or run: aether force-unlock"'; }
27
31
  _recovery_git_error() { echo '"Check git status and resolve conflicts"'; }
28
32
  _recovery_default() { echo 'null'; }
33
+ _recovery_dependency_missing() { echo '"Install the required dependency"'; }
34
+ _recovery_resource_not_found() { echo '"Check that the resource exists and try again"'; }
29
35
 
30
36
  # Get recovery suggestion based on error code
31
37
  _get_recovery() {
@@ -36,7 +42,10 @@ _get_recovery() {
36
42
  "$E_FILE_NOT_FOUND") _recovery_file_not_found ;;
37
43
  "$E_JSON_INVALID") _recovery_json_invalid ;;
38
44
  "$E_LOCK_FAILED") _recovery_lock_failed ;;
45
+ "$E_LOCK_STALE") _recovery_lock_stale ;;
39
46
  "$E_GIT_ERROR") _recovery_git_error ;;
47
+ "$E_DEPENDENCY_MISSING") _recovery_dependency_missing ;;
48
+ "$E_RESOURCE_NOT_FOUND") _recovery_resource_not_found ;;
40
49
  *) _recovery_default ;;
41
50
  esac
42
51
  }
@@ -78,8 +87,8 @@ json_err() {
78
87
  "$code" "$escaped_message" "$details_json" "$recovery" "$timestamp" >&2
79
88
 
80
89
  # Log to activity.log (best effort)
81
- if [[ -n "${DATA_DIR:-}" ]]; then
82
- echo "[$timestamp] ERROR $code: $escaped_message" >> "$DATA_DIR/activity.log" 2>/dev/null || true
90
+ if [[ -n "${COLONY_DATA_DIR:-}" ]]; then
91
+ echo "[$timestamp] ERROR $code: $escaped_message" >> "$COLONY_DATA_DIR/activity.log" 2>/dev/null || true
83
92
  fi
84
93
 
85
94
  exit 1
@@ -102,8 +111,22 @@ json_warn() {
102
111
  "$code" "$escaped_message" "$timestamp"
103
112
 
104
113
  # Log to activity.log (best effort)
105
- if [[ -n "${DATA_DIR:-}" ]]; then
106
- echo "[$timestamp] WARN $code: $escaped_message" >> "$DATA_DIR/activity.log" 2>/dev/null || true
114
+ if [[ -n "${COLONY_DATA_DIR:-}" ]]; then
115
+ echo "[$timestamp] WARN $code: $escaped_message" >> "$COLONY_DATA_DIR/activity.log" 2>/dev/null || true
116
+ fi
117
+ }
118
+
119
+ # --- _aether_log_error function for surfaced errors ---
120
+ # Dual output: [error] prefix to stderr (screen) + timestamped entry to errors.log (file)
121
+ # Distinct from: json_err (structured JSON), json_warn (non-fatal JSON), ⚠ (recovery), [trimmed] (budget)
122
+ _aether_log_error() {
123
+ local message="$1"
124
+ local timestamp
125
+ timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
126
+ echo "[error] $message" >&2
127
+ if [[ -n "${COLONY_DATA_DIR:-}" ]]; then
128
+ mkdir -p "$DATA_DIR" 2>/dev/null # SUPPRESS:OK -- idempotent: ensure dir exists
129
+ echo "[$timestamp] $message" >> "$COLONY_DATA_DIR/errors.log" 2>/dev/null # SUPPRESS:OK -- cleanup: log write is best-effort
107
130
  fi
108
131
  }
109
132
 
@@ -130,8 +153,8 @@ error_handler() {
130
153
  "$E_BASH_ERROR" "$details" "$(_recovery_default)" "$timestamp" >&2
131
154
 
132
155
  # Log to activity.log (best effort)
133
- if [[ -n "${DATA_DIR:-}" ]]; then
134
- echo "[$timestamp] ERROR $E_BASH_ERROR: Command failed at line $line_num (exit $exit_code)" >> "$DATA_DIR/activity.log" 2>/dev/null || true
156
+ if [[ -n "${COLONY_DATA_DIR:-}" ]]; then
157
+ echo "[$timestamp] ERROR $E_BASH_ERROR: Command failed at line $line_num (exit $exit_code)" >> "$COLONY_DATA_DIR/activity.log" 2>/dev/null || true
135
158
  fi
136
159
 
137
160
  exit 1
@@ -189,12 +212,15 @@ feature_log_degradation() {
189
212
  }
190
213
 
191
214
  # --- Export all functions and variables ---
192
- export -f json_err json_warn error_handler
215
+ export -f json_err json_warn _aether_log_error error_handler
193
216
  export -f feature_enable feature_disable feature_enabled feature_log_degradation
194
217
  export -f _get_recovery _recovery_hub_not_found _recovery_repo_not_init
195
218
  export -f _recovery_file_not_found _recovery_json_invalid _recovery_lock_failed
219
+ export -f _recovery_lock_stale
196
220
  export -f _recovery_git_error _recovery_default _feature_reason
221
+ export -f _recovery_dependency_missing _recovery_resource_not_found
197
222
  export E_UNKNOWN E_HUB_NOT_FOUND E_REPO_NOT_INITIALIZED E_FILE_NOT_FOUND
198
- export E_JSON_INVALID E_LOCK_FAILED E_GIT_ERROR E_VALIDATION_FAILED
223
+ export E_JSON_INVALID E_LOCK_FAILED E_LOCK_STALE E_GIT_ERROR E_VALIDATION_FAILED
199
224
  export E_FEATURE_UNAVAILABLE E_BASH_ERROR
225
+ export E_DEPENDENCY_MISSING E_RESOURCE_NOT_FOUND
200
226
  export _FEATURES_DISABLED
@@ -0,0 +1,313 @@
1
+ #!/bin/bash
2
+ # Aether File Lock Utility
3
+ # Implements file locking for concurrent colony access prevention
4
+ #
5
+ # Usage:
6
+ # source .aether/utils/file-lock.sh
7
+ # acquire_lock /path/to/file.lock
8
+ # # ... critical section ...
9
+ # release_lock
10
+
11
+ # Aether root detection - respect existing AETHER_ROOT, or use git root, or use current directory
12
+ if [[ -z "${AETHER_ROOT:-}" ]]; then
13
+ if git rev-parse --show-toplevel >/dev/null 2>&1; then
14
+ AETHER_ROOT="$(git rev-parse --show-toplevel)"
15
+ else
16
+ AETHER_ROOT="$(pwd)"
17
+ fi
18
+ fi
19
+
20
+ LOCK_DIR="$AETHER_ROOT/.aether/locks"
21
+ LOCK_TIMEOUT=300 # 5 minutes max lock time
22
+ LOCK_RETRY_INTERVAL=0.5 # Wait 500ms between retries
23
+ LOCK_MAX_RETRIES=100 # Total 50 seconds max wait
24
+ LOCK_AT_FILE="" # Tracks lock file path for acquire_lock_at/release_lock_at
25
+
26
+ # Fallback constant — ensures E_LOCK_STALE is defined whether or not error-handler.sh was loaded
27
+ : "${E_LOCK_STALE:=E_LOCK_STALE}"
28
+
29
+ # Create lock directory if it doesn't exist
30
+ mkdir -p "$LOCK_DIR"
31
+
32
+ # Acquire a file lock using noclobber
33
+ # Arguments: file_path (the resource to lock)
34
+ # Returns: 0 on success, 1 on failure
35
+ # Globals: LOCK_ACQUIRED (set to true when lock acquired), CURRENT_LOCK (set to lock file path)
36
+ # Behavior:
37
+ # - In non-interactive mode, stale locks are auto-cleaned by default.
38
+ # - Override with AETHER_STALE_LOCK_MODE=error|prompt|auto.
39
+ acquire_lock() {
40
+ local file_path="$1"
41
+ local lock_file="${LOCK_DIR}/$(basename "$file_path").lock"
42
+ local lock_pid_file="${lock_file}.pid"
43
+ local stale_mode="${AETHER_STALE_LOCK_MODE:-}"
44
+
45
+ if [[ -z "$stale_mode" ]]; then
46
+ if [[ -t 2 ]]; then
47
+ stale_mode="prompt"
48
+ else
49
+ stale_mode="auto"
50
+ fi
51
+ fi
52
+
53
+ # Check if lock file exists and is stale
54
+ if [ -f "$lock_file" ]; then
55
+ local lock_pid
56
+ lock_pid=$(cat "$lock_pid_file" 2>/dev/null || echo "")
57
+ if [[ -z "$lock_pid" ]]; then
58
+ # Fallback to lock file payload if .pid sidecar is missing/corrupt.
59
+ lock_pid=$(cat "$lock_file" 2>/dev/null || echo "")
60
+ fi
61
+ lock_pid=$(echo "$lock_pid" | tr -d '[:space:]')
62
+ [[ "$lock_pid" =~ ^[0-9]+$ ]] || lock_pid=""
63
+
64
+ local is_stale=false
65
+
66
+ # Compute lock age for timeout-based stale checks when PID is unavailable.
67
+ local lock_mtime=0
68
+ # Platform-portable mtime: macOS uses stat -f %m, Linux uses stat -c %Y
69
+ if stat -f %m "$lock_file" >/dev/null 2>&1; then
70
+ lock_mtime=$(stat -f %m "$lock_file" 2>/dev/null || echo 0)
71
+ else
72
+ lock_mtime=$(stat -c %Y "$lock_file" 2>/dev/null || echo 0)
73
+ fi
74
+ local lock_age=$(( $(date +%s) - lock_mtime ))
75
+
76
+ # Mark stale only when we can do so safely:
77
+ # - PID is known and not running
78
+ # - No PID could be determined and lock exceeded timeout
79
+ if [[ -n "$lock_pid" ]] && ! kill -0 "$lock_pid" 2>/dev/null; then
80
+ is_stale=true
81
+ elif [[ -z "$lock_pid" ]] && [[ $lock_age -gt $LOCK_TIMEOUT ]]; then
82
+ is_stale=true
83
+ fi
84
+
85
+ if [[ "$is_stale" == "true" ]]; then
86
+ case "$stale_mode" in
87
+ auto)
88
+ rm -f "$lock_file" "$lock_pid_file"
89
+ # Track stale lock cleanup in safety stats (best-effort)
90
+ type _safety_stats_increment &>/dev/null && _safety_stats_increment "stale_locks_cleaned" 2>/dev/null || true
91
+ ;;
92
+ prompt)
93
+ if [[ -t 2 ]]; then
94
+ echo "" >&2
95
+ echo "Warning: stale lock detected (PID ${lock_pid:-unknown} not running, age ${lock_age}s)" >&2
96
+ echo "Lock file: $lock_file" >&2
97
+ printf "Remove stale lock and continue? [y/N] " >&2
98
+ local response
99
+ read -r response < /dev/tty
100
+ if [[ "$response" =~ ^[Yy]$ ]]; then
101
+ rm -f "$lock_file" "$lock_pid_file"
102
+ type _safety_stats_increment &>/dev/null && _safety_stats_increment "stale_locks_cleaned" 2>/dev/null || true
103
+ else
104
+ echo "Lock removal declined. Remove manually: rm $lock_file" >&2
105
+ return 1
106
+ fi
107
+ else
108
+ printf '{"ok":false,"error":{"code":"%s","message":"Stale lock found. Remove manually: %s"}}\n' "$E_LOCK_STALE" "$lock_file" >&2
109
+ return 1
110
+ fi
111
+ ;;
112
+ error|*)
113
+ printf '{"ok":false,"error":{"code":"%s","message":"Stale lock found. Remove manually: %s"}}\n' "$E_LOCK_STALE" "$lock_file" >&2
114
+ return 1
115
+ ;;
116
+ esac
117
+ fi
118
+ fi
119
+
120
+ # Try to acquire lock with timeout
121
+ local retry_count=0
122
+ while [ $retry_count -lt $LOCK_MAX_RETRIES ]; do
123
+ # Try to create lock file atomically
124
+ if (set -o noclobber; echo $$ > "$lock_file") 2>/dev/null; then
125
+ echo $$ > "$lock_pid_file" 2>/dev/null || true
126
+ export LOCK_ACQUIRED=true
127
+ export CURRENT_LOCK="$lock_file"
128
+ return 0
129
+ fi
130
+
131
+ retry_count=$((retry_count + 1))
132
+ if [ $retry_count -lt $LOCK_MAX_RETRIES ]; then
133
+ sleep $LOCK_RETRY_INTERVAL
134
+ fi
135
+ done
136
+
137
+ echo "Failed to acquire lock for $file_path after $LOCK_MAX_RETRIES attempts" >&2
138
+ return 1
139
+ }
140
+
141
+ # Release a file lock
142
+ # Arguments: None (uses CURRENT_LOCK global set by acquire_lock)
143
+ release_lock() {
144
+ if [ "$LOCK_ACQUIRED" = "true" ] && [ -n "$CURRENT_LOCK" ]; then
145
+ rm -f "$CURRENT_LOCK" "${CURRENT_LOCK}.pid"
146
+ export LOCK_ACQUIRED=false
147
+ export CURRENT_LOCK=""
148
+ return 0
149
+ fi
150
+ return 1
151
+ }
152
+
153
+ # Acquire a lock in a specified directory with optional colony tag
154
+ # Unlike acquire_lock (which uses the global LOCK_DIR), this function takes an
155
+ # explicit lock directory parameter — no global state mutation required.
156
+ # Arguments:
157
+ # $1 = file_path (resource to lock)
158
+ # $2 = lock_dir (directory to place lock file in)
159
+ # $3 = colony_tag (optional, included in lock filename for debuggability)
160
+ # Returns: 0 on success, 1 on failure
161
+ # Sets: LOCK_AT_FILE (path to the acquired lock file for release)
162
+ acquire_lock_at() {
163
+ local file_path="$1"
164
+ local lock_dir="$2"
165
+ local colony_tag="${3:-}"
166
+
167
+ local lock_name
168
+ if [[ -n "$colony_tag" ]]; then
169
+ lock_name="$(basename "$file_path").${colony_tag}.lock"
170
+ else
171
+ lock_name="$(basename "$file_path").lock"
172
+ fi
173
+
174
+ local lock_file="${lock_dir}/${lock_name}"
175
+ local lock_pid_file="${lock_file}.pid"
176
+ mkdir -p "$lock_dir"
177
+
178
+ local stale_mode="${AETHER_STALE_LOCK_MODE:-}"
179
+ if [[ -z "$stale_mode" ]]; then
180
+ if [[ -t 2 ]]; then stale_mode="prompt"; else stale_mode="auto"; fi
181
+ fi
182
+
183
+ # Stale lock detection (reuses same logic as acquire_lock)
184
+ if [[ -f "$lock_file" ]]; then
185
+ local lock_pid
186
+ lock_pid=$(cat "$lock_pid_file" 2>/dev/null || echo "")
187
+ [[ -z "$lock_pid" ]] && lock_pid=$(cat "$lock_file" 2>/dev/null || echo "")
188
+ lock_pid=$(echo "$lock_pid" | tr -d '[:space:]')
189
+ [[ "$lock_pid" =~ ^[0-9]+$ ]] || lock_pid=""
190
+
191
+ local is_stale=false
192
+ local lock_mtime=0
193
+ if stat -f %m "$lock_file" >/dev/null 2>&1; then
194
+ lock_mtime=$(stat -f %m "$lock_file" 2>/dev/null || echo 0)
195
+ else
196
+ lock_mtime=$(stat -c %Y "$lock_file" 2>/dev/null || echo 0)
197
+ fi
198
+ local lock_age=$(( $(date +%s) - lock_mtime ))
199
+
200
+ if [[ -n "$lock_pid" ]] && ! kill -0 "$lock_pid" 2>/dev/null; then
201
+ is_stale=true
202
+ elif [[ -z "$lock_pid" ]] && [[ $lock_age -gt $LOCK_TIMEOUT ]]; then
203
+ is_stale=true
204
+ fi
205
+
206
+ if [[ "$is_stale" == "true" ]]; then
207
+ case "$stale_mode" in
208
+ auto)
209
+ rm -f "$lock_file" "$lock_pid_file"
210
+ type _safety_stats_increment &>/dev/null && _safety_stats_increment "stale_locks_cleaned" 2>/dev/null || true
211
+ ;;
212
+ prompt)
213
+ if [[ -t 2 ]]; then
214
+ echo "" >&2
215
+ echo "Warning: stale lock detected (PID ${lock_pid:-unknown} not running, age ${lock_age}s)" >&2
216
+ echo "Lock file: $lock_file" >&2
217
+ printf "Remove stale lock and continue? [y/N] " >&2
218
+ local response
219
+ read -r response < /dev/tty
220
+ if [[ "$response" =~ ^[Yy]$ ]]; then
221
+ rm -f "$lock_file" "$lock_pid_file"
222
+ type _safety_stats_increment &>/dev/null && _safety_stats_increment "stale_locks_cleaned" 2>/dev/null || true
223
+ else
224
+ echo "Lock removal declined. Remove manually: rm $lock_file" >&2
225
+ return 1
226
+ fi
227
+ else
228
+ printf '{"ok":false,"error":{"code":"%s","message":"Stale lock found. Remove manually: %s"}}\n' "$E_LOCK_STALE" "$lock_file" >&2
229
+ return 1
230
+ fi
231
+ ;;
232
+ error|*)
233
+ printf '{"ok":false,"error":{"code":"%s","message":"Stale lock found. Remove manually: %s"}}\n' "$E_LOCK_STALE" "$lock_file" >&2
234
+ return 1
235
+ ;;
236
+ esac
237
+ fi
238
+ fi
239
+
240
+ # Retry loop (same as acquire_lock)
241
+ local retry_count=0
242
+ while [[ $retry_count -lt $LOCK_MAX_RETRIES ]]; do
243
+ if (set -o noclobber; echo $$ > "$lock_file") 2>/dev/null; then
244
+ echo $$ > "$lock_pid_file" 2>/dev/null || true
245
+ LOCK_AT_FILE="$lock_file"
246
+ return 0
247
+ fi
248
+ retry_count=$((retry_count + 1))
249
+ [[ $retry_count -lt $LOCK_MAX_RETRIES ]] && sleep $LOCK_RETRY_INTERVAL
250
+ done
251
+
252
+ echo "Failed to acquire lock for $file_path in $lock_dir after $LOCK_MAX_RETRIES attempts" >&2
253
+ return 1
254
+ }
255
+
256
+ # Release a lock acquired with acquire_lock_at
257
+ # Arguments: $1 = lock_file (the LOCK_AT_FILE value from acquire_lock_at)
258
+ release_lock_at() {
259
+ local lock_file="${1:-$LOCK_AT_FILE}"
260
+ if [[ -n "$lock_file" && -f "$lock_file" ]]; then
261
+ rm -f "$lock_file" "${lock_file}.pid"
262
+ [[ "$lock_file" == "$LOCK_AT_FILE" ]] && LOCK_AT_FILE=""
263
+ return 0
264
+ fi
265
+ return 1
266
+ }
267
+
268
+ # Cleanup function for script exit
269
+ cleanup_locks() {
270
+ if [ "$LOCK_ACQUIRED" = "true" ]; then
271
+ release_lock
272
+ fi
273
+ if [[ -n "${LOCK_AT_FILE:-}" ]]; then
274
+ release_lock_at "$LOCK_AT_FILE"
275
+ fi
276
+ }
277
+
278
+ # Register cleanup on exit — includes HUP for SSH disconnect safety
279
+ trap cleanup_locks EXIT TERM INT HUP
280
+
281
+ # Check if a file is currently locked
282
+ is_locked() {
283
+ local file_path="$1"
284
+ local lock_file="${LOCK_DIR}/$(basename "$file_path").lock"
285
+ [ -f "$lock_file" ]
286
+ }
287
+
288
+ # Get PID of process holding lock
289
+ get_lock_holder() {
290
+ local file_path="$1"
291
+ local lock_file="${LOCK_DIR}/$(basename "$file_path").lock.pid"
292
+ cat "$lock_file" 2>/dev/null || echo ""
293
+ }
294
+
295
+ # Wait for lock to be released
296
+ wait_for_lock() {
297
+ local file_path="$1"
298
+ local max_wait=${2:-$LOCK_TIMEOUT}
299
+ local waited=0
300
+
301
+ while is_locked "$file_path" && [ $waited -lt $max_wait ]; do
302
+ sleep 1
303
+ waited=$((waited + 1))
304
+ done
305
+
306
+ if [ $waited -ge $max_wait ]; then
307
+ return 1
308
+ fi
309
+ return 0
310
+ }
311
+
312
+ # Export functions for use in other scripts
313
+ export -f acquire_lock release_lock acquire_lock_at release_lock_at is_locked get_lock_holder wait_for_lock cleanup_locks
@@ -0,0 +1,267 @@
1
+ #!/bin/bash
2
+ # Flag utility functions — extracted from aether-utils.sh
3
+ # Provides: _flag_add, _flag_check_blockers, _flag_resolve, _flag_acknowledge, _flag_list, _flag_auto_resolve
4
+ #
5
+ # These functions are sourced by aether-utils.sh at startup.
6
+ # All shared infrastructure (json_ok, json_err, json_warn, atomic_write, acquire_lock,
7
+ # release_lock, feature_enabled, LOCK_DIR, DATA_DIR, SCRIPT_DIR, error constants) is available.
8
+
9
+ _flag_add() {
10
+ # Add a project-specific flag (blocker, issue, or note)
11
+ # Usage: flag-add <type> <title> <description> [source] [phase]
12
+ # Types: blocker (critical, blocks advancement), issue (high, warning), note (low, info)
13
+ type="${1:-issue}"
14
+ title="${2:-}"
15
+ desc="${3:-}"
16
+ source="${4:-manual}"
17
+ phase="${5:-null}"
18
+ [[ -z "$title" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-add <type> <title> <description> [source] [phase]"
19
+
20
+ mkdir -p "$COLONY_DATA_DIR"
21
+ flags_file="$COLONY_DATA_DIR/flags.json"
22
+
23
+ if [[ ! -f "$flags_file" ]]; then
24
+ atomic_write "$flags_file" '{"version":1,"flags":[]}' || json_err "$E_UNKNOWN" "Failed to initialize flags file"
25
+ fi
26
+
27
+ id="flag_$(date -u +%s)_$(head -c 2 /dev/urandom | od -An -tx1 | tr -d ' ')"
28
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
29
+
30
+ # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
31
+ if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
32
+ json_warn "W_DEGRADED" "File locking disabled - proceeding without lock: $(type _feature_reason &>/dev/null && _feature_reason file_locking || echo 'unknown')"
33
+ else
34
+ acquire_lock "$flags_file" || {
35
+ if type json_err &>/dev/null; then
36
+ json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
37
+ else
38
+ echo '{"ok":false,"error":"Failed to acquire lock on flags.json"}' >&2
39
+ exit 1
40
+ fi
41
+ }
42
+ # Ensure lock is always released on exit (BUG-002 fix)
43
+ trap 'release_lock 2>/dev/null || true' EXIT # SUPPRESS:OK -- cleanup: lock may not be held
44
+ fi
45
+
46
+ # Map type to severity
47
+ case "$type" in
48
+ blocker) severity="critical" ;;
49
+ issue) severity="high" ;;
50
+ note) severity="low" ;;
51
+ *) severity="medium" ;;
52
+ esac
53
+
54
+ # Handle phase as number or null
55
+ if [[ "$phase" =~ ^[0-9]+$ ]]; then
56
+ phase_jq="$phase"
57
+ else
58
+ phase_jq="null"
59
+ fi
60
+
61
+ updated=$(jq --arg id "$id" --arg type "$type" --arg sev "$severity" \
62
+ --arg title "$title" --arg desc "$desc" --arg source "$source" \
63
+ --argjson phase "$phase_jq" --arg ts "$ts" '
64
+ .flags += [{
65
+ id: $id,
66
+ type: $type,
67
+ severity: $sev,
68
+ title: $title,
69
+ description: $desc,
70
+ source: $source,
71
+ phase: $phase,
72
+ created_at: $ts,
73
+ acknowledged_at: null,
74
+ resolved_at: null,
75
+ resolution: null,
76
+ auto_resolve_on: (if $type == "blocker" and ($source | test("chaos") | not) then "build_pass" else null end)
77
+ }]
78
+ ' "$flags_file") || { json_err "$E_JSON_INVALID" "Failed to add flag"; }
79
+
80
+ atomic_write "$flags_file" "$updated"
81
+ trap - EXIT
82
+ release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
83
+ json_ok "$(jq -n --arg id "$id" --arg type "$type" --arg severity "$severity" \
84
+ '{id: $id, type: $type, severity: $severity}')"
85
+ }
86
+
87
+ _flag_check_blockers() {
88
+ # Count unresolved blockers for the current phase
89
+ # Usage: flag-check-blockers [phase]
90
+ phase="${1:-}"
91
+ flags_file="$COLONY_DATA_DIR/flags.json"
92
+
93
+ if [[ ! -f "$flags_file" ]]; then
94
+ json_ok '{"blockers":0,"issues":0,"notes":0}'
95
+ exit 0
96
+ fi
97
+
98
+ if [[ -n "$phase" && "$phase" =~ ^[0-9]+$ ]]; then
99
+ # Filter by phase
100
+ result=$(jq --argjson phase "$phase" '{
101
+ blockers: [.flags[] | select(.type == "blocker" and .resolved_at == null and (.phase == $phase or .phase == null))] | length,
102
+ issues: [.flags[] | select(.type == "issue" and .resolved_at == null and (.phase == $phase or .phase == null))] | length,
103
+ notes: [.flags[] | select(.type == "note" and .resolved_at == null and (.phase == $phase or .phase == null))] | length
104
+ }' "$flags_file")
105
+ else
106
+ # All unresolved
107
+ result=$(jq '{
108
+ blockers: [.flags[] | select(.type == "blocker" and .resolved_at == null)] | length,
109
+ issues: [.flags[] | select(.type == "issue" and .resolved_at == null)] | length,
110
+ notes: [.flags[] | select(.type == "note" and .resolved_at == null)] | length
111
+ }' "$flags_file")
112
+ fi
113
+
114
+ json_ok "$result"
115
+ }
116
+
117
+ _flag_resolve() {
118
+ # Resolve a flag with optional resolution message
119
+ # Usage: flag-resolve <flag_id> [resolution_message]
120
+ flag_id="${1:-}"
121
+ resolution="${2:-Resolved}"
122
+ [[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-resolve <flag_id> [resolution_message]"
123
+
124
+ flags_file="$COLONY_DATA_DIR/flags.json"
125
+ [[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
126
+
127
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
128
+
129
+ # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
130
+ if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
131
+ json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
132
+ else
133
+ acquire_lock "$flags_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
134
+ trap 'release_lock 2>/dev/null || true' EXIT # SUPPRESS:OK -- cleanup: lock may not be held
135
+ fi
136
+
137
+ updated=$(jq --arg id "$flag_id" --arg res "$resolution" --arg ts "$ts" '
138
+ .flags = [.flags[] | if .id == $id then
139
+ .resolved_at = $ts |
140
+ .resolution = $res
141
+ else . end]
142
+ ' "$flags_file") || {
143
+ json_err "$E_JSON_INVALID" "Failed to resolve flag"
144
+ }
145
+
146
+ atomic_write "$flags_file" "$updated"
147
+ trap - EXIT
148
+ release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
149
+ json_ok "$(jq -n --arg id "$flag_id" '{resolved: $id}')"
150
+ }
151
+
152
+ _flag_acknowledge() {
153
+ # Acknowledge a flag (issue continues but noted)
154
+ # Usage: flag-acknowledge <flag_id>
155
+ flag_id="${1:-}"
156
+ [[ -z "$flag_id" ]] && json_err "$E_VALIDATION_FAILED" "Usage: flag-acknowledge <flag_id>"
157
+
158
+ flags_file="$COLONY_DATA_DIR/flags.json"
159
+ [[ ! -f "$flags_file" ]] && json_err "$E_FILE_NOT_FOUND" "No flags file found"
160
+
161
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
162
+
163
+ # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
164
+ if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
165
+ json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
166
+ else
167
+ acquire_lock "$flags_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
168
+ trap 'release_lock 2>/dev/null || true' EXIT # SUPPRESS:OK -- cleanup: lock may not be held
169
+ fi
170
+
171
+ updated=$(jq --arg id "$flag_id" --arg ts "$ts" '
172
+ .flags = [.flags[] | if .id == $id then
173
+ .acknowledged_at = $ts
174
+ else . end]
175
+ ' "$flags_file") || {
176
+ json_err "$E_JSON_INVALID" "Failed to acknowledge flag"
177
+ }
178
+
179
+ atomic_write "$flags_file" "$updated"
180
+ trap - EXIT
181
+ release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
182
+ json_ok "$(jq -n --arg id "$flag_id" '{acknowledged: $id}')"
183
+ }
184
+
185
+ _flag_list() {
186
+ # List flags, optionally filtered
187
+ # Usage: flag-list [--all] [--type blocker|issue|note] [--phase N]
188
+ flags_file="$COLONY_DATA_DIR/flags.json"
189
+ show_all="false"
190
+ filter_type=""
191
+ filter_phase=""
192
+
193
+ while [[ $# -gt 0 ]]; do
194
+ case "$1" in
195
+ --all) show_all="true"; shift ;;
196
+ --type) filter_type="$2"; shift 2 ;;
197
+ --phase) filter_phase="$2"; shift 2 ;;
198
+ *) shift ;;
199
+ esac
200
+ done
201
+
202
+ if [[ ! -f "$flags_file" ]]; then
203
+ json_ok '{"flags":[],"count":0}'
204
+ exit 0
205
+ fi
206
+
207
+ # Build jq filter
208
+ jq_filter='.flags'
209
+
210
+ if [[ "$show_all" != "true" ]]; then
211
+ jq_filter+=' | [.[] | select(.resolved_at == null)]'
212
+ fi
213
+
214
+ if [[ -n "$filter_type" ]]; then
215
+ jq_filter+=" | [.[] | select(.type == \"$filter_type\")]"
216
+ fi
217
+
218
+ if [[ -n "$filter_phase" && "$filter_phase" =~ ^[0-9]+$ ]]; then
219
+ jq_filter+=" | [.[] | select(.phase == $filter_phase or .phase == null)]"
220
+ fi
221
+
222
+ result=$(jq "{flags: ($jq_filter), count: ($jq_filter | length)}" "$flags_file")
223
+ json_ok "$result"
224
+ }
225
+
226
+ _flag_auto_resolve() {
227
+ # Auto-resolve flags based on trigger (e.g., build_pass)
228
+ # Usage: flag-auto-resolve <trigger>
229
+ trigger="${1:-build_pass}"
230
+ flags_file="$COLONY_DATA_DIR/flags.json"
231
+
232
+ if [[ ! -f "$flags_file" ]]; then json_ok '{"resolved":0}'; exit 0; fi
233
+
234
+ ts=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
235
+
236
+ # Acquire lock for atomic flag update (degrade gracefully if locking unavailable)
237
+ if type feature_enabled &>/dev/null && ! feature_enabled "file_locking"; then
238
+ json_warn "W_DEGRADED" "File locking disabled - proceeding without lock"
239
+ else
240
+ acquire_lock "$flags_file" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on flags.json"
241
+ # Ensure lock is always released on exit (BUG-005/BUG-011 fix)
242
+ trap 'release_lock 2>/dev/null || true' EXIT # SUPPRESS:OK -- cleanup: lock may not be held
243
+ fi
244
+
245
+ # Count how many will be resolved
246
+ count=$(jq --arg trigger "$trigger" '
247
+ [.flags[] | select(.auto_resolve_on == $trigger and .resolved_at == null)] | length
248
+ ' "$flags_file") || {
249
+ json_err "$E_JSON_INVALID" "Failed to count flags for auto-resolve"
250
+ }
251
+
252
+ # Resolve them
253
+ updated=$(jq --arg trigger "$trigger" --arg ts "$ts" '
254
+ .flags = [.flags[] | if .auto_resolve_on == $trigger and .resolved_at == null then
255
+ .resolved_at = $ts |
256
+ .resolution = "Auto-resolved on " + $trigger
257
+ else . end]
258
+ ' "$flags_file") || {
259
+ json_err "$E_JSON_INVALID" "Failed to auto-resolve flags"
260
+ }
261
+
262
+ atomic_write "$flags_file" "$updated"
263
+ trap - EXIT
264
+ release_lock 2>/dev/null || true # SUPPRESS:OK -- cleanup: lock may not be held
265
+ json_ok "$(jq -n --argjson count "$count" --arg trigger "$trigger" \
266
+ '{resolved: $count, trigger: $trigger}')"
267
+ }