@heretek-ai/epistemic-swarm 0.7.21 → 0.7.23

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 (104) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +1 -1
  3. package/bin/cli.js +3 -2
  4. package/extensions/pi/index.js +3 -2
  5. package/package.json +5 -1
  6. package/plugins/antigravity/plugin.json +1 -1
  7. package/plugins/factory/skills/factory/SKILL.md +1 -1
  8. package/plugins/opencode/analysis-bridge.js +566 -0
  9. package/plugins/opencode/config-io.js +598 -31
  10. package/plugins/opencode/dep-health.js +1039 -0
  11. package/plugins/opencode/hook-bus.js +434 -0
  12. package/plugins/opencode/index.js +252 -19
  13. package/plugins/opencode/tui.js +230 -17
  14. package/plugins/opencode/weight-baseline.json +16 -0
  15. package/plugins/opencode/weight-signals.js +769 -0
  16. package/plugins/research-cache/skills/research_cache/hasher.py +33 -8
  17. package/runner/__pycache__/__init__.cpython-311.pyc +0 -0
  18. package/runner/__pycache__/auctioneer.cpython-311.pyc +0 -0
  19. package/runner/__pycache__/auditor_engine.cpython-311.pyc +0 -0
  20. package/runner/__pycache__/claim_store.cpython-311.pyc +0 -0
  21. package/runner/__pycache__/claim_witness.cpython-311.pyc +0 -0
  22. package/runner/__pycache__/darkharvest_claims.cpython-311.pyc +0 -0
  23. package/runner/__pycache__/errors.cpython-311.pyc +0 -0
  24. package/runner/__pycache__/living_dossiers.cpython-311.pyc +0 -0
  25. package/runner/__pycache__/mcp_protocol.cpython-311.pyc +0 -0
  26. package/runner/__pycache__/mcp_server.cpython-311.pyc +0 -0
  27. package/runner/__pycache__/path_safety.cpython-311.pyc +0 -0
  28. package/runner/__pycache__/pcrb.cpython-311.pyc +0 -0
  29. package/runner/__pycache__/pcrb_verify.cpython-311.pyc +0 -0
  30. package/runner/__pycache__/preflight.cpython-311.pyc +0 -0
  31. package/runner/__pycache__/refinement.cpython-311.pyc +0 -0
  32. package/runner/__pycache__/research_swarm.cpython-311.pyc +0 -0
  33. package/runner/__pycache__/retrieval_log.cpython-311.pyc +0 -0
  34. package/runner/__pycache__/schema_validate.cpython-311.pyc +0 -0
  35. package/runner/__pycache__/schemas.cpython-311.pyc +0 -0
  36. package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
  37. package/runner/mcp_server.py +249 -81
  38. package/runner/refinement.py +5 -0
  39. package/runner/schema_validate.py +63 -1
  40. package/runner/schemas.py +9 -4
  41. package/runner/tests/__pycache__/test_analysis_bridge.cpython-311.pyc +0 -0
  42. package/runner/tests/__pycache__/test_antigravity_plugin.cpython-311.pyc +0 -0
  43. package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
  44. package/runner/tests/__pycache__/test_backends.cpython-311.pyc +0 -0
  45. package/runner/tests/__pycache__/test_bet1_spike.cpython-311.pyc +0 -0
  46. package/runner/tests/__pycache__/test_claim_store.cpython-311.pyc +0 -0
  47. package/runner/tests/__pycache__/test_claim_witness.cpython-311.pyc +0 -0
  48. package/runner/tests/__pycache__/test_claude_plugin.cpython-311.pyc +0 -0
  49. package/runner/tests/__pycache__/test_config_schema.cpython-311.pyc +0 -0
  50. package/runner/tests/__pycache__/test_contract_tooling.cpython-311.pyc +0 -0
  51. package/runner/tests/__pycache__/test_darkharvest_audit.cpython-311.pyc +0 -0
  52. package/runner/tests/__pycache__/test_dep_health.cpython-311.pyc +0 -0
  53. package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
  54. package/runner/tests/__pycache__/test_dossier_contract.cpython-311.pyc +0 -0
  55. package/runner/tests/__pycache__/test_factory.cpython-311.pyc +0 -0
  56. package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
  57. package/runner/tests/__pycache__/test_frontier_concurrency.cpython-311.pyc +0 -0
  58. package/runner/tests/__pycache__/test_frontier_mirror_standalone.cpython-311.pyc +0 -0
  59. package/runner/tests/__pycache__/test_grounding.cpython-311.pyc +0 -0
  60. package/runner/tests/__pycache__/test_hardening_contract.cpython-311.pyc +0 -0
  61. package/runner/tests/__pycache__/test_hook_bus.cpython-311.pyc +0 -0
  62. package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
  63. package/runner/tests/__pycache__/test_manifest_concurrency.cpython-311.pyc +0 -0
  64. package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
  65. package/runner/tests/__pycache__/test_opencode_ux.cpython-311.pyc +0 -0
  66. package/runner/tests/__pycache__/test_opencode_v2_registrars.cpython-311.pyc +0 -0
  67. package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
  68. package/runner/tests/__pycache__/test_phase08_waiver_reopens.cpython-311.pyc +0 -0
  69. package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
  70. package/runner/tests/__pycache__/test_search_anomaly.cpython-311.pyc +0 -0
  71. package/runner/tests/__pycache__/test_search_provider.cpython-311.pyc +0 -0
  72. package/runner/tests/__pycache__/test_search_provider_plugin.cpython-311.pyc +0 -0
  73. package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
  74. package/runner/tests/__pycache__/test_swarm_config.cpython-311.pyc +0 -0
  75. package/runner/tests/__pycache__/test_sweep_regressions.cpython-311.pyc +0 -0
  76. package/runner/tests/__pycache__/test_webcache.cpython-311.pyc +0 -0
  77. package/runner/tests/__pycache__/test_weight_signals.cpython-311.pyc +0 -0
  78. package/runner/tests/fixtures/config_validation_cases.json +60 -0
  79. package/runner/tests/test_analysis_bridge.py +356 -0
  80. package/runner/tests/test_config_schema.py +119 -15
  81. package/runner/tests/test_dep_health.py +484 -0
  82. package/runner/tests/test_hardening_contract.py +391 -0
  83. package/runner/tests/test_hook_bus.py +802 -0
  84. package/runner/tests/test_opencode_ux.py +232 -0
  85. package/runner/tests/test_opencode_v2_registrars.py +548 -16
  86. package/runner/tests/test_phase08_waiver_reopens.py +539 -0
  87. package/runner/tests/test_weight_signals.py +361 -0
  88. package/schemas/config.schema.json +4 -3
  89. package/scripts/__pycache__/bet1_advisory_spike.cpython-311.pyc +0 -0
  90. package/scripts/__pycache__/build_adapters.cpython-311.pyc +0 -0
  91. package/scripts/__pycache__/check_docs.cpython-311.pyc +0 -0
  92. package/scripts/__pycache__/divergence_experiment.cpython-311.pyc +0 -0
  93. package/scripts/__pycache__/gen_schemas.cpython-311.pyc +0 -0
  94. package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
  95. package/skills/factory/SKILL.md +1 -1
  96. package/skills/grilling/__pycache__/__init__.cpython-311.pyc +0 -0
  97. package/skills/grilling/__pycache__/socratic_tree.cpython-311.pyc +0 -0
  98. package/skills/research_cache/__pycache__/__init__.cpython-311.pyc +0 -0
  99. package/skills/research_cache/__pycache__/hasher.cpython-311.pyc +0 -0
  100. package/skills/research_cache/hasher.py +33 -8
  101. package/skills/swarm_config/SKILL.md +6 -1
  102. package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
  103. package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
  104. package/skills/swarm_config/configure.py +254 -17
