instar 0.28.95 → 0.28.96

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 (49) hide show
  1. package/dashboard/index.html +249 -2
  2. package/dist/commands/server.d.ts.map +1 -1
  3. package/dist/commands/server.js +90 -1
  4. package/dist/commands/server.js.map +1 -1
  5. package/dist/core/MachineHeartbeat.d.ts +94 -0
  6. package/dist/core/MachineHeartbeat.d.ts.map +1 -0
  7. package/dist/core/MachineHeartbeat.js +155 -0
  8. package/dist/core/MachineHeartbeat.js.map +1 -0
  9. package/dist/core/ProjectAutoAdvancePoller.d.ts +107 -0
  10. package/dist/core/ProjectAutoAdvancePoller.d.ts.map +1 -0
  11. package/dist/core/ProjectAutoAdvancePoller.js +176 -0
  12. package/dist/core/ProjectAutoAdvancePoller.js.map +1 -0
  13. package/dist/core/ProjectRoundCompleteMessage.d.ts +158 -0
  14. package/dist/core/ProjectRoundCompleteMessage.d.ts.map +1 -0
  15. package/dist/core/ProjectRoundCompleteMessage.js +253 -0
  16. package/dist/core/ProjectRoundCompleteMessage.js.map +1 -0
  17. package/dist/core/ProjectRoundExecution.d.ts +111 -0
  18. package/dist/core/ProjectRoundExecution.d.ts.map +1 -0
  19. package/dist/core/ProjectRoundExecution.js +359 -0
  20. package/dist/core/ProjectRoundExecution.js.map +1 -0
  21. package/dist/core/ProjectRoundLock.d.ts +67 -0
  22. package/dist/core/ProjectRoundLock.d.ts.map +1 -0
  23. package/dist/core/ProjectRoundLock.js +161 -0
  24. package/dist/core/ProjectRoundLock.js.map +1 -0
  25. package/dist/core/ProjectRoundRunner.d.ts +146 -0
  26. package/dist/core/ProjectRoundRunner.d.ts.map +1 -0
  27. package/dist/core/ProjectRoundRunner.js +371 -0
  28. package/dist/core/ProjectRoundRunner.js.map +1 -0
  29. package/dist/core/ProjectRoundWorktrees.d.ts +57 -0
  30. package/dist/core/ProjectRoundWorktrees.d.ts.map +1 -0
  31. package/dist/core/ProjectRoundWorktrees.js +111 -0
  32. package/dist/core/ProjectRoundWorktrees.js.map +1 -0
  33. package/dist/server/AgentServer.d.ts +12 -0
  34. package/dist/server/AgentServer.d.ts.map +1 -1
  35. package/dist/server/AgentServer.js +3 -0
  36. package/dist/server/AgentServer.js.map +1 -1
  37. package/dist/server/routes.d.ts +19 -0
  38. package/dist/server/routes.d.ts.map +1 -1
  39. package/dist/server/routes.js +590 -7
  40. package/dist/server/routes.js.map +1 -1
  41. package/package.json +1 -1
  42. package/src/data/builtin-manifest.json +47 -47
  43. package/upgrades/0.28.96.md +347 -0
  44. package/upgrades/side-effects/project-scope-phase1b-connect-the-dots.md +184 -0
  45. package/upgrades/side-effects/project-scope-phase1b-pr3.md +228 -0
  46. package/upgrades/side-effects/project-scope-phase1b-pr4.md +198 -0
  47. package/upgrades/side-effects/project-scope-phase1b-pr5.md +150 -0
  48. package/upgrades/side-effects/project-scope-phase1b-pr6.md +136 -0
  49. package/upgrades/side-effects/project-scope-phase1b-pr7.md +175 -0
@@ -2523,6 +2523,7 @@
2523
2523
  <button class="tab" data-tab="systems" onclick="switchTab('systems')">Health</button>
2524
2524
  <button class="tab" data-tab="integrated-being" onclick="switchTab('integrated-being')">Integrated-Being</button>
