@heretek-ai/epistemic-swarm 0.7.25 → 1.0.2

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 (350) hide show
  1. package/index.ts +1 -0
  2. package/package.json +63 -139
  3. package/rpc.ts +1 -0
  4. package/server.ts +1 -0
  5. package/src/agents.ts +69 -0
  6. package/src/continue.ts +40 -0
  7. package/src/hooks.ts +283 -0
  8. package/src/panels.tsx +220 -0
  9. package/src/policy.ts +221 -0
  10. package/src/pr.ts +37 -0
  11. package/src/research.ts +63 -0
  12. package/src/rpc-def.ts +136 -0
  13. package/src/rpc.ts +331 -0
  14. package/src/runtime.ts +112 -0
  15. package/src/server.ts +311 -0
  16. package/src/session.ts +61 -0
  17. package/src/tools.ts +61 -0
  18. package/src/tui.tsx +206 -0
  19. package/tui.tsx +1 -0
  20. package/.agents/skills/brainstorming/SKILL.md +0 -19
  21. package/.agents/skills/code_audit/SKILL.md +0 -19
  22. package/.agents/skills/darkharvest/SKILL.md +0 -19
  23. package/.agents/skills/epistemic_search/SKILL.md +0 -19
  24. package/.agents/skills/factory/SKILL.md +0 -19
  25. package/.agents/skills/grilling/SKILL.md +0 -19
  26. package/.agents/skills/oss_scout/SKILL.md +0 -19
  27. package/.agents/skills/research_cache/SKILL.md +0 -19
  28. package/.agents/skills/swarm_config/SKILL.md +0 -19
  29. package/.claude-plugin/marketplace.json +0 -61
  30. package/.claude-plugin/plugin.json +0 -76
  31. package/.omp/README.md +0 -39
  32. package/.omp/SYSTEM.md +0 -17
  33. package/.omp/commands/audit.md +0 -10
  34. package/.omp/commands/brainstorming.md +0 -12
  35. package/.omp/commands/darkharvest.md +0 -11
  36. package/.omp/commands/domainexpansion.md +0 -9
  37. package/.omp/commands/factory.md +0 -9
  38. package/.omp/commands/grill.md +0 -9
  39. package/.omp/commands/scout.md +0 -10
  40. package/.omp/commands/swarm-config.md +0 -10
  41. package/.omp/commands/swarm.md +0 -9
  42. package/.omp/hooks/post/epistemic-audit.ts +0 -16
  43. package/.omp/hooks/pre/epistemic-redirect.ts +0 -21
  44. package/.omp/prompts/brainstorming.md +0 -8
  45. package/.omp/prompts/swarm.md +0 -8
  46. package/LICENSE +0 -126
  47. package/MARKETPLACE.md +0 -130
  48. package/README.md +0 -403
  49. package/bin/cli.js +0 -297
  50. package/config/claude-settings-patch.json +0 -10
  51. package/config/docker-compose.infra.yml +0 -23
  52. package/config/domain_packs/biopharma.json +0 -23
  53. package/config/domain_packs/legal.json +0 -19
  54. package/config/domain_packs/quant.json +0 -19
  55. package/config/mcp-research-servers.json +0 -33
  56. package/config/mcp_launcher.py +0 -153
  57. package/config/opencode-snippet.json +0 -156
  58. package/config/searxng_mcp.py +0 -92
  59. package/extensions/pi/index.js +0 -357
  60. package/hooks/hooks.json +0 -45
  61. package/install.sh +0 -473
  62. package/plugins/antigravity/README.md +0 -72
  63. package/plugins/antigravity/agents/alpha-thesis.md +0 -36
  64. package/plugins/antigravity/agents/beta-antithesis.md +0 -35
  65. package/plugins/antigravity/agents/brainstormer.md +0 -27
  66. package/plugins/antigravity/agents/code-auditor.md +0 -28
  67. package/plugins/antigravity/agents/darkharvester.md +0 -30
  68. package/plugins/antigravity/agents/epistemic-auditor.md +0 -28
  69. package/plugins/antigravity/agents/manager.md +0 -26
  70. package/plugins/antigravity/agents/oss-scout.md +0 -27
  71. package/plugins/antigravity/agents/programmer.md +0 -25
  72. package/plugins/antigravity/agents/qa-a.md +0 -22
  73. package/plugins/antigravity/agents/qa-b.md +0 -22
  74. package/plugins/antigravity/antigravity_hooks.py +0 -265
  75. package/plugins/antigravity/commands/audit.md +0 -11
  76. package/plugins/antigravity/commands/brainstorm.md +0 -9
  77. package/plugins/antigravity/commands/brainstorming.md +0 -10
  78. package/plugins/antigravity/commands/darkharvest.md +0 -12
  79. package/plugins/antigravity/commands/domainexpansion.md +0 -12
  80. package/plugins/antigravity/commands/factory.md +0 -14
  81. package/plugins/antigravity/commands/grill.md +0 -10
  82. package/plugins/antigravity/commands/scout.md +0 -11
  83. package/plugins/antigravity/commands/swarm-config.md +0 -9
  84. package/plugins/antigravity/commands/swarm.md +0 -12
  85. package/plugins/antigravity/hook_bus_bridge.js +0 -352
  86. package/plugins/antigravity/hooks.json +0 -44
  87. package/plugins/antigravity/mcp_config.json +0 -33
  88. package/plugins/antigravity/mcp_launcher.py +0 -58
  89. package/plugins/antigravity/plugin.json +0 -13
  90. package/plugins/antigravity/rules/AGENTS.md +0 -127
  91. package/plugins/antigravity/rules/epistemic-integrity.md +0 -16
  92. package/plugins/antigravity/skills/brainstorming/SKILL.md +0 -19
  93. package/plugins/antigravity/skills/code_audit/SKILL.md +0 -19
  94. package/plugins/antigravity/skills/darkharvest/SKILL.md +0 -19
  95. package/plugins/antigravity/skills/epistemic_search/SKILL.md +0 -19
  96. package/plugins/antigravity/skills/factory/SKILL.md +0 -19
  97. package/plugins/antigravity/skills/grilling/SKILL.md +0 -19
  98. package/plugins/antigravity/skills/oss_scout/SKILL.md +0 -19
  99. package/plugins/antigravity/skills/research_cache/SKILL.md +0 -19
  100. package/plugins/antigravity/skills/swarm_config/SKILL.md +0 -19
  101. package/plugins/codex/AGENTS.md.snippet +0 -10
  102. package/plugins/codex/README.md +0 -37
  103. package/plugins/codex/config.toml.snippet +0 -28
  104. package/plugins/codex/openai.yaml +0 -24
  105. package/plugins/codex/skills/brainstorming/SKILL.md +0 -19
  106. package/plugins/codex/skills/code_audit/SKILL.md +0 -19
  107. package/plugins/codex/skills/darkharvest/SKILL.md +0 -19
  108. package/plugins/codex/skills/epistemic_search/SKILL.md +0 -19
  109. package/plugins/codex/skills/factory/SKILL.md +0 -19
  110. package/plugins/codex/skills/grilling/SKILL.md +0 -19
  111. package/plugins/codex/skills/oss_scout/SKILL.md +0 -19
  112. package/plugins/codex/skills/research_cache/SKILL.md +0 -19
  113. package/plugins/codex/skills/swarm_config/SKILL.md +0 -19
  114. package/plugins/darkharvest/.claude-plugin/plugin.json +0 -15
  115. package/plugins/darkharvest/agents/harvest-proponent.md +0 -22
  116. package/plugins/darkharvest/agents/harvest-redteam.md +0 -20
  117. package/plugins/darkharvest/evals/teardown-verdict/graders/license-line.md +0 -6
  118. package/plugins/darkharvest/evals/teardown-verdict/graders/skill-fired.md +0 -5
  119. package/plugins/darkharvest/evals/teardown-verdict/prompt.md +0 -6
  120. package/plugins/darkharvest/skills/darkharvest/SKILL.md +0 -70
  121. package/plugins/darkharvest/skills/darkharvest/scripts/harvest.py +0 -289
  122. package/plugins/factory/.claude-plugin/plugin.json +0 -15
  123. package/plugins/factory/agents/factory-manager.md +0 -22
  124. package/plugins/factory/agents/programmer.md +0 -16
  125. package/plugins/factory/agents/qa-adversarial.md +0 -17
  126. package/plugins/factory/agents/qa-functional.md +0 -17
  127. package/plugins/factory/evals/gate-halt/graders/gates-first.md +0 -6
  128. package/plugins/factory/evals/gate-halt/graders/skill-fired.md +0 -5
  129. package/plugins/factory/evals/gate-halt/prompt.md +0 -6
  130. package/plugins/factory/skills/factory/SKILL.md +0 -61
  131. package/plugins/factory/skills/factory/scripts/factory.py +0 -300
  132. package/plugins/gemini/GEMINI.md +0 -16
  133. package/plugins/gemini/README.md +0 -20
  134. package/plugins/gemini/commands/audit.toml +0 -6
  135. package/plugins/gemini/commands/brainstorming.toml +0 -10
  136. package/plugins/gemini/commands/darkharvest.toml +0 -8
  137. package/plugins/gemini/commands/domainexpansion.toml +0 -7
  138. package/plugins/gemini/commands/factory.toml +0 -8
  139. package/plugins/gemini/commands/grill.toml +0 -6
  140. package/plugins/gemini/commands/scout.toml +0 -7
  141. package/plugins/gemini/commands/swarm-config.toml +0 -7
  142. package/plugins/gemini/commands/swarm.toml +0 -8
  143. package/plugins/gemini/gemini-extension.json +0 -38
  144. package/plugins/gemini/hooks/hooks.json +0 -11
  145. package/plugins/gemini/skills/brainstorming/SKILL.md +0 -19
  146. package/plugins/gemini/skills/code_audit/SKILL.md +0 -19
  147. package/plugins/gemini/skills/darkharvest/SKILL.md +0 -19
  148. package/plugins/gemini/skills/epistemic_search/SKILL.md +0 -19
  149. package/plugins/gemini/skills/factory/SKILL.md +0 -19
  150. package/plugins/gemini/skills/grilling/SKILL.md +0 -19
  151. package/plugins/gemini/skills/oss_scout/SKILL.md +0 -19
  152. package/plugins/gemini/skills/research_cache/SKILL.md +0 -19
  153. package/plugins/gemini/skills/swarm_config/SKILL.md +0 -19
  154. package/plugins/opencode/analysis-bridge.js +0 -620
  155. package/plugins/opencode/config-io.js +0 -1651
  156. package/plugins/opencode/dep-health.js +0 -1039
  157. package/plugins/opencode/hook-bus.js +0 -496
  158. package/plugins/opencode/index.js +0 -2992
  159. package/plugins/opencode/tui.js +0 -1551
  160. package/plugins/opencode/weight-baseline.json +0 -16
  161. package/plugins/opencode/weight-signals.js +0 -769
  162. package/plugins/research-cache/.claude-plugin/plugin.json +0 -15
  163. package/plugins/research-cache/skills/research_cache/SKILL.md +0 -36
  164. package/plugins/research-cache/skills/research_cache/__init__.py +0 -0
  165. package/plugins/research-cache/skills/research_cache/hasher.py +0 -302
  166. package/plugins/socratic-grilling/.claude-plugin/plugin.json +0 -15
  167. package/plugins/socratic-grilling/skills/grilling/SKILL.md +0 -61
  168. package/plugins/socratic-grilling/skills/grilling/__init__.py +0 -0
  169. package/plugins/socratic-grilling/skills/grilling/socratic_tree.py +0 -363
  170. package/prompts/agent_alpha_thesis.md +0 -70
  171. package/prompts/agent_beta_antithesis.md +0 -78
  172. package/prompts/agent_brainstormer.md +0 -143
  173. package/prompts/agent_code_auditor.md +0 -88
  174. package/prompts/agent_darkharvest.md +0 -140
  175. package/prompts/agent_oss_scout.md +0 -78
  176. package/prompts/antigravity_repo_factcheck.md +0 -143
  177. package/prompts/base_epistemic_system.md +0 -50
  178. package/prompts/epistemic_auditor.md +0 -74
  179. package/prompts/orchestrator.md +0 -70
  180. package/runner/__init__.py +0 -0
  181. package/runner/__pycache__/__init__.cpython-311.pyc +0 -0
  182. package/runner/__pycache__/auctioneer.cpython-311.pyc +0 -0
  183. package/runner/__pycache__/auditor_engine.cpython-311.pyc +0 -0
  184. package/runner/__pycache__/claim_store.cpython-311.pyc +0 -0
  185. package/runner/__pycache__/claim_witness.cpython-311.pyc +0 -0
  186. package/runner/__pycache__/darkharvest_claims.cpython-311.pyc +0 -0
  187. package/runner/__pycache__/errors.cpython-311.pyc +0 -0
  188. package/runner/__pycache__/living_dossiers.cpython-311.pyc +0 -0
  189. package/runner/__pycache__/mcp_protocol.cpython-311.pyc +0 -0
  190. package/runner/__pycache__/mcp_server.cpython-311.pyc +0 -0
  191. package/runner/__pycache__/path_safety.cpython-311.pyc +0 -0
  192. package/runner/__pycache__/pcrb.cpython-311.pyc +0 -0
  193. package/runner/__pycache__/pcrb_verify.cpython-311.pyc +0 -0
  194. package/runner/__pycache__/preflight.cpython-311.pyc +0 -0
  195. package/runner/__pycache__/refinement.cpython-311.pyc +0 -0
  196. package/runner/__pycache__/research_swarm.cpython-311.pyc +0 -0
  197. package/runner/__pycache__/retrieval_log.cpython-311.pyc +0 -0
  198. package/runner/__pycache__/schema_validate.cpython-311.pyc +0 -0
  199. package/runner/__pycache__/schemas.cpython-311.pyc +0 -0
  200. package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
  201. package/runner/antigravity_hooks.py +0 -265
  202. package/runner/auctioneer.py +0 -169
  203. package/runner/auditor_engine.py +0 -711
  204. package/runner/claim_store.py +0 -417
  205. package/runner/claim_witness.py +0 -412
  206. package/runner/darkharvest_claims.py +0 -358
  207. package/runner/errors.py +0 -51
  208. package/runner/living_dossiers.py +0 -395
  209. package/runner/mcp_protocol.py +0 -188
  210. package/runner/mcp_server.py +0 -1447
  211. package/runner/path_safety.py +0 -62
  212. package/runner/pcrb.py +0 -290
  213. package/runner/pcrb_verify.py +0 -267
  214. package/runner/preflight.py +0 -1011
  215. package/runner/refinement.py +0 -580
  216. package/runner/research_swarm.py +0 -1848
  217. package/runner/retrieval_log.py +0 -90
  218. package/runner/schema_validate.py +0 -193
  219. package/runner/schemas.py +0 -630
  220. package/runner/state_machine.py +0 -463
  221. package/runner/tests/__pycache__/test_analysis_bridge.cpython-311.pyc +0 -0
  222. package/runner/tests/__pycache__/test_antigravity_plugin.cpython-311.pyc +0 -0
  223. package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
  224. package/runner/tests/__pycache__/test_backends.cpython-311.pyc +0 -0
  225. package/runner/tests/__pycache__/test_bet1_spike.cpython-311.pyc +0 -0
  226. package/runner/tests/__pycache__/test_claim_store.cpython-311.pyc +0 -0
  227. package/runner/tests/__pycache__/test_claim_witness.cpython-311.pyc +0 -0
  228. package/runner/tests/__pycache__/test_claude_plugin.cpython-311.pyc +0 -0
  229. package/runner/tests/__pycache__/test_config_schema.cpython-311.pyc +0 -0
  230. package/runner/tests/__pycache__/test_contract_tooling.cpython-311.pyc +0 -0
  231. package/runner/tests/__pycache__/test_darkharvest_audit.cpython-311.pyc +0 -0
  232. package/runner/tests/__pycache__/test_dep_health.cpython-311.pyc +0 -0
  233. package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
  234. package/runner/tests/__pycache__/test_dossier_contract.cpython-311.pyc +0 -0
  235. package/runner/tests/__pycache__/test_factory.cpython-311.pyc +0 -0
  236. package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
  237. package/runner/tests/__pycache__/test_frontier_concurrency.cpython-311.pyc +0 -0
  238. package/runner/tests/__pycache__/test_frontier_mirror_standalone.cpython-311.pyc +0 -0
  239. package/runner/tests/__pycache__/test_grounding.cpython-311.pyc +0 -0
  240. package/runner/tests/__pycache__/test_hardening_contract.cpython-311.pyc +0 -0
  241. package/runner/tests/__pycache__/test_hook_bus.cpython-311.pyc +0 -0
  242. package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
  243. package/runner/tests/__pycache__/test_manifest_concurrency.cpython-311.pyc +0 -0
  244. package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
  245. package/runner/tests/__pycache__/test_nk_hardening.cpython-311.pyc +0 -0
  246. package/runner/tests/__pycache__/test_opencode_ux.cpython-311.pyc +0 -0
  247. package/runner/tests/__pycache__/test_opencode_v2_registrars.cpython-311.pyc +0 -0
  248. package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
  249. package/runner/tests/__pycache__/test_phase08_waiver_reopens.cpython-311.pyc +0 -0
  250. package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
  251. package/runner/tests/__pycache__/test_search_anomaly.cpython-311.pyc +0 -0
  252. package/runner/tests/__pycache__/test_search_provider.cpython-311.pyc +0 -0
  253. package/runner/tests/__pycache__/test_search_provider_plugin.cpython-311.pyc +0 -0
  254. package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
  255. package/runner/tests/__pycache__/test_swarm_config.cpython-311.pyc +0 -0
  256. package/runner/tests/__pycache__/test_sweep_regressions.cpython-311.pyc +0 -0
  257. package/runner/tests/__pycache__/test_webcache.cpython-311.pyc +0 -0
  258. package/runner/tests/__pycache__/test_weight_signals.cpython-311.pyc +0 -0
  259. package/runner/tests/fixtures/auction_objective.json +0 -35
  260. package/runner/tests/fixtures/borderline_claims.json +0 -27
  261. package/runner/tests/fixtures/config_validation_cases.json +0 -276
  262. package/runner/tests/fixtures/ddg_anomaly_page.html +0 -176
  263. package/runner/tests/fixtures/ddg_results_page.html +0 -28
  264. package/runner/tests/fixtures/divergence_objectives.json +0 -31
  265. package/runner/tests/test_analysis_bridge.py +0 -450
  266. package/runner/tests/test_antigravity_plugin.py +0 -361
  267. package/runner/tests/test_auction_order.py +0 -173
  268. package/runner/tests/test_backends.py +0 -457
  269. package/runner/tests/test_bet1_spike.py +0 -95
  270. package/runner/tests/test_claim_store.py +0 -162
  271. package/runner/tests/test_claim_witness.py +0 -186
  272. package/runner/tests/test_claude_plugin.py +0 -170
  273. package/runner/tests/test_config_schema.py +0 -879
  274. package/runner/tests/test_contract_tooling.py +0 -312
  275. package/runner/tests/test_darkharvest_audit.py +0 -395
  276. package/runner/tests/test_dep_health.py +0 -595
  277. package/runner/tests/test_domain_packs.py +0 -217
  278. package/runner/tests/test_dossier_contract.py +0 -196
  279. package/runner/tests/test_factory.py +0 -258
  280. package/runner/tests/test_fleet_seam.py +0 -113
  281. package/runner/tests/test_frontier_concurrency.py +0 -288
  282. package/runner/tests/test_frontier_mirror_standalone.py +0 -156
  283. package/runner/tests/test_grounding.py +0 -183
  284. package/runner/tests/test_hardening_contract.py +0 -391
  285. package/runner/tests/test_hook_bus.py +0 -956
  286. package/runner/tests/test_living_dossiers.py +0 -204
  287. package/runner/tests/test_manifest_concurrency.py +0 -187
  288. package/runner/tests/test_mcp_server.py +0 -257
  289. package/runner/tests/test_nk_hardening.py +0 -1046
  290. package/runner/tests/test_opencode_ux.py +0 -2438
  291. package/runner/tests/test_opencode_v2_registrars.py +0 -2157
  292. package/runner/tests/test_pcrb.py +0 -240
  293. package/runner/tests/test_phase08_waiver_reopens.py +0 -539
  294. package/runner/tests/test_refinement.py +0 -255
  295. package/runner/tests/test_search_anomaly.py +0 -342
  296. package/runner/tests/test_search_provider.py +0 -1534
  297. package/runner/tests/test_search_provider_plugin.py +0 -401
  298. package/runner/tests/test_swarm.py +0 -444
  299. package/runner/tests/test_swarm_config.py +0 -520
  300. package/runner/tests/test_sweep_regressions.py +0 -453
  301. package/runner/tests/test_webcache.py +0 -271
  302. package/runner/tests/test_weight_signals.py +0 -401
  303. package/schemas/alpha_dossier.schema.json +0 -204
  304. package/schemas/audit_report.schema.json +0 -79
  305. package/schemas/beta_dossier.schema.json +0 -214
  306. package/schemas/config.schema.json +0 -311
  307. package/schemas/frontier.schema.json +0 -61
  308. package/schemas/manifest.schema.json +0 -49
  309. package/schemas/scope_manifest.schema.json +0 -47
  310. package/scripts/__pycache__/bet1_advisory_spike.cpython-311.pyc +0 -0
  311. package/scripts/__pycache__/build_adapters.cpython-311.pyc +0 -0
  312. package/scripts/__pycache__/check_docs.cpython-311.pyc +0 -0
  313. package/scripts/__pycache__/divergence_experiment.cpython-311.pyc +0 -0
  314. package/scripts/__pycache__/gen_schemas.cpython-311.pyc +0 -0
  315. package/scripts/auction_experiment.py +0 -188
  316. package/scripts/bet1_advisory_spike.py +0 -442
  317. package/scripts/build_adapters.py +0 -417
  318. package/scripts/check_docs.py +0 -66
  319. package/scripts/divergence_experiment.py +0 -214
  320. package/scripts/gen_schemas.py +0 -69
  321. package/skills/brainstorming/SKILL.md +0 -106
  322. package/skills/brainstorming/__init__.py +0 -1
  323. package/skills/brainstorming/scripts/brainstorm.py +0 -201
  324. package/skills/code_audit/SKILL.md +0 -45
  325. package/skills/darkharvest/SKILL.md +0 -70
  326. package/skills/darkharvest/scripts/harvest.py +0 -289
  327. package/skills/epistemic_search/SKILL.md +0 -67
  328. package/skills/epistemic_search/__init__.py +0 -1
  329. package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
  330. package/skills/epistemic_search/scripts/fetch.py +0 -156
  331. package/skills/epistemic_search/scripts/search.py +0 -551
  332. package/skills/epistemic_search/scripts/webcache.py +0 -339
  333. package/skills/factory/SKILL.md +0 -61
  334. package/skills/factory/scripts/factory.py +0 -300
  335. package/skills/grilling/SKILL.md +0 -61
  336. package/skills/grilling/__init__.py +0 -0
  337. package/skills/grilling/__pycache__/__init__.cpython-311.pyc +0 -0
  338. package/skills/grilling/__pycache__/socratic_tree.cpython-311.pyc +0 -0
  339. package/skills/grilling/socratic_tree.py +0 -363
  340. package/skills/oss_scout/SKILL.md +0 -49
  341. package/skills/research_cache/SKILL.md +0 -36
  342. package/skills/research_cache/__init__.py +0 -0
  343. package/skills/research_cache/__pycache__/__init__.cpython-311.pyc +0 -0
  344. package/skills/research_cache/__pycache__/hasher.cpython-311.pyc +0 -0
  345. package/skills/research_cache/hasher.py +0 -302
  346. package/skills/swarm_config/SKILL.md +0 -178
  347. package/skills/swarm_config/__init__.py +0 -35
  348. package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
  349. package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
  350. package/skills/swarm_config/configure.py +0 -1002
