claude-code-session-manager 0.75.3 → 0.77.0

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 (164) hide show
  1. package/dist/assets/{AgentLibrary-CzQqcObq.js → AgentLibrary-B2ie8bbw.js} +2 -2
  2. package/dist/assets/{DataModel-Bj_WlLz8.js → DataModel-BIJPYw32.js} +1 -1
  3. package/dist/assets/{History-DnSi_OHm.js → History-CeY6dk9S.js} +2 -2
  4. package/dist/assets/{Hooks-0BB0dp3S.js → Hooks-BFH2ocKg.js} +2 -2
  5. package/dist/assets/{HostBilko-DHpwwsLQ.js → HostBilko-36gj9wLz.js} +1 -1
  6. package/dist/assets/{Library-CaJVqVvi.js → Library-C-hBct39.js} +1 -1
  7. package/dist/assets/{ListDetail-C1W2HmC2.js → ListDetail-CNq64VWV.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-5Ob9FW3z.js → MarkdownEditor-Bh3qt5-1.js} +1 -1
  9. package/dist/assets/{McpServers-JxCSfm1S.js → McpServers-DpGN0oyz.js} +1 -1
  10. package/dist/assets/{Memory-BDeqlqwH.js → Memory-D59hUjC4.js} +6 -6
  11. package/dist/assets/{Panel-Dh9ZHuEj.js → Panel-DCgbaoci.js} +1 -1
  12. package/dist/assets/{Permissions-DXy-CbEY.js → Permissions-DAmQ0DYV.js} +2 -2
  13. package/dist/assets/{Plugins-_n1Iuc8T.js → Plugins-Dyfgn6Is.js} +2 -2
  14. package/dist/assets/{ProvenanceBadge-BP_evfxE.js → ProvenanceBadge-BiYhPO1U.js} +1 -1
  15. package/dist/assets/SaveBar-RV7B6sOh.js +1 -0
  16. package/dist/assets/Scheduler-BPaNqx1b.js +14 -0
  17. package/dist/assets/{ScopeSwitcher-CAWzM6RI.js → ScopeSwitcher-P4mdLGNU.js} +1 -1
  18. package/dist/assets/{Settings-DRRozLyT.js → Settings-BL4vf5aX.js} +1 -1
  19. package/dist/assets/{SkillReferenceGraph-DGHDWlz4.js → SkillReferenceGraph-BRBDyi1_.js} +1 -1
  20. package/dist/assets/{Skills-D8L66eiX.js → Skills-BV08gDUH.js} +2 -2
  21. package/dist/assets/{SystemPrompt-CYtUsonD.js → SystemPrompt-CLftSsDw.js} +1 -1
  22. package/dist/assets/TagLibrary-Bp8jGsd5.js +1 -0
  23. package/dist/assets/{TiptapBody-B2hRgbPE.js → TiptapBody-jCpuB6E5.js} +1 -1
  24. package/dist/assets/{Toggle-BTwsbxam.js → Toggle-D2paA1xf.js} +1 -1
  25. package/dist/assets/{index-DijufvkJ.js → index-BDRSqBl3.js} +704 -704
  26. package/dist/assets/{index-CMLnzdZC.css → index-CYhdtisq.css} +1 -1
  27. package/dist/assets/{settingsSchema-D6wzxAi6.js → settingsSchema-6IOLjZZN.js} +1 -1
  28. package/dist/index.html +2 -2
  29. package/package.json +8 -2
  30. package/plugins/session-manager-dev/skills/develop/standards.md +1 -1
  31. package/scripts/lib/activeSessions.cjs +116 -6
  32. package/scripts/project-pages-logic/dist/logic.cjs +4709 -0
  33. package/scripts/render-project-pages/dist/renderer.cjs +18900 -0
  34. package/scripts/render-project-pages.cjs +70 -0
  35. package/scripts/scheduler-mcp-server.cjs +269 -96
  36. package/scripts/validate-project-pages-summary.cjs +62 -0
  37. package/src/main/__tests__/agentModelResolve.test.cjs +66 -0
  38. package/src/main/__tests__/epicStatusMirror.test.cjs +110 -0
  39. package/src/main/__tests__/health-delegation-chain.test.cjs +106 -0
  40. package/src/main/__tests__/prdAdminRoutes.test.cjs +295 -0
  41. package/src/main/__tests__/prdAgentType.test.cjs +103 -0
  42. package/src/main/__tests__/prdCreate.test.cjs +247 -0
  43. package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +117 -0
  44. package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +108 -0
  45. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +485 -0
  46. package/src/main/__tests__/projectPages.test.cjs +73 -1
  47. package/src/main/__tests__/rcaReport.test.cjs +54 -0
  48. package/src/main/__tests__/runVerify.test.cjs +94 -0
  49. package/src/main/__tests__/scheduler-autofix-select.test.cjs +58 -3
  50. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +103 -0
  51. package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +41 -0
  52. package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +10 -0
  53. package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +78 -0
  54. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +242 -0
  55. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +31 -0
  56. package/src/main/__tests__/scheduler-launch-failure.test.cjs +201 -0
  57. package/src/main/__tests__/scheduler-leftover-fields.test.cjs +52 -0
  58. package/src/main/__tests__/scheduler-looks-done.test.cjs +241 -0
  59. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +135 -0
  60. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +222 -0
  61. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +207 -1
  62. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +212 -0
  63. package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +185 -0
  64. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +194 -0
  65. package/src/main/__tests__/seedAgentPersonas.test.cjs +75 -14
  66. package/src/main/__tests__/seedSchedulerMcp.test.cjs +66 -0
  67. package/src/main/__tests__/uniquePrdNumbers.test.cjs +14 -5
  68. package/src/main/bilkoHost.cjs +4 -3
  69. package/src/main/chatRunner.cjs +6 -1
  70. package/src/main/config.cjs +25 -33
  71. package/src/main/health.cjs +153 -2
  72. package/src/main/index.cjs +64 -5
  73. package/src/main/ipcSchemas.cjs +69 -1
  74. package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +179 -0
  75. package/src/main/lib/__tests__/childWithLog.test.cjs +141 -0
  76. package/src/main/lib/__tests__/delegationReadiness.test.cjs +391 -42
  77. package/src/main/lib/__tests__/ephemeralCwd.test.cjs +91 -0
  78. package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +5 -3
  79. package/src/main/lib/__tests__/fixChainDepth.test.cjs +40 -0
  80. package/src/main/lib/__tests__/gitWorktree.test.cjs +290 -5
  81. package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +107 -0
  82. package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +153 -0
  83. package/src/main/lib/__tests__/jobWorktree.test.cjs +6 -4
  84. package/src/main/lib/__tests__/landedSinceRun.test.cjs +73 -0
  85. package/src/main/lib/__tests__/launchFailure.test.cjs +220 -0
  86. package/src/main/lib/__tests__/loadGate.test.cjs +159 -0
  87. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +102 -0
  88. package/src/main/lib/__tests__/opsOwnership.test.cjs +7 -0
  89. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +151 -0
  90. package/src/main/lib/__tests__/opsRootResolve.test.cjs +149 -0
  91. package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +82 -0
  92. package/src/main/lib/__tests__/projectRootResolve.test.cjs +148 -0
  93. package/src/main/lib/__tests__/queueHealth.test.cjs +58 -0
  94. package/src/main/lib/__tests__/quietMachineLease.test.cjs +39 -0
  95. package/src/main/lib/__tests__/reaperHelpers.test.cjs +133 -0
  96. package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +19 -9
  97. package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +213 -0
  98. package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +125 -0
  99. package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +127 -0
  100. package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +109 -0
  101. package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +71 -0
  102. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +217 -0
  103. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +350 -0
  104. package/src/main/lib/activeIndexMerge.cjs +15 -0
  105. package/src/main/lib/activeIndexRebuild.cjs +133 -0
  106. package/src/main/lib/agentModelResolve.cjs +58 -0
  107. package/src/main/lib/buildTarget.cjs +3 -2
  108. package/src/main/lib/childWithLog.cjs +69 -2
  109. package/src/main/lib/claudeBin.cjs +54 -1
  110. package/src/main/lib/crossProjectFeedback.cjs +8 -1
  111. package/src/main/lib/definitionOfDone.cjs +3 -2
  112. package/src/main/lib/delegationReadiness.cjs +514 -26
  113. package/src/main/lib/ephemeralCwd.cjs +78 -0
  114. package/src/main/lib/epicDelegationStats.cjs +2 -1
  115. package/src/main/lib/epicMint.cjs +17 -1
  116. package/src/main/lib/epicStatusMirror.cjs +95 -0
  117. package/src/main/lib/epicValidationHook.cjs +2 -1
  118. package/src/main/lib/epicWorktreeMint.cjs +5 -2
  119. package/src/main/lib/fixChainDepth.cjs +45 -0
  120. package/src/main/lib/gitWorktree.cjs +520 -21
  121. package/src/main/lib/jobWorktree.cjs +2 -0
  122. package/src/main/lib/landedSinceRun.cjs +55 -0
  123. package/src/main/lib/launchFailure.cjs +357 -0
  124. package/src/main/lib/loadGate.cjs +134 -0
  125. package/src/main/lib/mcpToolCatalog.cjs +370 -0
  126. package/src/main/lib/opsErrorLog.cjs +12 -1
  127. package/src/main/lib/opsOwnership.cjs +106 -0
  128. package/src/main/lib/prdAdminRoutes.cjs +43 -3
  129. package/src/main/lib/prdAgentType.cjs +84 -0
  130. package/src/main/lib/prdCreate.cjs +103 -15
  131. package/src/main/lib/prdDeclaredPaths.cjs +70 -0
  132. package/src/main/lib/prdFrontmatter.cjs +17 -3
  133. package/src/main/lib/prdLocations.cjs +13 -6
  134. package/src/main/lib/projectHomeAdminRoutes.cjs +402 -0
  135. package/src/main/lib/projectPageSummarySchema.cjs +181 -0
  136. package/src/main/lib/projectRootResolve.cjs +134 -0
  137. package/src/main/lib/promptSessionSchema.cjs +7 -0
  138. package/src/main/lib/queueHealth.cjs +38 -0
  139. package/src/main/lib/queueStore.cjs +40 -7
  140. package/src/main/lib/quietMachineLease.cjs +48 -0
  141. package/src/main/lib/rcaReport.cjs +54 -4
  142. package/src/main/lib/reaperHelpers.cjs +64 -1
  143. package/src/main/lib/scheduleJobSchema.cjs +31 -0
  144. package/src/main/lib/scheduleJobTransitions.cjs +6 -2
  145. package/src/main/lib/schedulerBatch.cjs +301 -55
  146. package/src/main/lib/schedulerConfig.cjs +99 -0
  147. package/src/main/projectBrief.cjs +3 -2
  148. package/src/main/projectPages.cjs +162 -3
  149. package/src/main/promptSessionTranscript.cjs +0 -0
  150. package/src/main/pty.cjs +5 -0
  151. package/src/main/queueOps.cjs +15 -8
  152. package/src/main/runVerify.cjs +50 -9
  153. package/src/main/scheduler/prdParser.cjs +18 -1
  154. package/src/main/scheduler.cjs +1701 -130
  155. package/src/main/seedAgentPersonas.cjs +62 -21
  156. package/src/main/seedSchedulerMcp.cjs +58 -4
  157. package/src/main/templates/project-pages-catalog.json +741 -0
  158. package/src/main/templates/project-pages-pipeline.md +417 -0
  159. package/src/preload/api.d.ts +187 -3
  160. package/src/preload/index.cjs +9 -0
  161. package/src/seed/agents/project-home-builder.md +59 -0
  162. package/dist/assets/SaveBar-D-gCUx4n.js +0 -1
  163. package/dist/assets/Scheduler-Bpd4OGju.js +0 -14
  164. package/dist/assets/TagLibrary-E5CLeuVk.js +0 -1