2525
2525
  <button class="tab" data-tab="pr-pipeline" onclick="switchTab('pr-pipeline')">PR Pipeline</button>
2526
+ <button class="tab" data-tab="projects" onclick="switchTab('projects')">Projects <span class="tab-count" id="tabProjectCount">0</span></button>
2526
2527
  <button class="tab" data-tab="initiatives" onclick="switchTab('initiatives')">Initiatives <span class="tab-count" id="tabInitiativeCount">0</span></button>
2527
2528
  <button class="tab" data-tab="commitments" onclick="switchTab('commitments')">Commitments <span class="tab-count" id="tabCommitmentCount">0</span></button>
2528
2529
  <button class="tab" data-tab="tokens" onclick="switchTab('tokens')">Tokens</button>
@@ -2892,6 +2893,21 @@
2892
2893
  <div id="prPipelineList" style="display:flex;flex-direction:column;gap:12px"></div>
2893
2894
  </div>
2894
2895
 
2896
+ <!-- Projects Tab — PROJECT-SCOPE-SPEC Phase 1.10 read-only view -->
2897
+ <div id="projectsPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
2898
+ <div style="display:flex;justify-content:space-between;align-items:center">
2899
+ <h2 style="margin:0">Projects</h2>
2900
+ <button onclick="loadProjects()" style="padding:6px 12px">Refresh</button>
2901
+ </div>
2902
+ <div style="font-size:12px;color:var(--text-dim);line-height:1.4">
2903
+ Multi-spec project plans. Each project bundles related child initiatives across rounds with structurally-gated round advance.
2904
+ </div>
2905
+ <div id="projectsEmpty" style="padding:40px;text-align:center;color:var(--text-dim);display:none">
2906
+ No active projects. Create one via <code>POST /projects</code> with a plan-doc path.
2907
+ </div>
2908
+ <div id="projectsList" style="display:flex;flex-direction:column;gap:12px"></div>
2909
+ </div>
2910
+
2895
2911
  <!-- Initiatives Tab — multi-phase long-running work -->
2896
2912
  <div id="initiativesPanel" style="display:none;flex-direction:column;padding:20px;gap:16px;overflow-y:auto">
2897
2913
  <div style="display:flex;justify-content:space-between;align-items:center">
@@ -2904,6 +2920,9 @@
2904
2920
  <option value="archived">Archived</option>
2905
2921
  <option value="abandoned">Abandoned</option>
2906
2922
  </select>
2923
+ <label style="font-size:12px;color:var(--text-dim);display:flex;align-items:center;gap:4px">
2924
+ <input type="checkbox" id="initiativesShowAll" onchange="loadInitiatives()"> Show projects + children
2925
+ </label>
2907
2926
  <button onclick="loadInitiatives()" style="padding:6px 12px">Refresh</button>
2908
2927
  </div>
2909
2928
  </div>
@@ -4185,6 +4204,13 @@
4185
4204
  display: ['flex'],
4186
4205
  onActivate: () => { if (typeof loadPrPipeline === 'function') loadPrPipeline(); },
4187
4206
  },
4207
+ {
4208
+ id: 'projects',
4209
+ panels: ['projectsPanel'],
4210
+ display: ['flex'],
4211
+ onActivate: () => { if (typeof loadProjects === 'function') loadProjects(); if (typeof startProjectsPoll === 'function') startProjectsPoll(); },
4212
+ onDeactivate: () => { if (typeof stopProjectsPoll === 'function') stopProjectsPoll(); },
4213
+ },
4188
4214
  {
4189
4215
  id: 'initiatives',
4190
4216
  panels: ['initiativesPanel'],
@@ -6682,6 +6708,216 @@
6682
6708
  }
6683
6709
  }
6684
6710
 
