aether-colony 5.0.0 → 5.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/.aether/aether-utils.sh +3226 -3345
  2. package/.aether/agents-claude/aether-ambassador.md +265 -0
  3. package/.aether/agents-claude/aether-archaeologist.md +327 -0
  4. package/.aether/agents-claude/aether-architect.md +236 -0
  5. package/.aether/agents-claude/aether-auditor.md +271 -0
  6. package/.aether/agents-claude/aether-builder.md +224 -0
  7. package/.aether/agents-claude/aether-chaos.md +269 -0
  8. package/.aether/agents-claude/aether-chronicler.md +305 -0
  9. package/.aether/agents-claude/aether-gatekeeper.md +330 -0
  10. package/.aether/agents-claude/aether-includer.md +374 -0
  11. package/.aether/agents-claude/aether-keeper.md +272 -0
  12. package/.aether/agents-claude/aether-measurer.md +322 -0
  13. package/.aether/agents-claude/aether-oracle.md +237 -0
  14. package/.aether/agents-claude/aether-probe.md +211 -0
  15. package/.aether/agents-claude/aether-queen.md +330 -0
  16. package/.aether/agents-claude/aether-route-setter.md +178 -0
  17. package/.aether/agents-claude/aether-sage.md +418 -0
  18. package/.aether/agents-claude/aether-scout.md +179 -0
  19. package/.aether/agents-claude/aether-surveyor-disciplines.md +417 -0
  20. package/.aether/agents-claude/aether-surveyor-nest.md +355 -0
  21. package/.aether/agents-claude/aether-surveyor-pathogens.md +289 -0
  22. package/.aether/agents-claude/aether-surveyor-provisions.md +360 -0
  23. package/.aether/agents-claude/aether-tracker.md +270 -0
  24. package/.aether/agents-claude/aether-watcher.md +280 -0
  25. package/.aether/agents-claude/aether-weaver.md +248 -0
  26. package/.aether/commands/archaeology.yaml +653 -0
  27. package/.aether/commands/build.yaml +1221 -0
  28. package/.aether/commands/chaos.yaml +653 -0
  29. package/.aether/commands/colonize.yaml +442 -0
  30. package/.aether/commands/continue.yaml +1484 -0
  31. package/.aether/commands/council.yaml +509 -0
  32. package/.aether/commands/data-clean.yaml +80 -0
  33. package/.aether/commands/dream.yaml +275 -0
  34. package/.aether/commands/entomb.yaml +863 -0
  35. package/.aether/commands/export-signals.yaml +64 -0
  36. package/.aether/commands/feedback.yaml +158 -0
  37. package/.aether/commands/flag.yaml +160 -0
  38. package/.aether/commands/flags.yaml +177 -0
  39. package/.aether/commands/focus.yaml +112 -0
  40. package/.aether/commands/help.yaml +167 -0
  41. package/.aether/commands/history.yaml +137 -0
  42. package/.aether/commands/import-signals.yaml +79 -0
  43. package/.aether/commands/init.yaml +502 -0
  44. package/.aether/commands/insert-phase.yaml +102 -0
  45. package/.aether/commands/interpret.yaml +285 -0
  46. package/.aether/commands/lay-eggs.yaml +224 -0
  47. package/.aether/commands/maturity.yaml +122 -0
  48. package/.aether/commands/memory-details.yaml +74 -0
  49. package/.aether/commands/migrate-state.yaml +174 -0
  50. package/.aether/commands/oracle.yaml +1224 -0
  51. package/.aether/commands/organize.yaml +446 -0
  52. package/.aether/commands/patrol.yaml +621 -0
  53. package/.aether/commands/pause-colony.yaml +424 -0
  54. package/.aether/commands/phase.yaml +124 -0
  55. package/.aether/commands/pheromones.yaml +153 -0
  56. package/.aether/commands/plan.yaml +1364 -0
  57. package/.aether/commands/preferences.yaml +63 -0
  58. package/.aether/commands/quick.yaml +104 -0
  59. package/.aether/commands/redirect.yaml +123 -0
  60. package/.aether/commands/resume-colony.yaml +375 -0
  61. package/.aether/commands/resume.yaml +407 -0
  62. package/.aether/commands/run.yaml +229 -0
  63. package/.aether/commands/seal.yaml +1214 -0
  64. package/.aether/commands/skill-create.yaml +337 -0
  65. package/.aether/commands/status.yaml +408 -0
  66. package/.aether/commands/swarm.yaml +352 -0
  67. package/.aether/commands/tunnels.yaml +814 -0
  68. package/.aether/commands/update.yaml +131 -0
  69. package/.aether/commands/verify-castes.yaml +159 -0
  70. package/.aether/commands/watch.yaml +454 -0
  71. package/.aether/docs/INCIDENT_TEMPLATE.md +32 -0
  72. package/.aether/docs/QUEEN-SYSTEM.md +11 -11
  73. package/.aether/docs/README.md +32 -2
  74. package/.aether/docs/command-playbooks/README.md +23 -0
  75. package/.aether/docs/command-playbooks/build-complete.md +349 -0
  76. package/.aether/docs/command-playbooks/build-context.md +282 -0
  77. package/.aether/docs/command-playbooks/build-full.md +1683 -0
  78. package/.aether/docs/command-playbooks/build-prep.md +284 -0
  79. package/.aether/docs/command-playbooks/build-verify.md +405 -0
  80. package/.aether/docs/command-playbooks/build-wave.md +749 -0
  81. package/.aether/docs/command-playbooks/continue-advance.md +524 -0
  82. package/.aether/docs/command-playbooks/continue-finalize.md +447 -0
  83. package/.aether/docs/command-playbooks/continue-full.md +1725 -0
  84. package/.aether/docs/command-playbooks/continue-gates.md +686 -0
  85. package/.aether/docs/command-playbooks/continue-verify.md +407 -0
  86. package/.aether/docs/context-continuity.md +84 -0
  87. package/.aether/docs/disciplines/DISCIPLINES.md +9 -7
  88. package/.aether/docs/error-codes.md +1 -1
  89. package/.aether/docs/known-issues.md +34 -173
  90. package/.aether/docs/pheromones.md +86 -6
  91. package/.aether/docs/plans/pheromone-display-plan.md +257 -0
  92. package/.aether/docs/queen-commands.md +10 -9
  93. package/.aether/docs/source-of-truth-map.md +132 -0
  94. package/.aether/docs/xml-utilities.md +47 -0
  95. package/.aether/rules/aether-colony.md +23 -13
  96. package/.aether/scripts/incident-test-add.sh +47 -0
  97. package/.aether/scripts/weekly-audit.sh +79 -0
  98. package/.aether/skills/.index.json +649 -0
  99. package/.aether/skills/colony/.manifest.json +16 -0
  100. package/.aether/skills/colony/build-discipline/SKILL.md +78 -0
  101. package/.aether/skills/colony/colony-interaction/SKILL.md +56 -0
  102. package/.aether/skills/colony/colony-lifecycle/SKILL.md +77 -0
  103. package/.aether/skills/colony/colony-visuals/SKILL.md +112 -0
  104. package/.aether/skills/colony/context-management/SKILL.md +80 -0
  105. package/.aether/skills/colony/error-presentation/SKILL.md +99 -0
  106. package/.aether/skills/colony/pheromone-protocol/SKILL.md +79 -0
  107. package/.aether/skills/colony/pheromone-visibility/SKILL.md +81 -0
  108. package/.aether/skills/colony/state-safety/SKILL.md +84 -0
  109. package/.aether/skills/colony/worker-priming/SKILL.md +82 -0
  110. package/.aether/skills/domain/.manifest.json +24 -0
  111. package/.aether/skills/domain/README.md +33 -0
  112. package/.aether/skills/domain/django/SKILL.md +49 -0
  113. package/.aether/skills/domain/docker/SKILL.md +52 -0
  114. package/.aether/skills/domain/golang/SKILL.md +52 -0
  115. package/.aether/skills/domain/graphql/SKILL.md +51 -0
  116. package/.aether/skills/domain/html-css/SKILL.md +48 -0
  117. package/.aether/skills/domain/nextjs/SKILL.md +45 -0
  118. package/.aether/skills/domain/nodejs/SKILL.md +53 -0
  119. package/.aether/skills/domain/postgresql/SKILL.md +53 -0
  120. package/.aether/skills/domain/prisma/SKILL.md +59 -0
  121. package/.aether/skills/domain/python/SKILL.md +50 -0
  122. package/.aether/skills/domain/rails/SKILL.md +52 -0
  123. package/.aether/skills/domain/react/SKILL.md +45 -0
  124. package/.aether/skills/domain/rest-api/SKILL.md +58 -0
  125. package/.aether/skills/domain/svelte/SKILL.md +47 -0
  126. package/.aether/skills/domain/tailwind/SKILL.md +45 -0
  127. package/.aether/skills/domain/testing/SKILL.md +53 -0
  128. package/.aether/skills/domain/typescript/SKILL.md +58 -0
  129. package/.aether/skills/domain/vue/SKILL.md +49 -0
  130. package/.aether/templates/QUEEN.md.template +23 -41
  131. package/.aether/templates/colony-state-reset.jq.template +1 -0
  132. package/.aether/templates/colony-state.template.json +4 -0
  133. package/.aether/templates/learning-observations.template.json +6 -0
  134. package/.aether/templates/midden.template.json +13 -0
  135. package/.aether/templates/pheromones.template.json +6 -0
  136. package/.aether/templates/session.template.json +9 -0
  137. package/.aether/utils/atomic-write.sh +63 -17
  138. package/.aether/utils/chamber-utils.sh +145 -2
  139. package/.aether/utils/council.sh +425 -0
  140. package/.aether/utils/emoji-audit.sh +166 -0
  141. package/.aether/utils/error-handler.sh +21 -7
  142. package/.aether/utils/file-lock.sh +182 -27
  143. package/.aether/utils/flag.sh +278 -0
  144. package/.aether/utils/hive.sh +572 -0
  145. package/.aether/utils/immune.sh +508 -0
  146. package/.aether/utils/learning.sh +1928 -0
  147. package/.aether/utils/midden.sh +520 -0
  148. package/.aether/utils/oracle/oracle.md +168 -0
  149. package/.aether/utils/oracle/oracle.sh +1023 -0
  150. package/.aether/utils/pheromone.sh +2029 -0
  151. package/.aether/utils/queen.sh +1710 -0
  152. package/.aether/utils/scan.sh +860 -0
  153. package/.aether/utils/semantic-cli.sh +10 -8
  154. package/.aether/utils/session.sh +816 -0
  155. package/.aether/utils/skills.sh +509 -0
  156. package/.aether/utils/spawn-tree.sh +103 -271
  157. package/.aether/utils/spawn.sh +260 -0
  158. package/.aether/utils/state-api.sh +389 -0
  159. package/.aether/utils/state-loader.sh +8 -6
  160. package/.aether/utils/suggest.sh +611 -0
  161. package/.aether/utils/swarm-display.sh +10 -1
  162. package/.aether/utils/swarm.sh +1004 -0
  163. package/.aether/utils/watch-spawn-tree.sh +11 -2
  164. package/.aether/utils/xml-compose.sh +2 -2
  165. package/.aether/utils/xml-convert.sh +9 -5
  166. package/.aether/utils/xml-core.sh +5 -9
  167. package/.aether/utils/xml-query.sh +4 -4
  168. package/.aether/workers.md +86 -67
  169. package/.claude/agents/ant/aether-ambassador.md +2 -1
  170. package/.claude/agents/ant/aether-archaeologist.md +6 -1
  171. package/.claude/agents/ant/aether-architect.md +236 -0
  172. package/.claude/agents/ant/aether-auditor.md +6 -1
  173. package/.claude/agents/ant/aether-builder.md +38 -1
  174. package/.claude/agents/ant/aether-chaos.md +2 -1
  175. package/.claude/agents/ant/aether-chronicler.md +1 -0
  176. package/.claude/agents/ant/aether-gatekeeper.md +6 -1
  177. package/.claude/agents/ant/aether-includer.md +1 -0
  178. package/.claude/agents/ant/aether-keeper.md +1 -0
  179. package/.claude/agents/ant/aether-measurer.md +6 -1
  180. package/.claude/agents/ant/aether-oracle.md +237 -0
  181. package/.claude/agents/ant/aether-probe.md +2 -1
  182. package/.claude/agents/ant/aether-queen.md +6 -1
  183. package/.claude/agents/ant/aether-route-setter.md +6 -1
  184. package/.claude/agents/ant/aether-sage.md +68 -3
  185. package/.claude/agents/ant/aether-scout.md +38 -1
  186. package/.claude/agents/ant/aether-surveyor-disciplines.md +2 -1
  187. package/.claude/agents/ant/aether-surveyor-nest.md +2 -1
  188. package/.claude/agents/ant/aether-surveyor-pathogens.md +2 -1
  189. package/.claude/agents/ant/aether-surveyor-provisions.md +2 -1
  190. package/.claude/agents/ant/aether-tracker.md +6 -1
  191. package/.claude/agents/ant/aether-watcher.md +37 -1
  192. package/.claude/agents/ant/aether-weaver.md +2 -1
  193. package/.claude/commands/ant/archaeology.md +1 -8
  194. package/.claude/commands/ant/build.md +43 -1159
  195. package/.claude/commands/ant/chaos.md +1 -14
  196. package/.claude/commands/ant/colonize.md +3 -14
  197. package/.claude/commands/ant/continue.md +40 -1026
  198. package/.claude/commands/ant/council.md +213 -15
  199. package/.claude/commands/ant/data-clean.md +81 -0
  200. package/.claude/commands/ant/dream.md +12 -9
  201. package/.claude/commands/ant/entomb.md +62 -87
  202. package/.claude/commands/ant/export-signals.md +57 -0
  203. package/.claude/commands/ant/feedback.md +18 -0
  204. package/.claude/commands/ant/flag.md +12 -0
  205. package/.claude/commands/ant/flags.md +22 -8
  206. package/.claude/commands/ant/focus.md +18 -0
  207. package/.claude/commands/ant/help.md +40 -8
  208. package/.claude/commands/ant/history.md +3 -0
  209. package/.claude/commands/ant/import-signals.md +71 -0
  210. package/.claude/commands/ant/init.md +349 -191
  211. package/.claude/commands/ant/insert-phase.md +105 -0
  212. package/.claude/commands/ant/interpret.md +11 -0
  213. package/.claude/commands/ant/lay-eggs.md +167 -158
  214. package/.claude/commands/ant/maturity.md +22 -11
  215. package/.claude/commands/ant/memory-details.md +77 -0
  216. package/.claude/commands/ant/migrate-state.md +6 -0
  217. package/.claude/commands/ant/oracle.md +317 -62
  218. package/.claude/commands/ant/organize.md +10 -5
  219. package/.claude/commands/ant/patrol.md +620 -0
  220. package/.claude/commands/ant/pause-colony.md +8 -22
  221. package/.claude/commands/ant/phase.md +26 -37
  222. package/.claude/commands/ant/pheromones.md +156 -0
  223. package/.claude/commands/ant/plan.md +199 -50
  224. package/.claude/commands/ant/preferences.md +65 -0
  225. package/.claude/commands/ant/quick.md +100 -0
  226. package/.claude/commands/ant/redirect.md +18 -0
  227. package/.claude/commands/ant/resume-colony.md +37 -22
  228. package/.claude/commands/ant/resume.md +60 -7
  229. package/.claude/commands/ant/run.md +231 -0
  230. package/.claude/commands/ant/seal.md +506 -78
  231. package/.claude/commands/ant/skill-create.md +286 -0
  232. package/.claude/commands/ant/status.md +171 -1
  233. package/.claude/commands/ant/swarm.md +11 -23
  234. package/.claude/commands/ant/tunnels.md +1 -0
  235. package/.claude/commands/ant/update.md +58 -135
  236. package/.claude/commands/ant/verify-castes.md +90 -42
  237. package/.claude/commands/ant/watch.md +1 -0
  238. package/.opencode/agents/aether-ambassador.md +1 -1
  239. package/.opencode/agents/aether-architect.md +133 -0
  240. package/.opencode/agents/aether-builder.md +3 -3
  241. package/.opencode/agents/aether-oracle.md +137 -0
  242. package/.opencode/agents/aether-queen.md +1 -1
  243. package/.opencode/agents/aether-route-setter.md +1 -1
  244. package/.opencode/agents/aether-scout.md +1 -1
  245. package/.opencode/agents/aether-surveyor-disciplines.md +6 -1
  246. package/.opencode/agents/aether-surveyor-nest.md +6 -1
  247. package/.opencode/agents/aether-surveyor-pathogens.md +6 -1
  248. package/.opencode/agents/aether-surveyor-provisions.md +6 -1
  249. package/.opencode/agents/aether-tracker.md +1 -1
  250. package/.opencode/agents/aether-watcher.md +1 -1
  251. package/.opencode/agents/aether-weaver.md +1 -1
  252. package/.opencode/commands/ant/archaeology.md +7 -14
  253. package/.opencode/commands/ant/build.md +54 -88
  254. package/.opencode/commands/ant/chaos.md +7 -24
  255. package/.opencode/commands/ant/colonize.md +10 -17
  256. package/.opencode/commands/ant/continue.md +595 -66
  257. package/.opencode/commands/ant/council.md +150 -18
  258. package/.opencode/commands/ant/data-clean.md +77 -0
  259. package/.opencode/commands/ant/dream.md +15 -17
  260. package/.opencode/commands/ant/entomb.md +28 -18
  261. package/.opencode/commands/ant/export-signals.md +54 -0
  262. package/.opencode/commands/ant/feedback.md +24 -5
  263. package/.opencode/commands/ant/flag.md +16 -4
  264. package/.opencode/commands/ant/flags.md +24 -10
  265. package/.opencode/commands/ant/focus.md +22 -5
  266. package/.opencode/commands/ant/help.md +41 -8
  267. package/.opencode/commands/ant/history.md +9 -0
  268. package/.opencode/commands/ant/import-signals.md +68 -0
  269. package/.opencode/commands/ant/init.md +396 -154
  270. package/.opencode/commands/ant/insert-phase.md +111 -0
  271. package/.opencode/commands/ant/interpret.md +16 -0
  272. package/.opencode/commands/ant/lay-eggs.md +184 -112
  273. package/.opencode/commands/ant/maturity.md +18 -2
  274. package/.opencode/commands/ant/memory-details.md +83 -0
  275. package/.opencode/commands/ant/migrate-state.md +12 -0
  276. package/.opencode/commands/ant/oracle.md +322 -67
  277. package/.opencode/commands/ant/organize.md +14 -12
  278. package/.opencode/commands/ant/patrol.md +626 -0
  279. package/.opencode/commands/ant/pause-colony.md +12 -29
  280. package/.opencode/commands/ant/phase.md +30 -40
  281. package/.opencode/commands/ant/pheromones.md +162 -0
  282. package/.opencode/commands/ant/plan.md +210 -57
  283. package/.opencode/commands/ant/preferences.md +71 -0
  284. package/.opencode/commands/ant/quick.md +91 -0
  285. package/.opencode/commands/ant/redirect.md +22 -5
  286. package/.opencode/commands/ant/resume-colony.md +41 -29
  287. package/.opencode/commands/ant/resume.md +80 -20
  288. package/.opencode/commands/ant/run.md +237 -0
  289. package/.opencode/commands/ant/seal.md +230 -25
  290. package/.opencode/commands/ant/skill-create.md +63 -0
  291. package/.opencode/commands/ant/status.md +125 -30
  292. package/.opencode/commands/ant/swarm.md +3 -345
  293. package/.opencode/commands/ant/tunnels.md +3 -9
  294. package/.opencode/commands/ant/update.md +63 -127
  295. package/.opencode/commands/ant/verify-castes.md +96 -42
  296. package/.opencode/commands/ant/watch.md +7 -0
  297. package/CHANGELOG.md +368 -1
  298. package/README.md +195 -324
  299. package/bin/cli.js +236 -429
  300. package/bin/generate-commands.js +186 -0
  301. package/bin/generate-commands.sh +128 -89
  302. package/bin/lib/spawn-logger.js +0 -15
  303. package/bin/lib/update-transaction.js +285 -35
  304. package/bin/npx-install.js +178 -0
  305. package/bin/validate-package.sh +85 -3
  306. package/package.json +16 -4
  307. package/.aether/CONTEXT.md +0 -160
  308. package/.aether/docs/QUEEN.md +0 -84
  309. package/.aether/exchange/colony-registry.xml +0 -11
  310. package/.aether/exchange/pheromones.xml +0 -87
  311. package/.aether/exchange/queen-wisdom.xml +0 -14
  312. package/.aether/model-profiles.yaml +0 -100
  313. package/.aether/utils/spawn-with-model.sh +0 -56
  314. package/bin/lib/model-profiles.js +0 -445
  315. package/bin/lib/model-verify.js +0 -288
  316. package/bin/lib/proxy-health.js +0 -253
  317. package/bin/lib/telemetry.js +0 -441
