instar 0.28.51 → 0.28.53

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 (141) hide show
  1. package/dashboard/index.html +320 -11
  2. package/dist/cli.js +51 -2
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/backup.js +3 -3
  5. package/dist/commands/backup.js.map +1 -1
  6. package/dist/commands/gate.d.ts +33 -0
  7. package/dist/commands/gate.d.ts.map +1 -0
  8. package/dist/commands/gate.js +171 -0
  9. package/dist/commands/gate.js.map +1 -0
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +100 -2
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/migrate.d.ts +10 -0
  14. package/dist/commands/migrate.d.ts.map +1 -1
  15. package/dist/commands/migrate.js +71 -0
  16. package/dist/commands/migrate.js.map +1 -1
  17. package/dist/commands/server.d.ts.map +1 -1
  18. package/dist/commands/server.js +71 -18
  19. package/dist/commands/server.js.map +1 -1
  20. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  21. package/dist/config/ConfigDefaults.js +12 -0
  22. package/dist/config/ConfigDefaults.js.map +1 -1
  23. package/dist/core/BackupManager.d.ts.map +1 -1
  24. package/dist/core/BackupManager.js +34 -1
  25. package/dist/core/BackupManager.js.map +1 -1
  26. package/dist/core/CommitmentSweeper.d.ts +101 -0
  27. package/dist/core/CommitmentSweeper.d.ts.map +1 -0
  28. package/dist/core/CommitmentSweeper.js +222 -0
  29. package/dist/core/CommitmentSweeper.js.map +1 -0
  30. package/dist/core/LedgerSessionRegistry.d.ts +225 -0
  31. package/dist/core/LedgerSessionRegistry.d.ts.map +1 -0
  32. package/dist/core/LedgerSessionRegistry.js +667 -0
  33. package/dist/core/LedgerSessionRegistry.js.map +1 -0
  34. package/dist/core/MessageSentinel.d.ts +40 -0
  35. package/dist/core/MessageSentinel.d.ts.map +1 -1
  36. package/dist/core/MessageSentinel.js +80 -0
  37. package/dist/core/MessageSentinel.js.map +1 -1
  38. package/dist/core/PostUpdateMigrator.d.ts +81 -0
  39. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  40. package/dist/core/PostUpdateMigrator.js +436 -0
  41. package/dist/core/PostUpdateMigrator.js.map +1 -1
  42. package/dist/core/SessionManager.d.ts +29 -0
  43. package/dist/core/SessionManager.d.ts.map +1 -1
  44. package/dist/core/SessionManager.js +104 -1
  45. package/dist/core/SessionManager.js.map +1 -1
  46. package/dist/core/SharedStateLedger.d.ts.map +1 -1
  47. package/dist/core/SharedStateLedger.js +11 -0
  48. package/dist/core/SharedStateLedger.js.map +1 -1
  49. package/dist/core/StopGateDb.d.ts +97 -0
  50. package/dist/core/StopGateDb.d.ts.map +1 -0
  51. package/dist/core/StopGateDb.js +282 -0
  52. package/dist/core/StopGateDb.js.map +1 -0
  53. package/dist/core/UnjustifiedStopGate.d.ts +145 -0
  54. package/dist/core/UnjustifiedStopGate.d.ts.map +1 -0
  55. package/dist/core/UnjustifiedStopGate.js +325 -0
  56. package/dist/core/UnjustifiedStopGate.js.map +1 -0
  57. package/dist/core/WorktreeKeyVault.d.ts +50 -0
  58. package/dist/core/WorktreeKeyVault.d.ts.map +1 -0
  59. package/dist/core/WorktreeKeyVault.js +242 -0
  60. package/dist/core/WorktreeKeyVault.js.map +1 -0
  61. package/dist/core/WorktreeManager.d.ts +250 -0
  62. package/dist/core/WorktreeManager.d.ts.map +1 -0
  63. package/dist/core/WorktreeManager.js +833 -0
  64. package/dist/core/WorktreeManager.js.map +1 -0
  65. package/dist/core/types.d.ts +153 -3
  66. package/dist/core/types.d.ts.map +1 -1
  67. package/dist/data/pr-gate-artifacts.d.ts +38 -0
  68. package/dist/data/pr-gate-artifacts.d.ts.map +1 -0
  69. package/dist/data/pr-gate-artifacts.js +383 -0
  70. package/dist/data/pr-gate-artifacts.js.map +1 -0
  71. package/dist/messaging/shared/compactionResumePayload.d.ts +60 -0
  72. package/dist/messaging/shared/compactionResumePayload.d.ts.map +1 -0
  73. package/dist/messaging/shared/compactionResumePayload.js +88 -0
  74. package/dist/messaging/shared/compactionResumePayload.js.map +1 -0
  75. package/dist/monitoring/DegradationReporter.d.ts +15 -0
  76. package/dist/monitoring/DegradationReporter.d.ts.map +1 -1
  77. package/dist/monitoring/DegradationReporter.js +27 -0
  78. package/dist/monitoring/DegradationReporter.js.map +1 -1
  79. package/dist/monitoring/WorktreeReaper.d.ts +52 -0
  80. package/dist/monitoring/WorktreeReaper.d.ts.map +1 -0
  81. package/dist/monitoring/WorktreeReaper.js +199 -0
  82. package/dist/monitoring/WorktreeReaper.js.map +1 -0
  83. package/dist/scaffold/templates.d.ts.map +1 -1
  84. package/dist/scaffold/templates.js +8 -0
  85. package/dist/scaffold/templates.js.map +1 -1
  86. package/dist/server/AgentServer.d.ts +20 -0
  87. package/dist/server/AgentServer.d.ts.map +1 -1
  88. package/dist/server/AgentServer.js +25 -0
  89. package/dist/server/AgentServer.js.map +1 -1
  90. package/dist/server/middleware.d.ts.map +1 -1
  91. package/dist/server/middleware.js +20 -3
  92. package/dist/server/middleware.js.map +1 -1
  93. package/dist/server/routes.d.ts +12 -0
  94. package/dist/server/routes.d.ts.map +1 -1
  95. package/dist/server/routes.js +1277 -3
  96. package/dist/server/routes.js.map +1 -1
  97. package/dist/server/stopGate.d.ts +77 -0
  98. package/dist/server/stopGate.d.ts.map +1 -0
  99. package/dist/server/stopGate.js +161 -0
  100. package/dist/server/stopGate.js.map +1 -0
  101. package/dist/server/worktreeRoutes.d.ts +48 -0
  102. package/dist/server/worktreeRoutes.d.ts.map +1 -0
  103. package/dist/server/worktreeRoutes.js +247 -0
  104. package/dist/server/worktreeRoutes.js.map +1 -0
  105. package/package.json +1 -1
  106. package/scripts/destructive-command-shim.js +218 -0
  107. package/scripts/gh-ruleset-install.mjs +143 -0
  108. package/scripts/migrate-incident-2026-04-17.mjs +138 -0
  109. package/scripts/worktree-commit-msg-hook.js +168 -0
  110. package/scripts/worktree-precommit-gate.js +144 -0
  111. package/src/data/builtin-manifest.json +94 -94
  112. package/src/data/pr-gate-artifacts.ts +394 -0
  113. package/upgrades/0.28.51.md +10 -1
  114. package/upgrades/0.28.52.md +13 -61
  115. package/upgrades/0.28.53.md +70 -0
  116. package/upgrades/side-effects/NEXT.md +61 -0
  117. package/upgrades/side-effects/context-death-pr0a-server-infra.md +131 -0
  118. package/upgrades/side-effects/context-death-pr0b-sentinel-intent.md +130 -0
  119. package/upgrades/side-effects/context-death-pr0c-guardian-pulse-degradation-consumer.md +118 -0
  120. package/upgrades/side-effects/context-death-pr0d-e2e-compaction-harness.md +113 -0
  121. package/upgrades/side-effects/context-death-pr1-identity-text.md +110 -0
  122. package/upgrades/side-effects/context-death-pr2-e2e-compaction-recovery-test.md +82 -0
  123. package/upgrades/side-effects/context-death-pr3-gate-authority.md +193 -0
  124. package/upgrades/side-effects/context-death-pr4-gate-cli.md +91 -0
  125. package/upgrades/side-effects/integrated-being-ledger-v2-slice-1-foundation.md +192 -0
  126. package/upgrades/side-effects/integrated-being-ledger-v2-slice-2-session-bind.md +218 -0
  127. package/upgrades/side-effects/integrated-being-ledger-v2-slice-3-commitment-kind.md +105 -0
  128. package/upgrades/side-effects/integrated-being-ledger-v2-slice-4-resolve.md +98 -0
  129. package/upgrades/side-effects/integrated-being-ledger-v2-slice-5-sweepers.md +80 -0
  130. package/upgrades/side-effects/integrated-being-ledger-v2-slice-6-dashboard-rendering.md +67 -0
  131. package/upgrades/side-effects/integrated-being-ledger-v2-slice-7-bindings-revoke.md +111 -0
  132. package/upgrades/side-effects/parallel-dev-isolation.md +129 -0
  133. package/upgrades/side-effects/pr-gate-phase-a-commit-1-blocked-path-prefixes.md +135 -0
  134. package/upgrades/side-effects/pr-gate-phase-a-commit-2-backupconfig-plumbing.md +157 -0
  135. package/upgrades/side-effects/pr-gate-phase-a-commit-3-gitignore-pr-gate.md +130 -0
  136. package/upgrades/side-effects/pr-gate-phase-a-commit-4-pipeline-artifacts.md +157 -0
  137. package/upgrades/side-effects/pr-gate-phase-a-commit-5-backup-manifest.md +129 -0
  138. package/upgrades/side-effects/pr-gate-phase-a-commit-6-rollback-skill.md +116 -0
  139. package/upgrades/side-effects/pr-gate-phase-a-commit-7-dashboard-tab.md +124 -0
  140. package/upgrades/side-effects/pr-gate-phase-a-commit-8-phase-off-kill-switch.md +151 -0
  141. package/upgrades/side-effects/pr-gate-phase-a-spec-and-reports-docs-only.md +34 -0