6711
+ // ── Projects Tab (PROJECT-SCOPE-SPEC Phase 1.10) ─────────────
6712
+ //
6713
+ // Read-only project view. Per spec: progress bar per round +
6714
+ // pipelineStage histogram, drift status badge per pending item
6715
+ // (cached verdict only; clicking does NOT trigger a new check),
6716
+ // halt + ack buttons, 15s poll, textContent rendering on every
6717
+ // user/agent-authored string, 409 response silently refreshes.
6718
+ let __projectsPollTimer = null;
6719
+ function startProjectsPoll() {
6720
+ if (__projectsPollTimer) return;
6721
+ __projectsPollTimer = setInterval(() => { loadProjects().catch(() => {}); }, 15000);
6722
+ }
6723
+ function stopProjectsPoll() {
6724
+ if (__projectsPollTimer) { clearInterval(__projectsPollTimer); __projectsPollTimer = null; }
6725
+ }
6726
+
6727
+ function escapeText(s) {
6728
+ // textContent assignment handles XSS for us; this is a no-op
6729
+ // helper that documents the intent for future authors.
6730
+ return typeof s === 'string' ? s : '';
6731
+ }
6732
+
6733
+ async function loadProjects() {
6734
+ const list = document.getElementById('projectsList');
6735
+ const empty = document.getElementById('projectsEmpty');
6736
+ const countBadge = document.getElementById('tabProjectCount');
6737
+ if (!list || !empty) return;
6738
+
6739
+ let projRes = null;
6740
+ try {
6741
+ projRes = await apiFetch('/projects').catch(() => null);
6742
+ } catch { /* handled by null check below */ }
6743
+
6744
+ while (list.firstChild) list.removeChild(list.firstChild);
6745
+ empty.style.display = 'none';
6746
+
6747
+ if (!projRes || !Array.isArray(projRes.items)) {
6748
+ empty.style.display = 'block';
6749
+ empty.textContent = 'Projects endpoint unavailable.';
6750
+ if (countBadge) countBadge.textContent = '0';
6751
+ return;
6752
+ }
6753
+
6754
+ const items = projRes.items.filter((p) => p.status === 'active');
6755
+ if (countBadge) countBadge.textContent = String(items.length);
6756
+ if (items.length === 0) {
6757
+ empty.style.display = 'block';
6758
+ return;
6759
+ }
6760
+
6761
+ // We need each project's children for the progress bar; fetch
6762
+ // them in parallel via /projects/:id (server returns children
6763
+ // alongside the project record). Cap at 25 to avoid a flood.
6764
+ const detailed = await Promise.all(items.slice(0, 25).map((p) =>
6765
+ apiFetch('/projects/' + encodeURIComponent(p.id) + '?reconcile=false').catch(() => null)
6766
+ ));
6767
+
6768
+ for (const detail of detailed) {
6769
+ if (!detail || !detail.project) continue;
6770
+ list.appendChild(renderProjectCard(detail.project, detail.children || []));
6771
+ }
6772
+ }
6773
+
6774
+ function renderProjectCard(project, children) {
6775
+ const card = document.createElement('div');
6776
+ card.style.padding = '14px';
6777
+ card.style.border = '1px solid var(--border)';
6778
+ card.style.borderRadius = '6px';
6779
+ card.style.background = 'var(--bg-dim)';
6780
+
6781
+ const titleRow = document.createElement('div');
6782
+ titleRow.style.display = 'flex';
6783
+ titleRow.style.justifyContent = 'space-between';
6784
+ titleRow.style.alignItems = 'baseline';
6785
+ titleRow.style.gap = '12px';
6786
+
6787
+ const title = document.createElement('div');
6788
+ title.style.fontWeight = '600';
6789
+ title.style.fontSize = '15px';
6790
+ title.textContent = project.title || project.id;
6791
+ titleRow.appendChild(title);
6792
+
6793
+ const idEl = document.createElement('div');
6794
+ idEl.style.fontSize = '11px';
6795
+ idEl.style.color = 'var(--text-dim)';
6796
+ idEl.style.fontFamily = 'monospace';
6797
+ idEl.textContent = project.id + ' v' + project.version;
6798
+ titleRow.appendChild(idEl);
6799
+ card.appendChild(titleRow);
6800
+
6801
+ // Drift status badge.
6802
+ const drift = project.rounds && project.rounds[0] ? project.rounds[0].lastDriftVerdict : null;
6803
+ if (drift && drift.verdict) {
6804
+ const badge = document.createElement('span');
6805
+ badge.style.display = 'inline-block';
6806
+ badge.style.marginTop = '4px';
6807
+ badge.style.padding = '2px 8px';
6808
+ badge.style.fontSize = '11px';
6809
+ badge.style.borderRadius = '10px';
6810
+ badge.style.border = '1px solid var(--border)';
6811
+ const colors = {
6812
+ 'no-drift': '#4a9a4a',
6813
+ 'minor-drift': '#e2b94a',
6814
+ 'premise-violated': '#d65f5f',
6815
+ 'manual-review-required': '#888',
6816
+ };
6817
+ badge.style.color = colors[drift.verdict] || '#888';
6818
+ badge.textContent = 'drift: ' + drift.verdict;
6819
+ card.appendChild(badge);
6820
+ }
6821
+
6822
+ // Rounds progress bars.
6823
+ const rounds = Array.isArray(project.rounds) ? project.rounds : [];
6824
+ const childById = new Map(children.map((c) => [c.id, c]));
6825
+ for (let i = 0; i < rounds.length; i++) {
6826
+ const r = rounds[i];
6827
+ const wrap = document.createElement('div');
6828
+ wrap.style.marginTop = '10px';
6829
+
6830
+ const rTitle = document.createElement('div');
6831
+ rTitle.style.fontSize = '12px';
6832
+ rTitle.style.color = 'var(--text-dim)';
6833
+ rTitle.textContent = `Round ${i}: ${r.name} — ${r.status || 'pending'}`;
6834
+ wrap.appendChild(rTitle);
6835
+
6836
+ // pipelineStage histogram counts.
6837
+ const stages = {};
6838
+ let total = 0;
6839
+ for (const id of (r.itemIds || [])) {
6840
+ const child = childById.get(id);
6841
+ const stage = (child && child.pipelineStage) || 'outline';
6842
+ stages[stage] = (stages[stage] || 0) + 1;
6843
+ total++;
6844
+ }
6845
+ const merged = stages['merged'] || 0;
6846
+ const bar = document.createElement('div');
6847
+ bar.style.marginTop = '4px';
6848
+ bar.style.height = '6px';
6849
+ bar.style.borderRadius = '3px';
6850
+ bar.style.background = 'var(--bg)';
6851
+ bar.style.overflow = 'hidden';
6852
+ const fill = document.createElement('div');
6853
+ fill.style.height = '100%';
6854
+ fill.style.background = '#4a9a4a';
6855
+ fill.style.width = total > 0 ? `${Math.round((merged / total) * 100)}%` : '0%';
6856
+ bar.appendChild(fill);
6857
+ wrap.appendChild(bar);
6858
+
6859
+ const histo = document.createElement('div');
6860
+ histo.style.fontSize = '11px';
6861
+ histo.style.color = 'var(--text-dim)';
6862
+ histo.style.marginTop = '4px';
6863
+ histo.textContent = Object.entries(stages).map(([k, v]) => `${k}: ${v}`).join(' • ') || 'no items';
6864
+ wrap.appendChild(histo);
6865
+ card.appendChild(wrap);
6866
+ }
6867
+
6868
+ // Halt + ack buttons.
6869
+ const actions = document.createElement('div');
6870
+ actions.style.display = 'flex';
6871
+ actions.style.gap = '8px';
6872
+ actions.style.marginTop = '12px';
6873
+
6874
+ const haltBtn = document.createElement('button');
6875
+ haltBtn.textContent = 'Halt';
6876
+ haltBtn.style.padding = '4px 10px';
6877
+ haltBtn.style.fontSize = '12px';
6878
+ haltBtn.addEventListener('click', () => projectHalt(project.id));
6879
+ actions.appendChild(haltBtn);
6880
+
6881
+ const ackBtn = document.createElement('button');
6882
+ ackBtn.textContent = 'Ack round 0';
6883
+ ackBtn.style.padding = '4px 10px';
6884
+ ackBtn.style.fontSize = '12px';
6885
+ ackBtn.addEventListener('click', () => projectAck(project.id, 0));
6886
+ actions.appendChild(ackBtn);
6887
+ card.appendChild(actions);
6888
+
6889
+ return card;
6890
+ }
6891
+
6892
+ async function projectHalt(projectId) {
6893
+ try {
6894
+ const res = await apiFetch('/projects/' + encodeURIComponent(projectId) + '/halt', {
6895
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
6896
+ body: JSON.stringify({ reason: 'user-halt-from-dashboard' }),
6897
+ });
6898
+ // 409 from a stale read → silent refresh, no error toast.
6899
+ if (res && res.error && /version mismatch/.test(res.error)) {
6900
+ await loadProjects();
6901
+ return;
6902
+ }
6903
+ await loadProjects();
6904
+ } catch { /* ignore — refresh on next poll */ }
6905
+ }
6906
+
6907
+ async function projectAck(projectId, forRoundIndex) {
6908
+ try {
6909
+ const res = await apiFetch('/projects/' + encodeURIComponent(projectId) + '/ack', {
6910
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
6911
+ body: JSON.stringify({ forRoundIndex }),
6912
+ });
6913
+ if (res && res.error && /version mismatch/.test(res.error)) {
6914
+ await loadProjects();
6915
+ return;
6916
+ }
6917
+ await loadProjects();
6918
+ } catch { /* ignore — refresh on next poll */ }
6919
+ }
6920
+
6685
6921
  // ── Initiatives Tab (INITIATIVE-TRACKER-SPEC v1) ─────────────
