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,611 @@
1
+ #!/usr/bin/env bash
2
+ # Suggest utility functions -- extracted from aether-utils.sh
3
+ # Provides: _suggest_analyze, _suggest_record, _suggest_check,
4
+ # _suggest_clear, _suggest_approve, _suggest_quick_dismiss
5
+ # Also includes: get_type_emoji (helper used only by _suggest_approve)
6
+ # Note: suggest-clear is deprecated (moved with domain per user decision)
7
+
8
+ # ============================================================================
9
+ # get_type_emoji
10
+ # Helper function for suggest-approve display (bash 3.2 compatible)
11
+ # Usage: get_type_emoji <TYPE>
12
+ # Returns: emoji string for the given pheromone type
13
+ # ============================================================================
14
+ get_type_emoji() {
15
+ case "$1" in
16
+ FOCUS) echo "🎯" ;;
17
+ REDIRECT) echo "🚫" ;;
18
+ FEEDBACK) echo "💬" ;;
19
+ *) echo "📝" ;;
20
+ esac
21
+ }
22
+
23
+ # ============================================================================
24
+ # _suggest_analyze
25
+ # Analyze codebase and return pheromone suggestions based on code patterns
26
+ # Usage: _suggest_analyze [--source-dir DIR] [--max-suggestions N] [--dry-run]
27
+ # Returns: JSON with suggestions array and analysis metadata
28
+ # ============================================================================
29
+ _suggest_analyze() {
30
+ # Disable ERR trap for this command (grep returns 1 on no match, which triggers trap)
31
+ trap '' ERR
32
+
33
+ source_dir=""
34
+ max_suggestions=5
35
+ dry_run=false
36
+
37
+ # Parse arguments - note: $1 is already shifted by the main dispatch
38
+ # So $1 here is the first argument after 'suggest-analyze'
39
+ while [[ $# -gt 0 ]]; do
40
+ case "$1" in
41
+ --source-dir) source_dir="$2"; shift 2 ;;
42
+ --max-suggestions) max_suggestions="$2"; shift 2 ;;
43
+ --dry-run) dry_run=true; shift ;;
44
+ *) shift ;;
45
+ esac
46
+ done
47
+
48
+ # Auto-detect source directory if not provided
49
+ if [[ -z "$source_dir" ]]; then
50
+ if [[ -d "$AETHER_ROOT/src" ]]; then
51
+ source_dir="$AETHER_ROOT/src"
52
+ elif [[ -d "$AETHER_ROOT/lib" ]]; then
53
+ source_dir="$AETHER_ROOT/lib"
54
+ else
55
+ source_dir="$AETHER_ROOT"
56
+ fi
57
+ fi
58
+
59
+ # Validate source directory
60
+ if [[ ! -d "$source_dir" ]]; then
61
+ json_err "$E_FILE_NOT_FOUND" "Source directory not found: $source_dir"
62
+ fi
63
+
64
+ # Build JSON array of suggestions using jq
65
+ # We use jq to handle deduplication since bash 3.2 doesn't support associative arrays
66
+ pheromones_file="$COLONY_DATA_DIR/pheromones.json"
67
+ session_file="$COLONY_DATA_DIR/session.json"
68
+
69
+ # Create temp file for collecting raw suggestions
70
+ raw_suggestions=$(mktemp)
71
+ echo "[]" > "$raw_suggestions"
72
+
73
+ analyzed_count=0
74
+ patterns_found=0
75
+
76
+ # Define exclusions (use word boundaries to avoid matching partial paths)
77
+ exclude_pattern="node_modules/|/.aether/|/dist/|/build/|/\\.git/|/coverage/|\\.min\\.js"
78
+
79
+ # Find files to analyze (respecting exclusions)
80
+ while IFS= read -r file || [[ -n "$file" ]]; do
81
+ analyzed_count=$((analyzed_count + 1))
82
+
83
+ # Skip excluded paths
84
+ if echo "$file" | grep -qE "$exclude_pattern"; then
85
+ continue
86
+ fi
87
+
88
+ # Get file extension
89
+ ext="${file##*.}"
90
+
91
+ # Check file size (large files > 300 lines)
92
+ line_count=$(wc -l < "$file" 2>/dev/null || echo "0") # SUPPRESS:OK -- read-default: file may not exist
93
+ if [[ $line_count -gt 300 ]]; then
94
+ patterns_found=$((patterns_found + 1))
95
+ content="Large file: consider refactoring ($line_count lines)"
96
+ reason="File exceeds 300 lines, consider breaking into smaller modules"
97
+ hash=$(echo -n "$file:FOCUS:$content" | shasum -a 256 2>/dev/null | cut -d' ' -f1) || { # SUPPRESS:OK -- read-default: hash generation with fallback
98
+ _aether_log_error "Could not generate content hash -- using timestamp fallback"
99
+ hash="$(date +%s%N)"
100
+ }
101
+
102
+ # Append suggestion to raw_suggestions using jq
103
+ new_suggestion=$(jq -n --arg type "FOCUS" --arg content "$content" --arg file "$file" --arg reason "$reason" --arg hash "$hash" --arg priority "7" '{type: $type, content: $content, file: $file, reason: $reason, hash: $hash, priority: ($priority | tonumber)}')
104
+ jq --argjson suggestion "$new_suggestion" '. += [$suggestion]' "$raw_suggestions" > "${raw_suggestions}.tmp" && mv "${raw_suggestions}.tmp" "$raw_suggestions"
105
+ fi
106
+
107
+ # Check for TODO/FIXME/XXX comments
108
+ if [[ "$ext" =~ ^(ts|tsx|js|jsx|py|sh|md)$ ]]; then
109
+ todo_matches=$( (grep -n "TODO\\|FIXME\\|XXX" "$file" 2>/dev/null || true) | wc -l | tr -d ' \n') # SUPPRESS:OK -- read-default: file may not exist
110
+ if [[ $todo_matches -gt 0 ]]; then
111
+ patterns_found=$((patterns_found + 1))
112
+ content="$todo_matches pending TODO/FIXME comments"
113
+ reason="Unresolved markers indicate technical debt"
114
+ # SUPPRESS:OK -- read-default: hash generation with fallback
115
+ hash=$(echo -n "$file:FEEDBACK:$content" | shasum -a 256 2>/dev/null | cut -d' ' -f1) || {
116
+ _aether_log_error "Could not generate content hash -- using timestamp fallback"
117
+ hash="$(date +%s%N)"
118
+ }
119
+
120
+ new_suggestion=$(jq -n --arg type "FEEDBACK" --arg content "$content" --arg file "$file" --arg reason "$reason" --arg hash "$hash" --arg priority "4" '{type: $type, content: $content, file: $file, reason: $reason, hash: $hash, priority: ($priority | tonumber)}')
121
+ jq --argjson suggestion "$new_suggestion" '. += [$suggestion]' "$raw_suggestions" > "${raw_suggestions}.tmp" && mv "${raw_suggestions}.tmp" "$raw_suggestions"
122
+ fi
123
+ fi
124
+
125
+ # Check for debug artifacts (console.log, debugger)
126
+ if [[ "$ext" =~ ^(ts|tsx|js|jsx)$ ]]; then
127
+ # SUPPRESS:OK -- read-default: returns fallback on failure
128
+ debug_matches=$( (grep -n "console\\.log\\|debugger" "$file" 2>/dev/null || true) | wc -l | tr -d ' \n')
129
+ if [[ $debug_matches -gt 0 ]]; then
130
+ patterns_found=$((patterns_found + 1))
131
+ content="Remove debug artifacts before commit ($debug_matches found)"
132
+ reason="Debug statements should not be committed to production code"
133
+ # SUPPRESS:OK -- read-default: hash generation with fallback
134
+ hash=$(echo -n "$file:REDIRECT:$content" | shasum -a 256 2>/dev/null | cut -d' ' -f1) || {
135
+ _aether_log_error "Could not generate content hash -- using timestamp fallback"
136
+ hash="$(date +%s%N)"
137
+ }
138
+
139
+ new_suggestion=$(jq -n --arg type "REDIRECT" --arg content "$content" --arg file "$file" --arg reason "$reason" --arg hash "$hash" --arg priority "9" '{type: $type, content: $content, file: $file, reason: $reason, hash: $hash, priority: ($priority | tonumber)}')
140
+ jq --argjson suggestion "$new_suggestion" '. += [$suggestion]' "$raw_suggestions" > "${raw_suggestions}.tmp" && mv "${raw_suggestions}.tmp" "$raw_suggestions"
141
+ fi
142
+ fi
143
+
144
+ # Check for type safety gaps (: any, : unknown)
145
+ if [[ "$ext" =~ ^(ts|tsx)$ ]]; then
146
+ type_gaps=$( (grep -n ": any\\|: unknown" "$file" 2>/dev/null || true) | wc -l | tr -d ' \n') # SUPPRESS:OK -- read-default: file may not exist
147
+ if [[ $type_gaps -gt 0 ]]; then
148
+ patterns_found=$((patterns_found + 1))
149
+ content="Type safety gaps detected ($type_gaps instances)"
150
+ reason="Using 'any' or 'unknown' bypasses TypeScript's type checking"
151
+ # SUPPRESS:OK -- read-default: hash generation with fallback
152
+ hash=$(echo -n "$file:FEEDBACK:$content" | shasum -a 256 2>/dev/null | cut -d' ' -f1) || {
153
+ _aether_log_error "Could not generate content hash -- using timestamp fallback"
154
+ hash="$(date +%s%N)"
155
+ }
156
+
157
+ new_suggestion=$(jq -n --arg type "FEEDBACK" --arg content "$content" --arg file "$file" --arg reason "$reason" --arg hash "$hash" --arg priority "5" '{type: $type, content: $content, file: $file, reason: $reason, hash: $hash, priority: ($priority | tonumber)}')
158
+ jq --argjson suggestion "$new_suggestion" '. += [$suggestion]' "$raw_suggestions" > "${raw_suggestions}.tmp" && mv "${raw_suggestions}.tmp" "$raw_suggestions"
159
+ fi
160
+ fi
161
+
162
+ # Check for high complexity (function count)
163
+ if [[ "$ext" =~ ^(ts|tsx|js|jsx|py|sh)$ ]]; then
164
+ # SUPPRESS:OK -- existence-test: grep returns 1 when no matches
165
+ func_count=$(grep -cE "^function|^def |^const.*=.*function|^const.*=.*=>" "$file" 2>/dev/null | tr -d ' \n' || echo "0")
166
+ if [[ $func_count -gt 20 ]]; then
167
+ patterns_found=$((patterns_found + 1))
168
+ content="Complex module: test carefully ($func_count functions)"
169
+ reason="High function count may indicate multiple concerns; verify test coverage"
170
+ hash=$(echo -n "$file:FOCUS:$content" | shasum -a 256 2>/dev/null | cut -d' ' -f1) || { # SUPPRESS:OK -- read-default: hash generation with fallback
171
+ _aether_log_error "Could not generate content hash -- using timestamp fallback"
172
+ hash="$(date +%s%N)"
173
+ }
174
+
175
+ new_suggestion=$(jq -n --arg type "FOCUS" --arg content "$content" --arg file "$file" --arg reason "$reason" --arg hash "$hash" --arg priority "6" '{type: $type, content: $content, file: $file, reason: $reason, hash: $hash, priority: ($priority | tonumber)}')
176
+ jq --argjson suggestion "$new_suggestion" '. += [$suggestion]' "$raw_suggestions" > "${raw_suggestions}.tmp" && mv "${raw_suggestions}.tmp" "$raw_suggestions"
177
+ fi
178
+ fi
179
+
180
+ # Check for test coverage gaps
181
+ if [[ "$ext" =~ ^(ts|tsx|js|jsx|py)$ ]] && [[ ! "$file" =~ \\.test\\. ]] && [[ ! "$file" =~ \\.spec\\. ]]; then
182
+ base_name=$(basename "$file" ".${ext}")
183
+ dir_name=$(dirname "$file")
184
+
185
+ # Look for corresponding test file
186
+ if [[ -f "$dir_name/$base_name.test.$ext" ]] || [[ -f "$dir_name/$base_name.spec.$ext" ]] || \
187
+ [[ -f "$dir_name/__tests__/$base_name.test.$ext" ]] || [[ -f "$dir_name/../tests/$base_name.test.$ext" ]]; then
188
+ : # Test file exists
189
+ else
190
+ # Only suggest for files with functions (not config/pure data files)
191
+ # SUPPRESS:OK -- existence-test: grep returns 1 when no matches
192
+ if grep -qE "^function|^def |^const.*=.*function|^const.*=.*=>|^export.*function|^class " "$file" 2>/dev/null || false; then
193
+ patterns_found=$((patterns_found + 1))
194
+ content="Add tests for uncovered module: $base_name"
195
+ reason="No corresponding test file found for module with functions"
196
+ # SUPPRESS:OK -- read-default: hash generation with fallback
197
+ hash=$(echo -n "$file:FOCUS:$content" | shasum -a 256 2>/dev/null | cut -d' ' -f1) || {
198
+ _aether_log_error "Could not generate content hash -- using timestamp fallback"
199
+ hash="$(date +%s%N)"
200
+ }
201
+
202
+ new_suggestion=$(jq -n --arg type "FOCUS" --arg content "$content" --arg file "$file" --arg reason "$reason" --arg hash "$hash" --arg priority "5" '{type: $type, content: $content, file: $file, reason: $reason, hash: $hash, priority: ($priority | tonumber)}')
203
+ jq --argjson suggestion "$new_suggestion" '. += [$suggestion]' "$raw_suggestions" > "${raw_suggestions}.tmp" && mv "${raw_suggestions}.tmp" "$raw_suggestions"
204
+ fi
205
+ fi
206
+ fi
207
+
208
+ # SUPPRESS:OK -- existence-test: directory may not exist
209
+ done < <(find "$source_dir" -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" -o -name "*.py" -o -name "*.sh" -o -name "*.md" \) 2>/dev/null | head -100)
210
+
211
+ # Deduplicate against existing pheromones and session suggestions using jq
212
+ # Get existing signal content hashes
213
+ existing_hashes="[]"
214
+ if [[ -f "$pheromones_file" ]]; then
215
+ # SUPPRESS:OK -- read-default: query may return empty
216
+ existing_hashes=$(jq -r '[.signals[] | select(.active == true) | .content.text] | @json' "$pheromones_file" 2>/dev/null || echo "[]")
217
+ fi
218
+
219
+ session_hashes="[]"
220
+ if [[ -f "$session_file" ]]; then
221
+ # SUPPRESS:OK -- read-default: query may return empty
222
+ session_hashes=$(jq -r '[.suggested_pheromones[]?.hash // empty] | @json' "$session_file" 2>/dev/null || echo "[]")
223
+ fi
224
+
225
+ # Filter suggestions: remove duplicates and sort by priority
226
+ suggestions_json=$(jq --argjson existing "$existing_hashes" --argjson session "$session_hashes" --argjson max "$max_suggestions" '
227
+ # Remove suggestions whose content matches existing signals
228
+ map(select(.content as $c | $existing | index($c) | not)) |
229
+ # Remove suggestions whose hash is in session
230
+ map(select(.hash as $h | $session | index($h) | not)) |
231
+ # Sort by priority descending and limit
232
+ sort_by(.priority) | reverse | .[:$max]
233
+ ' "$raw_suggestions" 2>/dev/null || echo "[]") # SUPPRESS:OK -- read-default: file may not exist yet
234
+
235
+ # Clean up temp file
236
+ rm -f "$raw_suggestions"
237
+
238
+ # Build result
239
+ result=$(jq -n \
240
+ --argjson suggestions "$suggestions_json" \
241
+ --argjson analyzed "$analyzed_count" \
242
+ --argjson patterns "$patterns_found" \
243
+ '{suggestions: $suggestions, analyzed_files: $analyzed, patterns_found: $patterns}')
244
+
245
+ if [[ "$dry_run" == "true" ]]; then
246
+ echo "Dry run - analyzed: $source_dir" >&2
247
+ fi
248
+
249
+ # Re-enable ERR trap before exiting
250
+ trap 'if type error_handler &>/dev/null; then error_handler ${LINENO} "$BASH_COMMAND" $?; fi' ERR
251
+
252
+ json_ok "$result"
253
+ }
254
+
255
+ # ============================================================================
256
+ # _suggest_record
257
+ # Record a suggested pheromone hash to session.json for deduplication
258
+ # Usage: _suggest_record <hash> <type>
259
+ # Returns: JSON success/failure
260
+ # ============================================================================
261
+ _suggest_record() {
262
+ record_hash="${1:-}"
263
+ record_type="${2:-FEEDBACK}"
264
+
265
+ if [[ -z "$record_hash" ]]; then
266
+ json_err "$E_VALIDATION_FAILED" "suggest-record requires <hash> argument"
267
+ fi
268
+
269
+ session_file="$COLONY_DATA_DIR/session.json"
270
+
271
+ # Initialize suggested_pheromones array if missing
272
+ if [[ -f "$session_file" ]]; then
273
+ # Check if suggested_pheromones field exists
274
+ has_field=$(jq 'has("suggested_pheromones")' "$session_file" 2>/dev/null || echo "false") # SUPPRESS:OK -- read-default: file may not exist yet
275
+ if [[ "$has_field" != "true" ]]; then
276
+ # Add the field
277
+ jq '. + {"suggested_pheromones": []}' "$session_file" > "${session_file}.tmp" || {
278
+ _aether_log_error "Could not add suggestions field to session file"
279
+ rm -f "${session_file}.tmp"
280
+ }
281
+ if [[ -s "${session_file}.tmp" ]]; then
282
+ mv "${session_file}.tmp" "$session_file" || _aether_log_error "Could not finalize session field addition"
283
+ fi
284
+ fi
285
+
286
+ # Append new suggestion
287
+ record_entry=$(jq -n --arg hash "$record_hash" --arg type "$record_type" --arg suggested_at "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" '{hash: $hash, type: $type, suggested_at: $suggested_at}')
288
+ jq --argjson entry "$record_entry" '.suggested_pheromones += [$entry]' "$session_file" > "${session_file}.tmp" || {
289
+ _aether_log_error "Could not record suggestion in session file"
290
+ rm -f "${session_file}.tmp"
291
+ }
292
+ if [[ -s "${session_file}.tmp" ]]; then
293
+ mv "${session_file}.tmp" "$session_file" || _aether_log_error "Could not finalize suggestion recording"
294
+ fi
295
+ else
296
+ # Create session.json with suggested_pheromones
297
+ record_entry=$(jq -n --arg hash "$record_hash" --arg type "$record_type" --arg suggested_at "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" '{hash: $hash, type: $type, suggested_at: $suggested_at}')
298
+ local sr_init_content
299
+ sr_init_content=$(jq -n --argjson entry "$record_entry" '{suggested_pheromones: [$entry]}')
300
+ atomic_write "$session_file" "$sr_init_content" || _aether_log_error "Could not create session file with suggestion"
301
+ fi
302
+
303
+ json_ok '{"recorded":true}'
304
+ }
305
+
306
+ # ============================================================================
307
+ # _suggest_check
308
+ # Check if a hash was already suggested this session
309
+ # Usage: _suggest_check <hash>
310
+ # Returns: JSON {already_suggested: true/false}
311
+ # ============================================================================
312
+ _suggest_check() {
313
+ check_hash="${1:-}"
314
+
315
+ if [[ -z "$check_hash" ]]; then
316
+ json_err "$E_VALIDATION_FAILED" "suggest-check requires <hash> argument"
317
+ fi
318
+
319
+ session_file="$COLONY_DATA_DIR/session.json"
320
+ already_suggested="false"
321
+
322
+ if [[ -f "$session_file" ]]; then
323
+ # SUPPRESS:OK -- read-default: query may return empty
324
+ count=$(jq --arg hash "$check_hash" '[.suggested_pheromones[]? | select(.hash == $hash)] | length' "$session_file" 2>/dev/null || echo "0")
325
+ if [[ "$count" -gt 0 ]]; then
326
+ already_suggested="true"
327
+ fi
328
+ fi
329
+
330
+ json_ok "$(jq -n --argjson already "$already_suggested" '{already_suggested: $already}')"
331
+ }
332
+
333
+ # ============================================================================
334
+ # _suggest_clear
335
+ # Clear the suggested_pheromones array from session.json
336
+ # Usage: _suggest_clear
337
+ # Returns: JSON success with count cleared
338
+ # NOTE: This subcommand is deprecated
339
+ # ============================================================================
340
+ _suggest_clear() {
341
+ _deprecation_warning "suggest-clear"
342
+ session_file="$COLONY_DATA_DIR/session.json"
343
+ cleared_count=0
344
+
345
+ if [[ -f "$session_file" ]]; then
346
+ cleared_count=$(jq '.suggested_pheromones | length' "$session_file" 2>/dev/null || echo "0") # SUPPRESS:OK -- read-default: file may not exist yet
347
+ jq 'del(.suggested_pheromones)' "$session_file" > "${session_file}.tmp" || {
348
+ _aether_log_error "Could not clear suggestions from session file"
349
+ rm -f "${session_file}.tmp"
350
+ }
351
+ if [[ -s "${session_file}.tmp" ]]; then
352
+ mv "${session_file}.tmp" "$session_file" || _aether_log_error "Could not finalize suggestion clearing"
353
+ fi
354
+ fi
355
+
356
+ json_ok "$(jq -n --argjson cleared "$cleared_count" '{cleared: $cleared}')"
357
+ }
358
+
359
+ # ============================================================================
360
+ # _suggest_approve
361
+ # Orchestrate pheromone suggestion approval workflow
362
+ # Usage: _suggest_approve [--verbose] [--dry-run] [--yes] [--no-suggest]
363
+ # Returns: JSON summary {approved, rejected, skipped, signals_created}
364
+ # ============================================================================
365
+ _suggest_approve() {
366
+ verbose=false
367
+ dry_run=false
368
+ skip_confirm=false
369
+ no_suggest=false
370
+
371
+ # Parse arguments
372
+ for arg in "$@"; do
373
+ case "$arg" in
374
+ --verbose) verbose=true ;;
375
+ --dry-run) dry_run=true ;;
376
+ --yes) skip_confirm=true ;;
377
+ --no-suggest) no_suggest=true ;;
378
+ esac
379
+ done
380
+
381
+ # Handle --no-suggest flag - exit immediately
382
+ if [[ "$no_suggest" == "true" ]]; then
383
+ json_ok '{"approved":0,"rejected":0,"skipped":0,"signals_created":[],"reason":"--no-suggest flag"}'
384
+ exit 0
385
+ fi
386
+
387
+ # Check for non-interactive mode (no tty)
388
+ if [[ ! -t 0 ]] && [[ "$skip_confirm" != "true" ]]; then
389
+ echo "Non-interactive mode: skipping suggestions (use --yes to auto-approve)" >&2
390
+ json_ok '{"approved":0,"rejected":0,"skipped":0,"signals_created":[],"reason":"non-interactive mode"}'
391
+ exit 0
392
+ fi
393
+
394
+ # Get suggestions from suggest-analyze
395
+ suggestions_result=$(bash "$0" suggest-analyze 2>/dev/null || echo '{"suggestions":[]}') # SUPPRESS:OK -- read-default: subcommand may fail
396
+ suggestions_json=$(echo "$suggestions_result" | jq '.result.suggestions // []')
397
+
398
+ # Check if there are any suggestions
399
+ suggestion_count=$(echo "$suggestions_json" | jq 'length')
400
+ if [[ "$suggestion_count" -eq 0 ]]; then
401
+ # Exit silently when no suggestions
402
+ json_ok '{"approved":0,"rejected":0,"skipped":0,"signals_created":[]}'
403
+ exit 0
404
+ fi
405
+
406
+ # Arrays to track results
407
+ approved_suggestions=()
408
+ rejected_suggestions=()
409
+ skipped_suggestions=()
410
+ signals_created=()
411
+
412
+ # Display header (to stderr so stdout is valid JSON)
413
+ echo "" >&2
414
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
415
+ echo " S U G G E S T E D P H E R O M O N E S" >&2
416
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" >&2
417
+ echo "" >&2
418
+ echo "Based on code analysis, the colony suggests these signals:" >&2
419
+ echo "" >&2
420
+
421
+ # Process suggestions one at a time
422
+ for ((i=0; i<suggestion_count; i++)); do
423
+ suggestion=$(echo "$suggestions_json" | jq ".[$i]")
424
+ stype=$(echo "$suggestion" | jq -r '.type')
425
+ content=$(echo "$suggestion" | jq -r '.content')
426
+ file=$(echo "$suggestion" | jq -r '.file')
427
+ reason=$(echo "$suggestion" | jq -r '.reason')
428
+ priority=$(echo "$suggestion" | jq -r '.priority // 5')
429
+ hash=$(echo "$suggestion" | jq -r '.hash')
430
+
431
+ emoji=$(get_type_emoji "$stype")
432
+
433
+ # Display suggestion (to stderr so stdout is valid JSON)
434
+ echo "───────────────────────────────────────────────────" >&2
435
+ echo "Suggestion $((i+1)) of $suggestion_count" >&2
436
+ echo "───────────────────────────────────────────────────" >&2
437
+ echo "" >&2
438
+ echo "$emoji $stype (priority: $priority/10)" >&2
439
+ echo "" >&2
440
+ echo "$content" >&2
441
+ echo "" >&2
442
+ echo "Detected in: $file" >&2
443
+ echo "Reason: $reason" >&2
444
+ echo "" >&2
445
+ echo "───────────────────────────────────────────────────" >&2
446
+
447
+ # Handle dry-run mode
448
+ if [[ "$dry_run" == "true" ]]; then
449
+ echo "Dry run: would approve" >&2
450
+ approved_suggestions+=("$suggestion")
451
+ echo "" >&2
452
+ continue
453
+ fi
454
+
455
+ # Handle --yes mode (auto-approve all)
456
+ if [[ "$skip_confirm" == "true" ]]; then
457
+ approved_suggestions+=("$suggestion")
458
+ echo "✓ Auto-approved (--yes mode)" >&2
459
+ echo "" >&2
460
+ continue
461
+ fi
462
+
463
+ # Prompt for action (to stderr so stdout is valid JSON)
464
+ echo -n "[A]pprove [R]eject [S]kip [D]ismiss All Your choice: " >&2
465
+ read -r choice
466
+
467
+ case "$choice" in
468
+ [Aa]|"approve"|"Approve")
469
+ approved_suggestions+=("$suggestion")
470
+ echo "✓ Approved" >&2
471
+ ;;
472
+ [Rr]|"reject"|"Reject")
473
+ rejected_suggestions+=("$suggestion")
474
+ # Record hash to prevent re-suggestion
475
+ bash "$0" suggest-record "$hash" "$stype" >/dev/null 2>&1 || _aether_log_error "Could not record suggestion"
476
+ echo "✗ Rejected" >&2
477
+ ;;
478
+ [Dd]|"dismiss"|"Dismiss"|"dismiss all"|"Dismiss All")
479
+ # Dismiss all remaining suggestions
480
+ for ((j=i; j<suggestion_count; j++)); do
481
+ remaining=$(echo "$suggestions_json" | jq ".[$j]")
482
+ skipped_suggestions+=("$remaining")
483
+ done
484
+ echo "→ Dismissed all remaining suggestions" >&2
485
+ break
486
+ ;;
487
+ [Ss]|""|"skip"|"Skip")
488
+ skipped_suggestions+=("$suggestion")
489
+ echo "→ Skipped" >&2
490
+ ;;
491
+ *)
492
+ # Invalid input - default to skip
493
+ skipped_suggestions+=("$suggestion")
494
+ echo "→ Skipped (invalid input)" >&2
495
+ ;;
496
+ esac
497
+ echo "" >&2
498
+ done
499
+
500
+ # Execute approvals for approved suggestions
501
+ approved_count=0
502
+ if [[ ${#approved_suggestions[@]} -gt 0 ]]; then
503
+ echo "" >&2
504
+ echo "Creating pheromone signals for ${#approved_suggestions[@]} approved suggestion(s)..." >&2
505
+ echo "" >&2
506
+
507
+ for suggestion in "${approved_suggestions[@]}"; do
508
+ stype=$(echo "$suggestion" | jq -r '.type')
509
+ content=$(echo "$suggestion" | jq -r '.content')
510
+ reason=$(echo "$suggestion" | jq -r '.reason')
511
+ hash=$(echo "$suggestion" | jq -r '.hash')
512
+
513
+ if [[ "$dry_run" == "true" ]]; then
514
+ echo "Dry run: would create $stype signal: \"$content\"" >&2
515
+ ((approved_count++))
516
+ signals_created+=("dry_run_sig_$approved_count")
517
+ continue
518
+ fi
519
+
520
+ # Call pheromone-write to create the signal
521
+ signal_result=$(bash "$0" pheromone-write "$stype" "$content" --source "system:suggestion" --reason "$reason" --ttl "phase_end" 2>&1)
522
+
523
+ if echo "$signal_result" | jq -e '.ok' >/dev/null 2>&1; then # SUPPRESS:OK -- validation: testing JSON field
524
+ signal_id=$(echo "$signal_result" | jq -r '.result.signal_id // "unknown"')
525
+ signals_created+=("$signal_id")
526
+ echo "✓ Added $stype signal" >&2
527
+
528
+ # Record hash to prevent duplicates
529
+ bash "$0" suggest-record "$hash" "$stype" >/dev/null 2>&1 || _aether_log_error "Could not record suggestion"
530
+ ((approved_count++))
531
+ else
532
+ echo "✗ Failed to create signal: $content" >&2
533
+ echo " Error: $(echo "$signal_result" | jq -r '.error.message // "Unknown error"')" >&2
534
+ fi
535
+ done
536
+ fi
537
+
538
+ # Record rejected suggestions (already recorded during loop, but ensure consistency)
539
+ rejected_count=${#rejected_suggestions[@]}
540
+
541
+ # Skipped suggestions (not recorded, may suggest again)
542
+ skipped_count=${#skipped_suggestions[@]}
543
+
544
+ # Display summary (to stderr so stdout is valid JSON)
545
+ echo "" >&2
546
+ echo "═══════════════════════════════════════════════════" >&2
547
+ echo "Summary: $approved_count approved, $rejected_count rejected, $skipped_count skipped" >&2
548
+ echo "═══════════════════════════════════════════════════" >&2
549
+ echo "" >&2
550
+
551
+ # Build result with signals_created as JSON array (handle empty array case)
552
+ if [[ ${#signals_created[@]} -gt 0 ]]; then
553
+ signals_json=$(printf '%s\n' "${signals_created[@]}" | jq -R . | jq -s .)
554
+ else
555
+ signals_json="[]"
556
+ fi
557
+ result=$(jq -n \
558
+ --argjson approved "$approved_count" \
559
+ --argjson rejected "$rejected_count" \
560
+ --argjson skipped "$skipped_count" \
561
+ --argjson signals "$signals_json" \
562
+ '{approved: $approved, rejected: $rejected, skipped: $skipped, signals_created: $signals}')
563
+
564
+ json_ok "$result"
565
+ }
566
+
567
+ # ============================================================================
568
+ # _suggest_quick_dismiss
569
+ # Quick dismiss all current suggestions - records hashes to prevent re-suggestion
570
+ # Usage: _suggest_quick_dismiss
571
+ # Returns: JSON {dismissed, hashes_recorded}
572
+ # ============================================================================
573
+ _suggest_quick_dismiss() {
574
+ # Get current suggestions
575
+ suggestions_result=$(bash "$0" suggest-analyze 2>/dev/null || echo '{"suggestions":[]}') # SUPPRESS:OK -- read-default: subcommand may fail
576
+ suggestions_json=$(echo "$suggestions_result" | jq '.result.suggestions // []')
577
+
578
+ dismissed_count=0
579
+ hashes_recorded=()
580
+
581
+ suggestion_count=$(echo "$suggestions_json" | jq 'length')
582
+
583
+ if [[ "$suggestion_count" -gt 0 ]]; then
584
+ for ((i=0; i<suggestion_count; i++)); do
585
+ suggestion=$(echo "$suggestions_json" | jq ".[$i]")
586
+ hash=$(echo "$suggestion" | jq -r '.hash')
587
+ stype=$(echo "$suggestion" | jq -r '.type')
588
+
589
+ # Record hash to prevent re-suggestion
590
+ bash "$0" suggest-record "$hash" "$stype" >/dev/null 2>&1 || _aether_log_error "Could not record suggestion"
591
+ hashes_recorded+=("$hash")
592
+ ((dismissed_count++))
593
+ done
594
+ fi
595
+
596
+ # Output message to stderr so stdout is valid JSON only
597
+ echo "Suggestions dismissed. Run with --yes to auto-approve in future." >&2
598
+
599
+ # Build result with hashes as JSON array (handle empty array case)
600
+ if [[ ${#hashes_recorded[@]} -gt 0 ]]; then
601
+ hashes_json=$(printf '%s\n' "${hashes_recorded[@]}" | jq -R . | jq -s .)
602
+ else
603
+ hashes_json="[]"
604
+ fi
605
+ result=$(jq -n \
606
+ --argjson dismissed "$dismissed_count" \
607
+ --argjson hashes "$hashes_json" \
608
+ '{dismissed: $dismissed, hashes_recorded: $hashes}')
609
+
610
+ json_ok "$result"
611
+ }
@@ -4,7 +4,16 @@
4
4
 
5
5
  SWARM_ID="${1:-current}"
6
6
  DATA_DIR="${DATA_DIR:-.aether/data}"
7
- DISPLAY_FILE="$DATA_DIR/swarm-display.json"
7
+ # Resolve COLONY_DATA_DIR for per-colony files (standalone script)
8
+ COLONY_DATA_DIR="${COLONY_DATA_DIR:-$DATA_DIR}"
9
+ if [[ -f "$DATA_DIR/COLONY_STATE.json" ]]; then
10
+ _cn=$(jq -r '.colony_name // empty' "$DATA_DIR/COLONY_STATE.json" 2>/dev/null)
11
+ if [[ -n "$_cn" ]]; then
12
+ _cn_safe=$(echo "$_cn" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | sed 's/^-//;s/-$//')
13
+ [[ -n "$_cn_safe" ]] && COLONY_DATA_DIR="$DATA_DIR/colonies/$_cn_safe"
14
+ fi
15
+ fi
16
+ DISPLAY_FILE="$COLONY_DATA_DIR/swarm-display.json"
8
17
 
9
18
  # ANSI colors (matching caste-colors.js)
10
19
  BLUE='\033[34m'