@@ -9,13 +9,16 @@ const fsp = require('node:fs/promises');
9
9
  const path = require('node:path');
10
10
  const os = require('node:os');
11
11
  const { execFileSync } = require('node:child_process');
12
- const { POLL_INTERVAL_MS } = require('./lib/schedulerConfig.cjs');
12
+ const { POLL_INTERVAL_MS, loadGateThreshold } = require('./lib/schedulerConfig.cjs');
13
+ const { opsPath } = require('./lib/opsOwnership.cjs');
13
14
  const { checkPersonaImports } = require('./lib/personaImportHealth.cjs');
15
+ const { checkDelegationReadiness } = require('./lib/delegationReadiness.cjs');
14
16
  const { resolvePrdsDirs } = require('./lib/prdLocations.cjs');
15
17
  const { migratePrds } = require('./lib/prdMigration.cjs');
16
18
  const queueStore = require('./lib/queueStore.cjs');
17
19
  const { computeStallSummary } = require('./scheduler.cjs');
18
20
  const { DEFAULT_RUNS_DIR, computeReport, isRetentionEnabled, liveKeysFromJobs } = require('./lib/runLogRetention.cjs');
21
+ const { allProjectCwds } = require('../../scripts/lib/activeSessions.cjs');
19
22
 
20
23
  const MAX_LOG_AGE_MS = 5 * 60_000; // 5 min — warn if no logs this old