@@ -2414,6 +2414,7 @@
2414
2414
  <button class="tab" data-tab="features" onclick="switchTab('features')">Features</button>
2415
2415
  <button class="tab" data-tab="systems" onclick="switchTab('systems')">Health</button>
2416
2416
  <button class="tab" data-tab="integrated-being" onclick="switchTab('integrated-being')">Integrated-Being</button>
2417
+ <button class="tab" data-tab="pr-pipeline" onclick="switchTab('pr-pipeline')">PR Pipeline</button>
2417
2418
  </nav>
2418
2419
  </div>
2419
2420
  <div class="vital-signs" id="vitalSigns">
@@ -2731,6 +2732,51 @@
2731
2732
  </div>
2732
2733
  <pre id="ibChainResult" style="margin-top:10px;max-height:200px;overflow:auto;font-size:11px"></pre>
2733
2734
  </div>
2735
+
2736
+ <!-- Bindings subtab (slice 7) -->
2737
+ <div id="ibBindingsSection" style="background:var(--panel);border-radius:8px;border:1px solid var(--border);padding:12px;display:none">
2738
+ <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
2739
+ <div style="font-size:13px;font-weight:600">Session bindings (v2)</div>
2740
+ <button onclick="loadIntegratedBeingBindings()" style="padding:4px 10px;font-size:11px">Refresh</button>
2741
+ </div>
2742
+ <div id="ibBindingsNotice" style="font-size:11px;color:var(--text-dim);margin-bottom:8px">
2743
+ Each registered session has a binding token. Revoke to invalidate it — subsequent writes from that session fail 401. Revocation is logged as a subsystem-asserted note.
2744
+ </div>
2745
+ <table style="width:100%;border-collapse:collapse;font-size:11px">
2746
+ <thead>
2747
+ <tr style="background:var(--bg-alt)">
2748
+ <th style="text-align:left;padding:6px">Session ID</th>
2749
+ <th style="text-align:left;padding:6px">Label</th>
2750
+ <th style="text-align:left;padding:6px">Registered</th>
2751
+ <th style="text-align:left;padding:6px">Last active</th>
2752
+ <th style="text-align:left;padding:6px">Absolute TTL</th>
2753
+ <th style="text-align:left;padding:6px">Status</th>
2754
+ <th style="text-align:left;padding:6px">Action</th>
2755
+ </tr>
2756
+ </thead>
2757
+ <tbody id="ibBindingsTbody">
2758
+ <tr><td colspan="7" style="padding:14px;color:var(--text-dim);text-align:center">Loading…</td></tr>
2759
+ </tbody>
2760
+ </table>
2761
+ </div>
2762
+ </div>
2763
+
2764
+ <!-- PR Pipeline Tab (PR-REVIEW-HARDENING-SPEC Phase A) -->
2765
+ <!-- Read-only: NO action buttons that mutate eligibility. All PR-authored -->
2766
+ <!-- content rendered via textContent, never innerHTML — XSS defense rule -->
2767
+ <!-- for the dashboard per spec §"Dashboard surface rules". -->
2768
+ <div id="prPipelinePanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
2769
+ <div style="display:flex;justify-content:space-between;align-items:center">
2770
+ <h2 style="margin:0">PR Pipeline</h2>
2771
+ <div>
2772
+ <button onclick="loadPrPipeline()" style="padding:6px 12px">Refresh</button>
2773
+ </div>
2774
+ </div>
2775
+ <div id="prPipelinePhaseNotice" style="font-size:12px;color:var(--text-dim)"></div>
2776
+ <div id="prPipelineEmpty" style="padding:40px;text-align:center;color:var(--text-dim);display:none">
2777
+ No PR activity to display.
2778
+ </div>
2779
+ <div id="prPipelineList" style="display:flex;flex-direction:column;gap:12px"></div>
2734
2780
  </div>
