@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
@@ -26,7 +26,8 @@ import {
26
26
  saveConfig,
27
27
  validateConfig,
28
28
  } from './config-io.js';
29
- import { OPENCODE_COMMANDS, SettingsRpc } from './index.js';
29
+ import { gatePreCommit } from './hook-bus.js';
30
+ import { OPENCODE_COMMANDS, SettingsRpc, hookBus as sharedHookBus } from './index.js';
30
31
 
31
32
  const PLUGIN_ID = 'heretek.iumbtems.epistemic-swarm.tui';
32
33
  const REFRESH_MS = 5000;
@@ -100,6 +101,70 @@ export function subscribeSettingsChanged(api, onChanged) {
100
101
  }
101
102
  }
102
103
 
104
+ /**
105
+ * Phase 01-hook-bus-spec fix round (QA-B FAIL 1): `pre-commit` gate for the
106
+ * TUI direct-fs fallback leg (`saveConfig(draft, baseDir, guard)` in
107
+ * `openSettingsWizard`). The RPC leg already gates in
108
+ * `createSettingsHandlers().set`; without this the fallback wrote with zero
109
+ * `pre-commit` emit and zero audit entry.
110
+ *
111
+ * Emits `pre-commit` through the bus and maps the verdict per the tier table
112
+ * (deny blocks, bus faults fail open). The stale-write guard is NOT replaced
113
+ * — the caller still passes `guard` to `saveConfig`, which enforces
114
+ * `CONFIG_STALE` downstream. On RPC-null hosts the bus remains the
115
+ * enforcement point: pass `opts.hookBus` (or a host carrying the adopted
116
+ * `host.iumbtemsHookBus`); a null bus fails open with `{ allowed: true }`.
117
+ * The emit itself audit-logs the decision, so a denied fallback write always
118
+ * leaves an audit entry. Never throws.
119
+ *
120
+ * Narrow fix round (qa-b bypass): the shipped TUI path previously called
121
+ * `openSettingsWizard(api, root)` with no opts on an api that carried no
122
+ * `iumbtemsHookBus`, so the fallback gate resolved null and failed open past
123
+ * a denying pre-commit handler. `resolveHookBus` closes that wiring gap: the
124
+ * shared bus (`hookBus` from `index.js`, same instance the settings-RPC leg
125
+ * gates on [VERIFIED: sha256:52ac5b4d26062cfc6093440faa415ab152f8acd0d18c4496eb0087ed18a27d76])
126
+ * is the final fallback, so the shipped configuration never resolves a null
127
+ * bus. Fail-open remains only for genuine bus faults (null/non-emit bus
128
+ * passed EXPLICITLY to `gateDirectFsPreCommit`), not for missing wiring.
129
+ */
130
+ export async function gateDirectFsPreCommit(bus, { draft, baseDir, guard } = {}) {
131
+ try {
132
+ if (!bus || typeof bus.emit !== 'function') return { allowed: true };
133
+ const expected = guard?.expected_hash ?? guard?.expectedHash ?? null;
134
+ return await gatePreCommit(bus, {
135
+ updates: draft ?? {},
136
+ root: baseDir ?? null,
137
+ ...(expected !== null && expected !== undefined ? { expected_hash: expected } : {}),
138
+ });
139
+ } catch {
140
+ return { allowed: true };
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Resolve the pre-commit bus for the wizard fallback leg. Precedence:
146
+ * explicit `opts.hookBus` > adopted `host.iumbtemsHookBus` > shared bus.
147
+ * The shared-bus fallback is what keeps the shipped TUI path (slash command
148
+ * + palette, both RPC-null) from resolving a null bus. Never throws.
149
+ */
150
+ export function resolveHookBus(host, opts) {
151
+ try {
152
+ const explicit = opts && opts.hookBus;
153
+ if (explicit && typeof explicit.emit === 'function') return explicit;
154
+ const adopted = host && host.iumbtemsHookBus;
155
+ if (adopted && typeof adopted.emit === 'function') return adopted;
156
+ if (sharedHookBus && typeof sharedHookBus.emit === 'function') return sharedHookBus;
157
+ return null;
158
+ } catch {
159
+ try {
160
+ if (sharedHookBus && typeof sharedHookBus.emit === 'function') return sharedHookBus;
161
+ } catch {
162
+ /* fall through */
163
+ }
164
+ return null;
165
+ }
166
+ }
167
+
103
168
  /** Read UI-only prefs ({} when the host has no TUI storage). Never throws. */
104
169
  export function readUiPrefs(api) {
105
170
  try {
@@ -426,7 +491,7 @@ function SwarmKeymapLayer(api) {
426
491
  'Edit IUMBTEMS settings in a dialog wizard (validates input, writes atomically)',
427
492
  group: 'IUMBTEMS',
428
493
  palette: true,
429
- run: () => openSettingsWizard(api, resolveRoot(api)),
494
+ run: () => openSettingsWizard(api, resolveRoot(api), { hookBus: resolveHookBus(api, null) }),
430
495
  },
431
496
  {
432
497
  id: SETTINGS_PANEL_COMMAND_ID,
@@ -504,9 +569,98 @@ const WIZARD_KEEP_VALUE = '__iumbtems_keep__';
504
569
 
505
570
  function wizardToast(host, title, message) {
506
571
  try {
507
- host?.ui?.toast?.show?.({ title, message, variant: 'info', duration: 6000 });
572
+ const toast = host?.ui?.toast;
573
+ const show = toast?.show;
574
+ if (typeof show !== 'function') return false;
575
+ show.call(toast, { title, message, variant: 'info', duration: 6000 });
576
+ return true;
577
+ } catch {
578
+ return false;
579
+ }
580
+ }
581
+
582
+ // ---------------------------------------------------------------------------
583
+ // Phase 07 (H8): keyed advisory dedup window for the repeating R4 advisories.
584
+ //
585
+ // Editing an env-shadowed or deprecated row fires its advisory on EVERY edit,
586
+ // so a multi-edit wizard session spams one toast per row visit. `warnOnce`
587
+ // collapses repeats of the SAME advisory key inside a TTL window: the first
588
+ // shows, repeats within the window are suppressed (counted), expiry re-shows
589
+ // (annotated with the suppressed count), and distinct keys stay independent.
590
+ //
591
+ // SAFETY: only the two repeating R4 advisories route here. Degraded-path,
592
+ // malformed-config, validation-error, stale, and save-failure toasts call
593
+ // `wizardToast` directly and are NEVER deduped (missed-action safety).
594
+ // Durations are preserved: wizard advisories stay 6000ms via `wizardToast`
595
+ // (the 4000ms keymap usage toast is untouched).
596
+ // ---------------------------------------------------------------------------
597
+
598
+ /** Dedup window for repeating R4 advisories, in ms. Exported for tests. */
599
+ export const ADVISORY_DEDUP_MS = 30000;
600
+
601
+ /** key -> { ts, suppressed }: last show time + repeats collapsed since. */
602
+ const advisorySeen = new Map();
603
+
604
+ /**
605
+ * Show a keyed advisory toast at most once per `ttlMs` window.
606
+ * Returns true when a toast was actually delivered, false when collapsed
607
+ * or when delivery failed (failed deliveries never arm the window).
608
+ * `opts.now` injects the clock (tests); `opts.ttlMs` overrides the window.
609
+ * Session scope: each `openSettingsWizard` call resets the window (fresh
610
+ * wizard session always warns at least once); repeats within one session
611
+ * still collapse. Never throws.
612
+ */
613
+ export function warnOnce(host, key, title, message, opts = {}) {
614
+ try {
615
+ const ttl =
616
+ opts && opts.ttlMs !== undefined && opts.ttlMs !== null
617
+ ? Number(opts.ttlMs)
618
+ : ADVISORY_DEDUP_MS;
619
+ const now =
620
+ opts && opts.now !== undefined && opts.now !== null
621
+ ? Number(opts.now)
622
+ : Date.now();
623
+ const entry = advisorySeen.get(key);
624
+ if (entry && Number.isFinite(ttl) && ttl > 0) {
625
+ const dt = now - entry.ts;
626
+ if (Number.isFinite(dt) && dt >= 0 && dt < ttl) {
627
+ entry.suppressed += 1;
628
+ return false;
629
+ }
630
+ // dt < 0 (clock moved backwards) or dt >= ttl (expiry) fall through
631
+ // to reshow — a backwards clock must never suppress indefinitely.
632
+ }
633
+ let text = message;
634
+ if (entry && entry.suppressed > 0) {
635
+ text += ` (repeated ${entry.suppressed + 1}× since last notice)`;
636
+ }
637
+ // Arm the window ONLY on actual delivery: a failed toast (missing/broken
638
+ // host) leaves state untouched so the next real warning still delivers.
639
+ const delivered = wizardToast(host, title, text);
640
+ if (!delivered) return false;
641
+ advisorySeen.set(key, { ts: now, suppressed: 0 });
642
+ return true;
643
+ } catch {
644
+ return false;
645
+ }
646
+ }
647
+
648
+ /** Test hook: forget all dedup state. Never throws. */
649
+ export function _resetAdvisoryDedup() {
650
+ try {
651
+ advisorySeen.clear();
652
+ } catch {
653
+ /* never throw */
654
+ }
655
+ }
656
+
657
+ /** Test hook: suppressed-repeat count pending for `key`. Never throws. */
658
+ export function _advisorySuppressed(key) {
659
+ try {
660
+ const entry = advisorySeen.get(key);
661
+ return entry ? entry.suppressed : 0;
508
662
  } catch {
509
- /* toast is best-effort */
663
+ return 0;
510
664
  }
511
665
  }
512
666
 
@@ -715,23 +869,29 @@ async function editRow(dialog, host, draft, row) {
715
869
  try {
716
870
  const sub = row.schema || {};
717
871
  // R4 advisories (never block the edit; the write still proceeds):
718
- // an env-shadowed value has no runtime effect until the override is
719
- // unset, and a deprecated key is display-only.
872
+ // an env-shadowed value is not effective while the override is set, and
873
+ // a deprecated key is ignored at runtime. Both route through the keyed
874
+ // dedup window (per-key repeats collapse); every other toast stays
875
+ // un-deduped so no error or degraded-path notice is ever swallowed.
720
876
  if (row.source === 'env-override') {
721
- wizardToast(
877
+ warnOnce(
722
878
  host,
879
+ `env:${row.key}`,
723
880
  'Environment override',
724
- `The environment shadows '${row.key}' — editing the file value has ` +
725
- 'no runtime effect until the override is unset. The stored value is ' +
726
- 'still saved on confirm.'
881
+ `The environment shadows '${row.key}' — the file value is not ` +
882
+ 'effective while the override is set and has no runtime effect ' +
883
+ 'until the override is unset. The edited value is still stored ' +
884
+ 'on save (cancel writes nothing).'
727
885
  );
728
886
  }
729
887
  if (row.deprecated) {
730
- wizardToast(
888
+ warnOnce(
731
889
  host,
890
+ `deprecated:${row.key}`,
732
891
  'Deprecated field',
733
- `'${row.key}' is deprecated and display-only — it never selects the ` +
734
- 'workspace. The stored value is still saved on confirm.'
892
+ `'${row.key}' is deprecated and ignored — it has no runtime ` +
893
+ 'effect. The edited value is still stored on save for reference, ' +
894
+ 'but changes nothing (cancel writes nothing).'
735
895
  );
736
896
  }
737
897
  const types = Array.isArray(sub.type) ? sub.type : [sub.type];
@@ -999,6 +1159,15 @@ async function editAgentsRow(dialog, host, draft, row) {
999
1159
  */
1000
1160
  export async function openSettingsWizard(host, root, opts = {}) {
1001
1161
  try {
1162
+ // R1 session scope: a fresh wizard session always warns at least once.
1163
+ // The keyed R4 window collapses repeats WITHIN one session only; reset
1164
+ // the boundary here so a prior session (same key, <30s ago) never leaks
1165
+ // into the new session. Never throws.
1166
+ try {
1167
+ advisorySeen.clear();
1168
+ } catch {
1169
+ /* dedup reset is best-effort */
1170
+ }
1002
1171
  const dialog = dialogOf(host);
1003
1172
  const baseDir = researchDirOf(host, root);
1004
1173
  const env = (opts && opts.env) || process.env;
@@ -1164,6 +1333,32 @@ export async function openSettingsWizard(host, root, opts = {}) {
1164
1333
  opts && (opts.expected_hash !== undefined || opts.expectedHash !== undefined)
1165
1334
  ? { expected_hash: opts.expected_hash, expectedHash: opts.expectedHash }
1166
1335
  : { expectedHash: startHash };
1336
+ // Phase 01-hook-bus-spec fix round (QA-B FAIL 1): the direct-fs
1337
+ // fallback is gated by `pre-commit` too — a tier-table deny blocks
1338
+ // the write (toast + `{ ok: false, reason: 'pre-commit-denied' }`,
1339
+ // nothing written, decision audit-logged on the bus). Bus faults
1340
+ // fail open; the stale-write guard below (`saveConfig` raising
1341
+ // `CONFIG_STALE`) is preserved unchanged.
1342
+ try {
1343
+ // Narrow fix round (qa-b bypass): resolve via the shared-bus
1344
+ // fallback so the shipped RPC-null path never gates on null.
1345
+ const fsBus = resolveHookBus(host, opts);
1346
+ const fsGate = await gateDirectFsPreCommit(fsBus, {
1347
+ draft,
1348
+ baseDir,
1349
+ guard,
1350
+ });
1351
+ if (fsGate && fsGate.allowed === false) {
1352
+ wizardToast(
1353
+ host,
1354
+ 'IUMBTEMS settings',
1355
+ 'Save denied by pre-commit hook — nothing was written.'
1356
+ );
1357
+ return { ok: false, reason: 'pre-commit-denied', written: false };
1358
+ }
1359
+ } catch {
1360
+ /* gate faults fail open; the write below still runs */
1361
+ }
1167
1362
  try {
1168
1363
  const res = saveConfig(draft, baseDir, guard);
1169
1364
  wizardToast(
@@ -1248,7 +1443,7 @@ export async function openSettingsPanel(host, root, opts = {}) {
1248
1443
  * without `command.transform` there is nothing to register (the palette
1249
1444
  * entries from the keymap layer still work). Never throws.
1250
1445
  */
1251
- export async function registerWizardSlashCommand(api) {
1446
+ export async function registerWizardSlashCommand(api, opts = {}) {
1252
1447
  try {
1253
1448
  if (typeof api?.command?.transform !== 'function') return null;
1254
1449
  let existing = new Set();
@@ -1270,7 +1465,13 @@ export async function registerWizardSlashCommand(api) {
1270
1465
  'Inspect or update Epistemic Swarm parameters in a dialog wizard (validates + writes atomically)',
1271
1466
  execute: async () => {
1272
1467
  try {
1273
- await openSettingsWizard(api, resolveRoot(api));
1468
+ // Narrow fix round (qa-b bypass): thread the bus on the shipped
1469
+ // path — explicit opt > adopted host bus > shared bus — so the
1470
+ // fallback gate never resolves null here.
1471
+ await openSettingsWizard(api, resolveRoot(api), {
1472
+ ...(opts || {}),
1473
+ hookBus: resolveHookBus(api, opts),
1474
+ });
1274
1475
  } catch {
1275
1476
  /* the wizard never throws into the host */
1276
1477
  }
@@ -1292,8 +1493,20 @@ export async function registerWizardSlashCommand(api) {
1292
1493
  }
1293
1494
 
1294
1495
  /** TUI setup: sidebar slot + palette command. Never throws. */
1295
- export function setupTui(api) {
1496
+ export function setupTui(api, opts = {}) {
1296
1497
  try {
1498
+ // Narrow fix round (qa-b bypass): adopt the shared bus onto the TUI api
1499
+ // so `opts.hookBus || host.iumbtemsHookBus` never resolves null on the
1500
+ // shipped path. Best-effort; never throws. The shared instance is the
1501
+ // same one the settings-RPC leg gates on.
1502
+ try {
1503
+ if (api && typeof api === 'object' && !(api.iumbtemsHookBus && typeof api.iumbtemsHookBus.emit === 'function')) {
1504
+ const bus = (opts && opts.hookBus) || sharedHookBus || null;
1505
+ if (bus && typeof bus.emit === 'function') api.iumbtemsHookBus = bus;
1506
+ }
1507
+ } catch {
1508
+ /* host attach is best-effort */
1509
+ }
1297
1510
  const root = resolveRoot(api);
1298
1511
  const offSidebar = registerSlot(api, 'sidebar.content', () =>
1299
1512
  SwarmSidebar(api, root)
@@ -1304,7 +1517,7 @@ export function setupTui(api) {
1304
1517
  // setup stays synchronous; the handle lands when the host resolves it.
1305
1518
  let slashRegistration = null;
1306
1519
  try {
1307
- Promise.resolve(registerWizardSlashCommand(api))
1520
+ Promise.resolve(registerWizardSlashCommand(api, opts))
1308
1521
  .then((reg) => {
1309
1522
  slashRegistration = reg;
1310
1523
  })
@@ -0,0 +1,16 @@
1
+ {
2
+ "$comment": "Phase 04-coverage-treeshake-signals bundle baseline: measured once at build (esbuild metafile:true, write:false — measure, never emit) over the plugin host surface. Deltas compute against `bytes`. Refresh is an explicit user-confirmed step — NEVER automatic (a stale baseline annotates `stale-baseline` and still allows).",
3
+ "entry": "plugins/opencode/index.js",
4
+ "bytes": 182262,
5
+ "files": 6,
6
+ "topHeaviest": [
7
+ { "file": "plugins/opencode/index.js", "bytes": 119966 },
8
+ { "file": "plugins/opencode/config-io.js", "bytes": 60042 },
9
+ { "file": "plugins/opencode/dep-health.js", "bytes": 40199 },
10
+ { "file": "plugins/opencode/weight-signals.js", "bytes": 29350 },
11
+ { "file": "plugins/opencode/analysis-bridge.js", "bytes": 20953 }
12
+ ],
13
+ "measuredAt": "2026-10-03T00:00:00.000Z",
14
+ "measuredBy": "esbuild 0.28.2 (pinned devDep; bundle:true, metafile:true, write:false, platform:node, format:esm, external:[esbuild])",
15
+ "phase": "04-coverage-treeshake-signals"
16
+ }