@@ -2202,5 +2202,237 @@ class TestUiPrefsBoundary(unittest.TestCase):
2202
2202
  self.assertEqual(data["bare"], {})
2203
2203
 
2204
2204
 
2205
+ # ---------------------------------------------------------------------------
2206
+ # Phase 07 (tui-polish, H8): R4 advisory wording polish + keyed toast dedup.
2207
+ # Mock-host tests per T1-T2. Fresh node process per test (run_node), so the
2208
+ # module-level dedup window never leaks between tests.
2209
+ # ---------------------------------------------------------------------------
2210
+
2211
+ PHASE07_HARNESS = """
2212
+ import {
2213
+ openSettingsWizard, warnOnce, _resetAdvisoryDedup, _advisorySuppressed,
2214
+ ADVISORY_DEDUP_MS,
2215
+ } from "./plugins/opencode/tui.js";
2216
+ import fs from "node:fs";
2217
+ import path from "node:path";
2218
+ function mockHost(queues, opts = {}) {
2219
+ const toasts = [];
2220
+ const pop = (arr) => ((!arr || arr.length === 0) ? null : arr.shift());
2221
+ const host = {
2222
+ directory: opts.root || null,
2223
+ ui: { toast: { show: (t) => toasts.push(t) } },
2224
+ };
2225
+ if (!opts.noDialog) {
2226
+ host.ui.dialog = {
2227
+ select: async (a) => pop(queues.select),
2228
+ prompt: async (a) => pop(queues.prompt),
2229
+ confirm: async (a) => pop(queues.confirm),
2230
+ };
2231
+ }
2232
+ return { host, toasts };
2233
+ }
2234
+ """
2235
+
2236
+
2237
+ class TestPhase07AdvisoryWording(unittest.TestCase):
2238
+ """T1: advisories accurate without self-contradiction on all paths."""
2239
+
2240
+ def test_env_advisory_accurate_and_write_proceeds(self):
2241
+ with tempfile.TemporaryDirectory() as tmp:
2242
+ res = run_node(
2243
+ PHASE07_HARNESS
2244
+ + f"""
2245
+ const root = {tmp!r};
2246
+ const m = mockHost({{select: ["searxng_url", "__iumbtems_save__"], prompt: ["http://new:8080"], confirm: [true]}}, {{root}});
2247
+ m.host.directory = root;
2248
+ const r = await openSettingsWizard(m.host, root, {{env: {{SEARXNG_URL: "http://env:1"}}}});
2249
+ const saved = JSON.parse(fs.readFileSync(path.join(root, ".research", "config.json"), "utf-8"));
2250
+ const text = m.toasts.map((t) => JSON.stringify(t)).join("\\n");
2251
+ console.log(JSON.stringify({{written: r.written, saved: saved.searxng_url, text}}));
2252
+ """
2253
+ )
2254
+ self.assertEqual(res.returncode, 0, f"env wording failed: {res.stderr}")
2255
+ data = last_json_object(res.stdout)
2256
+ self.assertTrue(data["written"])
2257
+ self.assertEqual(data["saved"], "http://new:8080")
2258
+ # Accuracy phrases retained (blast-radius: existing R4 test regex).
2259
+ self.assertRegex(
2260
+ data["text"],
2261
+ r"(?i)environment shadows.*no runtime effect until.*unset",
2262
+ )
2263
+ self.assertIn("cancel writes nothing", data["text"])
2264
+ # No self-contradiction: the file value is "not effective", never
2265
+ # presented as effective; no display-only claim on a saved value.
2266
+ env_toasts = [
2267
+ line for line in data["text"].split("\n") if "nvironment" in line
2268
+ ]
2269
+ self.assertTrue(env_toasts)
2270
+ for line in env_toasts:
2271
+ self.assertNotIn("display-only", line)
2272
+
2273
+ def test_deprecated_advisory_generic_and_write_proceeds(self):
2274
+ with tempfile.TemporaryDirectory() as tmp:
2275
+ res = run_node(
2276
+ PHASE07_HARNESS
2277
+ + f"""
2278
+ const root = {tmp!r};
2279
+ const m = mockHost({{select: ["output_dir", "__iumbtems_save__"], prompt: [".renamed"], confirm: [true]}}, {{root}});
2280
+ m.host.directory = root;
2281
+ const r = await openSettingsWizard(m.host, root, {{env: {{}}}});
2282
+ const saved = JSON.parse(fs.readFileSync(path.join(root, ".research", "config.json"), "utf-8"));
2283
+ const text = m.toasts.map((t) => JSON.stringify(t)).join("\\n");
2284
+ console.log(JSON.stringify({{written: r.written, saved: saved.output_dir, text}}));
2285
+ """
2286
+ )
2287
+ self.assertEqual(
2288
+ res.returncode, 0, f"deprecated wording failed: {res.stderr}"
2289
+ )
2290
+ data = last_json_object(res.stdout)
2291
+ self.assertTrue(data["written"])
2292
+ self.assertEqual(data["saved"], ".renamed")
2293
+ self.assertIn("eprecat", data["text"])
2294
+ self.assertIn("no runtime effect", data["text"])
2295
+ self.assertIn("cancel writes nothing", data["text"])
2296
+ # The old wording was output_dir-specific ("never selects the
2297
+ # workspace") and self-contradictory ("display-only" + "still
2298
+ # saved"): both must be gone from the deprecated advisory.
2299
+ dep_toasts = [
2300
+ line for line in data["text"].split("\n") if "eprecat" in line
2301
+ ]
2302
+ self.assertTrue(dep_toasts)
2303
+ for line in dep_toasts:
2304
+ self.assertNotIn("display-only", line)
2305
+ self.assertNotIn("never selects the workspace", line)
2306
+
2307
+ def test_degraded_and_malformed_notices_retained_direct_fs(self):
2308
+ # Degraded fallback + malformed advisories survive the polish.
2309
+ with tempfile.TemporaryDirectory() as tmp:
2310
+ research = Path(tmp) / ".research"
2311
+ research.mkdir()
2312
+ garbage = "{oops not json"
2313
+ research.joinpath("config.json").write_text(garbage, encoding="utf-8")
2314
+ res = run_node(
2315
+ PHASE07_HARNESS
2316
+ + f"""
2317
+ const root = {tmp!r};
2318
+ const m = mockHost({{select: ["__iumbtems_cancel__"]}}, {{root}});
2319
+ m.host.directory = root;
2320
+ const r = await openSettingsWizard(m.host, root, {{env: {{}}}});
2321
+ const text = m.toasts.map((t) => JSON.stringify(t)).join("\\n");
2322
+ console.log(JSON.stringify({{cancelled: r.cancelled, written: r.written, text}}));
2323
+ """
2324
+ )
2325
+ self.assertEqual(res.returncode, 0, f"degraded path failed: {res.stderr}")
2326
+ data = last_json_object(res.stdout)
2327
+ self.assertTrue(data["cancelled"])
2328
+ self.assertFalse(data["written"])
2329
+ self.assertIn("degraded path", data["text"])
2330
+ self.assertIn("Could not parse", data["text"])
2331
+
2332
+
2333
+ class TestPhase07ToastDedup(unittest.TestCase):
2334
+ """T2: keyed window collapses repeats, re-shows after expiry, never
2335
+ swallows errors/degraded; durations preserved."""
2336
+
2337
+ def test_warnonce_collapse_expiry_and_independence(self):
2338
+ res = run_node(
2339
+ PHASE07_HARNESS
2340
+ + """
2341
+ _resetAdvisoryDedup();
2342
+ const toasts = [];
2343
+ const host = { ui: { toast: { show: (t) => toasts.push(t) } } };
2344
+ const t0 = 1000000;
2345
+ const ttl = 5000;
2346
+ const first = warnOnce(host, "env:searxng_url", "Environment override", "msg-a", { ttlMs: ttl, now: t0 });
2347
+ const repeat = warnOnce(host, "env:searxng_url", "Environment override", "msg-a", { ttlMs: ttl, now: t0 + 1000 });
2348
+ const repeat2 = warnOnce(host, "env:searxng_url", "Environment override", "msg-a", { ttlMs: ttl, now: t0 + 2000 });
2349
+ const other = warnOnce(host, "deprecated:output_dir", "Deprecated field", "msg-b", { ttlMs: ttl, now: t0 + 2000 });
2350
+ const pending = _advisorySuppressed("env:searxng_url");
2351
+ const reshow = warnOnce(host, "env:searxng_url", "Environment override", "msg-a", { ttlMs: ttl, now: t0 + ttl + 1 });
2352
+ console.log(JSON.stringify({
2353
+ first, repeat, repeat2, other, pending, reshow,
2354
+ shown: toasts.length,
2355
+ durations: toasts.map((t) => t.duration),
2356
+ variants: toasts.map((t) => t.variant),
2357
+ lastText: toasts[toasts.length - 1].message,
2358
+ windowMs: ADVISORY_DEDUP_MS,
2359
+ }));
2360
+ """
2361
+ )
2362
+ self.assertEqual(res.returncode, 0, f"warnOnce failed: {res.stderr}")
2363
+ data = last_json_object(res.stdout)
2364
+ self.assertTrue(data["first"])
2365
+ self.assertFalse(data["repeat"])
2366
+ self.assertFalse(data["repeat2"])
2367
+ # Distinct keys are independent.
2368
+ self.assertTrue(data["other"])
2369
+ # Repeats within the window collapse with a count.
2370
+ self.assertEqual(data["pending"], 2)
2371
+ self.assertEqual(data["shown"], 3) # first + other + reshow
2372
+ # Expiry re-shows, annotated with the collapsed count (2 + this one).
2373
+ self.assertTrue(data["reshow"])
2374
+ self.assertIn("3×", data["lastText"])
2375
+ # Durations preserved (wizard 6000), variant untouched.
2376
+ self.assertTrue(all(d == 6000 for d in data["durations"]))
2377
+ self.assertTrue(all(v == "info" for v in data["variants"]))
2378
+ self.assertGreater(data["windowMs"], 0)
2379
+
2380
+ def test_wizard_repeats_collapse_but_errors_and_degraded_never_swallowed(self):
2381
+ with tempfile.TemporaryDirectory() as tmp:
2382
+ res = run_node(
2383
+ PHASE07_HARNESS
2384
+ + f"""
2385
+ const root = {tmp!r};
2386
+ _resetAdvisoryDedup();
2387
+ const m = mockHost({{
2388
+ select: ["searxng_url", "searxng_url", "__iumbtems_save__"],
2389
+ prompt: ["http://a:1", "http://b:2"],
2390
+ confirm: [true],
2391
+ }}, {{root}});
2392
+ m.host.directory = root;
2393
+ const r = await openSettingsWizard(m.host, root, {{env: {{SEARXNG_URL: "http://env:9"}}}});
2394
+ const saved = JSON.parse(fs.readFileSync(path.join(root, ".research", "config.json"), "utf-8"));
2395
+ const text = m.toasts.map((t) => JSON.stringify(t)).join("\\n");
2396
+ const advisory = m.toasts.filter((t) => t.title === "Environment override").length;
2397
+ const degraded = m.toasts.filter((t) => JSON.stringify(t).includes("degraded path")).length;
2398
+ console.log(JSON.stringify({{written: r.written, saved: saved.searxng_url, advisory, degraded, text}}));
2399
+ """
2400
+ )
2401
+ self.assertEqual(res.returncode, 0, f"wizard dedup failed: {res.stderr}")
2402
+ data = last_json_object(res.stdout)
2403
+ self.assertTrue(data["written"])
2404
+ self.assertEqual(data["saved"], "http://b:2")
2405
+ # Two edits of the same env-shadowed key -> one advisory toast.
2406
+ self.assertEqual(data["advisory"], 1)
2407
+ # The degraded-path toast is never deduped away.
2408
+ self.assertEqual(data["degraded"], 1)
2409
+
2410
+ def test_wizard_error_toasts_never_deduped(self):
2411
+ with tempfile.TemporaryDirectory() as tmp:
2412
+ res = run_node(
2413
+ PHASE07_HARNESS
2414
+ + f"""
2415
+ const root = {tmp!r};
2416
+ _resetAdvisoryDedup();
2417
+ const m = mockHost({{
2418
+ select: ["max_iterations", "__iumbtems_save__"],
2419
+ prompt: ["99", "98", "3"],
2420
+ confirm: [true],
2421
+ }}, {{root}});
2422
+ m.host.directory = root;
2423
+ const r = await openSettingsWizard(m.host, root, {{env: {{}}}});
2424
+ const saved = JSON.parse(fs.readFileSync(path.join(root, ".research", "config.json"), "utf-8"));
2425
+ const invalid = m.toasts.filter((t) => t.title === "Invalid value").length;
2426
+ console.log(JSON.stringify({{written: r.written, depth: saved.max_iterations, invalid}}));
2427
+ """
2428
+ )
2429
+ self.assertEqual(res.returncode, 0, f"error dedup failed: {res.stderr}")
2430
+ data = last_json_object(res.stdout)
2431
+ self.assertTrue(data["written"])
2432
+ self.assertEqual(data["depth"], 3)
2433
+ # Both validation errors surface: error toasts bypass the window.
2434
+ self.assertEqual(data["invalid"], 2)
2435
+
2436
+
2205
2437
  if __name__ == "__main__":
2206
2438
  unittest.main()