2735
2781
 
2736
2782
  <!-- Health Tab (was Systems) -->
@@ -3754,9 +3800,15 @@
3754
3800
  id: 'integrated-being',
3755
3801
  panels: ['integratedBeingPanel'],
3756
3802
  display: ['flex'],
3757
- onActivate: () => { if (typeof loadIntegratedBeing === 'function') loadIntegratedBeing(); if (typeof startIntegratedBeingPoll === 'function') startIntegratedBeingPoll(); },
3803
+ onActivate: () => { if (typeof loadIntegratedBeing === 'function') loadIntegratedBeing(); if (typeof loadIntegratedBeingBindings === 'function') loadIntegratedBeingBindings(); if (typeof startIntegratedBeingPoll === 'function') startIntegratedBeingPoll(); },
3758
3804
  onDeactivate: () => { if (typeof stopIntegratedBeingPoll === 'function') stopIntegratedBeingPoll(); },
3759
3805
  },
3806
+ {
3807
+ id: 'pr-pipeline',
3808
+ panels: ['prPipelinePanel'],
3809
+ display: ['flex'],
3810
+ onActivate: () => { if (typeof loadPrPipeline === 'function') loadPrPipeline(); },
3811
+ },
3760
3812
  ];
3761
3813
 
3762
3814
  function switchTab(tabName) {
@@ -5545,6 +5597,108 @@
5545
5597
  }
