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,860 @@
1
+ #!/usr/bin/env bash
2
+ # Scan utility -- repo scanning for smart init research data
3
+ # Provides: _scan_init_research, _scan_tech_stack, _scan_directory_structure,
4
+ # _scan_git_history, _scan_survey_status, _scan_prior_colonies, _scan_complexity
5
+ #
6
+ # These functions are sourced by aether-utils.sh at startup.
7
+ # All shared infrastructure (json_ok, json_err, DATA_DIR, SCRIPT_DIR) is available.
8
+
9
+ # Directories to exclude from scanning
10
+ _SCAN_EXCLUDE_DIRS=(
11
+ node_modules
12
+ .git
13
+ .aether
14
+ dist
15
+ build
16
+ __pycache__
17
+ .next
18
+ target
19
+ vendor
20
+ .venv
21
+ venv
22
+ coverage
23
+ )
24
+
25
+ # Build -not -path flags from _SCAN_EXCLUDE_DIRS for use with find
26
+ _scan_find_exclude_flags() {
27
+ local flags=""
28
+ for dir in "${_SCAN_EXCLUDE_DIRS[@]}"; do
29
+ flags+=" -not -path '*/${dir}/*'"
30
+ done
31
+ printf '%s' "$flags"
32
+ }
33
+
34
+ # Scan tech stack -- detect languages, frameworks, and package managers
35
+ # Usage: _scan_tech_stack <repo_root>
36
+ # Returns: raw JSON via stdout (caller wraps in json_ok)
37
+ _scan_tech_stack() {
38
+ local root="${1:-.}"
39
+ local languages="[]" frameworks="[]" package_managers="[]"
40
+
41
+ # Language detection via file presence
42
+ [[ -f "$root/tsconfig.json" ]] && languages=$(echo "$languages" | jq '. + ["typescript"]')
43
+ [[ -f "$root/package.json" ]] && languages=$(echo "$languages" | jq '. + ["javascript"]')
44
+ [[ -f "$root/requirements.txt" || -f "$root/pyproject.toml" ]] && languages=$(echo "$languages" | jq '. + ["python"]')
45
+ [[ -f "$root/go.mod" ]] && languages=$(echo "$languages" | jq '. + ["go"]')
46
+ [[ -f "$root/Cargo.toml" ]] && languages=$(echo "$languages" | jq '. + ["rust"]')
47
+ [[ -f "$root/Gemfile" ]] && languages=$(echo "$languages" | jq '. + ["ruby"]')
48
+ [[ -f "$root/pom.xml" || -f "$root/build.gradle" ]] && languages=$(echo "$languages" | jq '. + ["java"]')
49
+
50
+ # Framework detection via file presence and package.json deps
51
+ if [[ -f "$root/next.config.js" || -f "$root/next.config.ts" || -f "$root/next.config.mjs" ]]; then
52
+ frameworks=$(echo "$frameworks" | jq '. + ["nextjs"]')
53
+ fi
54
+ if [[ -f "$root/angular.json" ]]; then
55
+ frameworks=$(echo "$frameworks" | jq '. + ["angular"]')
56
+ fi
57
+ if [[ -f "$root/vue.config.js" || -f "$root/vite.config.ts" || -f "$root/vite.config.js" ]]; then
58
+ frameworks=$(echo "$frameworks" | jq '. + ["vue"]')
59
+ fi
60
+
61
+ # Framework detection from package.json dependencies (targeted jq, no full reads)
62
+ if [[ -f "$root/package.json" ]]; then
63
+ local pkg_deps
64
+ pkg_deps=$(jq -r '[(.dependencies // {} | keys[]), (.devDependencies // {} | keys[])] | join("\n")' "$root/package.json" 2>/dev/null || true)
65
+
66
+ if echo "$pkg_deps" | grep -qx 'react'; then
67
+ frameworks=$(echo "$frameworks" | jq '. + ["react"]')
68
+ fi
69
+ if echo "$pkg_deps" | grep -qx 'express'; then
70
+ frameworks=$(echo "$frameworks" | jq '. + ["express"]')
71
+ fi
72
+ if echo "$pkg_deps" | grep -qx 'fastify'; then
73
+ frameworks=$(echo "$frameworks" | jq '. + ["fastify"]')
74
+ fi
75
+ if echo "$pkg_deps" | grep -qx 'svelte'; then
76
+ frameworks=$(echo "$frameworks" | jq '. + ["svelte"]')
77
+ fi
78
+ if echo "$pkg_deps" | grep -qx 'nest'; then
79
+ frameworks=$(echo "$frameworks" | jq '. + ["nestjs"]')
80
+ fi
81
+ fi
82
+
83
+ # Package manager detection
84
+ if [[ -f "$root/package.json" ]]; then
85
+ if [[ -f "$root/pnpm-lock.yaml" ]]; then
86
+ package_managers=$(echo "$package_managers" | jq '. + ["pnpm"]')
87
+ elif [[ -f "$root/yarn.lock" ]]; then
88
+ package_managers=$(echo "$package_managers" | jq '. + ["yarn"]')
89
+ elif [[ -f "$root/package-lock.json" ]]; then
90
+ package_managers=$(echo "$package_managers" | jq '. + ["npm"]')
91
+ else
92
+ package_managers=$(echo "$package_managers" | jq '. + ["npm"]')
93
+ fi
94
+ fi
95
+ [[ -f "$root/go.mod" ]] && package_managers=$(echo "$package_managers" | jq '. + ["go-modules"]')
96
+ [[ -f "$root/Cargo.toml" ]] && package_managers=$(echo "$package_managers" | jq '. + ["cargo"]')
97
+ if [[ -f "$root/Gemfile" ]]; then
98
+ package_managers=$(echo "$package_managers" | jq '. + ["bundler"]')
99
+ fi
100
+ [[ -f "$root/requirements.txt" ]] && package_managers=$(echo "$package_managers" | jq '. + ["pip"]')
101
+ if [[ -f "$root/pyproject.toml" ]]; then
102
+ package_managers=$(echo "$package_managers" | jq '. + ["poetry"]')
103
+ fi
104
+
105
+ jq -n \
106
+ --argjson langs "$languages" \
107
+ --argjson fwks "$frameworks" \
108
+ --argjson pkg_mgrs "$package_managers" \
109
+ '{languages: $langs, frameworks: $fwks, package_managers: $pkg_mgrs}'
110
+ }
111
+
112
+ # Scan directory structure -- measure repo surface
113
+ # Usage: _scan_directory_structure <repo_root>
114
+ # Returns: raw JSON via stdout
115
+ _scan_directory_structure() {
116
+ local root="${1:-.}"
117
+ local exclude_flags
118
+ exclude_flags=$(_scan_find_exclude_flags)
119
+
120
+ # Count files (cap depth at 5 for performance)
121
+ local file_count
122
+ file_count=$(find "$root" -maxdepth 5 -type f $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
123
+
124
+ # Calculate max directory depth
125
+ local max_depth
126
+ max_depth=$(find "$root" -type d $exclude_flags 2>/dev/null | awk -F/ '{print NF-2}' | sort -rn | head -1)
127
+ [[ -z "$max_depth" || "$max_depth" == "0" ]] && max_depth=1
128
+
129
+ # List top-level directories (exclude hidden dirs and excluded dirs)
130
+ local top_dirs
131
+ top_dirs=$(ls -1d "$root"/*/ 2>/dev/null | while read -r d; do
132
+ local dirname
133
+ dirname=$(basename "$d")
134
+ # Skip hidden dirs
135
+ [[ "$dirname" == .* ]] && continue
136
+ # Skip excluded dirs
137
+ local skip=false
138
+ for excluded in "${_SCAN_EXCLUDE_DIRS[@]}"; do
139
+ [[ "$dirname" == "$excluded" ]] && skip=true && break
140
+ done
141
+ [[ "$skip" == "true" ]] && continue
142
+ echo "$dirname"
143
+ done | jq -R . | jq -s .)
144
+
145
+ jq -n \
146
+ --argjson dirs "$top_dirs" \
147
+ --argjson file_count "$file_count" \
148
+ --argjson max_depth "$max_depth" \
149
+ '{top_level_dirs: $dirs, file_count: $file_count, max_depth: $max_depth}'
150
+ }
151
+
152
+ # Scan git history -- summarize git repo state
153
+ # Usage: _scan_git_history <repo_root>
154
+ # Returns: raw JSON via stdout
155
+ _scan_git_history() {
156
+ local root="${1:-.}"
157
+
158
+ # Check for .git directory
159
+ if [[ ! -d "$root/.git" ]]; then
160
+ jq -n '{is_git_repo: false, commit_count: 0, recent_commits: []}'
161
+ return
162
+ fi
163
+
164
+ # Count commits
165
+ local commit_count
166
+ commit_count=$(git -C "$root" rev-list --count HEAD 2>/dev/null || echo 0)
167
+
168
+ # Get recent commits (oneline format)
169
+ local recent_log
170
+ recent_log=$(git -C "$root" log --oneline -n 10 2>/dev/null || echo "")
171
+
172
+ # Parse recent commits into JSON array
173
+ local recent_commits="[]"
174
+ if [[ -n "$recent_log" ]]; then
175
+ recent_commits=$(echo "$recent_log" | while read -r line; do
176
+ local hash message
177
+ hash=$(echo "$line" | awk '{print $1}')
178
+ message=$(echo "$line" | cut -d' ' -f2-)
179
+ jq -n --arg hash "$hash" --arg message "$message" '{hash: $hash, message: $message}'
180
+ done | jq -s '.')
181
+ fi
182
+
183
+ jq -n \
184
+ --argjson commit_count "$commit_count" \
185
+ --argjson recent_commits "$recent_commits" \
186
+ '{is_git_repo: true, commit_count: $commit_count, recent_commits: $recent_commits}'
187
+ }
188
+
189
+ # Scan survey status -- check territory survey freshness (SCAN-02)
190
+ # Usage: _scan_survey_status <repo_root>
191
+ # Returns: raw JSON via stdout
192
+ _scan_survey_status() {
193
+ local root="${1:-.}"
194
+ local survey_dir="$root/.aether/data/survey"
195
+ local state_file="$root/.aether/data/COLONY_STATE.json"
196
+
197
+ # Check if survey directory exists
198
+ if [[ ! -d "$survey_dir" ]]; then
199
+ jq -n '{has_survey: false, is_stale: false, suggestion: {action: "colonize", reason: "No territory survey found. Run /ant:colonize to map the codebase before planning."}}'
200
+ return
201
+ fi
202
+
203
+ # Check survey completeness (7 required docs)
204
+ local required="PROVISIONS.md TRAILS.md BLUEPRINT.md CHAMBERS.md DISCIPLINES.md SENTINEL-PROTOCOLS.md PATHOGENS.md"
205
+ local missing=""
206
+ for doc in $required; do
207
+ [[ ! -f "$survey_dir/$doc" ]] && missing="$missing $doc"
208
+ done
209
+
210
+ if [[ -n "$missing" ]]; then
211
+ local missing_json
212
+ missing_json=$(echo "$missing" | jq -R 'split(" ") | map(select(length > 0))')
213
+ jq -n \
214
+ --argjson missing "$missing_json" \
215
+ '{has_survey: true, is_stale: false, is_complete: false, missing: $missing, suggestion: {action: "colonize", reason: "Survey is incomplete (missing documents). Run /ant:colonize --force-resurvey to remap."}}'
216
+ return
217
+ fi
218
+
219
+ # Check staleness from COLONY_STATE.json territory_surveyed field
220
+ local surveyed_at=""
221
+ if [[ -f "$state_file" ]]; then
222
+ surveyed_at=$(jq -r '.territory_surveyed // empty' "$state_file" 2>/dev/null || echo "")
223
+ fi
224
+
225
+ if [[ -z "$surveyed_at" ]]; then
226
+ # No timestamp in state -- fall back to file modification times
227
+ local oldest_ts
228
+ if [[ "$(uname)" == "Linux" ]]; then
229
+ oldest_ts=$(find "$survey_dir" -name "*.md" -exec stat -c %Y {} \; 2>/dev/null | sort -n | head -1)
230
+ else
231
+ oldest_ts=$(find "$survey_dir" -name "*.md" -exec stat -f %m {} \; 2>/dev/null | sort -n | head -1)
232
+ fi
233
+ if [[ -n "$oldest_ts" ]]; then
234
+ local now_ts
235
+ now_ts=$(date +%s)
236
+ local age_days=$(( (now_ts - oldest_ts) / 86400 ))
237
+ if [[ "$age_days" -gt 7 ]]; then
238
+ jq -n \
239
+ --argjson age "$age_days" \
240
+ '{has_survey: true, is_stale: true, age_days: $age, suggestion: {action: "colonize", reason: "Survey is \($age) days old. Run /ant:colonize --force-resurvey for fresh data."}}'
241
+ return
242
+ fi
243
+ fi
244
+ else
245
+ # Parse ISO-8601 timestamp and compare
246
+ local surveyed_epoch
247
+ if [[ "$(uname)" == "Linux" ]]; then
248
+ surveyed_epoch=$(date -d "$surveyed_at" "+%s" 2>/dev/null || echo 0)
249
+ else
250
+ surveyed_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$surveyed_at" "+%s" 2>/dev/null || echo 0)
251
+ fi
252
+ local now_epoch
253
+ now_epoch=$(date +%s)
254
+ local age_days=$(( (now_epoch - surveyed_epoch) / 86400 ))
255
+ if [[ "$age_days" -gt 7 ]]; then
256
+ jq -n \
257
+ --argjson age "$age_days" \
258
+ --arg surveyed_at "$surveyed_at" \
259
+ '{has_survey: true, is_stale: true, age_days: $age, surveyed_at: $surveyed_at, suggestion: {action: "colonize", reason: "Survey is \($age) days old. Run /ant:colonize --force-resurvey for fresh data."}}'
260
+ return
261
+ fi
262
+ fi
263
+
264
+ # Survey is fresh and complete
265
+ jq -n '{has_survey: true, is_stale: false, is_complete: true}'
266
+ }
267
+
268
+ # Scan prior colonies -- detect active colony state and archived colonies
269
+ # Usage: _scan_prior_colonies <repo_root>
270
+ # Returns: raw JSON via stdout
271
+ _scan_prior_colonies() {
272
+ local root="${1:-.}"
273
+ local chambers_dir="$root/.aether/chambers"
274
+ local state_file="$root/.aether/data/COLONY_STATE.json"
275
+
276
+ local colonies="[]"
277
+ local has_active="false"
278
+ local active_goal=""
279
+
280
+ # Check for active colony
281
+ if [[ -f "$state_file" ]]; then
282
+ local goal state
283
+ goal=$(jq -r '.goal // empty' "$state_file" 2>/dev/null || echo "")
284
+ state=$(jq -r '.state // empty' "$state_file" 2>/dev/null || echo "")
285
+ if [[ -n "$goal" && "$state" != "SEALED" ]]; then
286
+ has_active="true"
287
+ active_goal="$goal"
288
+ fi
289
+ fi
290
+
291
+ # Check for archived colonies in chambers
292
+ if [[ -d "$chambers_dir" ]]; then
293
+ for chamber in "$chambers_dir"/*/; do
294
+ [[ -d "$chamber" ]] || continue
295
+ local chamber_name
296
+ chamber_name=$(basename "$chamber")
297
+
298
+ # Skip hidden dirs
299
+ [[ "$chamber_name" == .* ]] && continue
300
+
301
+ local chamber_state="$chamber/COLONY_STATE.json"
302
+ [[ -f "$chamber_state" ]] || continue
303
+
304
+ local chamber_goal chamber_date
305
+ chamber_goal=$(jq -r '.goal // "unknown"' "$chamber_state" 2>/dev/null || echo "unknown")
306
+ chamber_date=$(jq -r '.initialized_at // "unknown"' "$chamber_state" 2>/dev/null || echo "unknown")
307
+
308
+ colonies=$(echo "$colonies" | jq \
309
+ --arg name "$chamber_name" \
310
+ --arg goal "$chamber_goal" \
311
+ --arg date "$chamber_date" \
312
+ '. + [{name: $name, goal: $goal, initialized_at: $date}]')
313
+ done
314
+ fi
315
+
316
+ jq -n \
317
+ --argjson has_active "$has_active" \
318
+ --arg active_goal "$active_goal" \
319
+ --argjson colonies "$colonies" \
320
+ '{has_active_colony: $has_active, active_goal: $active_goal, archived_colonies: $colonies}'
321
+ }
322
+
323
+ # Scan complexity -- estimate repo complexity (SCAN-03)
324
+ # Usage: _scan_complexity <repo_root>
325
+ # Returns: raw JSON via stdout
326
+ _scan_complexity() {
327
+ local root="${1:-.}"
328
+ local exclude_flags
329
+ exclude_flags=$(_scan_find_exclude_flags)
330
+
331
+ # File count (excluding common directories)
332
+ local file_count
333
+ file_count=$(find "$root" -maxdepth 5 -type f $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
334
+
335
+ # Max directory depth
336
+ local max_depth
337
+ max_depth=$(find "$root" -type d $exclude_flags 2>/dev/null | awk -F/ '{print NF-2}' | sort -rn | head -1)
338
+ [[ -z "$max_depth" || "$max_depth" == "0" ]] && max_depth=1
339
+
340
+ # Dependency count from package manifests
341
+ local dep_count=0
342
+ if [[ -f "$root/package.json" ]]; then
343
+ dep_count=$(jq '[.dependencies // {}, .devDependencies // {}] | add | keys | length' "$root/package.json" 2>/dev/null || echo 0)
344
+ elif [[ -f "$root/Cargo.toml" ]]; then
345
+ dep_count=$(grep -c '^\[' "$root/Cargo.toml" 2>/dev/null || echo 0)
346
+ elif [[ -f "$root/go.mod" ]]; then
347
+ dep_count=$(grep -c '^[a-z]' "$root/go.mod" 2>/dev/null || echo 0)
348
+ fi
349
+
350
+ # Classification thresholds
351
+ local size="small"
352
+ if [[ "$file_count" -gt 500 ]] || [[ "$max_depth" -gt 8 ]] || [[ "$dep_count" -gt 50 ]]; then
353
+ size="large"
354
+ elif [[ "$file_count" -gt 100 ]] || [[ "$max_depth" -gt 5 ]] || [[ "$dep_count" -gt 15 ]]; then
355
+ size="medium"
356
+ fi
357
+
358
+ jq -n \
359
+ --arg size "$size" \
360
+ --argjson file_count "$file_count" \
361
+ --argjson max_depth "$max_depth" \
362
+ --argjson dep_count "$dep_count" \
363
+ '{size: $size, metrics: {file_count: $file_count, max_directory_depth: $max_depth, dependency_count: $dep_count}}'
364
+ }
365
+
366
+ # Scan colony context -- extract prior colony summaries and existing charter content
367
+ # Usage: _scan_colony_context <repo_root>
368
+ # Returns: raw JSON via stdout
369
+ _scan_colony_context() {
370
+ local root="${1:-.}"
371
+ local chambers_dir="$root/.aether/chambers"
372
+ local queen_file="$root/.aether/QUEEN.md"
373
+
374
+ local prior_colonies="[]"
375
+
376
+ # Extract prior colony summaries from chambers (max 3, most recent first)
377
+ if [[ -d "$chambers_dir" ]]; then
378
+ # Sort chamber dirs by name descending (date-prefixed names = reverse alpha = most recent first)
379
+ local chamber_list
380
+ chamber_list=$(ls -1d "$chambers_dir"/*/ 2>/dev/null | sort -r)
381
+
382
+ local count=0
383
+ while IFS= read -r chamber; do
384
+ [[ -z "$chamber" ]] && continue
385
+ [[ "$count" -ge 3 ]] && break
386
+
387
+ local chamber_name
388
+ chamber_name=$(basename "$chamber")
389
+ # Skip hidden dirs
390
+ [[ "$chamber_name" == .* ]] && continue
391
+
392
+ local manifest="$chamber/manifest.json"
393
+ local crowned="$chamber/CROWNED-ANTHILL.md"
394
+
395
+ # Skip if neither manifest nor crowned exists
396
+ [[ ! -f "$manifest" && ! -f "$crowned" ]] && continue
397
+
398
+ local goal="" phases="" outcome="" summary=""
399
+
400
+ if [[ -f "$manifest" ]]; then
401
+ goal=$(jq -r '.goal // "unknown"' "$manifest" 2>/dev/null || echo "unknown")
402
+ # Handle phases_completed being either a number or an array (older manifest formats)
403
+ local phases_completed total_phases
404
+ phases_completed=$(jq -r 'if (.phases_completed | type) == "array" then (.phases_completed | length) else (.phases_completed // 0) end' "$manifest" 2>/dev/null || echo "0")
405
+ total_phases=$(jq -r '.total_phases // 0' "$manifest" 2>/dev/null || echo "0")
406
+ phases="${phases_completed}/${total_phases}"
407
+ outcome=$(jq -r '.milestone // "unknown"' "$manifest" 2>/dev/null || echo "unknown")
408
+ fi
409
+
410
+ if [[ -f "$crowned" ]]; then
411
+ # Extract "The Work" section: lines between "## The Work" and next "## " header (or EOF)
412
+ # Use sed to get the range, strip header lines, take first 2 content lines, join
413
+ summary=$(sed -n '/^## The Work$/,/^## /p' "$crowned" 2>/dev/null \
414
+ | grep -v '^## ' \
415
+ | sed '/^$/d' \
416
+ | head -2 \
417
+ | tr '\n' ' ' \
418
+ | sed 's/ */ /g; s/^ *//; s/ *$//')
419
+ fi
420
+
421
+ prior_colonies=$(echo "$prior_colonies" | jq \
422
+ --arg goal "$goal" \
423
+ --arg phases "$phases" \
424
+ --arg outcome "$outcome" \
425
+ --arg summary "$summary" \
426
+ '. + [{goal: $goal, phases: $phases, outcome: $outcome, summary: $summary}]')
427
+
428
+ count=$((count + 1))
429
+ done <<< "$chamber_list"
430
+ fi
431
+
432
+ # Extract existing charter content from QUEEN.md
433
+ local charter_intent="" charter_vision="" charter_governance=""
434
+
435
+ if [[ -f "$queen_file" ]]; then
436
+ charter_intent=$(grep '\[charter\] \*\*Intent\*\*:' "$queen_file" 2>/dev/null \
437
+ | sed 's/.*\*\*Intent\*\*: //' \
438
+ | sed 's/ (Colony:.*//' || true)
439
+ charter_vision=$(grep '\[charter\] \*\*Vision\*\*:' "$queen_file" 2>/dev/null \
440
+ | sed 's/.*\*\*Vision\*\*: //' \
441
+ | sed 's/ (Colony:.*//' || true)
442
+ charter_governance=$(grep '\[charter\] \*\*Governance\*\*:' "$queen_file" 2>/dev/null \
443
+ | sed 's/.*\*\*Governance\*\*: //' \
444
+ | sed 's/ (Colony:.*//' || true)
445
+ fi
446
+
447
+ jq -n \
448
+ --argjson prior_colonies "$prior_colonies" \
449
+ --arg intent "$charter_intent" \
450
+ --arg vision "$charter_vision" \
451
+ --arg governance "$charter_governance" \
452
+ '{prior_colonies: $prior_colonies, existing_charter: {intent: $intent, vision: $vision, governance: $governance}}'
453
+ }
454
+
455
+ # Scan governance -- detect governance-related config files and produce prescriptive rules
456
+ # Usage: _scan_governance <repo_root>
457
+ # Returns: raw JSON via stdout
458
+ _scan_governance() {
459
+ local root="${1:-.}"
460
+ local exclude_flags
461
+ exclude_flags=$(_scan_find_exclude_flags)
462
+
463
+ local rules="[]"
464
+ local sources_checked=0
465
+
466
+ # 1. CONTRIBUTING.md
467
+ sources_checked=$((sources_checked + 1))
468
+ if [[ -f "$root/CONTRIBUTING.md" ]]; then
469
+ local contrib_summary
470
+ contrib_summary=$(head -20 "$root/CONTRIBUTING.md" 2>/dev/null \
471
+ | tr '\n' ' ' \
472
+ | sed 's/ */ /g' \
473
+ | cut -c1-200)
474
+ # Skip if file is effectively empty (just whitespace)
475
+ if [[ -n "$(echo "$contrib_summary" | tr -d '[:space:]')" ]]; then
476
+ rules=$(echo "$rules" | jq \
477
+ --arg rule "Follow CONTRIBUTING.md guidelines" \
478
+ --arg source "CONTRIBUTING.md" \
479
+ --arg detail "$contrib_summary" \
480
+ '. + [{rule: $rule, source: $source, detail: $detail, strength: "required"}]')
481
+ fi
482
+ fi
483
+
484
+ # 2. Test configs -- only emit "TDD required" if test FILES also exist
485
+ sources_checked=$((sources_checked + 1))
486
+ local has_test_config=false
487
+ for tc in "$root"/jest.config.* "$root"/vitest.config.* "$root/pytest.ini"; do
488
+ if [[ -f "$tc" ]]; then
489
+ has_test_config=true
490
+ break
491
+ fi
492
+ done
493
+ # Also check pyproject.toml for pytest section
494
+ if [[ "$has_test_config" == "false" && -f "$root/pyproject.toml" ]]; then
495
+ if grep -q '\[tool\.pytest' "$root/pyproject.toml" 2>/dev/null; then
496
+ has_test_config=true
497
+ fi
498
+ fi
499
+ # Check Cargo.toml for [test] section
500
+ if [[ "$has_test_config" == "false" && -f "$root/Cargo.toml" ]]; then
501
+ if grep -q '^\[test\]' "$root/Cargo.toml" 2>/dev/null; then
502
+ has_test_config=true
503
+ fi
504
+ fi
505
+ # Check for go test files
506
+ if [[ "$has_test_config" == "false" ]]; then
507
+ local go_test_count
508
+ go_test_count=$(find "$root" -maxdepth 4 -type f -name "*_test.go" $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
509
+ if [[ "$go_test_count" -gt 0 ]]; then
510
+ has_test_config=true
511
+ fi
512
+ fi
513
+
514
+ if [[ "$has_test_config" == "true" ]]; then
515
+ # Cross-reference: check if test files actually exist
516
+ local test_file_count
517
+ test_file_count=$(find "$root" -maxdepth 4 -type f \
518
+ \( -name "*.test.*" -o -name "*.spec.*" -o -name "test_*" -o -name "*_test.go" \) \
519
+ $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
520
+
521
+ # Also check tests/ and __tests__/ directories
522
+ if [[ "$test_file_count" -eq 0 ]]; then
523
+ for tdir in "$root/tests" "$root/__tests__" "$root/test"; do
524
+ if [[ -d "$tdir" ]]; then
525
+ local dir_count
526
+ dir_count=$(find "$tdir" -maxdepth 3 -type f $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
527
+ test_file_count=$((test_file_count + dir_count))
528
+ fi
529
+ done
530
+ fi
531
+
532
+ if [[ "$test_file_count" -gt 0 ]]; then
533
+ rules=$(echo "$rules" | jq \
534
+ --arg rule "TDD required -- test config and existing tests detected" \
535
+ --arg source "test configuration" \
536
+ '. + [{rule: $rule, source: $source, strength: "required"}]')
537
+ fi
538
+ fi
539
+
540
+ # 3. Linter/formatter configs
541
+ sources_checked=$((sources_checked + 1))
542
+ # ESLint
543
+ local has_eslint=false
544
+ for ec in "$root"/.eslintrc.* "$root"/eslint.config.*; do
545
+ if [[ -f "$ec" ]]; then
546
+ has_eslint=true
547
+ break
548
+ fi
549
+ done
550
+ if [[ "$has_eslint" == "true" ]]; then
551
+ rules=$(echo "$rules" | jq \
552
+ --arg rule "ESLint enforced -- follow existing lint rules" \
553
+ --arg source "ESLint" \
554
+ '. + [{rule: $rule, source: $source, strength: "required"}]')
555
+ fi
556
+
557
+ # Prettier
558
+ local has_prettier=false
559
+ for pc in "$root"/.prettierrc*; do
560
+ if [[ -f "$pc" ]]; then
561
+ has_prettier=true
562
+ break
563
+ fi
564
+ done
565
+ if [[ "$has_prettier" == "true" ]]; then
566
+ rules=$(echo "$rules" | jq \
567
+ --arg rule "Prettier formatting enforced -- maintain consistent code style" \
568
+ --arg source "Prettier" \
569
+ '. + [{rule: $rule, source: $source, strength: "required"}]')
570
+ fi
571
+
572
+ # rustfmt
573
+ if [[ -f "$root/rustfmt.toml" ]]; then
574
+ rules=$(echo "$rules" | jq \
575
+ --arg rule "rustfmt enforced -- maintain Rust formatting standards" \
576
+ --arg source "rustfmt" \
577
+ '. + [{rule: $rule, source: $source, strength: "required"}]')
578
+ fi
579
+
580
+ # .flake8
581
+ if [[ -f "$root/.flake8" ]]; then
582
+ rules=$(echo "$rules" | jq \
583
+ --arg rule "Flake8 enforced -- follow Python linting rules" \
584
+ --arg source "Flake8" \
585
+ '. + [{rule: $rule, source: $source, strength: "required"}]')
586
+ fi
587
+
588
+ # pyproject.toml [tool.black]
589
+ if [[ -f "$root/pyproject.toml" ]]; then
590
+ if grep -q '\[tool\.black\]' "$root/pyproject.toml" 2>/dev/null; then
591
+ rules=$(echo "$rules" | jq \
592
+ --arg rule "Black formatter enforced -- maintain Python code style" \
593
+ --arg source "Black" \
594
+ '. + [{rule: $rule, source: $source, strength: "required"}]')
595
+ fi
596
+ fi
597
+
598
+ # 4. CI/CD
599
+ sources_checked=$((sources_checked + 1))
600
+ local has_ci=false
601
+ if [[ -d "$root/.github/workflows" ]]; then
602
+ has_ci=true
603
+ elif [[ -f "$root/Jenkinsfile" ]]; then
604
+ has_ci=true
605
+ elif [[ -f "$root/.gitlab-ci.yml" ]]; then
606
+ has_ci=true
607
+ elif [[ -d "$root/.circleci" ]]; then
608
+ has_ci=true
609
+ fi
610
+
611
+ if [[ "$has_ci" == "true" ]]; then
612
+ rules=$(echo "$rules" | jq \
613
+ --arg rule "CI/CD pipeline active -- ensure all checks pass before merging" \
614
+ --arg source "CI configuration" \
615
+ '. + [{rule: $rule, source: $source, strength: "required"}]')
616
+ fi
617
+
618
+ jq -n \
619
+ --argjson rules "$rules" \
620
+ --argjson sources_checked "$sources_checked" \
621
+ '{rules: $rules, sources_checked: $sources_checked}'
622
+ }
623
+
624
+ # Scan pheromone suggestions -- derive FOCUS/REDIRECT signals from codebase patterns
625
+ # Usage: _scan_pheromone_suggestions <repo_root>
626
+ # Returns: raw JSON array via stdout (max 5, sorted by priority descending)
627
+ _scan_pheromone_suggestions() {
628
+ local root="${1:-.}"
629
+ local exclude_flags
630
+ exclude_flags=$(_scan_find_exclude_flags)
631
+
632
+ local suggestions="[]"
633
+
634
+ # 1. Security -- .env files (priority 9, REDIRECT)
635
+ if [[ -f "$root/.env" || -f "$root/.env.local" || -f "$root/.env.example" ]]; then
636
+ suggestions=$(echo "$suggestions" | jq \
637
+ --arg content 'Environment files detected -- never commit secrets or .env files' \
638
+ --arg reason 'Found .env files in repo root' \
639
+ '. + [{type: "REDIRECT", content: $content, reason: $reason, priority: 9}]')
640
+ fi
641
+
642
+ # 2. Testing gap (priority 9, REDIRECT): test config exists but NO test files
643
+ local has_test_config=false
644
+ for tc in "$root"/jest.config.* "$root"/vitest.config.* "$root/pytest.ini"; do
645
+ if [[ -f "$tc" ]]; then
646
+ has_test_config=true
647
+ break
648
+ fi
649
+ done
650
+ if [[ "$has_test_config" == "false" && -f "$root/pyproject.toml" ]]; then
651
+ grep -q '\[tool\.pytest' "$root/pyproject.toml" 2>/dev/null && has_test_config=true
652
+ fi
653
+
654
+ local test_file_count=0
655
+ if [[ "$has_test_config" == "true" ]]; then
656
+ test_file_count=$(find "$root" -maxdepth 4 -type f \
657
+ \( -name "*.test.*" -o -name "*.spec.*" -o -name "test_*" -o -name "*_test.go" \) \
658
+ $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
659
+ # Also check tests/ and __tests__/ directories
660
+ if [[ "$test_file_count" -eq 0 ]]; then
661
+ for tdir in "$root/tests" "$root/__tests__" "$root/test"; do
662
+ if [[ -d "$tdir" ]]; then
663
+ local dir_count
664
+ dir_count=$(find "$tdir" -maxdepth 3 -type f $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
665
+ test_file_count=$((test_file_count + dir_count))
666
+ fi
667
+ done
668
+ fi
669
+ fi
670
+
671
+ if [[ "$has_test_config" == "true" && "$test_file_count" -eq 0 ]]; then
672
+ suggestions=$(echo "$suggestions" | jq \
673
+ --arg content 'Test config exists but no test files -- do not skip writing tests' \
674
+ --arg reason 'Test configuration found without corresponding test files' \
675
+ '. + [{type: "REDIRECT", content: $content, reason: $reason, priority: 9}]')
676
+ fi
677
+
678
+ # 3. Testing present (priority 8, FOCUS): test config + test files exist
679
+ if [[ "$has_test_config" == "true" && "$test_file_count" -gt 0 ]]; then
680
+ suggestions=$(echo "$suggestions" | jq \
681
+ --arg content "Testing infrastructure present ($test_file_count test files) -- maintain TDD discipline" \
682
+ --arg reason "Detected test config and $test_file_count test files" \
683
+ '. + [{type: "FOCUS", content: $content, reason: $reason, priority: 8}]')
684
+ fi
685
+
686
+ # Also check for tests when no formal test config exists (e.g., npm test script)
687
+ if [[ "$has_test_config" == "false" ]]; then
688
+ test_file_count=$(find "$root" -maxdepth 4 -type f \
689
+ \( -name "*.test.*" -o -name "*.spec.*" -o -name "test_*" -o -name "*_test.go" \) \
690
+ $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
691
+ if [[ "$test_file_count" -eq 0 ]]; then
692
+ for tdir in "$root/tests" "$root/__tests__" "$root/test"; do
693
+ if [[ -d "$tdir" ]]; then
694
+ local dir_count
695
+ dir_count=$(find "$tdir" -maxdepth 3 -type f $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
696
+ test_file_count=$((test_file_count + dir_count))
697
+ fi
698
+ done
699
+ fi
700
+ if [[ "$test_file_count" -gt 0 ]]; then
701
+ suggestions=$(echo "$suggestions" | jq \
702
+ --arg content "Testing infrastructure present ($test_file_count test files) -- maintain TDD discipline" \
703
+ --arg reason "Detected $test_file_count test files" \
704
+ '. + [{type: "FOCUS", content: $content, reason: $reason, priority: 8}]')
705
+ fi
706
+ fi
707
+
708
+ # 4. Security deps (priority 8, FOCUS): check package.json for security-related packages
709
+ if [[ -f "$root/package.json" ]]; then
710
+ local pkg_deps
711
+ pkg_deps=$(jq -r '[(.dependencies // {} | keys[]), (.devDependencies // {} | keys[])] | join("\n")' "$root/package.json" 2>/dev/null || true)
712
+ local has_security_dep=false
713
+ for dep in helmet cors bcrypt passport jsonwebtoken; do
714
+ if echo "$pkg_deps" | grep -qx "$dep"; then
715
+ has_security_dep=true
716
+ break
717
+ fi
718
+ done
719
+ if [[ "$has_security_dep" == "true" ]]; then
720
+ suggestions=$(echo "$suggestions" | jq \
721
+ --arg content 'Security dependencies detected -- maintain security best practices' \
722
+ --arg reason 'Found security-related packages (helmet, cors, bcrypt, passport, or jsonwebtoken)' \
723
+ '. + [{type: "FOCUS", content: $content, reason: $reason, priority: 8}]')
724
+ fi
725
+ fi
726
+
727
+ # 5. Linter/formatter (priority 7, FOCUS)
728
+ local has_linter=false
729
+ for lc in "$root"/.eslintrc.* "$root"/eslint.config.* "$root"/.prettierrc* "$root/.flake8" "$root/rustfmt.toml"; do
730
+ if [[ -f "$lc" ]]; then
731
+ has_linter=true
732
+ break
733
+ fi
734
+ done
735
+ if [[ "$has_linter" == "true" ]]; then
736
+ suggestions=$(echo "$suggestions" | jq \
737
+ --arg content 'Code quality tools configured -- follow existing lint/format rules' \
738
+ --arg reason 'Detected linter/formatter configuration' \
739
+ '. + [{type: "FOCUS", content: $content, reason: $reason, priority: 7}]')
740
+ fi
741
+
742
+ # 6. CI/CD (priority 7, FOCUS)
743
+ if [[ -d "$root/.github/workflows" || -f "$root/Jenkinsfile" || -f "$root/.gitlab-ci.yml" ]]; then
744
+ suggestions=$(echo "$suggestions" | jq \
745
+ --arg content 'CI/CD pipeline active -- ensure changes pass all checks' \
746
+ --arg reason 'Detected CI/CD configuration' \
747
+ '. + [{type: "FOCUS", content: $content, reason: $reason, priority: 7}]')
748
+ fi
749
+
750
+ # 7. TypeScript strict (priority 6, FOCUS)
751
+ if [[ -f "$root/tsconfig.json" ]]; then
752
+ if grep -q '"strict"[[:space:]]*:[[:space:]]*true' "$root/tsconfig.json" 2>/dev/null; then
753
+ suggestions=$(echo "$suggestions" | jq \
754
+ --arg content 'TypeScript strict mode enabled -- maintain type safety' \
755
+ --arg reason 'tsconfig.json has strict: true' \
756
+ '. + [{type: "FOCUS", content: $content, reason: $reason, priority: 6}]')
757
+ fi
758
+ fi
759
+
760
+ # 8. CONTRIBUTING.md (priority 6, FOCUS)
761
+ if [[ -f "$root/CONTRIBUTING.md" ]]; then
762
+ suggestions=$(echo "$suggestions" | jq \
763
+ --arg content 'Contribution guidelines documented -- follow project conventions' \
764
+ --arg reason 'CONTRIBUTING.md found in repo root' \
765
+ '. + [{type: "FOCUS", content: $content, reason: $reason, priority: 6}]')
766
+ fi
767
+
768
+ # 9. Large codebase (priority 6, FOCUS): file_count > 500
769
+ local file_count
770
+ file_count=$(find "$root" -maxdepth 5 -type f $exclude_flags 2>/dev/null | wc -l | tr -d ' ')
771
+ if [[ "$file_count" -gt 500 ]]; then
772
+ suggestions=$(echo "$suggestions" | jq \
773
+ --arg content 'Large codebase -- scope changes carefully, prefer incremental work' \
774
+ --arg reason "Detected $file_count files (threshold: 500)" \
775
+ '. + [{type: "FOCUS", content: $content, reason: $reason, priority: 6}]')
776
+ fi
777
+
778
+ # 10. Docker (priority 5, FOCUS)
779
+ if [[ -f "$root/Dockerfile" || -f "$root/docker-compose.yml" || -f "$root/docker-compose.yaml" ]]; then
780
+ suggestions=$(echo "$suggestions" | jq \
781
+ --arg content 'Containerized deployment -- test in container environment' \
782
+ --arg reason 'Detected Docker configuration' \
783
+ '. + [{type: "FOCUS", content: $content, reason: $reason, priority: 5}]')
784
+ fi
785
+
786
+ # Sort by priority descending and truncate to max 5
787
+ echo "$suggestions" | jq '[sort_by(-.priority)[:5] | .[] ]'
788
+ }
789
+
790
+ # Main entry point: scan repo and produce structured research JSON
791
+ # Usage: _scan_init_research [--target <dir>]
792
+ # Options:
793
+ # --target <dir> Directory to scan (default: $AETHER_ROOT or current dir)
794
+ _scan_init_research() {
795
+ local target_dir=""
796
+
797
+ # Parse arguments
798
+ while [[ $# -gt 0 ]]; do
799
+ case "$1" in
800
+ --target)
801
+ target_dir="$2"
802
+ shift 2
803
+ ;;
804
+ *)
805
+ shift
806
+ ;;
807
+ esac
808
+ done
809
+
810
+ # Default target
811
+ target_dir="${target_dir:-${AETHER_ROOT:-.}}"
812
+
813
+ # Validate target exists
814
+ if [[ ! -d "$target_dir" ]]; then
815
+ json_err "$E_FILE_NOT_FOUND" "Target directory does not exist: $target_dir"
816
+ return 1
817
+ fi
818
+
819
+ # Run sub-scans (each returns raw JSON, caller wraps in json_ok)
820
+ local tech_stack directory_structure git_history survey_status prior_colonies complexity
821
+ local colony_context governance pheromone_suggestions
822
+
823
+ tech_stack=$(_scan_tech_stack "$target_dir")
824
+ directory_structure=$(_scan_directory_structure "$target_dir")
825
+ git_history=$(_scan_git_history "$target_dir")
826
+ survey_status=$(_scan_survey_status "$target_dir")
827
+ prior_colonies=$(_scan_prior_colonies "$target_dir")
828
+ complexity=$(_scan_complexity "$target_dir")
829
+ colony_context=$(_scan_colony_context "$target_dir")
830
+ governance=$(_scan_governance "$target_dir")
831
+ pheromone_suggestions=$(_scan_pheromone_suggestions "$target_dir")
832
+
833
+ # Assemble final output via jq
834
+ local result
835
+ result=$(jq -n \
836
+ --argjson tech_stack "$tech_stack" \
837
+ --argjson directory_structure "$directory_structure" \
838
+ --argjson git_history "$git_history" \
839
+ --argjson survey_status "$survey_status" \
840
+ --argjson prior_colonies "$prior_colonies" \
841
+ --argjson complexity "$complexity" \
842
+ --argjson colony_context "$colony_context" \
843
+ --argjson governance "$governance" \
844
+ --argjson pheromone_suggestions "$pheromone_suggestions" \
845
+ '{
846
+ schema_version: 1,
847
+ tech_stack: $tech_stack,
848
+ directory_structure: $directory_structure,
849
+ git_history: $git_history,
850
+ survey_status: $survey_status,
851
+ prior_colonies: $prior_colonies,
852
+ complexity: $complexity,
853
+ colony_context: $colony_context,
854
+ governance: $governance,
855
+ pheromone_suggestions: $pheromone_suggestions,
856
+ scanned_at: (now | todate)
857
+ }')
858
+
859
+ json_ok "$result"
860
+ }