@@ -1,520 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Config-merge and agent-spawn regressions.
3
-
4
- Covers two live failures observed on an OpenCode host running the installed
5
- package against an operator project:
6
-
7
- 1. A pre-0.7.6 config persisted `agents.<role>.backend = ["claude", "-p"]`.
8
- The shallow merge in `load_config` preserved it and it out-ranked the
9
- host-native default, so an OpenCode host silently spawned Claude.
10
- 2. Agents were spawned with `cwd=PROJECT_ROOT` (the installed package) while
11
- evidence lives in the project's `.research/`, so prompt-mandated relative
12
- writes landed inside node_modules and were denied.
13
- """
14
-
15
- import io
16
- import json
17
- import os
18
- import subprocess
19
- import sys
20
- import tempfile
21
- import time
22
- import unittest
23
- from contextlib import redirect_stderr
24
- from pathlib import Path
25
- from unittest import mock
26
-
27
- PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
28
- if str(PROJECT_ROOT) not in sys.path:
29
- sys.path.insert(0, str(PROJECT_ROOT))
30
-
31
- from runner.research_swarm import ( # noqa: E402
32
- PROJECT_ROOT as PACKAGE_ROOT,
33
- SwarmRunner,
34
- _agent_cwd,
35
- )
36
- from skills.swarm_config.configure import ( # noqa: E402
37
- DEFAULT_CONFIG,
38
- ConfigHashError,
39
- ConfigStaleError,
40
- ConfigValidationError,
41
- _FALLBACK_DEFAULT_CONFIG,
42
- config_hash,
43
- heal_config,
44
- load_config,
45
- merge_config,
46
- migrate_legacy_agent_backends,
47
- save_config,
48
- )
49
-
50
-
51
- def _write_config(base_dir: Path, payload: dict) -> Path:
52
- base_dir.mkdir(parents=True, exist_ok=True)
53
- cfg_file = base_dir / "config.json"
54
- cfg_file.write_text(json.dumps(payload, indent=2), encoding="utf-8")
55
- return cfg_file
56
-
57
-
58
- class TestFallbackDrift(unittest.TestCase):
59
- """Phase-03 R2: the standalone fallback literal tracks the canonical defaults.
60
-
61
- Replaces the old comment-only "drift guard" (which named a test that never
62
- existed): this is the real test. `_FALLBACK_DEFAULT_CONFIG` is the
63
- standalone path of `DEFAULT_CONFIG`, so any key added to
64
- `runner.schemas.CONFIG_DEFAULTS` without updating the literal fails here.
65
- """
66
-
67
- def test_fallback_literal_matches_canonical_defaults(self):
68
- from runner.schemas import CONFIG_DEFAULTS
69
-
70
- self.assertEqual(_FALLBACK_DEFAULT_CONFIG, CONFIG_DEFAULTS)
71
-
72
- def test_fallback_literal_covers_every_schema_key(self):
73
- from runner.schemas import CONFIG
74
-
75
- self.assertEqual(set(_FALLBACK_DEFAULT_CONFIG), set(CONFIG["properties"]))
76
-
77
-
78
- class TestLegacyAgentPinMigration(unittest.TestCase):
79
- def test_legacy_pin_is_dropped_when_backend_is_auto(self):
80
- with tempfile.TemporaryDirectory() as tmp:
81
- base = Path(tmp) / ".research"
82
- _write_config(
83
- base,
84
- {
85
- "backend": "auto",
86
- "agents": {
87
- "alpha": {"backend": ["claude", "-p"], "model": None},
88
- "beta": {"backend": ["claude", "-p"], "model": None},
89
- },
90
- },
91
- )
92
- cfg = load_config(str(base))
93
- self.assertIsNone(cfg["agents"]["alpha"]["backend"])
94
- self.assertIsNone(cfg["agents"]["beta"]["backend"])
95
-
96
- def test_legacy_pin_no_longer_forces_claude_on_opencode_host(self):
97
- with tempfile.TemporaryDirectory() as tmp:
98
- base = Path(tmp) / ".research"
99
- _write_config(
100
- base,
101
- {
102
- "backend": "auto",
103
- "agents": {
104
- "alpha": {"backend": ["claude", "-p"], "model": None},
105
- "beta": {"backend": ["claude", "-p"], "model": None},
106
- },
107
- },
108
- )
109
- with mock.patch.dict(os.environ, {"IUMBTEMS_HOST": "opencode"}):
110
- runner = SwarmRunner(base_dir=base, mock_mode=True, mode="darkharvest")
111
- cmd = runner.build_agent_cmd("objective", role="alpha")
112
- self.assertEqual(cmd[:2], ["opencode", "run"])
113
-
114
- def test_explicit_claude_choice_is_preserved(self):
115
- with tempfile.TemporaryDirectory() as tmp:
116
- base = Path(tmp) / ".research"
117
- _write_config(
118
- base,
119
- {
120
- "backend": "claude",
121
- "agents": {
122
- "alpha": {"backend": ["claude", "-p"], "model": None},
123
- "beta": {"backend": ["claude", "-p"], "model": None},
124
- },
125
- },
126
- )
127
- with mock.patch.dict(os.environ, {"IUMBTEMS_HOST": "opencode"}):
128
- runner = SwarmRunner(base_dir=base, mock_mode=True, mode="research")
129
- with redirect_stderr(io.StringIO()):
130
- cmd = runner.build_agent_cmd("objective", role="alpha")
131
- self.assertEqual(cmd[:2], ["claude", "-p"])
132
-
133
- def test_backend_opencode_top_level_drops_legacy_pin(self):
134
- with tempfile.TemporaryDirectory() as tmp:
135
- base = Path(tmp) / ".research"
136
- _write_config(
137
- base,
138
- {
139
- "backend": "opencode",
140
- "agents": {
141
- "alpha": {"backend": ["claude", "-p"], "model": None},
142
- },
143
- },
144
- )
145
- cfg = load_config(str(base))
146
- self.assertIsNone(cfg["agents"]["alpha"]["backend"])
147
-
148
- def test_migration_helper_reports_change(self):
149
- cfg = {
150
- "backend": "auto",
151
- "agents": {"alpha": {"backend": ["claude", "-p"]}},
152
- }
153
- self.assertTrue(migrate_legacy_agent_backends(cfg))
154
- self.assertFalse(migrate_legacy_agent_backends(cfg))
155
-
156
- def test_heal_config_persists_and_is_idempotent(self):
157
- with tempfile.TemporaryDirectory() as tmp:
158
- base = Path(tmp) / ".research"
159
- cfg_file = _write_config(
160
- base,
161
- {
162
- "backend": "auto",
163
- "agents": {"alpha": {"backend": ["claude", "-p"], "model": None}},
164
- },
165
- )
166
- self.assertTrue(heal_config(str(base)))
167
- on_disk = json.loads(cfg_file.read_text(encoding="utf-8"))
168
- self.assertIsNone(on_disk["agents"]["alpha"]["backend"])
169
- # Second pass has nothing left to migrate.
170
- self.assertFalse(heal_config(str(base)))
171
-
172
- def test_heal_config_refuses_to_persist_schema_rejected_values(self):
173
- """R1 repro: a file with an invalid value must never be healed/written.
174
-
175
- `{"max_iterations": 99, agents.alpha.backend == ["claude","-p"]}` used to
176
- be rewritten by `heal_config(validate=False)`, legitimising the 99 and
177
- feeding it to SwarmRunner. It must now stay in memory only.
178
- """
179
- with tempfile.TemporaryDirectory() as tmp:
180
- base = Path(tmp) / ".research"
181
- cfg_file = _write_config(
182
- base,
183
- {
184
- "max_iterations": 99,
185
- "agents": {"alpha": {"backend": ["claude", "-p"]}},
186
- },
187
- )
188
- before = cfg_file.read_bytes()
189
- buf = io.StringIO()
190
- with redirect_stderr(buf):
191
- healed = heal_config(str(base))
192
- self.assertFalse(healed)
193
- self.assertEqual(cfg_file.read_bytes(), before)
194
- self.assertIn("max_iterations", buf.getvalue())
195
-
196
- # The in-memory migration still protects this process ...
197
- cfg = load_config(str(base))
198
- self.assertIsNone(cfg["agents"]["alpha"]["backend"])
199
- self.assertEqual(cfg["max_iterations"], 99)
200
- # ... and the invalid value survives on disk until fixed.
201
- on_disk = json.loads(cfg_file.read_text(encoding="utf-8"))
202
- self.assertEqual(on_disk["max_iterations"], 99)
203
-
204
- def test_heal_config_persists_once_invalid_value_is_fixed(self):
205
- with tempfile.TemporaryDirectory() as tmp:
206
- base = Path(tmp) / ".research"
207
- cfg_file = _write_config(
208
- base,
209
- {
210
- "max_iterations": 99,
211
- "agents": {"alpha": {"backend": ["claude", "-p"]}},
212
- },
213
- )
214
- # The typed path (MCP/CLI): effective config + the corrected value.
215
- candidate = merge_config(load_config(str(base)), {"max_iterations": 2})
216
- save_config(candidate, str(base))
217
- on_disk = json.loads(cfg_file.read_text(encoding="utf-8"))
218
- self.assertEqual(on_disk["max_iterations"], 2)
219
- self.assertIsNone(on_disk["agents"]["alpha"]["backend"])
220
-
221
-
222
- class TestConfigMergeSemantics(unittest.TestCase):
223
- def test_partial_agents_block_does_not_clobber_defaults(self):
224
- with tempfile.TemporaryDirectory() as tmp:
225
- base = Path(tmp) / ".research"
226
- _write_config(base, {"agents": {"alpha": {"model": "some-model"}}})
227
- cfg = load_config(str(base))
228
- self.assertEqual(cfg["agents"]["alpha"]["model"], "some-model")
229
- self.assertIsNone(cfg["agents"]["alpha"]["backend"])
230
- # Beta was absent from the file and keeps its defaults.
231
- self.assertIsNone(cfg["agents"]["beta"]["backend"])
232
-
233
- def test_loaded_config_does_not_alias_default_config(self):
234
- with tempfile.TemporaryDirectory() as tmp:
235
- base = Path(tmp) / ".research"
236
- cfg = load_config(str(base))
237
- cfg["agents"]["alpha"]["backend"] = "MUTATED"
238
- cfg["license_whitelist"].append("MUTATED")
239
- self.assertIsNone(DEFAULT_CONFIG["agents"]["alpha"]["backend"])
240
- self.assertNotIn("MUTATED", DEFAULT_CONFIG["license_whitelist"])
241
-
242
- def test_malformed_agents_block_is_tolerated(self):
243
- with tempfile.TemporaryDirectory() as tmp:
244
- base = Path(tmp) / ".research"
245
- _write_config(base, {"agents": None})
246
- cfg = load_config(str(base))
247
- self.assertIsNone(cfg["agents"]["alpha"]["backend"])
248
-
249
- _write_config(base, {"agents": {"alpha": None}})
250
- cfg = load_config(str(base))
251
- self.assertIsNone(cfg["agents"]["alpha"])
252
- self.assertIsNone(cfg["agents"]["beta"]["backend"])
253
-
254
- def test_non_object_config_falls_back_to_defaults(self):
255
- with tempfile.TemporaryDirectory() as tmp:
256
- base = Path(tmp) / ".research"
257
- base.mkdir(parents=True, exist_ok=True)
258
- (base / "config.json").write_text("[1, 2, 3]", encoding="utf-8")
259
- with redirect_stderr(io.StringIO()):
260
- cfg = load_config(str(base))
261
- self.assertEqual(cfg["search_engine"], "duckduckgo")
262
-
263
- def test_save_load_roundtrip_is_idempotent(self):
264
- with tempfile.TemporaryDirectory() as tmp:
265
- base = Path(tmp) / ".research"
266
- save_config(load_config(str(base)), str(base))
267
- first = load_config(str(base))
268
- save_config(first, str(base))
269
- self.assertEqual(first, load_config(str(base)))
270
-
271
-
272
- class TestAgentSpawnWorkingDirectory(unittest.TestCase):
273
- def test_project_root_is_evidence_dir_parent(self):
274
- with tempfile.TemporaryDirectory() as tmp:
275
- base = Path(tmp) / ".research"
276
- runner = SwarmRunner(base_dir=base, mock_mode=True, mode="research")
277
- self.assertEqual(
278
- runner.project_root, Path(os.path.realpath(str(base))).parent
279
- )
280
-
281
- def test_agent_cwd_defaults_to_project_root(self):
282
- project = Path("/tmp/some-project")
283
- with mock.patch.dict(os.environ, {}, clear=True):
284
- self.assertEqual(_agent_cwd(project), str(project))
285
-
286
- def test_agent_cwd_package_override(self):
287
- with mock.patch.dict(os.environ, {"IUMBTEMS_AGENT_CWD": "package"}):
288
- self.assertEqual(_agent_cwd(Path("/tmp/x")), str(PACKAGE_ROOT))
289
-
290
- def test_agent_cwd_absolute_override(self):
291
- with mock.patch.dict(os.environ, {"IUMBTEMS_AGENT_CWD": "/custom/dir"}):
292
- self.assertEqual(_agent_cwd(Path("/tmp/x")), "/custom/dir")
293
-
294
- def test_scope_prompt_names_absolute_scratchpad(self):
295
- with tempfile.TemporaryDirectory() as tmp:
296
- base = Path(tmp) / ".research"
297
- runner = SwarmRunner(base_dir=base, mock_mode=True, mode="darkharvest")
298
- prompt = runner._scope_prompt("Alpha", {"scope_id": "s1"}, "s1")
299
- expected = str(runner.state_machine.get_scope_dir("s1"))
300
- self.assertIn(expected, prompt)
301
- self.assertTrue(Path(expected).is_absolute())
302
-
303
- def test_spawn_uses_project_root_not_package_root(self):
304
- with tempfile.TemporaryDirectory() as tmp:
305
- base = Path(tmp) / ".research"
306
- runner = SwarmRunner(base_dir=base, mock_mode=False, mode="research")
307
- fake = mock.Mock()
308
- fake.stdout = "ok"
309
- with mock.patch.dict(os.environ, {}, clear=True):
310
- with mock.patch("subprocess.run", return_value=fake) as run_mock:
311
- runner.run_claude_process("objective text")
312
- kwargs = run_mock.call_args.kwargs
313
- self.assertEqual(kwargs["cwd"], str(runner.project_root))
314
- self.assertNotEqual(kwargs["cwd"], str(PACKAGE_ROOT))
315
- self.assertEqual(kwargs["stdin"], subprocess.DEVNULL)
316
- # `opencode run` follows $PWD, which subprocess.run(cwd=...) does not
317
- # update — keep them identical.
318
- self.assertEqual(kwargs["env"]["PWD"], kwargs["cwd"])
319
-
320
-
321
- class TestBackendHostMismatchWarning(unittest.TestCase):
322
- def test_warns_when_pin_contradicts_host(self):
323
- with tempfile.TemporaryDirectory() as tmp:
324
- base = Path(tmp) / ".research"
325
- _write_config(
326
- base,
327
- {
328
- "backend": "claude",
329
- "agents": {"alpha": {"backend": ["claude", "-p"]}},
330
- },
331
- )
332
- with mock.patch.dict(os.environ, {"IUMBTEMS_HOST": "opencode"}):
333
- runner = SwarmRunner(base_dir=base, mock_mode=True, mode="research")
334
- buf = io.StringIO()
335
- with redirect_stderr(buf):
336
- runner.build_agent_cmd("objective", role="alpha")
337
- self.assertIn("IUMBTEMS_HOST", buf.getvalue())
338
- self.assertIn("alpha", buf.getvalue())
339
-
340
-
341
- class TestSingleWriter(unittest.TestCase):
342
- """`save_config`: atomic, locked, validated, unknown-key preserving."""
343
-
344
- def test_crash_between_temp_write_and_rename_keeps_original(self):
345
- # F9 (recorded limitation): this proves the in-process failure path
346
- # (patched os.replace) leaves the original byte-identical and cleans up
347
- # the temp file. A real SIGKILL between write and rename can still leave
348
- # a `config.json.*.tmp` behind — inherent to temp+rename; the config
349
- # itself is never torn.
350
- with tempfile.TemporaryDirectory() as tmp:
351
- base = Path(tmp)
352
- original = json.dumps({"search_engine": "brave", "custom_key": 1})
353
- cfg_file = _write_config(base, json.loads(original))
354
- before = cfg_file.read_bytes()
355
- with mock.patch("os.replace", side_effect=OSError("simulated crash")):
356
- with self.assertRaises(OSError):
357
- save_config({"mode": "audit"}, str(base))
358
- self.assertEqual(cfg_file.read_bytes(), before)
359
- self.assertEqual(load_config(str(base))["mode"], "research")
360
- # The failed write must not leave temp litter behind.
361
- self.assertEqual(list(base.glob("config.json.*.tmp")), [])
362
-
363
- def test_unknown_keys_on_disk_survive_a_partial_save(self):
364
- with tempfile.TemporaryDirectory() as tmp:
365
- base = Path(tmp)
366
- cfg_file = _write_config(
367
- base,
368
- {
369
- "search_engine": "brave",
370
- "custom_key": {"nested": [1, 2]},
371
- "agents": {"beta": {"model": "keep-me"}},
372
- "websearch": {"provider": "searxng"},
373
- },
374
- )
375
- save_config({"mode": "audit"}, str(base))
376
- data = json.loads(cfg_file.read_text(encoding="utf-8"))
377
- self.assertEqual(data["custom_key"], {"nested": [1, 2]})
378
- self.assertEqual(data["websearch"], {"provider": "searxng"})
379
- self.assertEqual(data["agents"]["beta"]["model"], "keep-me")
380
- self.assertEqual(data["mode"], "audit")
381
- self.assertEqual(data["search_engine"], "brave")
382
-
383
- def test_nested_unknown_keys_merge_instead_of_clobber(self):
384
- with tempfile.TemporaryDirectory() as tmp:
385
- base = Path(tmp)
386
- cfg_file = _write_config(
387
- base, {"agents": {"beta": {"model": "keep-me", "extra": 1}}}
388
- )
389
- save_config({"agents": {"alpha": {"model": "new"}}}, str(base))
390
- data = json.loads(cfg_file.read_text(encoding="utf-8"))
391
- self.assertEqual(data["agents"]["beta"]["model"], "keep-me")
392
- self.assertEqual(data["agents"]["alpha"]["model"], "new")
393
-
394
- def test_expected_hash_mismatch_rejects_and_writes_nothing(self):
395
- with tempfile.TemporaryDirectory() as tmp:
396
- base = Path(tmp)
397
- _write_config(base, {"mode": "research"})
398
- stale_hash = config_hash(str(base))
399
- # A concurrent writer lands a newer version.
400
- cfg_file = _write_config(base, {"mode": "scout"})
401
- before = cfg_file.read_bytes()
402
-
403
- with self.assertRaises(ConfigStaleError) as ctx:
404
- save_config({"mode": "audit"}, str(base), expected_hash=stale_hash)
405
- self.assertEqual(cfg_file.read_bytes(), before)
406
- details = ctx.exception.details
407
- self.assertEqual(details["expected_hash"], stale_hash)
408
- self.assertEqual(details["current_hash"], config_hash(str(base)))
409
- self.assertEqual(details["config"]["mode"], "scout")
410
- self.assertFalse(details["written"])
411
- self.assertEqual(ctx.exception.code, "CONFIG_STALE")
412
- # R5: the stale payload carries no absolute workspace path.
413
- self.assertNotIn("path", details)
414
-
415
- def test_expected_hash_prefix_match_proceeds(self):
416
- with tempfile.TemporaryDirectory() as tmp:
417
- base = Path(tmp)
418
- _write_config(base, {"mode": "research"})
419
- prefix = config_hash(str(base))[:8]
420
- save_config({"mode": "audit"}, str(base), expected_hash=prefix)
421
- self.assertEqual(load_config(str(base))["mode"], "audit")
422
-
423
- def test_expected_hash_requires_at_least_eight_hex(self):
424
- """R4: a 4-hex guard is too collision-prone and is rejected outright."""
425
- with tempfile.TemporaryDirectory() as tmp:
426
- base = Path(tmp)
427
- cfg_file = _write_config(base, {"mode": "research"})
428
- before = cfg_file.read_bytes()
429
- for bad in ("abcd", "abc", "deadbeefz", "zzzzzzzz", "12 34"):
430
- with self.subTest(expected_hash=bad):
431
- with self.assertRaises(ConfigHashError) as ctx:
432
- save_config({"mode": "audit"}, str(base), expected_hash=bad)
433
- self.assertEqual(ctx.exception.code, "CONFIG_INVALID_EXPECTED_HASH")
434
- self.assertEqual(cfg_file.read_bytes(), before)
435
- # 64-hex, 8-hex, and uppercase spellings proceed.
436
- live = config_hash(str(base))
437
- save_config({"mode": "audit"}, str(base), expected_hash=live.upper())
438
- self.assertEqual(load_config(str(base))["mode"], "audit")
439
- # None / empty string mean "no guard" (documented).
440
- save_config({"mode": "scout"}, str(base), expected_hash="")
441
- self.assertEqual(load_config(str(base))["mode"], "scout")
442
-
443
- def test_expected_hash_rejects_before_any_directory_side_effect(self):
444
- with tempfile.TemporaryDirectory() as tmp:
445
- base = Path(tmp) / "fresh"
446
- with self.assertRaises(ConfigHashError):
447
- save_config({"mode": "audit"}, str(base), expected_hash="beef")
448
- self.assertFalse(base.exists())
449
-
450
- def test_expected_hash_for_absent_file_is_stale(self):
451
- with tempfile.TemporaryDirectory() as tmp:
452
- base = Path(tmp)
453
- base.mkdir(parents=True, exist_ok=True)
454
- with self.assertRaises(ConfigStaleError):
455
- save_config({"mode": "audit"}, str(base), expected_hash="a" * 8)
456
- self.assertFalse((base / "config.json").exists())
457
-
458
- def test_invalid_config_is_rejected_without_write(self):
459
- with tempfile.TemporaryDirectory() as tmp:
460
- base = Path(tmp)
461
- with self.assertRaises(ConfigValidationError) as ctx:
462
- save_config({"max_iterations": 99}, str(base))
463
- self.assertFalse((base / "config.json").exists())
464
- self.assertTrue(any("maximum" in e for e in ctx.exception.errors))
465
- self.assertEqual(
466
- ctx.exception.to_dict()["code"], "CONFIG_VALIDATION_FAILED"
467
- )
468
-
469
- def test_non_object_config_is_rejected(self):
470
- with tempfile.TemporaryDirectory() as tmp:
471
- base = Path(tmp)
472
- with self.assertRaises(ConfigValidationError):
473
- save_config([1, 2, 3], str(base))
474
- self.assertFalse((base / "config.json").exists())
475
-
476
- def test_concurrent_writers_never_tear_or_lose_keys(self):
477
- script = (
478
- "import json, sys, time\n"
479
- "sys.path.insert(0, sys.argv[1])\n"
480
- "from skills.swarm_config.configure import save_config\n"
481
- "base, name = sys.argv[2], sys.argv[3]\n"
482
- "for i in range(20):\n"
483
- " save_config({'search_engine': 'brave', 'probe_' + name: i}, base)\n"
484
- " time.sleep(0.002)\n"
485
- )
486
- with tempfile.TemporaryDirectory() as tmp:
487
- base = Path(tmp)
488
- cfg_file = _write_config(base, {"mode": "research"})
489
- procs = [
490
- subprocess.Popen(
491
- [
492
- sys.executable,
493
- "-c",
494
- script,
495
- str(PROJECT_ROOT),
496
- str(base),
497
- name,
498
- ],
499
- stdout=subprocess.PIPE,
500
- stderr=subprocess.PIPE,
501
- text=True,
502
- )
503
- for name in ("a", "b")
504
- ]
505
- # While the writers race, every observable state must be complete JSON.
506
- deadline = time.time() + 30
507
- while any(p.poll() is None for p in procs):
508
- self.assertLess(time.time(), deadline, "writers did not finish")
509
- json.loads(cfg_file.read_text(encoding="utf-8"))
510
- for proc in procs:
511
- _, err = proc.communicate()
512
- self.assertEqual(proc.returncode, 0, err)
513
- data = json.loads(cfg_file.read_text(encoding="utf-8"))
514
- self.assertEqual(data["probe_a"], 19)
515
- self.assertEqual(data["probe_b"], 19)
516
- self.assertEqual(list(base.glob("config.json.*.tmp")), [])
517
-
518
-
519
- if __name__ == "__main__":
520
- unittest.main()