5546
5598
  }
5547
5599
 
5600
+ // ── PR Pipeline Tab (PR-REVIEW-HARDENING-SPEC Phase A) ───────
5601
+ //
5602
+ // Fetches /pr-gate/metrics on panel activation and renders the
5603
+ // current pipeline state. Hard rules (enforced structurally here):
5604
+ // - NO action buttons that mutate eligibility (read-only tab).
5605
+ // - NO innerHTML — all PR content goes through textContent.
5606
+ // - NO form submissions to /pr-gate/* endpoints.
5607
+ // - On 404 (prGate.phase='off' — commit 8), render a disabled
5608
+ // placeholder rather than erroring.
5609
+ async function loadPrPipeline() {
5610
+ const list = document.getElementById('prPipelineList');
5611
+ const notice = document.getElementById('prPipelinePhaseNotice');
5612
+ const empty = document.getElementById('prPipelineEmpty');
5613
+ // Reset view via DOM mutation, not innerHTML, to keep the
5614
+ // "never inject content as HTML" invariant trivially true.
5615
+ while (list.firstChild) list.removeChild(list.firstChild);
5616
+ notice.textContent = 'Loading…';
5617
+ empty.style.display = 'none';
5618
+
5619
+ // Use raw fetch (not apiFetch) so we can distinguish 404 (gate
5620
+ // disabled, expected during phase=off) from other errors without
5621
+ // apiFetch's throw-on-non-ok behavior.
5622
+ let res = null;
5623
+ let httpStatus = 0;
5624
+ try {
5625
+ const raw = await fetch('/pr-gate/metrics', {
5626
+ headers: { 'Authorization': `Bearer ${token}` },
5627
+ });
5628
+ httpStatus = raw.status;
5629
+ if (raw.ok) res = await raw.json();
5630
+ } catch {
5631
+ res = null;
5632
+ }
5633
+
5634
+ if (httpStatus === 404 || (res && res.disabled === true)) {
5635
+ notice.textContent = 'Gate disabled (phase=off). No pipeline activity recorded yet.';
5636
+ empty.style.display = 'block';
5637
+ return;
5638
+ }
5639
+ if (!res) {
5640
+ notice.textContent = 'PR Pipeline metrics unavailable. Endpoint did not respond.';
5641
+ empty.style.display = 'block';
5642
+ return;
5643
+ }
5644
+
5645
+ const phase = typeof res.phase === 'string' ? res.phase : 'unknown';
5646
+ notice.textContent = `Phase: ${phase}`;
5647
+
5648
+ const entries = Array.isArray(res.entries) ? res.entries : [];
5649
+ if (entries.length === 0) {
5650
+ empty.style.display = 'block';
5651
+ return;
5652
+ }
5653
+
5654
+ for (const entry of entries) {
5655
+ const card = document.createElement('div');
5656
+ card.style.background = 'var(--panel)';
5657
+ card.style.padding = '12px';
5658
+ card.style.borderRadius = '6px';
5659
+ card.style.border = '1px solid var(--border)';
5660
+
5661
+ const header = document.createElement('div');
5662
+ header.style.display = 'flex';
5663
+ header.style.justifyContent = 'space-between';
5664
+ header.style.marginBottom = '6px';
5665
+
5666
+ const left = document.createElement('div');
5667
+ left.style.fontWeight = '600';
5668
+ const pr = typeof entry.pr_number === 'number' ? `PR #${entry.pr_number}` : 'PR #—';
5669
+ const sha = typeof entry.head_sha === 'string' ? ` @ ${entry.head_sha.slice(0, 8)}` : '';
5670
+ left.textContent = pr + sha;
5671
+
5672
+ const right = document.createElement('div');
5673
+ right.style.fontSize = '12px';
5674
+ right.style.color = entry.eligible ? 'var(--accent)' : 'var(--text-dim)';
5675
+ right.textContent = entry.eligible ? 'eligible' : 'not eligible';
5676
+
5677
+ header.appendChild(left);
5678
+ header.appendChild(right);
5679
+ card.appendChild(header);
5680
+
5681
+ if (typeof entry.reason === 'string' && entry.reason.length > 0) {
5682
+ const reason = document.createElement('div');
5683
+ reason.style.fontSize = '13px';
5684
+ reason.style.color = 'var(--text-dim)';
5685
+ reason.textContent = entry.reason;
5686
+ card.appendChild(reason);
5687
+ }
5688
+
5689
+ if (typeof entry.created_at === 'string') {
5690
+ const time = document.createElement('div');
5691
+ time.style.fontSize = '11px';
5692
+ time.style.color = 'var(--text-dim)';
5693
+ time.style.marginTop = '4px';
5694
+ time.textContent = entry.created_at;
5695
+ card.appendChild(time);
5696
+ }
5697
+
5698
+ list.appendChild(card);
5699
+ }
5700
+ }
5701
+
5548
5702
  // ── Integrated-Being Tab (v1) ────────────────────────────────