21
24
  const PROJECT_ROOT = path.resolve(__dirname, '../..');
@@ -225,6 +228,113 @@ function evaluatePrdMigrationHealth(migrationResult, legacyPrdsDir) {
225
228
  };
226
229
  }
227
230
 
231
+ // Pure shaping of checkDelegationReadiness()'s result (delegationReadiness.cjs)
232
+ // into a health component + issue lines — kept separate from the fs-touching
233
+ // check() call site so it's directly unit-testable, matching
234
+ // evaluatePrdMigrationHealth's pattern. delegationReadiness.cjs already wraps
235
+ // every filesystem read (readJsonSafe / try-catch), so a missing/unreadable
236
+ // config surfaces here as one of `checks` with ok:false, never a throw.
237
+ function evaluateDelegationChainHealth(delegationResult) {
238
+ const component = { ok: delegationResult.ok, checks: delegationResult.checks };
239
+ const issues = delegationResult.checks
240
+ .filter((c) => !c.ok)
241
+ .map((c) => `Delegation readiness: ${c.label} failed — ${c.detail}${c.fix ? ` (fix: ${c.fix})` : ''}`);
242
+ return { component, issues };
243
+ }
244
+
245
+ // computeEpicIndexDrift(cwd) → { orphan_rows, orphan_files, unmirrored,
246
+ // orphanRowIds, orphanFileIds, unmirroredFiles }
247
+ //
248
+ // One project's drift between active-index.json (the index) and prompt-
249
+ // sessions/<id>.json (the status mirror epicStatusMirror.cjs writes on every
250
+ // status-changing write path). Pure filesystem read, never throws — an
251
+ // unreadable index or prompt-sessions dir degrades to empty rather than
252
+ // aborting the whole health check (matches evaluatePrdMigrationHealth's
253
+ // fail-open-on-read spirit).
254
+ function computeEpicIndexDrift(cwd) {
255
+ let dir;
256
+ try {
257
+ dir = opsPath(cwd, 'prompt-sessions');
258
+ } catch {
259
+ return { orphan_rows: 0, orphan_files: 0, unmirrored: 0 }; // unusable cwd degrades to empty, same as an unreadable index below
260
+ }
261
+ const indexPath = path.join(dir, 'active-index.json');
262
+ let sessions = {};
263
+ let tombstones = {};
264
+ try {
265
+ const parsed = JSON.parse(fs.readFileSync(indexPath, 'utf8'));
266
+ sessions = parsed && typeof parsed.sessions === 'object' && parsed.sessions ? parsed.sessions : {};
267
+ tombstones = parsed && typeof parsed.tombstones === 'object' && parsed.tombstones ? parsed.tombstones : {};
268
+ } catch { /* missing/unreadable index — treat as empty, not a throw */ }
269
+
270
+ let files = [];
271
+ try {
272
+ files = fs.readdirSync(dir).filter((f) => f.endsWith('.json') && f !== 'active-index.json');
273
+ } catch { /* prompt-sessions/ doesn't exist yet — nothing to compare */ }
274
+
275
+ const fileIds = new Set(files.map((f) => f.slice(0, -'.json'.length)));
276
+ const orphanRowIds = Object.keys(sessions).filter((id) => !fileIds.has(id));
277
+
278
+ const orphanFileIds = [];
279
+ const unmirroredFiles = [];
280
+ for (const file of files) {
281
+ const id = file.slice(0, -'.json'.length);
282
+ let parsed;
283
+ try {
284
+ parsed = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf8'));
285
+ } catch {
286
+ continue; // unreadable file — neither orphan nor unmirrored classification applies
287
+ }
288
+ if (!parsed || typeof parsed !== 'object' || !parsed.status) {
289
+ unmirroredFiles.push(file);
290
+ continue;
291
+ }
292
+ const looksLive = (parsed.status === 'proposed' || parsed.status === 'active') && !parsed.archivedAt;
293
+ const hasRow = Object.prototype.hasOwnProperty.call(sessions, id);
294
+ const isTombstoned = Object.prototype.hasOwnProperty.call(tombstones, id);
295
+ if (looksLive && !hasRow && !isTombstoned) orphanFileIds.push(file);
296
+ }
297
+
298
+ return {
299
+ orphan_rows: orphanRowIds.length,
300
+ orphan_files: orphanFileIds.length,
301
+ unmirrored: unmirroredFiles.length,
302
+ orphanRowIds,
303
+ orphanFileIds,
304
+ unmirroredFiles,
305
+ };
306
+ }
307
+
308
+ // evaluateEpicIndexHealth(cwds) → { component, issues }
309
+ //
310
+ // Shapes computeEpicIndexDrift's per-project result into a health component +
311
+ // issue lines, matching evaluateDelegationChainHealth's pattern. Only
312
+ // orphan_rows (an index row whose Epic file is gone — the actual data-loss
313
+ // signal) is an issue; orphan_files and unmirrored are informational drift,
314
+ // not failures — a fresh install or an Epic mid-migration legitimately has
315
+ // unmirrored files.
316
+ function evaluateEpicIndexHealth(cwds) {
317
+ const byProject = {};
318
+ const issues = [];
319
+ let anyOrphanRows = false;
320
+ for (const cwd of cwds) {
321
+ const drift = computeEpicIndexDrift(cwd);
322
+ byProject[cwd] = {
323
+ orphan_rows: drift.orphan_rows,
324
+ orphan_files: drift.orphan_files,
325
+ unmirrored: drift.unmirrored,
326
+ };
327
+ if (drift.orphan_rows > 0) {
328
+ anyOrphanRows = true;
329
+ issues.push(
330
+ `Epic index drift: ${cwd} has ${drift.orphan_rows} active-index.json row(s) with no matching `
331
+ + `prompt-sessions/<id>.json file (${drift.orphanRowIds.join(', ')})`
332
+ );
333
+ }
334
+ }
335
+ return { component: { ok: !anyOrphanRows, byProject }, issues };
336
+ }
337
+
228
338
  async function check() {
229
339
  const start = Date.now();
230
340
  const status = {
@@ -340,6 +450,15 @@ async function check() {
340
450
  byProject: computeProjectProblemCounts(queueState.jobs),
341
451
  perProjectStall,
342
452
  tickLiveness: liveness.reason,
453
+ // Informational only (PRD 1085): current 1-min loadavg per core vs the
454
+ // launch-gate threshold, so a "nothing is launching" report can be
455
+ // read next to the reason without opening the app.
456
+ loadRatio: (() => {
457
+ const cores = (os.cpus() || []).length;
458
+ const l1 = os.loadavg()[0];
459
+ return cores > 0 && Number.isFinite(l1) ? Number((l1 / cores).toFixed(3)) : null;
460
+ })(),
461
+ loadGateThreshold: loadGateThreshold(),
343
462
  };
344
463
  if (liveness.stalled) {
345
464
  const ageMin = Math.round(liveness.tickAgeMs / 60_000);
@@ -505,6 +624,22 @@ async function check() {
505
624
  }
506
625
  }
507
626
 
627
+ // 6.55. Delegation-chain readiness — answers "can this machine actually
628
+ // delegate work to the scheduler?" (see delegationReadiness.cjs's header:
629
+ // when the scheduler MCP isn't registered, scheduler_create_prd is simply
630
+ // absent from the agent's tool list — no error to catch, so an agent asked
631
+ // to delegate just implements inline instead). Critical: a failing check
632
+ // here means Epics silently stop queueing PRDs on this machine.
633
+ try {
634
+ const delegation = await checkDelegationReadiness({ cwd: PROJECT_ROOT });
635
+ const { component, issues: delegationIssues } = evaluateDelegationChainHealth(delegation);
636
+ status.components.delegation_chain = component;
637
+ status.issues.push(...delegationIssues);
638
+ } catch (e) {
639
+ status.components.delegation_chain = { ok: false, error: e.message };
640
+ status.issues.push(`Delegation readiness check failed: ${e.message}`);
641
+ }
642
+
508
643
  // 6.6. Run-log retention report (informational only — never blocks health).
509
644
  // Read-only against the REAL scheduled-plans/runs/ dir: reports current
510
645
  // usage + what the configured policy (if any) would remove. Nothing is
@@ -536,11 +671,24 @@ async function check() {
536
671
  status.components.run_log_retention = { ok: true, error: e.message };
537
672
  }
538
673
 
674
+ // 6.65. Epic index drift — orphan_rows/orphan_files/unmirrored across every
675
+ // project this machine has ever opened (allProjectCwds, no recency filter
676
+ // — a quiet project still owns its Epics). See computeEpicIndexDrift's
677
+ // header.
678
+ try {
679
+ const { component, issues: epicIndexIssues } = evaluateEpicIndexHealth(allProjectCwds());
680
+ status.components.epic_index = component;
681
+ status.issues.push(...epicIndexIssues);
682
+ } catch (e) {
683
+ status.components.epic_index = { ok: false, error: e.message };
684
+ status.issues.push(`Epic index health check failed: ${e.message}`);
685
+ }
686
+
539
687
  // 7. Summary scoring: ok if all critical components pass.
540
688
  // Critical: nodejs, config dir, typescript, build artifact, test infrastructure.
541
689
  // Non-fatal: scheduler/transcripts dirs may not exist on fresh install.
542
690
  // Informational: app log age (shows if app is running, but not blocking).
543
- const criticalComponents = ['nodejs', 'config_dir', 'typescript', 'build_artifact', 'test_infrastructure', 'scheduler_queue', 'prd_migration', 'claude_md_budget'];
691
+ const criticalComponents = ['nodejs', 'config_dir', 'typescript', 'build_artifact', 'test_infrastructure', 'scheduler_queue', 'prd_migration', 'claude_md_budget', 'delegation_chain'];
544
692
  status.ok = criticalComponents.every((c) => status.components[c]?.ok !== false);
545
693
 
546
694
  status.elapsedMs = Date.now() - start;
@@ -561,6 +709,9 @@ module.exports = {
561
709
  evaluateTickLiveness,
562
710
  readFreshHeartbeat,
563
711
  evaluatePrdMigrationHealth,
712
+ evaluateDelegationChainHealth,
713
+ computeEpicIndexDrift,
714
+ evaluateEpicIndexHealth,
564
715
  computeProjectProblemCounts,
565
716
  evaluatePerProjectStall,
566
717
  parseClaudeMdBudget,
@@ -27,6 +27,7 @@ const scheduler = require('./scheduler.cjs');
27
27
  const { createAdminHttp } = require('./lib/localAdminHttp.cjs');
28
28
  const prdCreate = require('./lib/prdCreate.cjs');
29
29
  const prdAdminRoutes = require('./lib/prdAdminRoutes.cjs');
30
+ const projectHomeAdminRoutes = require('./lib/projectHomeAdminRoutes.cjs');
30
31
  const { appendAuditEvent } = require('./lib/auditLog.cjs');
31
32
  const chatRunner = require('./chatRunner.cjs');
32
33
  const promptSessionEvents = require('./promptSessionEvents.cjs');
@@ -36,7 +37,8 @@ const epicWorktreeMint = require('./lib/epicWorktreeMint.cjs');
36
37
  const epicWorktreeMerge = require('./lib/epicWorktreeMerge.cjs');
37
38
  const epicWorktreeProjectConfig = require('./lib/epicWorktreeProjectConfig.cjs');
38
39
  const agentLibrary = require('./agentLibrary.cjs');
39
- const { checkDelegationReadiness } = require('./lib/delegationReadiness.cjs');
40
+ const { checkDelegationReadiness, installPrdWriteGuard } = require('./lib/delegationReadiness.cjs');
41
+ const { MCP_TOOL_CATALOG, MCP_RECIPES } = require('./lib/mcpToolCatalog.cjs');
40
42
  const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
41
43
  const { resolveBuildTarget } = require('./lib/buildTarget.cjs');
42
44
  const crossProjectFeedback = require('./lib/crossProjectFeedback.cjs');
@@ -44,6 +46,7 @@ const adminHttp = createAdminHttp();
44
46
  scheduler.registerAdminRoutes(adminHttp);
45
47
  prdCreate.registerAdminRoute(adminHttp, scheduler.remote);
46
48
  prdAdminRoutes.registerAdminRoute(adminHttp, scheduler.remote);
49
+ projectHomeAdminRoutes.registerAdminRoute(adminHttp);
47
50
  chatRunner.registerAdminRoute(adminHttp);
48
51
  // Project-to-project feedback conduit — the app is the only process holding
49
52
  // several projects' operations roots open, so it performs the cross-folder
@@ -68,7 +71,7 @@ const schedulerConfig = require('./lib/schedulerConfig.cjs');
68
71
  const memoryTool = require('./memoryTool.cjs');
69
72
  const { registerMemoryAggregateIpc } = require('./memoryAggregate.cjs');
70
73
  const { registerProjectBriefIpc } = require('./projectBrief.cjs');
71
- const { registerProjectPagesIpc } = require('./projectPages.cjs');
74
+ const { registerProjectPagesIpc, attachWindow: attachProjectPagesWindow, closeAllOutputWatchers: closeAllProjectPagesWatchers } = require('./projectPages.cjs');
72
75
  const { registerBilkoHostIpc } = require('./bilkoHost.cjs');
73
76
  const promptSessionTranscript = require('./promptSessionTranscript.cjs');
74
77
  const { computeEpicDelegationStats } = require('./lib/epicDelegationStats.cjs');
@@ -81,6 +84,9 @@ const { listExchanges } = require('./exchanges.cjs');
81
84
  const { resolveClaudeBin } = require('./lib/claudeBin.cjs');
82
85
  const { checkInsideHome, assertInsideHome } = require('./lib/insideHome.cjs');
83
86
  const { openInEditor, openFileInEditor, openInFinder, openInTerminal } = require('./lib/openExternalApp.cjs');
87
+ const { rebuildActiveIndex } = require('./lib/activeIndexRebuild.cjs');
88
+ const { activeIndexPath: promptSessionsActiveIndexPath } = require('./lib/epicMint.cjs');
89
+ const { allProjectCwds } = require('../../scripts/lib/activeSessions.cjs');
84
90
 
85
91
  let mainWindow = null;
86
92
  let rebooting = false;
@@ -296,6 +302,7 @@ function relaunchViaNpx() {
296
302
  async function rebootApp() {
297
303
  ptyManager.killAll();
298
304
  configMgr.closeAllWatchers();
305
+ closeAllProjectPagesWatchers();
299
306
  transcripts.closeAll();
300
307
  watchers.manager.killAll();
301
308
 
@@ -322,6 +329,7 @@ async function rebootApp() {
322
329
  chatRunner.attachWindow(mainWindow);
323
330
  promptSessionEvents.attachWindow(mainWindow);
324
331
  attachDocEditWindow(mainWindow);
332
+ attachProjectPagesWindow(mainWindow);
325
333
  rebooting = false;
326
334
  return;
327
335
  }
@@ -456,6 +464,11 @@ ipcMain.handle('app:launch-mode', () => ({
456
464
  // `claude mcp list`. Read-only, single in-flight call — no polling.
457
465
  ipcMain.handle('mcp:status', () => probeMcpStatus());
458
466
 
467
+ // Project Home's "Agent tools" block (PhAgentTools): the same catalog data
468
+ // GET /admin/mcp/catalog serves to external callers, read in-process here so
469
+ // the renderer never makes a direct HTTP call. Static data, no cwd needed.
470
+ ipcMain.handle('mcp:catalog', () => ({ ok: true, tools: MCP_TOOL_CATALOG, recipes: MCP_RECIPES }));
471
+
459
472
  // Agent Library nav page (Home face only): global `~/.claude/agents/*.md`
460
473
  // personas plus, per currently-open project tab, whether that project
461
474
  // overlays the same agent name at `<cwd>/.claude/agents/<name>.md`. Read-only.
@@ -494,12 +507,19 @@ ipcMain.handle('agents:get-persona-body', validated(schemas.agentsGetPersonaBody
494
507
  agentLibrary.getPersonaBody(payload)));
495
508
 
496
509
  // "Can this project actually delegate?" probe (PRD: delegation-readiness).
497
- // Structured data over the four preconditions for scheduler_create_prd being
498
- // in an agent's tool list at all — surfacing it in the UI is a sibling PRD.
499
- ipcMain.handle('app:delegation-readiness', validated(schemas.delegationReadinessCwd, (payload) =>
510
+ // Structured data over the preconditions for scheduler_create_prd being in an
511
+ // agent's tool list at all — surfacing it in the UI is a sibling PRD.
512
+ ipcMain.handle('app:delegation-readiness', validated(schemas.delegationReadinessCwd, async (payload) =>
500
513
  checkDelegationReadiness(payload)
501
514
  ));
502
515
 
516
+ // The one readiness check with a sanctioned one-press install. Session Manager
517
+ // standardizes on the REFERENCE approach (absolute path to this repo's guard
518
+ // script, never a vendored copy) — see installPrdWriteGuard's header.
519
+ ipcMain.handle('app:install-prd-write-guard', validated(schemas.delegationReadinessCwd, (payload) =>
520
+ installPrdWriteGuard(payload)
521
+ ));
522
+
503
523
  ipcMain.handle('app:engage-rules-path', () => process.env.SESSION_MANAGER_ENGAGE_RULES || null);
504
524
 
505
525
  // Boot diagnostics — renderer polls these to surface toasts when `claude` isn't
@@ -1152,6 +1172,7 @@ app.whenReady().then(async () => {
1152
1172
  chatRunner.attachWindow(mainWindow);
1153
1173
  promptSessionEvents.attachWindow(mainWindow);
1154
1174
  attachDocEditWindow(mainWindow);
1175
+ attachProjectPagesWindow(mainWindow);
1155
1176
  // Scheduler ownership (PRD 834): exactly one instance machine-wide runs
1156
1177
  // scheduler mutation (boot reconciliation, ticking, sweep, supervisor) and
1157
1178
  // the admin API. Electron's own single-instance lock is keyed by userData
@@ -1193,6 +1214,42 @@ app.whenReady().then(async () => {
1193
1214
  seedSchedulerMcp({ logger: console, writeLog: logs.writeLine }).catch((e) => {
1194
1215
  logs.writeLine({ scope: 'seed-scheduler-mcp', level: 'error', message: 'seed failed', meta: { error: e?.message } });
1195
1216
  });
1217
+ // Epic index self-heal: a project whose active-index.json is MISSING or
1218
+ // fails to parse has lost every open Epic's status unless the per-Epic
1219
+ // status mirror (epicStatusMirror.cjs) can rebuild it. Runs once per boot,
1220
+ // fire-and-forget, and only touches a project whose index reads unclean —
1221
+ // a clean index is never rewritten here. See activeIndexRebuild.cjs.
1222
+ try {
1223
+ for (const projectCwd of allProjectCwds()) {
1224
+ const indexPath = promptSessionsActiveIndexPath(projectCwd);
1225
+ let unclean = false;
1226
+ let parseError = null;
1227
+ if (!fs.existsSync(indexPath)) {
1228
+ unclean = true;
1229
+ } else {
1230
+ try {
1231
+ JSON.parse(fs.readFileSync(indexPath, 'utf8'));
1232
+ } catch (e) {
1233
+ unclean = true;
1234
+ parseError = e.message;
1235
+ }
1236
+ }
1237
+ if (!unclean) continue;
1238
+ try {
1239
+ const { rows, skipped } = rebuildActiveIndex(projectCwd);
1240
+ logs.writeLine({
1241
+ scope: 'active-index-rebuild',
1242
+ level: 'info',
1243
+ message: 'rebuilt active-index.json from status mirrors',
1244
+ meta: { cwd: projectCwd, parseError, restored: rows.length, skipped: skipped.length },
1245
+ });
1246
+ } catch (e) {
1247
+ logs.writeLine({ scope: 'active-index-rebuild', level: 'error', message: 'rebuild failed', meta: { cwd: projectCwd, error: e?.message } });
1248
+ }
1249
+ }
1250
+ } catch (e) {
1251
+ logs.writeLine({ scope: 'active-index-rebuild', level: 'error', message: 'boot sweep failed', meta: { error: e?.message } });
1252
+ }
1196
1253
  // History rollup finalize pass: deferred 30s past boot so it never competes
1197
1254
  // with first-paint, fire-and-forget (cron/offline refresh is PRD 651 — this
1198
1255
  // is just the one-shot in-app pass).
@@ -1292,6 +1349,7 @@ app.on('window-all-closed', () => {
1292
1349
  if (rebooting) return; // new window is about to be created
1293
1350
  ptyManager.killAll();
1294
1351
  configMgr.closeAllWatchers();
1352
+ closeAllProjectPagesWatchers();
1295
1353
  transcripts.closeAll();
1296
1354
  watchers.manager.killAll();
1297
1355
  if (process.platform !== 'darwin') app.quit();
@@ -1306,6 +1364,7 @@ app.on('before-quit', () => {
1306
1364
  supervisor.stopSupervisor();
1307
1365
  ptyManager.killAll();
1308
1366
  configMgr.closeAllWatchers();
1367
+ closeAllProjectPagesWatchers();
1309
1368
  transcripts.closeAll();
1310
1369
  watchers.manager.killAll();
1311
1370
  adminHttp.stop().catch(() => {});
@@ -10,6 +10,7 @@ const os = require('node:os');
10
10
  const path = require('node:path');
11
11
  const { PromptSessionSchema, EpicTagSchema, EpicSourceSchema, EpicIntakeSectionSchema } = require('./lib/promptSessionSchema.cjs');
12
12
  const { PrdWorkTypeSchema } = require('./lib/workTypeLibrary.cjs');
13
+ const { PERSONA_NAME_RE } = require('./agentLibrary.cjs');
13
14
  const { AgentPersonaSaveSchema } = require('./lib/agentPersonaSchema.cjs');
14
15
 
15
16
  // ──────────────────────────────────────────── PTY
@@ -224,6 +225,12 @@ const promptSessionsMergeToMain = z.object({
224
225
  epicId: z.string().regex(PROMPT_SESSION_ID_RE),
225
226
  branch: z.string().regex(/^sm-epic\/[A-Za-z0-9_-]{1,64}$/),
226
227
  dir: z.string().min(1).max(4096),
228
+ // Base-tree WIP paths carried into this worktree at creation time
229
+ // (gitWorktree.cjs's createWorktree, PRD 1094) — threaded through so
230
+ // integrateEpicBranch can apply the same carried-wip-only skip the job
231
+ // path already gets. Absent on a worktree created before this field
232
+ // existed, or one whose base tree was clean at creation.
233
+ carriedPaths: z.array(z.string().min(1).max(4096)).max(2000).optional(),
227
234
  });
228
235
 
229
236
  // Renderer entry points for the per-project "disable Epic worktree
@@ -299,9 +306,21 @@ const PRD_CREATE_SLUG_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
299
306
  // frontmatter-shaped lines into the body. Block newlines at the boundary
300
307
  // instead of trying to escape them later.
301
308
  const NO_NEWLINE_RE = /^[^\r\n]*$/;
309
+ // WHO executes a PRD — a persona name (agentLibrary.cjs's PERSONA_NAME_RE),
310
+ // distinct from `tag` (the WORK TYPE, above). Not a closed enum here: the
311
+ // Agent Library is user-editable, so the actual FK check (does this name
312
+ // resolve to a readable persona file) happens at write time in
313
+ // prdCreate.cjs's createPrd via prdAgentType.cjs's assertAgentTypeWritable —
314
+ // this schema only enforces the name SHAPE.
315
+ const PrdAgentTypeSchema = z.string().regex(PERSONA_NAME_RE, 'agent name must be lowercase, hyphenated (e.g. "dev-lead")');
302
316
  const schedulerCreatePrd = z.object({
303
317
  title: z.string().min(1).max(200).regex(NO_NEWLINE_RE, 'must not contain newlines'),
304
- cwd: z.string().min(1).max(4096).regex(NO_NEWLINE_RE, 'must not contain newlines'),
318
+ // Optional (PRD: worktree-cwd Epic-lookup hazard) — when omitted, prdCreate.cjs's
319
+ // createPrd resolves the real project cwd server-side via
320
+ // projectRootResolve.cjs's resolveProjectContext, using originClaudeSessionId/
321
+ // sourcePromptId/originProjectRoot. A call with none of those still fails with
322
+ // a clear "cwd is required" error.
323
+ cwd: z.string().min(1).max(4096).regex(NO_NEWLINE_RE, 'must not contain newlines').optional(),
305
324
  estimateMinutes: z.number().int().min(1).max(100000),
306
325
  goal: z.string().min(1).max(20000),
307
326
  acceptanceCriteria: z.array(z.string().min(1).max(2000)).min(1).max(100),
@@ -333,6 +352,13 @@ const schedulerCreatePrd = z.object({
333
352
  // within an Epic's own chat session joins that Epic even if the tool call
334
353
  // forgot to pass sourcePromptId explicitly.
335
354
  originClaudeSessionId: z.string().min(1).max(128).regex(NO_NEWLINE_RE, 'must not contain newlines').optional(),
355
+ // Trusted project-root hint (SM_PROJECT_ROOT env, stamped by chatRunner.cjs/
356
+ // pty.cjs/scheduler.cjs's job spawn onto the child that ultimately calls this
357
+ // route, forwarded by scheduler-mcp-server.cjs as originProjectRoot) — never
358
+ // written to frontmatter; projectRootResolve.cjs's resolveProjectContext
359
+ // treats it as a higher-priority hint than the caller-supplied cwd, but
360
+ // still loses to a resolved Epic's own project cwd.
361
+ originProjectRoot: z.string().min(1).max(4096).regex(NO_NEWLINE_RE, 'must not contain newlines').optional(),
336
362
  // User-selected work-type tag (PRD 774; independence from the parent
337
363
  // Epic's own tag confirmed PRD 1041) — deterministic, never LLM-classified.
338
364
  // Epic tag and PRD tag draw on the same WorkType vocabulary
@@ -340,11 +366,27 @@ const schedulerCreatePrd = z.object({
340
366
  // excluded here for the same reason adminPrdFrontmatterPatch excludes it
341
367
  // below — a discussion-tagged Epic never reaches PRD authoring.
342
368
  tag: PrdWorkTypeSchema.optional(),
369
+ // WHO executes this PRD (persona name, `~/.claude/agents/<name>.md`) —
370
+ // independent of `tag` above. Optional: prdCreate.cjs's buildPrdBody fills
371
+ // in DEFAULT_PRD_AGENT_TYPE ('dev-lead') when omitted.
372
+ agentType: PrdAgentTypeSchema.optional(),
373
+ // Opt-in exclusive-lease flag (PRD 1107): the scheduler dispatches this PRD
374
+ // only once zero other jobs are running machine-wide, and holds every other
375
+ // job off the whole session-slot pool while it runs — for a PRD whose
376
+ // acceptance criteria are wall-clock/timing measurements (frame time,
377
+ // performance fences) that CPU contention from sibling jobs would otherwise
378
+ // invalidate. See prdCreate.cjs's buildPrdBody and schedulerBatch.cjs's
379
+ // pickNextBatch.
380
+ quietMachine: z.boolean().optional(),
343
381
  });
344
382
 
345
383
  // Bulk archive: slug list, capped to limit unbounded retag/archive payloads.
384
+ // Optional cwd narrows every slug's directory search to one project (same
385
+ // role as scheduler_get_prd/scheduler_update_prd's cwd hint) — omitted, the
386
+ // search still spans every candidate PRD dir machine-wide.
346
387
  const scheduleArchivePrd = z.object({
347
388
  slugs: z.array(z.string().regex(SCHEDULE_SLUG_RE)).min(1).max(500),
389
+ cwd: z.string().min(1).max(4096).optional(),
348
390
  });
349
391
 
350
392
  const scheduleRetagItem = z.object({
@@ -402,12 +444,17 @@ const adminPrdFrontmatterPatch = z.object({
402
444
  // discussion-tagged Epic never reaches PRD authoring) — see
403
445
  // workTypeLibrary.cjs's PrdWorkTypeSchema.
404
446
  tag: PrdWorkTypeSchema.optional(),
447
+ // WHO executes this PRD — patchable after creation too, same as `tag`.
448
+ agentType: PrdAgentTypeSchema.optional(),
405
449
  // Provenance stamp (PRD-authoring lockdown). Free-text rather than an enum
406
450
  // — 'scheduler-api' (create-time) and 'legacy-adopted' (migration/manual
407
451
  // adopt) are the two values this codebase writes today, but the field
408
452
  // itself is descriptive, not a closed set.
409
453
  createdVia: z.string().min(1).max(64).regex(NO_NEWLINE_RE, 'must not contain newlines').optional(),
410
454
  issuedAt: z.string().min(1).max(64).regex(NO_NEWLINE_RE, 'must not contain newlines').optional(),
455
+ // Opt-in exclusive-lease flag (PRD 1107) — patchable after creation too,
456
+ // same as every other recognized key here.
457
+ quietMachine: z.boolean().optional(),
411
458
  });
412
459
 
413
460
  const adminUpdatePrd = z.object({
@@ -425,6 +472,7 @@ const adminUpdatePrd = z.object({
425
472
 
426
473
  const adminCancelJob = z.object({
427
474
  slug: z.string().regex(SCHEDULE_SLUG_RE),
475
+ cwd: z.string().min(1).max(4096).optional(),
428
476
  });
429
477
 
430
478
  // ──────────────────────────────────────────── Projects
@@ -565,6 +613,23 @@ const projectPagesCwd = z.object({
565
613
  cwd: z.string().min(1).max(4096),
566
614
  }).strict();
567
615
 
616
+ // ──────────────────────────────────────────── Project Home admin routes
617
+ // (PRD: project-home-admin-routes). Same validation split as
618
+ // projectPagesCwd above: real path validation is projectRootResolve.cjs +
619
+ // config.cjs's validatePath at first fs access.
620
+ const projectHomeAdminCwdQuery = z.object({
621
+ cwd: z.string().min(1).max(4096),
622
+ }).strict();
623
+ const projectHomeAdminValidateSummaryBody = z.object({
624
+ cwd: z.string().min(1).max(4096),
625
+ summary: z.unknown(),
626
+ }).strict();
627
+ const projectHomeAdminRenderBody = z.object({
628
+ cwd: z.string().min(1).max(4096),
629
+ summary: z.unknown(),
630
+ picks: z.unknown(),
631
+ }).strict();
632
+
568
633
  // ──────────────────────────────────────────── Host on Bilko.run
569
634
  // Same validation split: real path validation is config.cjs's validatePath
570
635
  // at first fs access in bilkoHost.cjs — these schemas only bound wire shape.
@@ -973,6 +1038,9 @@ module.exports = {
973
1038
  memoryStale,
974
1039
  projectBriefCwd,
975
1040
  projectPagesCwd,
1041
+ projectHomeAdminCwdQuery,
1042
+ projectHomeAdminValidateSummaryBody,
1043
+ projectHomeAdminRenderBody,
976
1044
  bilkoHostCwd,
977
1045
  bilkoHostPrepareBundle,
978
1046
  bilkoHostAddDocument,