@@ -0,0 +1,572 @@
1
+ #!/bin/bash
2
+ # Hive Brain utility functions — extracted from aether-utils.sh
3
+ # Provides: _hive_init, _hive_store, _hive_read, _hive_abstract, _hive_promote
4
+ #
5
+ # These functions are sourced by aether-utils.sh at startup.
6
+ # All shared infrastructure (json_ok, json_err, atomic_write, acquire_lock,
7
+ # release_lock, LOCK_DIR, DATA_DIR, SCRIPT_DIR, error constants) is available.
8
+ #
9
+ # Lock handling: hive-store, hive-read, hive-init use acquire_lock_at/release_lock_at
10
+ # with colony-tagged lock files for cross-repo mutual exclusion on hub-level resources.
11
+ # This avoids global LOCK_DIR mutation (previous approach).
12
+
13
+ _hive_init() {
14
+ # Initialize the ~/.aether/hive/ directory and wisdom.json schema
15
+ # Usage: hive-init
16
+ # Idempotent: safe to call multiple times — will NOT overwrite existing wisdom.json
17
+
18
+ hv_hive_dir="$HOME/.aether/hive"
19
+ hv_wisdom_file="$hv_hive_dir/wisdom.json"
20
+ hv_already_existed="false"
21
+
22
+ mkdir -p "$hv_hive_dir"
23
+
24
+ if [[ -f "$hv_wisdom_file" ]]; then
25
+ hv_already_existed="true"
26
+ else
27
+ hv_created_at=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
28
+ hv_initial_schema=$(jq -n \
29
+ --arg created_at "$hv_created_at" \
30
+ --arg last_updated "$hv_created_at" \
31
+ '{
32
+ version: "1.0.0",
33
+ created_at: $created_at,
34
+ last_updated: $last_updated,
35
+ entries: [],
36
+ metadata: {
37
+ total_entries: 0,
38
+ max_entries: 200,
39
+ contributing_repos: []
40
+ }
41
+ }')
42
+
43
+ hv_lock_held=false
44
+ hv_lock_file=""
45
+ if type acquire_lock_at &>/dev/null; then
46
+ hv_colony_tag=$(bash "$0" colony-name 2>/dev/null | jq -r '.result.name // ""' | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//') || true
47
+ [[ -z "$hv_colony_tag" ]] && hv_colony_tag="unknown"
48
+ acquire_lock_at "$hv_wisdom_file" "$hv_hive_dir" "$hv_colony_tag" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on wisdom.json"
49
+ hv_lock_file="$LOCK_AT_FILE"
50
+ hv_lock_held=true
51
+ fi
52
+
53
+ atomic_write "$hv_wisdom_file" "$hv_initial_schema" || {
54
+ [[ "$hv_lock_held" == "true" ]] && { release_lock_at "$hv_lock_file" 2>/dev/null || true; hv_lock_held=false; }
55
+ json_err "$E_JSON_INVALID" "Failed to write wisdom.json"
56
+ }
57
+
58
+ [[ "$hv_lock_held" == "true" ]] && { release_lock_at "$hv_lock_file" 2>/dev/null || true; hv_lock_held=false; }
59
+ fi
60
+
61
+ json_ok "$(jq -n --arg dir "$hv_hive_dir" --argjson already_existed "$hv_already_existed" '{dir: $dir, initialized: true, already_existed: $already_existed}')"
62
+ }
63
+
64
+ _hive_store() {
65
+ # Store a wisdom entry in ~/.aether/hive/wisdom.json
66
+ # Usage: hive-store --text <text> --domain <csv> --source-repo <path> --confidence <0-1> --category <cat>
67
+ # Deduplicates by content hash. Same-repo dups skipped, cross-repo dups merged.
68
+ # Enforces 200 entry cap — evicts oldest by last_accessed when full.
69
+
70
+ hs_text=""
71
+ hs_domain=""
72
+ hs_source_repo=""
73
+ hs_confidence="0.5"
74
+ hs_category="general"
75
+
76
+ while [[ $# -gt 0 ]]; do
77
+ case "$1" in
78
+ --text) hs_text="${2:-}"; shift 2 ;;
79
+ --domain) hs_domain="${2:-}"; shift 2 ;;
80
+ --source-repo) hs_source_repo="${2:-}"; shift 2 ;;
81
+ --confidence) hs_confidence="${2:-0.5}"; shift 2 ;;
82
+ --category) hs_category="${2:-general}"; shift 2 ;;
83
+ *) shift ;;
84
+ esac
85
+ done
86
+
87
+ # Validate required fields
88
+ [[ -z "$hs_text" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --text argument" '{"missing":"text"}'
89
+ [[ -z "$hs_source_repo" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --source-repo argument" '{"missing":"source_repo"}'
90
+
91
+ # Validate confidence range
92
+ if ! [[ "$hs_confidence" =~ ^(0(\.[0-9]+)?|1(\.0+)?)$ ]]; then
93
+ json_err "$E_VALIDATION_FAILED" "Confidence must be a number between 0.0 and 1.0" "{\"provided\":\"$hs_confidence\"}"
94
+ fi
95
+
96
+ # Content sanitization (matches pheromone-write pattern)
97
+ if echo "$hs_text" | grep -Eiq '<[[:space:]]*/?(system|prompt|instructions|system-reminder|assistant|user|human)'; then
98
+ json_err "$E_VALIDATION_FAILED" "Wisdom content rejected: XML tag injection pattern detected"
99
+ fi
100
+ hs_text="${hs_text//</&lt;}"
101
+ hs_text="${hs_text//>/&gt;}"
102
+ hs_text="${hs_text:0:500}"
103
+ if echo "$hs_text" | grep -Eiq '(\$\(|`|(^|[[:space:]])curl([[:space:]]|$)|(^|[[:space:]])wget([[:space:]]|$)|(^|[[:space:]])rm([[:space:]]|$))'; then
104
+ json_err "$E_VALIDATION_FAILED" "Wisdom content rejected: potential injection pattern"
105
+ fi
106
+ if echo "$hs_text" | grep -Eiq '(ignore\s+(all\s+)?(previous\s+|prior\s+|above\s+)?instructions|disregard\s+(above|previous|all)|you are now |new instructions:|system prompt)'; then
107
+ json_err "$E_VALIDATION_FAILED" "Wisdom content rejected: prompt injection pattern detected"
108
+ fi
109
+
110
+ # Ensure hive is initialized
111
+ bash "$0" hive-init >/dev/null 2>&1 || json_err "$E_FILE_NOT_FOUND" "Unable to initialize hive"
112
+
113
+ hs_wisdom_file="$HOME/.aether/hive/wisdom.json"
114
+ [[ -f "$hs_wisdom_file" ]] || json_err "$E_FILE_NOT_FOUND" "Hive wisdom file not found"
115
+
116
+ if ! jq -e . "$hs_wisdom_file" >/dev/null 2>&1; then
117
+ json_err "$E_JSON_INVALID" "Hive wisdom JSON is invalid"
118
+ fi
119
+
120
+ # Generate content hash (first 12 chars of SHA-256)
121
+ hs_content_hash=$(printf '%s' "$hs_text" | shasum -a 256 | cut -c1-12)
122
+
123
+ # Parse domain tags CSV into JSON array
124
+ hs_domain_json="[]"
125
+ if [[ -n "$hs_domain" ]]; then
126
+ hs_domain_json=$(echo "$hs_domain" | tr ',' '\n' | jq -R 'gsub("^\\s+|\\s+$";"")' | jq -s '.')
127
+ fi
128
+
129
+ hs_now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
130
+
131
+ # Acquire lock — use colony-tagged lock in hub dir for cross-repo mutual exclusion
132
+ hs_lock_held=false
133
+ hs_lock_file=""
134
+ if type acquire_lock_at &>/dev/null; then
135
+ hs_colony_tag=$(bash "$0" colony-name 2>/dev/null | jq -r '.result.name // ""' | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//') || true
136
+ [[ -z "$hs_colony_tag" ]] && hs_colony_tag="unknown"
137
+ acquire_lock_at "$hs_wisdom_file" "$HOME/.aether/hive" "$hs_colony_tag" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on wisdom.json"
138
+ hs_lock_file="$LOCK_AT_FILE"
139
+ hs_lock_held=true
140
+ fi
141
+
142
+ # Check for existing entry with same content hash
143
+ hs_existing_idx=$(jq --arg hash "$hs_content_hash" '
144
+ .entries | to_entries | map(select(.value.id == $hash)) | .[0].key // -1
145
+ ' "$hs_wisdom_file" 2>/dev/null)
146
+
147
+ if [[ "$hs_existing_idx" != "-1" ]] && [[ "$hs_existing_idx" != "null" ]] && [[ -n "$hs_existing_idx" ]]; then
148
+ # Entry exists — check if same repo
149
+ hs_has_repo=$(jq --arg hash "$hs_content_hash" --arg repo "$hs_source_repo" '
150
+ .entries[] | select(.id == $hash) | .source_repos | map(select(. == $repo)) | length > 0
151
+ ' "$hs_wisdom_file" 2>/dev/null)
152
+
153
+ if [[ "$hs_has_repo" == "true" ]]; then
154
+ # Same repo duplicate — skip
155
+ [[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
156
+ json_ok "$(jq -n --arg id "$hs_content_hash" \
157
+ '{action: "skipped", reason: "duplicate from same repo", id: $id}')"
158
+ else
159
+ # Different repo — merge: increment validated_count, add repo, boost confidence
160
+ hs_updated=$(jq --arg hash "$hs_content_hash" \
161
+ --arg repo "$hs_source_repo" \
162
+ --arg now "$hs_now_iso" '
163
+ .entries = [.entries[] |
164
+ if .id == $hash then
165
+ .validated_count = (.validated_count + 1) |
166
+ .source_repos = (.source_repos + [$repo] | unique) |
167
+ .last_accessed = $now |
168
+ # Confidence boosting: tier based on source_repos count
169
+ # 2 repos -> 0.7, 3 repos -> 0.85, 4+ repos -> 0.95
170
+ # Never downgrade: use max(current, tier)
171
+ (.source_repos | length) as $repo_count |
172
+ (if $repo_count >= 4 then 0.95
173
+ elif $repo_count == 3 then 0.85
174
+ elif $repo_count == 2 then 0.7
175
+ else .confidence end) as $tier_confidence |
176
+ .confidence = ([.confidence, $tier_confidence] | max)
177
+ else . end
178
+ ] |
179
+ .metadata.contributing_repos = ([.entries[].source_repos[]] | unique) |
180
+ .last_updated = $now
181
+ ' "$hs_wisdom_file" 2>/dev/null) || {
182
+ [[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
183
+ json_err "$E_JSON_INVALID" "Failed to merge wisdom entry"
184
+ }
185
+
186
+ atomic_write "$hs_wisdom_file" "$hs_updated" || {
187
+ [[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
188
+ json_err "$E_JSON_INVALID" "Failed to write merged wisdom entry"
189
+ }
190
+
191
+ [[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
192
+ hs_new_count=$(echo "$hs_updated" | jq --arg hash "$hs_content_hash" '.entries[] | select(.id == $hash) | .validated_count')
193
+ hs_new_confidence=$(echo "$hs_updated" | jq --arg hash "$hs_content_hash" '.entries[] | select(.id == $hash) | .confidence')
194
+ json_ok "$(jq -n --arg id "$hs_content_hash" --argjson validated_count "$hs_new_count" \
195
+ --argjson confidence "$hs_new_confidence" \
196
+ '{action: "merged", id: $id, validated_count: $validated_count, confidence: $confidence}')"
197
+ fi
198
+ else
199
+ # New entry — build and append
200
+ hs_entry=$(jq -n \
201
+ --arg id "$hs_content_hash" \
202
+ --arg text "$hs_text" \
203
+ --arg category "$hs_category" \
204
+ --argjson confidence "$hs_confidence" \
205
+ --argjson domain_tags "$hs_domain_json" \
206
+ --arg source_repo "$hs_source_repo" \
207
+ --arg created_at "$hs_now_iso" \
208
+ --arg last_accessed "$hs_now_iso" \
209
+ '{
210
+ id: $id,
211
+ text: $text,
212
+ category: $category,
213
+ confidence: $confidence,
214
+ domain_tags: $domain_tags,
215
+ source_repos: [$source_repo],
216
+ validated_count: 1,
217
+ created_at: $created_at,
218
+ last_accessed: $last_accessed,
219
+ access_count: 0
220
+ }')
221
+
222
+ # Append entry and enforce 200 cap (evict oldest by last_accessed)
223
+ hs_updated=$(jq --argjson entry "$hs_entry" --arg now "$hs_now_iso" '
224
+ .entries = (.entries + [$entry]) |
225
+ if (.entries | length) > 200 then
226
+ .entries = (.entries | sort_by(.last_accessed) | .[-200:])
227
+ else . end |
228
+ .metadata.total_entries = (.entries | length) |
229
+ .metadata.contributing_repos = ([.entries[].source_repos[]] | unique) |
230
+ .last_updated = $now
231
+ ' "$hs_wisdom_file" 2>/dev/null) || {
232
+ [[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
233
+ json_err "$E_JSON_INVALID" "Failed to append wisdom entry"
234
+ }
235
+
236
+ atomic_write "$hs_wisdom_file" "$hs_updated" || {
237
+ [[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
238
+ json_err "$E_JSON_INVALID" "Failed to write new wisdom entry"
239
+ }
240
+
241
+ [[ "$hs_lock_held" == "true" ]] && { release_lock_at "$hs_lock_file" 2>/dev/null || true; hs_lock_held=false; }
242
+ json_ok "$(jq -n --arg id "$hs_content_hash" --arg category "$hs_category" \
243
+ '{action: "stored", id: $id, category: $category}')"
244
+ fi
245
+ }
246
+
247
+ _hive_read() {
248
+ # Read wisdom entries from ~/.aether/hive/wisdom.json with filtering and access tracking
249
+ # Usage: hive-read [--domain <csv>] [--limit <N>] [--min-confidence <0.0-1.0>] [--format <json|text>]
250
+ # Increments access_count and updates last_accessed for returned entries.
251
+
252
+ hr_domain=""
253
+ hr_limit="10"
254
+ hr_min_confidence="0.0"
255
+ hr_format="json"
256
+
257
+ while [[ $# -gt 0 ]]; do
258
+ case "$1" in
259
+ --domain) hr_domain="${2:-}"; shift 2 ;;
260
+ --limit) hr_limit="${2:-10}"; shift 2 ;;
261
+ --min-confidence) hr_min_confidence="${2:-0.0}"; shift 2 ;;
262
+ --format) hr_format="${2:-json}"; shift 2 ;;
263
+ *) shift ;;
264
+ esac
265
+ done
266
+
267
+ # Validate limit is a positive integer
268
+ if ! [[ "$hr_limit" =~ ^[0-9]+$ ]] || [[ "$hr_limit" -lt 1 ]]; then
269
+ json_err "$E_VALIDATION_FAILED" "Limit must be a positive integer" "{\"provided\":\"$hr_limit\"}"
270
+ fi
271
+
272
+ # Validate min-confidence is a valid number 0.0-1.0
273
+ if ! [[ "$hr_min_confidence" =~ ^(0(\.[0-9]+)?|1(\.0+)?)$ ]]; then
274
+ json_err "$E_VALIDATION_FAILED" "Min-confidence must be a number between 0.0 and 1.0" "{\"provided\":\"$hr_min_confidence\"}"
275
+ fi
276
+
277
+ # Validate format
278
+ if [[ "$hr_format" != "json" ]] && [[ "$hr_format" != "text" ]]; then
279
+ json_err "$E_VALIDATION_FAILED" "Format must be 'json' or 'text'" "{\"provided\":\"$hr_format\"}"
280
+ fi
281
+
282
+ hr_wisdom_file="$HOME/.aether/hive/wisdom.json"
283
+
284
+ # Fallback: no wisdom file
285
+ if [[ ! -f "$hr_wisdom_file" ]]; then
286
+ json_ok '{"entries":[],"total_matched":0,"fallback":"no_hive"}'
287
+ exit 0
288
+ fi
289
+
290
+ # Validate JSON
291
+ if ! jq -e . "$hr_wisdom_file" >/dev/null 2>&1; then
292
+ json_ok '{"entries":[],"total_matched":0,"fallback":"invalid_json"}'
293
+ exit 0
294
+ fi
295
+
296
+ # Parse domain tags CSV into JSON array for jq filtering
297
+ hr_domain_json="[]"
298
+ if [[ -n "$hr_domain" ]]; then
299
+ hr_domain_json=$(echo "$hr_domain" | tr ',' '\n' | jq -R 'gsub("^\\s+|\\s+$";"")' | jq -s '.')
300
+ fi
301
+
302
+ # Filter, sort, and select entries using jq
303
+ hr_filtered=$(jq \
304
+ --argjson domain_filter "$hr_domain_json" \
305
+ --argjson min_conf "$hr_min_confidence" \
306
+ --argjson limit "$hr_limit" '
307
+ .entries
308
+ | map(
309
+ select(((.confidence // 0) | tonumber) >= $min_conf)
310
+ | if ($domain_filter | length) > 0 then
311
+ select(
312
+ [.domain_tags[] as $dt | $domain_filter[] | select(. == $dt)] | length > 0
313
+ )
314
+ else . end
315
+ )
316
+ | sort_by(-((.confidence // 0) | tonumber), -.validated_count)
317
+ | { total_matched: length, entries: .[:$limit], returned_ids: [.[:$limit][].id] }
318
+ ' "$hr_wisdom_file" 2>/dev/null) || {
319
+ json_ok '{"entries":[],"total_matched":0,"fallback":"filter_error"}'
320
+ exit 0
321
+ }
322
+
323
+ hr_total_matched=$(echo "$hr_filtered" | jq -r '.total_matched')
324
+ hr_returned_ids=$(echo "$hr_filtered" | jq -c '.returned_ids')
325
+ hr_entries=$(echo "$hr_filtered" | jq -c '.entries')
326
+
327
+ # Update access_count and last_accessed for returned entries
328
+ if [[ "$hr_total_matched" -gt 0 ]] && [[ $(echo "$hr_returned_ids" | jq 'length') -gt 0 ]]; then
329
+ hr_now_iso=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
330
+
331
+ hr_lock_held=false
332
+ hr_lock_file=""
333
+ if type acquire_lock_at &>/dev/null; then
334
+ hr_colony_tag=$(bash "$0" colony-name 2>/dev/null | jq -r '.result.name // ""' | tr '[:upper:]' '[:lower:]' | tr -cs '[:alnum:]' '-' | sed 's/^-//;s/-$//') || true
335
+ [[ -z "$hr_colony_tag" ]] && hr_colony_tag="unknown"
336
+ acquire_lock_at "$hr_wisdom_file" "$HOME/.aether/hive" "$hr_colony_tag" || json_err "$E_LOCK_FAILED" "Failed to acquire lock on wisdom.json"
337
+ hr_lock_file="$LOCK_AT_FILE"
338
+ hr_lock_held=true
339
+ fi
340
+
341
+ hr_updated=$(jq \
342
+ --argjson returned_ids "$hr_returned_ids" \
343
+ --arg now "$hr_now_iso" '
344
+ .entries = [.entries[] |
345
+ if (.id as $id | $returned_ids | index($id)) != null then
346
+ .access_count = ((.access_count // 0) + 1) |
347
+ .last_accessed = $now
348
+ else . end
349
+ ] |
350
+ .last_updated = $now
351
+ ' "$hr_wisdom_file" 2>/dev/null) || {
352
+ [[ "$hr_lock_held" == "true" ]] && { release_lock_at "$hr_lock_file" 2>/dev/null || true; hr_lock_held=false; }
353
+ json_err "$E_JSON_INVALID" "Failed to update access tracking in wisdom.json"
354
+ }
355
+
356
+ atomic_write "$hr_wisdom_file" "$hr_updated" || {
357
+ [[ "$hr_lock_held" == "true" ]] && { release_lock_at "$hr_lock_file" 2>/dev/null || true; hr_lock_held=false; }
358
+ json_err "$E_JSON_INVALID" "Failed to write updated wisdom.json"
359
+ }
360
+
361
+ [[ "$hr_lock_held" == "true" ]] && { release_lock_at "$hr_lock_file" 2>/dev/null || true; hr_lock_held=false; }
362
+ fi
363
+
364
+ # Format output
365
+ if [[ "$hr_format" == "text" ]]; then
366
+ hr_text_output=$(echo "$hr_entries" | jq -r '
367
+ . as $entries |
368
+ if ($entries | length) == 0 then "(no wisdom entries)"
369
+ else
370
+ [range($entries | length)] |
371
+ map(
372
+ $entries[.] |
373
+ "[\(.confidence | tostring)] [\(.category)] \(.text) (validated: \(.validated_count), domains: \(.domain_tags | join(", ")))"
374
+ ) | join("\n")
375
+ end
376
+ ' 2>/dev/null)
377
+
378
+ hr_text_escaped=$(echo "$hr_text_output" | jq -Rs '.')
379
+ json_ok "$(jq -n --argjson entries "$hr_entries" --argjson total_matched "$hr_total_matched" \
380
+ --argjson text "$hr_text_escaped" \
381
+ '{entries: $entries, total_matched: $total_matched, text: $text}')"
382
+ else
383
+ json_ok "$(jq -n --argjson entries "$hr_entries" --argjson total_matched "$hr_total_matched" \
384
+ '{entries: $entries, total_matched: $total_matched}')"
385
+ fi
386
+ }
387
+
388
+ _hive_abstract() {
389
+ # Abstract a repo-specific instinct into generalized cross-colony wisdom
390
+ # Usage: hive-abstract --text <instinct_text> --source-repo <repo_path> [--domain <csv>]
391
+ # Returns: JSON with original text, abstracted text, and transformations applied.
392
+ # This is a TEXT TRANSFORMATION only — does NOT write to wisdom.json.
393
+
394
+ ha_text=""
395
+ ha_source_repo=""
396
+ ha_domain=""
397
+
398
+ while [[ $# -gt 0 ]]; do
399
+ case "$1" in
400
+ --text) ha_text="${2:-}"; shift 2 ;;
401
+ --source-repo) ha_source_repo="${2:-}"; shift 2 ;;
402
+ --domain) ha_domain="${2:-}"; shift 2 ;;
403
+ *) shift ;;
404
+ esac
405
+ done
406
+
407
+ # Validate required fields
408
+ [[ -z "$ha_text" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --text argument" '{"missing":"text"}'
409
+ [[ -z "$ha_source_repo" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --source-repo argument" '{"missing":"source_repo"}'
410
+
411
+ # Content sanitization (same pattern as hive-store)
412
+ if echo "$ha_text" | grep -Eiq '<[[:space:]]*/?(system|prompt|instructions|system-reminder|assistant|user|human)'; then
413
+ json_err "$E_VALIDATION_FAILED" "Content rejected: XML tag injection pattern detected"
414
+ fi
415
+ ha_text="${ha_text//</&lt;}"
416
+ ha_text="${ha_text//>/&gt;}"
417
+ ha_text="${ha_text:0:500}"
418
+ if echo "$ha_text" | grep -Eiq '(\$\(|`|(^|[[:space:]])curl([[:space:]]|$)|(^|[[:space:]])wget([[:space:]]|$)|(^|[[:space:]])rm([[:space:]]|$))'; then
419
+ json_err "$E_VALIDATION_FAILED" "Content rejected: potential injection pattern"
420
+ fi
421
+ if echo "$ha_text" | grep -Eiq '(ignore\s+(all\s+)?(previous\s+|prior\s+|above\s+)?instructions|disregard\s+(above|previous|all)|you are now |new instructions:|system prompt)'; then
422
+ json_err "$E_VALIDATION_FAILED" "Content rejected: prompt injection pattern detected"
423
+ fi
424
+
425
+ # Save original (post-sanitization) for output
426
+ ha_original="$ha_text"
427
+
428
+ # Track which transformations are applied
429
+ ha_transforms=()
430
+
431
+ # Extract repo basename for name stripping
432
+ ha_repo_basename=$(basename "$ha_source_repo")
433
+
434
+ # 1. Strip absolute file paths (match /path/to/file.ext patterns)
435
+ # Replace paths that start with / and contain at least 2 segments
436
+ ha_abstracted="$ha_text"
437
+ if echo "$ha_abstracted" | grep -qE '/[A-Za-z_][A-Za-z0-9_./-]*/[A-Za-z0-9_./-]+'; then
438
+ ha_abstracted=$(echo "$ha_abstracted" | sed -E 's|/[A-Za-z_][A-Za-z0-9_./-]*/[A-Za-z0-9_./-]+|<source-file>|g')
439
+ ha_transforms+=("path_strip")
440
+ fi
441
+
442
+ # 2. Strip repo basename (case-sensitive match of the project name)
443
+ if [[ -n "$ha_repo_basename" ]] && echo "$ha_abstracted" | grep -qF "$ha_repo_basename"; then
444
+ ha_abstracted=$(echo "$ha_abstracted" | sed "s|${ha_repo_basename}|<project>|g")
445
+ ha_transforms+=("repo_name_strip")
446
+ fi
447
+
448
+ # 3. Strip version numbers (v1.2.3, v2.0.0, etc.)
449
+ if echo "$ha_abstracted" | grep -qE 'v[0-9]+\.[0-9]+\.[0-9]+'; then
450
+ ha_abstracted=$(echo "$ha_abstracted" | sed -E 's/v[0-9]+\.[0-9]+\.[0-9]+/<version>/g')
451
+ ha_transforms+=("version_strip")
452
+ fi
453
+
454
+ # 4. Strip branch names (feature/xyz, bugfix/abc, hotfix/def, release/xyz)
455
+ if echo "$ha_abstracted" | grep -qE '(feature|bugfix|hotfix|release)/[A-Za-z0-9_.-]+'; then
456
+ ha_abstracted=$(echo "$ha_abstracted" | sed -E 's/(feature|bugfix|hotfix|release)\/[A-Za-z0-9_.-]+/<branch>/g')
457
+ ha_transforms+=("branch_strip")
458
+ fi
459
+
460
+ # Parse domain tags CSV into JSON array
461
+ ha_domain_json="[]"
462
+ if [[ -n "$ha_domain" ]]; then
463
+ ha_domain_json=$(echo "$ha_domain" | tr ',' '\n' | jq -R 'gsub("^\\s+|\\s+$";"")' | jq -s '.')
464
+ fi
465
+
466
+ # Build transformations JSON array
467
+ ha_transforms_json="[]"
468
+ if [[ ${#ha_transforms[@]} -gt 0 ]]; then
469
+ ha_transforms_json=$(printf '%s\n' "${ha_transforms[@]}" | jq -R '.' | jq -s '.')
470
+ fi
471
+
472
+ # Build result JSON using jq for proper escaping
473
+ ha_result=$(jq -n \
474
+ --arg original "$ha_original" \
475
+ --arg abstracted "$ha_abstracted" \
476
+ --arg source_repo "$ha_source_repo" \
477
+ --argjson domain_tags "$ha_domain_json" \
478
+ --argjson transformations "$ha_transforms_json" \
479
+ '{
480
+ original: $original,
481
+ abstracted: $abstracted,
482
+ source_repo: $source_repo,
483
+ domain_tags: $domain_tags,
484
+ transformations_applied: $transformations
485
+ }')
486
+
487
+ json_ok "$ha_result"
488
+ }
489
+
490
+ _hive_promote() {
491
+ # Orchestrate the full promotion pipeline: abstract an instinct, then store it
492
+ # Usage: hive-promote --text <instinct_text> --source-repo <repo_path> [--domain <csv>] [--confidence <0-1>] [--category <cat>]
493
+ # Calls hive-abstract internally to generalize the text, then hive-store to persist it.
494
+ # Returns: combined result with action mapping (stored->promoted, merged->merged, skipped->skipped).
495
+
496
+ hp_text=""
497
+ hp_source_repo=""
498
+ hp_domain=""
499
+ hp_confidence="0.7"
500
+ hp_category="pattern"
501
+
502
+ while [[ $# -gt 0 ]]; do
503
+ case "$1" in
504
+ --text) hp_text="${2:-}"; shift 2 ;;
505
+ --source-repo) hp_source_repo="${2:-}"; shift 2 ;;
506
+ --domain) hp_domain="${2:-}"; shift 2 ;;
507
+ --confidence) hp_confidence="${2:-0.7}"; shift 2 ;;
508
+ --category) hp_category="${2:-pattern}"; shift 2 ;;
509
+ *) shift ;;
510
+ esac
511
+ done
512
+
513
+ # Validate required fields
514
+ [[ -z "$hp_text" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --text argument" '{"missing":"text"}'
515
+ [[ -z "$hp_source_repo" ]] && json_err "$E_VALIDATION_FAILED" "Missing required --source-repo argument" '{"missing":"source_repo"}'
516
+
517
+ # Ensure hive is initialized (idempotent)
518
+ bash "$0" hive-init >/dev/null 2>&1 || json_err "$E_FILE_NOT_FOUND" "Unable to initialize hive"
519
+
520
+ # Step 1: Abstract the instinct text
521
+ hp_abstract_args=(hive-abstract --text "$hp_text" --source-repo "$hp_source_repo")
522
+ [[ -n "$hp_domain" ]] && hp_abstract_args+=(--domain "$hp_domain")
523
+
524
+ hp_abstract_result=$(bash "$0" "${hp_abstract_args[@]}" 2>&1) || {
525
+ json_err "$E_VALIDATION_FAILED" "Abstraction failed: $(echo "$hp_abstract_result" | jq -r '.error.message // "unknown error"' 2>/dev/null)"
526
+ }
527
+
528
+ # Extract abstracted text and transformations from abstract result
529
+ hp_abstracted=$(echo "$hp_abstract_result" | jq -r '.result.abstracted // empty' 2>/dev/null)
530
+ hp_original=$(echo "$hp_abstract_result" | jq -r '.result.original // empty' 2>/dev/null)
531
+ hp_transforms=$(echo "$hp_abstract_result" | jq -c '.result.transformations_applied // []' 2>/dev/null)
532
+
533
+ if [[ -z "$hp_abstracted" ]]; then
534
+ json_err "$E_VALIDATION_FAILED" "Abstraction returned empty text"
535
+ fi
536
+
537
+ # Step 2: Store the abstracted text in the hive
538
+ hp_store_args=(hive-store --text "$hp_abstracted" --source-repo "$hp_source_repo" --confidence "$hp_confidence" --category "$hp_category")
539
+ [[ -n "$hp_domain" ]] && hp_store_args+=(--domain "$hp_domain")
540
+
541
+ hp_store_result=$(bash "$0" "${hp_store_args[@]}" 2>&1) || {
542
+ json_err "$E_VALIDATION_FAILED" "Store failed: $(echo "$hp_store_result" | jq -r '.error.message // "unknown error"' 2>/dev/null)"
543
+ }
544
+
545
+ # Extract store action
546
+ hp_store_action=$(echo "$hp_store_result" | jq -r '.result.action // "unknown"' 2>/dev/null)
547
+
548
+ # Map store action to promote action: stored->promoted, merged->merged, skipped->skipped
549
+ hp_action="$hp_store_action"
550
+ [[ "$hp_store_action" == "stored" ]] && hp_action="promoted"
551
+
552
+ # Build combined result
553
+ hp_result=$(jq -n \
554
+ --arg action "$hp_action" \
555
+ --arg original "$hp_original" \
556
+ --arg abstracted "$hp_abstracted" \
557
+ --argjson transformations "$hp_transforms" \
558
+ --arg store_action "$hp_store_action" \
559
+ --argjson confidence "$hp_confidence" \
560
+ --arg source_repo "$hp_source_repo" \
561
+ '{
562
+ action: $action,
563
+ original: $original,
564
+ abstracted: $abstracted,
565
+ transformations: $transformations,
566
+ store_action: $store_action,
567
+ confidence: $confidence,
568
+ source_repo: $source_repo
569
+ }')
570
+
571
+ json_ok "$hp_result"
572
+ }