5549
5703
  let integratedBeingPollTimer = null;
5550
5704
 
@@ -5568,16 +5722,98 @@
5568
5722
  if (entries.length === 0) {
5569
5723
  tbody.innerHTML = '<tr><td colspan="7" style="padding:14px;color:var(--text-dim);text-align:center">No entries</td></tr>';
5570
5724
  } else {
5571
- tbody.innerHTML = entries.map(e => `
5725
+ // Build a map of commitmentId → effective-status derivation
5726
+ // from supersession pointers: if a later note with subject
5727
+ // starting "expired:" or "stranded:" or "resolved:"/"cancelled:"
5728
+ // points back at a commitment, that commitment's effective
5729
+ // status flips to the supersession. (Slice 5 + slice 6.)
5730
+ const supersessionByTarget = new Map();
5731
+ for (const ent of entries) {
5732
+ if (ent.supersedes) {
5733
+ const existing = supersessionByTarget.get(ent.supersedes);
5734
+ // Prefer most-recent note with a lifecycle prefix.
5735
+ if (!existing || new Date(ent.t) > new Date(existing.t)) {
5736
+ supersessionByTarget.set(ent.supersedes, ent);
5737
+ }
5738
+ }
5739
+ }
5740
+ const disputeCountByTarget = new Map();
5741
+ for (const ent of entries) {
5742
+ if (ent.disputes) {
5743
+ disputeCountByTarget.set(
5744
+ ent.disputes,
5745
+ (disputeCountByTarget.get(ent.disputes) || 0) + 1,
5746
+ );
5747
+ }
5748
+ }
5749
+ const esc2 = (s) => esc(String(s == null ? '' : s));
5750
+ const badge = (label, bg, fg) =>
5751
+ `<span style="display:inline-block;padding:1px 6px;font-size:10px;border-radius:3px;background:${bg};color:${fg};margin-right:4px">${esc2(label)}</span>`;
5752
+ tbody.innerHTML = entries.map(e => {
5753
+ const ts = (e.t || '').slice(0, 19).replace('T', ' ');
5754
+ let subjectCell = esc2(e.subject);
5755
+ let kindCell = esc2(e.kind);
5756
+ // Commitment-kind enrichment (slice 6): badges for mechanism
5757
+ // type, deadline w/ overdue highlight, refStatus, and any
5758
+ // derived effective-status from the supersession chain.
5759
+ if (e.kind === 'commitment' && e.commitment) {
5760
+ const mech = e.commitment.mechanism || {};
5761
+ const mechBg = mech.type === 'passive-wait' ? '#d4a017' : '#4a6a8a';
5762
+ kindCell = badge('commitment', '#6a4a8a', '#fff')
5763
+ + badge(mech.type || '(?)', mechBg, '#fff');
5764
+ if (mech.refStatus && mech.refStatus !== 'valid') {
5765
+ kindCell += badge('ref:' + mech.refStatus, '#888', '#fff');
5766
+ }
5767
+ if (e.commitment.deadline) {
5768
+ const dlMs = Date.parse(e.commitment.deadline);
5769
+ const overdue = !Number.isNaN(dlMs) && dlMs < Date.now();
5770
+ const dlLabel = (e.commitment.deadline || '').slice(0, 16).replace('T', ' ');
5771
+ subjectCell += '<div style="font-size:11px;color:'
5772
+ + (overdue ? '#e74c3c' : 'var(--text-dim)')
5773
+ + ';margin-top:2px">'
5774
+ + (overdue ? '⚠ overdue ' : 'due ')
5775
+ + esc2(dlLabel)
5776
+ + '</div>';
5777
+ }
5778
+ // Effective status from supersession chain.
5779
+ const superseder = supersessionByTarget.get(e.id);
5780
+ if (superseder && typeof superseder.subject === 'string') {
5781
+ const s = superseder.subject;
5782
+ let statusLabel = 'resolved';
5783
+ let statusBg = '#27ae60';
5784
+ if (s.startsWith('expired:')) { statusLabel = 'expired'; statusBg = '#e74c3c'; }
5785
+ else if (s.startsWith('stranded:')) { statusLabel = 'stranded'; statusBg = '#e67e22'; }
5786
+ else if (s.startsWith('cancelled:')) { statusLabel = 'cancelled'; statusBg = '#7f8c8d'; }
5787
+ subjectCell = badge(statusLabel, statusBg, '#fff') + subjectCell;
5788
+ }
5789
+ const disputes = disputeCountByTarget.get(e.id) || 0;
5790
+ if (disputes > 0) {
5791
+ subjectCell = badge('disputed × ' + disputes, '#c0392b', '#fff') + subjectCell;
5792
+ }
5793
+ } else if (e.kind === 'note' && e.disputes) {
5794
+ // Dispute note — highlight the disputes-pointer.
5795
+ kindCell = badge('note', '#444', '#fff') + badge('dispute', '#c0392b', '#fff');
5796
+ } else if (e.kind === 'note' && typeof e.subject === 'string' && e.subject.startsWith('expired:')) {
5797
+ kindCell = badge('note', '#444', '#fff') + badge('expired', '#e74c3c', '#fff');
5798
+ } else if (e.kind === 'note' && typeof e.subject === 'string' && e.subject.startsWith('stranded:')) {
5799
+ kindCell = badge('note', '#444', '#fff') + badge('stranded', '#e67e22', '#fff');
5800
+ }
5801
+ const ptr = e.supersedes
5802
+ ? 'supersedes:' + e.supersedes
5803
+ : e.disputes
5804
+ ? 'disputes:' + e.disputes
5805
+ : '';
5806
+ return `
5572
5807
  <tr style="border-top:1px solid var(--border)">
5573
- <td style="padding:6px;font-family:monospace">${esc(e.id)}</td>
5574
- <td style="padding:6px">${esc((e.t || '').slice(0, 19).replace('T', ' '))}</td>
5575
- <td style="padding:6px">${esc(e.kind)}</td>
5576
- <td style="padding:6px">${esc(e.subject)}</td>
5577
- <td style="padding:6px">${esc(e.counterparty?.type)}/${esc(e.counterparty?.name)}</td>
5578
- <td style="padding:6px;font-size:11px;color:var(--text-dim)">${esc(e.provenance)}${e.source ? ' · '+esc(e.source):''}</td>
5579
- <td style="padding:6px;font-family:monospace;font-size:11px">${esc(e.supersedes || '')}</td>
5580
- </tr>`).join('');
5808
+ <td style="padding:6px;font-family:monospace">${esc2(e.id)}</td>
5809
+ <td style="padding:6px">${esc2(ts)}</td>
5810
+ <td style="padding:6px">${kindCell}</td>
5811
+ <td style="padding:6px">${subjectCell}</td>
5812
+ <td style="padding:6px">${esc2(e.counterparty?.type)}/${esc2(e.counterparty?.name)}</td>
5813
+ <td style="padding:6px;font-size:11px;color:var(--text-dim)">${esc2(e.provenance)}${e.source ? ' · '+esc2(e.source):''}</td>
5814
+ <td style="padding:6px;font-family:monospace;font-size:11px">${esc2(ptr)}</td>
5815
+ </tr>`;
5816
+ }).join('');
5581
5817
  }