6686
6922
  //
6687
6923
  // Fetches /initiatives and /initiatives/digest on panel activation.
@@ -6703,9 +6939,20 @@
6703
6939
  let itemsRes = null;
6704
6940
  let digestRes = null;
6705
6941
  try {
6706
- const statusQ = filter && filter.value ? `?status=${encodeURIComponent(filter.value)}` : '';
6942
+ // PROJECT-SCOPE-SPEC Phase 1.10 — by default hide project-kind
6943
+ // records (shown in the Projects tab) and parented children
6944
+ // (shown inside their parent's card). Server-side filter so
6945
+ // we don't ship them over the wire.
6946
+ const showAll = document.getElementById('initiativesShowAll');
6947
+ const params = [];
6948
+ if (filter && filter.value) params.push(`status=${encodeURIComponent(filter.value)}`);
6949
+ if (!showAll || !showAll.checked) {
6950
+ params.push('excludeKind=project');
6951
+ params.push('excludeParented=true');
6952
+ }
6953
+ const q = params.length ? '?' + params.join('&') : '';
6707
6954
  const [a, b] = await Promise.all([
6708
- apiFetch('/initiatives' + statusQ).catch(() => null),
6955
+ apiFetch('/initiatives' + q).catch(() => null),
6709
6956
  apiFetch('/initiatives/digest').catch(() => null),
6710
6957
  ]);
6711
6958
  itemsRes = a;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkQH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAw4CD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAqhKtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/commands/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAkQH,UAAU,YAAY;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;2DACuD;IACvD,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAw4CD,wBAAsB,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAknKtE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsDzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuD5E"}
@@ -5860,6 +5860,95 @@ export async function startServer(options) {
5860
5860
  const projectDigestCache = new ProjectDigestCache(config.stateDir, initiativeTracker);
5861
5861
  initiativeTracker.setDigestCacheInvalidator(() => projectDigestCache.writeDigestCache());
5862
5862
  projectDigestCache.writeDigestCache();
5863
+ // Project-scope Phase 1b PR 3 — round runner (single chokepoint for
5864
+ // /advance, /halt, /ack, /accept-partial; lock-protected; future
5865
+ // autonomous-delegating run loop).
5866
+ const machineIdForProjects = coordinator.identity?.machineId ?? os.hostname();
5867
+ const { ProjectRoundRunner } = await import('../core/ProjectRoundRunner.js');
5868
+ const projectRoundRunner = new ProjectRoundRunner({
5869
+ tracker: initiativeTracker,
5870
+ stateDir: config.stateDir,
5871
+ machineId: machineIdForProjects,
5872
+ });
5873
+ // Project-scope Phase 1b PR 4 — machine heartbeat + auto-advance poller.
5874
+ // Heartbeat writes .instar/machine-health/<machineId>.json every 30 min
5875
+ // (git-synced); the claim-ownership endpoint queries it for the >48h
5876
+ // staleness check that spec § P5 requires. Auto-advance poller scans
5877
+ // for project rounds whose autoAdvanceAt has elapsed and bookkeeps the
5878
+ // next round.
5879
+ const { MachineHeartbeat } = await import('../core/MachineHeartbeat.js');
5880
+ const machineHeartbeatApi = new MachineHeartbeat({
5881
+ stateDir: config.stateDir,
5882
+ machineId: machineIdForProjects,
5883
+ });
5884
+ machineHeartbeatApi.start();
5885
+ const machineHeartbeat = { api: machineHeartbeatApi, config: { machineId: machineIdForProjects } };
5886
+ // Wire the run-round executor: fire-and-forget launch of
5887
+ // ProjectRoundExecution.runRound() after a successful auto-advance.
5888
+ // The executor itself acquires the lock + spawns + polls + cleans up.
5889
+ // Errors are surfaced via the poller's result.executorErrors and
5890
+ // logged to stderr; they don't take the server down.
5891
+ const { runRound } = await import('../core/ProjectRoundExecution.js');
5892
+ const { ProjectAutoAdvancePoller } = await import('../core/ProjectAutoAdvancePoller.js');
5893
+ const projectAutoAdvancePoller = new ProjectAutoAdvancePoller({
5894
+ tracker: initiativeTracker,
5895
+ runner: projectRoundRunner,
5896
+ machineId: machineIdForProjects,
5897
+ executor: async ({ projectId, roundIndex }) => {
5898
+ const proj = initiativeTracker.get(projectId);
5899
+ if (!proj || !proj.targetRepoPath)
5900
+ return;
5901
+ await runRound({
5902
+ tracker: initiativeTracker,
5903
+ projectId,
5904
+ roundIndex,
5905
+ targetRepoPath: proj.targetRepoPath,
5906
+ }, { stateDir: config.stateDir });
5907
+ },
5908
+ });
5909
+ // Tick once a minute; .unref() so the timer never keeps the process alive.
5910
+ const projectAutoAdvanceTimer = setInterval(() => {
5911
+ projectAutoAdvancePoller.tick().catch((err) => {
5912
+ console.error('[ProjectAutoAdvancePoller] tick error:', err);
5913
+ });
5914
+ }, 60_000);
5915
+ if (typeof projectAutoAdvanceTimer.unref === 'function')
5916
+ projectAutoAdvanceTimer.unref();
5917
+ // Post-restore reconciler — downgrade any round still flagged
5918
+ // in-progress to pending. The previous owner may have crashed or
5919
+ // migrated, and no TaskFlow exists yet to detect an actually-live
5920
+ // run. One-shot at startup.
5921
+ try {
5922
+ for (const proj of initiativeTracker.list({ kind: 'project', status: 'active' })) {
5923
+ const rounds = proj.rounds ?? [];
5924
+ let any = false;
5925
+ const nextRounds = rounds.map((r) => {
5926
+ if (r.status === 'in-progress') {
5927
+ any = true;
5928
+ return { ...r, status: 'pending' };
5929
+ }
5930
+ return r;
5931
+ });
5932
+ if (any) {
5933
+ await initiativeTracker.update(proj.id, {
5934
+ rounds: nextRounds,
5935
+ ifMatch: proj.version,
5936
+ }).catch(() => { });
5937
+ }
5938
+ }
5939
+ }
5940
+ catch (err) {
5941
+ console.error('[ProjectAutoAdvancePoller] post-restore reconciler error:', err);
5942
+ }
5943
+ // Project drift checker — used by POST /projects/:id/drift-check.
5944
+ // Reuses the shared IntelligenceProvider. Cache + ledger are optional
5945
+ // and not wired here yet (the dashboard caller can pass per-call
5946
+ // overrides); a follow-up can wire ProjectDriftCheckerCache + the
5947
+ // DriftSpendLedger once spend telemetry is needed in prod.
5948
+ const { ProjectDriftChecker } = await import('../core/ProjectDriftChecker.js');
5949
+ const projectDriftChecker = new ProjectDriftChecker({
5950
+ intelligence: sharedIntelligence,
5951
+ });
5863
5952
  // TaskFlow registry — opt-in via config.taskFlow.enabled (default: off in v1).
5864
5953
  // Owns its own SQLite file under .instar/task-flows.db. The maintenance
5865
5954
  // sweeper and due-waker start with the registry; both .unref() their timers
@@ -5964,7 +6053,7 @@ export async function startServer(options) {
5964
6053
  },
5965
6054
  });