5582
5818
  }
5583
5819
  if (statsRes) {
@@ -5599,9 +5835,82 @@
5599
5835
  }
5600
5836
  }
5601
5837
 
5838
+ async function loadIntegratedBeingBindings() {
5839
+ // GET /shared-state/sessions returns 503 when v2Enabled=false.
5840
+ // In that case, keep the Bindings section hidden.
5841
+ const section = document.getElementById('ibBindingsSection');
5842
+ const tbody = document.getElementById('ibBindingsTbody');
5843
+ try {
5844
+ const res = await apiFetch('/shared-state/sessions').catch(() => null);
5845
+ if (!res || !Array.isArray(res.sessions)) {
5846
+ if (section) section.style.display = 'none';
5847
+ return;
5848
+ }
5849
+ if (section) section.style.display = 'block';
5850
+ if (res.sessions.length === 0) {
5851
+ tbody.innerHTML = '<tr><td colspan="7" style="padding:14px;color:var(--text-dim);text-align:center">No sessions registered</td></tr>';
5852
+ return;
5853
+ }
5854
+ const nowMs = Date.now();
5855
+ const esc2 = (s) => esc(String(s == null ? '' : s));
5856
+ tbody.innerHTML = res.sessions.map(s => {
5857
+ const ttlMs = Date.parse(s.absoluteExpiresAt || '') - nowMs;
5858
+ const ttlLabel = Number.isFinite(ttlMs)
5859
+ ? (ttlMs < 0 ? 'expired' : Math.round(ttlMs / 3600000) + 'h')
5860
+ : '';
5861
+ const status = s.revoked
5862
+ ? '<span style="color:#e74c3c">revoked</span>'
5863
+ : (Date.parse(s.absoluteExpiresAt || '') < nowMs
5864
+ ? '<span style="color:#888">expired</span>'
5865
+ : (s.hasWritten ? '<span style="color:#27ae60">active</span>' : '<span style="color:var(--text-dim)">bound</span>'));
5866
+ const idShort = (s.sessionId || '').slice(0, 8) + '…';
5867
+ const action = s.revoked
5868
+ ? '<span style="color:var(--text-dim);font-size:10px">—</span>'
5869
+ : `<button onclick="revokeIntegratedBeingBinding('${esc2(s.sessionId)}')" style="padding:2px 8px;font-size:10px;background:#c0392b;color:#fff;border:none;border-radius:3px;cursor:pointer">Revoke</button>`;
5870
+ return `
5871
+ <tr style="border-top:1px solid var(--border)">
5872
+ <td style="padding:4px;font-family:monospace" title="${esc2(s.sessionId)}">${esc2(idShort)}</td>
5873
+ <td style="padding:4px">${esc2(s.label || '')}</td>
5874
+ <td style="padding:4px">${esc2((s.registeredAt || '').slice(0, 16).replace('T', ' '))}</td>
5875
+ <td style="padding:4px">${esc2((s.lastActiveAt || '').slice(0, 16).replace('T', ' '))}</td>
5876
+ <td style="padding:4px">${esc2(ttlLabel)}</td>
5877
+ <td style="padding:4px">${status}</td>
5878
+ <td style="padding:4px">${action}</td>
5879
+ </tr>`;
5880
+ }).join('');
5881
+ } catch (err) {
5882
+ console.error('[integrated-being-v2] bindings load error', err);
5883
+ if (section) section.style.display = 'none';
5884
+ }
5885
+ }
5886
+
5887
+ async function revokeIntegratedBeingBinding(sid) {
5888
+ if (!sid) return;
5889
+ if (!confirm('Revoke the binding for session ' + sid.slice(0, 8) + '…?\n\nThis invalidates the session token. Subsequent writes from that session will fail 401.')) return;
5890
+ try {
5891
+ const res = await apiFetch(`/shared-state/sessions/${encodeURIComponent(sid)}/revoke`, {
5892
+ method: 'POST',
5893
+ headers: { 'X-Instar-Request': '1' },
5894
+ });
5895
+ if (res && res.revoked) {
5896
+ await loadIntegratedBeingBindings();
5897
+ await loadIntegratedBeing();
5898
+ } else {
5899
+ alert('Revocation did not succeed — see console for details.');
5900
+ console.warn('[integrated-being-v2] revoke unexpected', res);
5901
+ }
5902
+ } catch (err) {
5903
+ console.error('[integrated-being-v2] revoke error', err);
5904
+ alert('Revoke failed: ' + (err && err.message ? err.message : err));
5905
+ }
5906
+ }
5907
+
5602
5908
  function startIntegratedBeingPoll() {
5603
5909
  if (integratedBeingPollTimer) return;
5604
- integratedBeingPollTimer = setInterval(loadIntegratedBeing, 30000);
5910
+ integratedBeingPollTimer = setInterval(() => {
5911
+ loadIntegratedBeing();
5912
+ loadIntegratedBeingBindings();
5913
+ }, 30000);
5605
5914
  }