5966
6055
  }
5967
- const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, proxyCoordinator, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, workingMemory, taskFlowRegistry, threadlineFlowBridge });
6056
+ const server = new AgentServer({ config, sessionManager, state, scheduler, telegram, relationships, feedback, feedbackAnomalyDetector, dispatches, updateChecker, autoUpdater, autoDispatcher, quotaTracker, quotaManager, publisher, viewer, tunnel, evolution, watchdog, topicMemory, triageNurse, projectMapper, coherenceGate: scopeVerifier, contextHierarchy, canonicalState, operationGate, sentinel, adaptiveTrust, memoryMonitor, orphanReaper, coherenceMonitor, commitmentTracker, semanticMemory, activitySentinel, messageRouter, summarySentinel, spawnManager, systemReviewer, capabilityMapper, selfKnowledgeTree, coverageAuditor, topicResumeMap: _topicResumeMap ?? undefined, sessionRefresh: _sessionRefresh ?? undefined, autonomyManager, trustElevationTracker, autonomousEvolution, coordinator: coordinator.enabled ? coordinator : undefined, localSigningKeyPem, whatsapp: whatsappAdapter, slack: slackAdapter, imessage: imessageAdapter, whatsappBusinessBackend, messageBridge, hookEventReceiver, worktreeMonitor, subagentTracker, instructionsVerifier, handshakeManager: threadlineHandshake, threadlineRouter, threadResumeMap, topicLinkageHandler: topicLinkageHandler ?? undefined, threadlineRelayClient, threadlineReplyWaiters, listenerManager: listenerManager ?? undefined, responseReviewGate, messagingToneGate, outboundDedupGate, telemetryHeartbeat, pasteManager, featureRegistry, discoveryEvaluator, unifiedTrust, liveConfig, sharedStateLedger, ledgerSessionRegistry, worktreeManager, oidcEnrolledRepos: parallelDevConfig?.oidcEnrolledRepos, initiativeTracker, projectRoundRunner, projectDriftChecker, machineHeartbeat, proxyCoordinator, telegramBridgeConfig, telegramBridge: telegramBridge ?? undefined, threadlineObservability, workingMemory, taskFlowRegistry, threadlineFlowBridge });
5968
6057
  await server.start();
5969
6058
  void taskFlowSweeper;
5970
6059
  void taskFlowDueWaker;