5606
5915
  function stopIntegratedBeingPoll() {
5607
5916
  if (integratedBeingPollTimer) {
package/dist/cli.js CHANGED
@@ -1557,13 +1557,22 @@ const migrateCmd = program
1557
1557
  // /shared-state/render injection after an update.
1558
1558
  migrateCmd
1559
1559
  .command('sync-session-hook')
1560
- .description('Sync .claude/hooks/instar/session-start.sh with the latest template (Integrated-Being v1)')
1560
+ .description('Sync .claude/hooks/instar/session-start.sh with the latest template (Integrated-Being v1 + v2)')
1561
1561
  .option('-d, --dir <path>', 'Project directory')
1562
1562
  .option('--force', 'Overwrite a divergent local hook')
1563
+ .option('--v2-mode <mode>', 'v2 migration mode: "inject" (update only v2 section, preserve customizations) or "overwrite" (replace entire hook, save backup)')
1563
1564
  .action(async (opts) => {
1564
1565
  try {
1566
+ if (opts.v2Mode && opts.v2Mode !== 'inject' && opts.v2Mode !== 'overwrite') {
1567
+ console.error(JSON.stringify({ error: '--v2-mode must be "inject" or "overwrite"' }));
1568
+ process.exit(2);
1569
+ }
1565
1570
  const { syncSessionHook } = await import('./commands/migrate.js');
1566
- const result = await syncSessionHook(opts);
1571
+ const result = await syncSessionHook({
1572
+ dir: opts.dir,
1573
+ force: opts.force,
1574
+ v2Mode: opts.v2Mode,
1575
+ });
1567
1576
  console.log(JSON.stringify(result));
1568
1577
  }
1569
1578
  catch (err) {
@@ -1898,5 +1907,45 @@ playbookCmd
1898
1907
  const { playbookUserAudit } = await import('./commands/playbook.js');
1899
1908
  return playbookUserAudit(userId, opts);
1900
1909
  });
1910
+ // ── `instar gate` — UnjustifiedStopGate operator tooling (PR4) ──────
1911
+ const gateCmd = program
1912
+ .command('gate')
1913
+ .description('Operator tooling for the UnjustifiedStopGate (context-death-pitfall-prevention)');
1914
+ gateCmd
1915
+ .command('status')
1916
+ .description('Show gate mode, kill-switch state, autonomous flag')
1917
+ .option('-d, --dir <path>', 'Project directory')
1918
+ .action(async (opts) => {
1919
+ const { gateStatus } = await import('./commands/gate.js');
1920
+ return gateStatus(opts);
1921
+ });
1922
+ gateCmd
1923
+ .command('set <subject>')
1924
+ .description('Set a gate mode. Subjects: unjustified-stop')
1925
+ .requiredOption('--mode <mode>', 'off | shadow | enforce')
1926
+ .option('-d, --dir <path>', 'Project directory')
1927
+ .action(async (subject, opts) => {
1928
+ const { gateSet } = await import('./commands/gate.js');
1929
+ return gateSet(subject, opts);
1930
+ });
1931
+ gateCmd
1932
+ .command('kill-switch')
1933
+ .description('Set or clear the gate kill-switch (fast-path allow-everything override)')
1934
+ .option('--set', 'Set the kill-switch (all evaluations short-circuit to allow)')
1935
+ .option('--clear', 'Clear the kill-switch (restore normal evaluation)')
1936
+ .option('-d, --dir <path>', 'Project directory')
1937
+ .action(async (opts) => {
1938
+ const { gateKillSwitch } = await import('./commands/gate.js');
1939
+ return gateKillSwitch(opts);
1940
+ });
1941
+ gateCmd
1942
+ .command('log')
1943
+ .description('Show the most recent gate evaluation events')
1944
+ .option('--tail <n>', 'Number of events to show (default 20)')
1945
+ .option('-d, --dir <path>', 'Project directory')
1946
+ .action(async (opts) => {
1947
+ const { gateLog } = await import('./commands/gate.js');
1948
+ return gateLog(opts);
1949
+ });
1901
1950
  program.parse();
1902
1951
  //# sourceMappingURL=cli.js.map