claude-code-session-manager 0.86.0 → 0.88.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 (335) hide show
  1. package/README.md +26 -53
  2. package/dist/assets/AgentLibrary-BZ6IY2g1.js +3 -0
  3. package/dist/assets/{DataModel-Q4jhl24R.js → DataModel-dAoGAODX.js} +1 -1
  4. package/dist/assets/{History-Cj2FejEo.js → History-CF157HyX.js} +2 -2
  5. package/dist/assets/{Hooks-CaelQI6t.js → Hooks-CCPPipBk.js} +3 -3
  6. package/dist/assets/{HostBilko--v7cMR8I.js → HostBilko-Dlmz2m06.js} +1 -1
  7. package/dist/assets/{Library-DgI9oCCZ.js → Library-BaXcboWX.js} +1 -1
  8. package/dist/assets/{ListDetail-DYUZN-x-.js → ListDetail-CoUCzG5S.js} +1 -1
  9. package/dist/assets/MarkdownEditor-DTg3JlOT.js +1 -0
  10. package/dist/assets/{McpServers-ypCYURh3.js → McpServers-DdjY1sA3.js} +2 -2
  11. package/dist/assets/{Memory-C2qYp-3M.js → Memory-BJ3Jlw0C.js} +4 -4
  12. package/dist/assets/{Panel-Cj2kw-Zv.js → Panel-ygRSgc41.js} +1 -1
  13. package/dist/assets/Permissions-m0KAnccF.js +3 -0
  14. package/dist/assets/{Plugins-C1Vj8_dU.js → Plugins-CJRl6B3Q.js} +2 -2
  15. package/dist/assets/{ProvenanceBadge-DczPNM5U.js → ProvenanceBadge-w8cnlVvW.js} +1 -1
  16. package/dist/assets/{SaveBar-Cd_7U6Gb.js → SaveBar-BpyPcUIk.js} +1 -1
  17. package/dist/assets/Scheduler-D2ppxSfy.js +14 -0
  18. package/dist/assets/{ScopeSwitcher-DVSyI44-.js → ScopeSwitcher-EyuIwt-j.js} +1 -1
  19. package/dist/assets/Settings-BHSGGFEc.js +3 -0
  20. package/dist/assets/{SkillReferenceGraph-CUv1_Q2c.js → SkillReferenceGraph-BNaZ2JCF.js} +1 -1
  21. package/dist/assets/{Skills-C_YHkAy-.js → Skills-Dl3q75QV.js} +2 -2
  22. package/dist/assets/{SystemPrompt-B8R7T9xn.js → SystemPrompt-Bg6F-Cyp.js} +1 -1
  23. package/dist/assets/{TagLibrary-dj9YHWyy.js → TagLibrary-BSutflCy.js} +1 -1
  24. package/dist/assets/{TiptapBody-DnSBUjHE.js → TiptapBody-CXih4xrc.js} +1 -1
  25. package/dist/assets/{Toggle-CjV_BJn6.js → Toggle-2qtUHhA9.js} +1 -1
  26. package/dist/assets/{index-CXFQIPhO.js → index-B3emy2oI.js} +583 -583
  27. package/dist/assets/{index-CDo9xBR9.css → index-BHTX4OTc.css} +1 -1
  28. package/dist/assets/settingsSchema-CdzxaqwM.js +3 -0
  29. package/dist/assets/{whisperWorker-Dbia1OpC.js → whisperWorker-C7ZGQwKg.js} +7 -7
  30. package/dist/index.html +2 -2
  31. package/dist/vad/ort-wasm-simd-threaded.asyncify.mjs +106 -110
  32. package/dist/vad/ort-wasm-simd-threaded.asyncify.wasm +0 -0
  33. package/dist/vad/ort-wasm-simd-threaded.jsep.mjs +98 -98
  34. package/dist/vad/ort-wasm-simd-threaded.jsep.wasm +0 -0
  35. package/dist/vad/ort-wasm-simd-threaded.jspi.mjs +99 -102
  36. package/dist/vad/ort-wasm-simd-threaded.jspi.wasm +0 -0
  37. package/dist/vad/ort-wasm-simd-threaded.mjs +46 -46
  38. package/dist/vad/ort-wasm-simd-threaded.wasm +0 -0
  39. package/package.json +14 -17
  40. package/plugins/CLAUDE.md +61 -0
  41. package/plugins/session-manager-dev/.claude-plugin/plugin.json +1 -1
  42. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +1 -1
  43. package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +1 -1
  44. package/scripts/README.md +59 -0
  45. package/scripts/audit-ops-hygiene.cjs +350 -0
  46. package/scripts/hooks/guard-destructive-git.cjs +10 -34
  47. package/scripts/hooks/guard-inline-implementation.cjs +14 -8
  48. package/scripts/hooks/guard-prd-writes.cjs +7 -36
  49. package/scripts/hooks/guard-self-schedule.cjs +175 -0
  50. package/scripts/ops-sweep.cjs +355 -0
  51. package/scripts/scheduler-mcp-server.cjs +35 -71
  52. package/src/main/__tests__/agentModelResolve.test.cjs +100 -9
  53. package/src/main/__tests__/bilkoHost-integration.test.cjs +3 -3
  54. package/src/main/__tests__/broadcastCoalescer.test.cjs +18 -0
  55. package/src/main/__tests__/chat-cancel-terminal.test.cjs +6 -9
  56. package/src/main/__tests__/chat-exit-close-race.test.cjs +3 -3
  57. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +4 -5
  58. package/src/main/__tests__/chat-queue.test.cjs +2 -2
  59. package/src/main/__tests__/chat-stop-signal.test.cjs +2 -2
  60. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +77 -0
  61. package/src/main/__tests__/dod-batchkey.test.cjs +2 -2
  62. package/src/main/__tests__/dod-drain-hook.test.cjs +2 -2
  63. package/src/main/__tests__/dod-report.test.cjs +2 -2
  64. package/src/main/__tests__/dod-reverify.test.cjs +2 -2
  65. package/src/main/__tests__/epicMint.test.cjs +4 -4
  66. package/src/main/__tests__/exchanges.test.cjs +2 -2
  67. package/src/main/__tests__/extractJson.test.cjs +2 -2
  68. package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
  69. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +556 -0
  70. package/src/main/__tests__/health-build-freshness.test.cjs +39 -0
  71. package/src/main/__tests__/health-delegation-chain.test.cjs +17 -2
  72. package/src/main/__tests__/health-queue-dispatch.test.cjs +58 -7
  73. package/src/main/__tests__/health-tick-liveness.test.cjs +11 -3
  74. package/src/main/__tests__/health-usage-poller.test.cjs +70 -23
  75. package/src/main/__tests__/historyRollup.test.cjs +2 -2
  76. package/src/main/__tests__/kg-augment.test.cjs +2 -2
  77. package/src/main/__tests__/mcpStatus.test.cjs +2 -2
  78. package/src/main/__tests__/memoryAggregate.test.cjs +1 -1
  79. package/src/main/__tests__/memoryStale.test.cjs +1 -1
  80. package/src/main/__tests__/needsReviewLedger.test.cjs +162 -0
  81. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +28 -4
  82. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +48 -4
  83. package/src/main/__tests__/prd-group-allocator.test.cjs +2 -2
  84. package/src/main/__tests__/prdAdminRouteParity.test.cjs +2 -0
  85. package/src/main/__tests__/prdAdminRoutes.test.cjs +14 -2
  86. package/src/main/__tests__/prdAuthoringSeed.test.cjs +39 -0
  87. package/src/main/__tests__/prdCreateDisposition.test.cjs +201 -0
  88. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +125 -0
  89. package/src/main/__tests__/prdLocations.test.cjs +100 -2
  90. package/src/main/__tests__/prdLocationsArchived.test.cjs +50 -1
  91. package/src/main/__tests__/prdSetDisposition.test.cjs +222 -0
  92. package/src/main/__tests__/proc-role-env.test.cjs +125 -0
  93. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +304 -0
  94. package/src/main/__tests__/procname-sm-processes.test.cjs +127 -0
  95. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +81 -401
  96. package/src/main/__tests__/projectPages.test.cjs +63 -149
  97. package/src/main/__tests__/queue-health-verdict.test.cjs +170 -0
  98. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +117 -20
  99. package/src/main/__tests__/queue-starvation-per-project.test.cjs +14 -2
  100. package/src/main/__tests__/queueHistory.test.cjs +65 -2
  101. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +38 -3
  102. package/src/main/__tests__/reconcileTiming.test.cjs +135 -0
  103. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +181 -0
  104. package/src/main/__tests__/runVerify.test.cjs +5 -5
  105. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +3 -3
  106. package/src/main/__tests__/scheduleJobTransitions.test.cjs +103 -3
  107. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +143 -0
  108. package/src/main/__tests__/scheduler-autofix-select.test.cjs +2 -2
  109. package/src/main/__tests__/scheduler-autopromote.test.cjs +2 -2
  110. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +3 -5
  111. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +79 -98
  112. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +121 -0
  113. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +43 -0
  114. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +161 -0
  115. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +58 -0
  116. package/src/main/__tests__/scheduler-epic-digest.test.cjs +3 -5
  117. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +2 -2
  118. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +119 -0
  119. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +390 -0
  120. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +80 -0
  121. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +25 -18
  122. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +4 -4
  123. package/src/main/__tests__/scheduler-job-budget.test.cjs +172 -0
  124. package/src/main/__tests__/scheduler-launch-failure.test.cjs +3 -5
  125. package/src/main/__tests__/scheduler-looks-done.test.cjs +119 -10
  126. package/src/main/__tests__/scheduler-manual-pause.test.cjs +118 -0
  127. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +26 -3
  128. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +164 -0
  129. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +17 -2
  130. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +3 -5
  131. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +41 -6
  132. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +62 -6
  133. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +3 -5
  134. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +182 -3
  135. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +87 -0
  136. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +100 -0
  137. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +3 -3
  138. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +4 -4
  139. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +110 -0
  140. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +168 -0
  141. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +18 -8
  142. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +245 -0
  143. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +81 -0
  144. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +2 -2
  145. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +172 -0
  146. package/src/main/__tests__/scheduler-transient-failure.test.cjs +2 -2
  147. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +51 -11
  148. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +3 -5
  149. package/src/main/__tests__/usageSingleFlight.test.cjs +157 -0
  150. package/src/main/__tests__/workTypeLibrary.test.cjs +1 -1
  151. package/src/main/bilkoHost.cjs +16 -7
  152. package/src/main/build-info.json +8 -0
  153. package/src/main/chatRunner.cjs +12 -7
  154. package/src/main/config.cjs +2 -3
  155. package/src/main/docEdit.cjs +4 -2
  156. package/src/main/health.cjs +268 -52
  157. package/src/main/heapSnapshot.cjs +2 -2
  158. package/src/main/historyAggregator.cjs +6 -1
  159. package/src/main/index.cjs +152 -50
  160. package/src/main/ipcSchemas.cjs +53 -17
  161. package/src/main/lib/__tests__/active-sessions.test.cjs +251 -0
  162. package/src/main/lib/__tests__/auditLog.test.cjs +38 -0
  163. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +107 -0
  164. package/src/main/lib/__tests__/buildIdentity.test.cjs +121 -0
  165. package/src/main/lib/__tests__/cwdClassify.test.cjs +111 -0
  166. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +95 -0
  167. package/src/main/lib/__tests__/delegationReadiness.test.cjs +323 -44
  168. package/src/main/lib/__tests__/dispatchLoop.test.cjs +63 -0
  169. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +239 -0
  170. package/src/main/lib/__tests__/gateFixtures.json +20 -0
  171. package/src/main/lib/__tests__/gitWorktree.test.cjs +97 -1
  172. package/src/main/lib/__tests__/guardShims.test.cjs +151 -0
  173. package/src/main/lib/__tests__/instanceLock.test.cjs +93 -7
  174. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +78 -0
  175. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +11 -0
  176. package/src/main/lib/__tests__/localAdminHttp.test.cjs +1 -1
  177. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +6 -1
  178. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +5 -5
  179. package/src/main/lib/__tests__/prdDisposition.test.cjs +224 -0
  180. package/src/main/lib/__tests__/procIdentity.test.cjs +119 -0
  181. package/src/main/lib/__tests__/procName.test.cjs +92 -0
  182. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +67 -0
  183. package/src/main/lib/__tests__/reaperHelpers.test.cjs +179 -1
  184. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +6 -7
  185. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +37 -204
  186. package/src/main/lib/__tests__/schedulerPaths.test.cjs +226 -0
  187. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +94 -0
  188. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +56 -0
  189. package/src/main/lib/__tests__/sessionSlots.test.cjs +45 -0
  190. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +1 -1
  191. package/src/main/lib/__tests__/upgradeDrain.test.cjs +130 -0
  192. package/src/main/lib/__tests__/usageCircuit.test.cjs +285 -0
  193. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +375 -0
  194. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +266 -0
  195. package/src/main/lib/activeIndexRebuild.cjs +1 -3
  196. package/{scripts → src/main}/lib/activeSessions.cjs +67 -89
  197. package/src/main/lib/adoptedRunSupervisor.cjs +136 -0
  198. package/src/main/lib/agentModelResolve.cjs +94 -24
  199. package/src/main/lib/agentPersonaSchema.cjs +2 -2
  200. package/src/main/lib/auditLog.cjs +30 -5
  201. package/src/main/lib/bootSelfHeal.cjs +88 -0
  202. package/src/main/lib/buildIdentity.cjs +113 -0
  203. package/src/main/lib/classifyPromptTicket.cjs +3 -2
  204. package/src/main/lib/claudeBin.cjs +37 -2
  205. package/src/main/lib/cleanEnv.cjs +27 -1
  206. package/src/main/lib/credentials.cjs +4 -2
  207. package/src/main/lib/cwdClassify.cjs +185 -0
  208. package/src/main/lib/definitionOfDone.cjs +296 -52
  209. package/src/main/lib/delegationReadiness.cjs +290 -225
  210. package/src/main/lib/dispatchLoop.cjs +40 -0
  211. package/src/main/lib/effectiveModelInfo.cjs +332 -0
  212. package/src/main/lib/ephemeralCwd.cjs +7 -28
  213. package/src/main/lib/epicMint.cjs +3 -3
  214. package/src/main/lib/gitWorktree.cjs +136 -19
  215. package/src/main/lib/guardShims.cjs +156 -0
  216. package/src/main/lib/historyRollup.cjs +6 -7
  217. package/src/main/lib/instanceLock.cjs +32 -5
  218. package/src/main/lib/jobDirtFilter.cjs +7 -2
  219. package/src/main/lib/jobSupervisorRecord.cjs +147 -0
  220. package/src/main/lib/jobWorktreeBootLive.cjs +9 -5
  221. package/src/main/lib/launchFailure.cjs +2 -1
  222. package/src/main/lib/localAdminHttp.cjs +9 -27
  223. package/src/main/lib/mcpToolCatalog.cjs +33 -78
  224. package/src/main/lib/needsReviewLedger.cjs +205 -0
  225. package/src/main/lib/opsErrorLog.cjs +1 -1
  226. package/src/main/lib/opsOwnership.cjs +27 -19
  227. package/src/main/lib/prdAuthoringSeed.cjs +36 -0
  228. package/src/main/lib/prdCreate.cjs +56 -1
  229. package/src/main/lib/prdDisposition.cjs +199 -0
  230. package/src/main/lib/prdFrontmatter.cjs +8 -2
  231. package/src/main/lib/prdLocations.cjs +214 -45
  232. package/src/main/lib/procIdentity.cjs +126 -0
  233. package/src/main/lib/procName.cjs +98 -0
  234. package/src/main/lib/projectHomeAdminRoutes.cjs +56 -327
  235. package/src/main/lib/projectRootResolve.cjs +1 -1
  236. package/src/main/lib/queueHistory.cjs +27 -8
  237. package/src/main/lib/queueStore.cjs +63 -38
  238. package/src/main/lib/quietMachineLease.cjs +19 -2
  239. package/src/main/lib/reaperHelpers.cjs +181 -15
  240. package/src/main/lib/reservationExpiry.cjs +30 -0
  241. package/src/main/lib/runClaudeP.cjs +4 -2
  242. package/src/main/lib/runLogRetention.cjs +3 -2
  243. package/src/main/lib/scheduleJobSchema.cjs +10 -2
  244. package/src/main/lib/scheduleJobTransitions.cjs +60 -0
  245. package/src/main/lib/schedulerBatch.cjs +27 -2
  246. package/src/main/lib/schedulerConfig.cjs +24 -0
  247. package/src/main/lib/schedulerPaths.cjs +175 -0
  248. package/src/main/lib/schedulerRuntimeState.cjs +59 -0
  249. package/src/main/lib/sessionSlots.cjs +47 -9
  250. package/src/main/lib/smProcNames.cjs +51 -0
  251. package/src/main/lib/upgradeDrain.cjs +188 -0
  252. package/src/main/lib/usageCircuit.cjs +206 -0
  253. package/{scripts → src/main}/lib/watchdogHelpers.cjs +71 -38
  254. package/src/main/lib/withTimeout.cjs +33 -0
  255. package/src/main/mcpStatus.cjs +4 -2
  256. package/src/main/pluginInstall.cjs +6 -2
  257. package/src/main/projectPages.cjs +41 -145
  258. package/src/main/pty.cjs +8 -1
  259. package/src/main/queueOps.cjs +10 -10
  260. package/src/main/runVerify.cjs +69 -3
  261. package/src/main/scheduler/prdParser.cjs +13 -0
  262. package/src/main/scheduler.cjs +2841 -482
  263. package/src/main/seedAgentPersonas.cjs +1 -1
  264. package/src/main/seedDevPlugin.cjs +1 -1
  265. package/src/main/seedSchedulerMcp.cjs +7 -6
  266. package/src/main/seedStatus.cjs +1 -1
  267. package/src/main/supervisor.cjs +5 -3
  268. package/src/main/templates/PRD_AUTHORING.md +50 -0
  269. package/src/main/usage.cjs +140 -58
  270. package/src/preload/api.d.ts +110 -65
  271. package/src/preload/index.cjs +12 -4
  272. package/src/seed/agents/project-home-builder.md +31 -48
  273. package/dist/assets/AgentLibrary-DTFL7y8G.js +0 -3
  274. package/dist/assets/MarkdownEditor-DCIubYWf.js +0 -1
  275. package/dist/assets/Permissions-BiYZNGYW.js +0 -3
  276. package/dist/assets/Scheduler-DcLBiJBq.js +0 -14
  277. package/dist/assets/Settings-Cv-pRyms.js +0 -3
  278. package/dist/assets/settingsSchema-BJVciriw.js +0 -3
  279. package/screenshots/.gitkeep +0 -0
  280. package/screenshots/README-screenshots.md +0 -13
  281. package/scripts/project-pages-logic/dist/logic.cjs +0 -4709
  282. package/scripts/render-project-pages/dist/renderer.cjs +0 -18900
  283. package/scripts/render-project-pages.cjs +0 -70
  284. package/scripts/validate-project-pages-summary.cjs +0 -62
  285. package/src/main/lib/projectPageSummarySchema.cjs +0 -181
  286. package/src/main/teams.cjs +0 -95
  287. package/src/main/templates/project-pages-catalog.json +0 -741
  288. package/src/main/templates/project-pages-default-home.html +0 -123
  289. package/src/main/templates/project-pages-pipeline.md +0 -417
  290. package/src/seed/prompts/code-review/ac-coverage-check.md +0 -8
  291. package/src/seed/prompts/code-review/correctness-only.md +0 -8
  292. package/src/seed/prompts/code-review/full-spectrum-high.md +0 -8
  293. package/src/seed/prompts/code-review/hallucination-check.md +0 -8
  294. package/src/seed/prompts/code-review/public-api-compat.md +0 -8
  295. package/src/seed/prompts/code-review/readability-naming.md +0 -8
  296. package/src/seed/prompts/debugging/bug-as-failing-test.md +0 -8
  297. package/src/seed/prompts/debugging/git-bisect-regression.md +0 -8
  298. package/src/seed/prompts/debugging/instrument-intermittent-bug.md +0 -8
  299. package/src/seed/prompts/debugging/localize-pipeline-failure.md +0 -8
  300. package/src/seed/prompts/debugging/reproduce-then-diagnose.md +0 -8
  301. package/src/seed/prompts/documentation/adr-from-change.md +0 -8
  302. package/src/seed/prompts/documentation/module-readme.md +0 -8
  303. package/src/seed/prompts/documentation/onboarding-plan.md +0 -8
  304. package/src/seed/prompts/documentation/refresh-claude-md.md +0 -8
  305. package/src/seed/prompts/documentation/tsdoc-public-exports.md +0 -8
  306. package/src/seed/prompts/git-pr/conventional-commit.md +0 -8
  307. package/src/seed/prompts/git-pr/draft-pr-title-body.md +0 -8
  308. package/src/seed/prompts/git-pr/pre-commit-safety-sweep.md +0 -8
  309. package/src/seed/prompts/git-pr/release-notes-block.md +0 -8
  310. package/src/seed/prompts/git-pr/split-large-pr.md +0 -8
  311. package/src/seed/prompts/performance/bundle-startup-audit.md +0 -8
  312. package/src/seed/prompts/performance/complexity-audit.md +0 -8
  313. package/src/seed/prompts/performance/cpu-profile-hot-path.md +0 -8
  314. package/src/seed/prompts/performance/db-query-plan-review.md +0 -8
  315. package/src/seed/prompts/performance/memory-leak-hunt.md +0 -8
  316. package/src/seed/prompts/qa/api-contract-tests.md +0 -8
  317. package/src/seed/prompts/qa/e2e-critical-path.md +0 -8
  318. package/src/seed/prompts/qa/failing-test-for-bug.md +0 -8
  319. package/src/seed/prompts/qa/find-missing-test-coverage.md +0 -8
  320. package/src/seed/prompts/qa/stabilize-flaky-test.md +0 -8
  321. package/src/seed/prompts/qa/tdd-red-first.md +0 -8
  322. package/src/seed/prompts/qa/visual-regression-review.md +0 -8
  323. package/src/seed/prompts/qa/wcag-axe-scan.md +0 -8
  324. package/src/seed/prompts/refactoring/dead-code-sweep.md +0 -8
  325. package/src/seed/prompts/refactoring/extract-duplicated-pattern.md +0 -8
  326. package/src/seed/prompts/refactoring/modernize-legacy-file.md +0 -8
  327. package/src/seed/prompts/refactoring/reduce-cyclomatic-complexity.md +0 -8
  328. package/src/seed/prompts/refactoring/tighten-module-boundaries.md +0 -8
  329. package/src/seed/prompts/security/authz-audit.md +0 -8
  330. package/src/seed/prompts/security/crypto-correctness.md +0 -8
  331. package/src/seed/prompts/security/cwe-top-25-hunt.md +0 -8
  332. package/src/seed/prompts/security/dependency-audit.md +0 -8
  333. package/src/seed/prompts/security/ipc-boundary-hardening.md +0 -8
  334. package/src/seed/prompts/security/owasp-top-10-staged-diff.md +0 -8
  335. package/src/seed/prompts/security/secret-credential-scan.md +0 -8
@@ -16,14 +16,21 @@
16
16
 
17
17
  'use strict';
18
18
 
19
- import { test } from 'vitest';
19
+ import { test, afterAll } from 'vitest';
20
20
  const assert = require('node:assert/strict');
21
21
  const fs = require('node:fs');
22
22
  const os = require('node:os');
23
23
  const path = require('node:path');
24
24
 
25
25
  // HOME before require: every state path is baked from os.homedir() at load.
26
- process.env.HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'starve-per-project-test-'));
26
+ // beforeAll runs AFTER module evaluation, so this assignment must stay at
27
+ // top level — see pollLoop-dispatch-on-failure.test.cjs:40-56 for the
28
+ // beforeAll/afterAll pattern this restore mirrors.
29
+ const originalHome = process.env.HOME;
30
+ const tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'starve-per-project-test-'));
31
+ process.env.HOME = tmpHome;
32
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'session-manager'), { recursive: true });
33
+ fs.mkdirSync(path.join(tmpHome, '.claude', 'projects'), { recursive: true });
27
34
 
28
35
  const {
29
36
  classifyQueueStarvation,
@@ -31,6 +38,11 @@ const {
31
38
  QUEUE_STARVATION_MS,
32
39
  } = require('../scheduler.cjs');
33
40
 
41
+ afterAll(() => {
42
+ process.env.HOME = originalHome;
43
+ fs.rmSync(tmpHome, { recursive: true, force: true });
44
+ });
45
+
34
46
  const PROJECT_A = '/home/bilko/Projects/starry-night-ships';
35
47
  const PROJECT_B = '/home/bilko/Projects/session-manager';
36
48
  const NOW = Date.parse('2026-09-12T20:00:00.000Z');
@@ -53,11 +53,11 @@ beforeEach(() => {
53
53
  // Fresh require per test so HISTORY_PATH's target file starts clean.
54
54
  delete require.cache[require.resolve('../lib/queueHistory.cjs')];
55
55
  queueHistory = require('../lib/queueHistory.cjs');
56
- try { fs.unlinkSync(queueHistory.HISTORY_PATH); } catch { /* ok if absent */ }
56
+ try { fs.unlinkSync(queueHistory.historyPath()); } catch { /* ok if absent */ }
57
57
  });
58
58
 
59
59
  afterEach(() => {
60
- try { fs.unlinkSync(queueHistory.HISTORY_PATH); } catch { /* ok if absent */ }
60
+ try { fs.unlinkSync(queueHistory.historyPath()); } catch { /* ok if absent */ }
61
61
  if (prevOverride === undefined) delete process.env.SM_HISTORY_PATH_OVERRIDE;
62
62
  else process.env.SM_HISTORY_PATH_OVERRIDE = prevOverride;
63
63
  });
@@ -163,6 +163,25 @@ test('appendHistory dedupes by slug+runId against what is already on disk', asyn
163
163
  expect(read.length).toBe(1);
164
164
  });
165
165
 
166
+ test('appendHistory: a needs_review ledger line and a terminal row sharing slug+runId do NOT dedupe against each other', async () => {
167
+ // An entry and its resolution line (needsReviewLedger.cjs) deliberately
168
+ // reference the SAME runId as each other — and a slug's terminal archive
169
+ // row can later reuse that same runId too (same run, different `kind`).
170
+ // jobKey must bucket by kind or the second/third write looks like a
171
+ // crash-replay of the first and silently never lands.
172
+ const entryLine = { kind: 'needs_review_entry', slug: 'ledger-dup', runId: 'shared-run', at: new Date(NOW).toISOString(), reason: 'uncommitted_changes', source: 'spawnJob:finalize' };
173
+ const resolutionLine = { kind: 'needs_review_resolution', slug: 'ledger-dup', runId: 'shared-run', at: new Date(NOW).toISOString(), resolvedTo: 'completed', ladderRung: 'reverify', source: 'reverifyNeedsReview:heal', dwellMs: 1000 };
174
+ const terminalLine = old({ slug: 'ledger-dup', runId: 'shared-run' });
175
+ const r1 = await queueHistory.appendHistory([entryLine]);
176
+ const r2 = await queueHistory.appendHistory([resolutionLine]);
177
+ const r3 = await queueHistory.appendHistory([terminalLine]);
178
+ expect(r1.appended).toBe(1);
179
+ expect(r2.appended).toBe(1);
180
+ expect(r3.appended).toBe(1);
181
+ const read = await queueHistory.readHistory({ limit: 10 });
182
+ expect(read.filter((j) => j.slug === 'ledger-dup')).toHaveLength(3);
183
+ });
184
+
166
185
  test('readHistory respects limit and returns [] when file is absent', async () => {
167
186
  const empty = await queueHistory.readHistory({ limit: 5 });
168
187
  expect(empty).toEqual([]);
@@ -220,6 +239,18 @@ test('selectHistoryJobs: default historyEntries=[] preserves old call shape', ()
220
239
  expect(merged.map((j) => j.slug)).toEqual(['01-fresh']);
221
240
  });
222
241
 
242
+ test('selectHistoryJobs: needs_review ledger lines (kind !== "terminal") are excluded from the merged result', () => {
243
+ // The History view renders ScheduleJob-shaped rows (status, exitCode, ...)
244
+ // — a needs_review_entry/needs_review_resolution line (needsReviewLedger.cjs)
245
+ // has neither, and must not slip through as a statusless row in that table.
246
+ const { selectHistoryJobs } = require('../scheduler.cjs');
247
+ const terminalEntry = old({ slug: 'hist-1', runId: 'rhist1' });
248
+ const ledgerEntry = { kind: 'needs_review_entry', slug: 'ledger-1', runId: 'rledger1', at: new Date(NOW).toISOString(), reason: 'uncommitted_changes', source: 'spawnJob:finalize' };
249
+ const ledgerResolution = { kind: 'needs_review_resolution', slug: 'ledger-1', runId: 'rledger1', at: new Date(NOW).toISOString(), resolvedTo: 'completed', ladderRung: 'reverify', source: 'reverifyNeedsReview:heal', dwellMs: 500 };
250
+ const merged = selectHistoryJobs([], 10, [terminalEntry, ledgerEntry, ledgerResolution]);
251
+ expect(merged.map((j) => j.slug)).toEqual(['hist-1']);
252
+ });
253
+
223
254
  // ---------- historyTerminalBySlug ----------
224
255
  //
225
256
  // Guards against scheduler.cjs's reconcile() resurrecting an already-
@@ -258,6 +289,22 @@ test('historyTerminalBySlug: cache invalidates after a new appendHistory call (m
258
289
  expect(second.has('cache-2')).toBe(true);
259
290
  });
260
291
 
292
+ test('historyTerminalBySlug: ignores needs_review ledger lines, even a LATER one for an already-terminal slug', async () => {
293
+ // Chronological order matters here: the terminal row lands first, and a
294
+ // needs_review_entry for the SAME slug (a later re-run parking again)
295
+ // lands after it. Without the kind guard, last-line-wins would replace
296
+ // the true 'completed' record with a status-less ledger row.
297
+ const terminal = old({ slug: 'mixed-1', status: 'completed', runId: 'rterm' });
298
+ await queueHistory.appendHistory([terminal]);
299
+ await queueHistory.appendHistory([{
300
+ kind: 'needs_review_entry', slug: 'mixed-1', runId: 'rlater', cwd: null,
301
+ at: new Date(NOW).toISOString(), reason: 'uncommitted_changes', source: 'spawnJob:finalize',
302
+ }]);
303
+
304
+ const map = await queueHistory.historyTerminalBySlug();
305
+ expect(map.get('mixed-1')).toEqual({ status: 'completed', finishedAt: terminal.finishedAt, landedCommit: null });
306
+ });
307
+
261
308
  // ---------- completedSlugsForCwd (PRD 1122) ----------
262
309
  //
263
310
  // Feeds scheduler.cjs's computeDepHistorySatisfaction: a dependsOn slug with
@@ -290,3 +337,19 @@ test('completedSlugsForCwd: returns an empty Set when the project has no history
290
337
  const slugs = await queueHistory.completedSlugsForCwd(freshCwd);
291
338
  expect(slugs.size).toBe(0);
292
339
  });
340
+
341
+ test('completedSlugsForCwd: ignores needs_review ledger lines even when they are the newest line for a completed slug', async () => {
342
+ const projectCwd = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-1122-ledger-'));
343
+ await queueHistory.appendHistory([
344
+ old({ slug: '900-base', status: 'completed', runId: 'ra', cwd: projectCwd }),
345
+ ]);
346
+ // A later needs_review park for a NEW run of the same slug must not blank
347
+ // out the earlier 'completed' record read by completedSlugsForCwd.
348
+ await queueHistory.appendHistory([{
349
+ kind: 'needs_review_entry', slug: '900-base', runId: 'rb', cwd: projectCwd,
350
+ at: new Date(NOW).toISOString(), reason: 'uncommitted_changes', source: 'spawnJob:finalize',
351
+ }]);
352
+
353
+ const slugs = await queueHistory.completedSlugsForCwd(projectCwd);
354
+ expect(slugs.has('900-base')).toBe(true);
355
+ });
@@ -4,9 +4,11 @@
4
4
  * pollLoop): the 'meter_rate_limited' branch used to never back off and
5
5
  * never call persistSchedulerState(), letting scheduler-state.json freeze
6
6
  * stale while the loop kept failing every POLL_INTERVAL_MS underneath it.
7
- * This suite covers the two pure pieces of the fix: the shared backoff
8
- * curve/cap (nextBackoffMs) and the once-per-streak WARN gate
9
- * (shouldWarnFailureStreak).
7
+ * This suite covers the pure pieces of the fix: the shared backoff
8
+ * curve/cap (nextBackoffMs), the once-per-streak WARN gate
9
+ * (shouldWarnFailureStreak), and the periodic escalation ladder that fires
10
+ * every FAILURE_STREAK_ESCALATION_MS while a streak PERSISTS past the
11
+ * initial WARN, re-arming once the streak clears (shouldEscalateFailureStreak).
10
12
  *
11
13
  * Run: timeout 120 npx vitest run src/main/__tests__/rateLimitPollerStreak.test.cjs
12
14
  */
@@ -17,8 +19,10 @@ import { test, expect } from 'vitest';
17
19
  const {
18
20
  nextBackoffMs,
19
21
  shouldWarnFailureStreak,
22
+ shouldEscalateFailureStreak,
20
23
  BACKOFF_MAX_MS,
21
24
  FAILURE_STREAK_WARN_THRESHOLD,
25
+ FAILURE_STREAK_ESCALATION_MS,
22
26
  } = require('../scheduler.cjs');
23
27
 
24
28
  test('nextBackoffMs starts at 30s from a zero/falsy backoff', () => {
@@ -77,3 +81,34 @@ test('shouldWarnFailureStreak respects a custom threshold', () => {
77
81
  expect(shouldWarnFailureStreak(3, false, 3)).toBe(true);
78
82
  expect(shouldWarnFailureStreak(2, false, 3)).toBe(false);
79
83
  });
84
+
85
+ test('shouldEscalateFailureStreak: false below the WARN threshold, regardless of elapsed time', () => {
86
+ expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD - 1, null, Date.now())).toBe(false);
87
+ });
88
+
89
+ test('shouldEscalateFailureStreak: fires immediately the first time (lastEscalatedAtMs null) once past the WARN threshold', () => {
90
+ expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, null, Date.now())).toBe(true);
91
+ });
92
+
93
+ test('shouldEscalateFailureStreak: does not re-fire before FAILURE_STREAK_ESCALATION_MS has elapsed since the last one', () => {
94
+ const lastEscalatedAtMs = 1_000_000;
95
+ const tooSoon = lastEscalatedAtMs + FAILURE_STREAK_ESCALATION_MS - 1;
96
+ expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, lastEscalatedAtMs, tooSoon)).toBe(false);
97
+ });
98
+
99
+ test('shouldEscalateFailureStreak: fires again once FAILURE_STREAK_ESCALATION_MS has elapsed — the persisting-streak ladder', () => {
100
+ const lastEscalatedAtMs = 1_000_000;
101
+ const dueNow = lastEscalatedAtMs + FAILURE_STREAK_ESCALATION_MS;
102
+ expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, lastEscalatedAtMs, dueNow)).toBe(true);
103
+ });
104
+
105
+ test('shouldEscalateFailureStreak: re-arms after a streak clears (lastEscalatedAtMs reset to null) — a later independent streak escalates again', () => {
106
+ const firstStreakNow = 1_000_000;
107
+ expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, null, firstStreakNow)).toBe(true);
108
+ // Streak clears (a success resets both failureStreakWarned and the
109
+ // escalation timestamp) — a LATER, independent streak crossing the WARN
110
+ // threshold again must escalate again immediately, not wait out a stale
111
+ // 30-minute window from the previous incident.
112
+ const laterStreakNow = firstStreakNow + 5 * 60_000; // well under 30 min later
113
+ expect(shouldEscalateFailureStreak(FAILURE_STREAK_WARN_THRESHOLD, null, laterStreakNow)).toBe(true);
114
+ });
@@ -0,0 +1,135 @@
1
+ /**
2
+ * reconcileTiming.test.cjs — proves reconcile() (src/main/scheduler.cjs)
3
+ * records a per-phase timing breakdown and emits exactly one warn-level
4
+ * `logs.writeLine` when a pass exceeds RECONCILE_SLOW_PASS_MS
5
+ * (src/main/lib/schedulerConfig.cjs), and stays silent on a normal-speed
6
+ * pass. Before this, diagnosing a slow reconcile pass (the
7
+ * "schedule.state timed out after 5000ms" toast) required hand-timing the
8
+ * filesystem scans from a throwaway script.
9
+ *
10
+ * HOME-isolated (mirrors reconcileFlatPrdSweep.test.cjs): stub HOME to a
11
+ * mkdtemp dir before requiring scheduler.cjs, and register the fixture
12
+ * project as "active" via a fake ~/.claude/projects transcript — reconcile's
13
+ * PRD discovery (prdLocations.cjs) only scans cwds it can find that way.
14
+ *
15
+ * Run: timeout 120 npx vitest run src/main/__tests__/reconcileTiming.test.cjs
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ import { test, expect, beforeAll, afterAll, afterEach, vi } from 'vitest';
21
+ const fs = require('node:fs');
22
+ const os = require('node:os');
23
+ const path = require('node:path');
24
+
25
+ let tmpHome;
26
+ let originalHome;
27
+ let scheduler;
28
+ let queueHistory;
29
+ let logs;
30
+ let RECONCILE_SLOW_PASS_MS;
31
+
32
+ beforeAll(() => {
33
+ originalHome = process.env.HOME;
34
+ tmpHome = fs.mkdtempSync(path.join(os.tmpdir(), 'sm-reconcile-timing-home-'));
35
+ process.env.HOME = tmpHome;
36
+
37
+ scheduler = require('../scheduler.cjs');
38
+ queueHistory = require('../lib/queueHistory.cjs');
39
+ logs = require('../logs.cjs');
40
+ ({ RECONCILE_SLOW_PASS_MS } = require('../lib/schedulerConfig.cjs'));
41
+
42
+ if (!scheduler.PRDS_DIR.startsWith(tmpHome)) {
43
+ throw new Error(`refusing to run: PRDS_DIR (${scheduler.PRDS_DIR}) is not under the temp HOME (${tmpHome})`);
44
+ }
45
+ });
46
+
47
+ afterAll(() => {
48
+ process.env.HOME = originalHome;
49
+ fs.rmSync(tmpHome, { recursive: true, force: true });
50
+ });
51
+
52
+ afterEach(() => {
53
+ vi.restoreAllMocks();
54
+ });
55
+
56
+ function registerActiveProject(cwd) {
57
+ const projectsDir = path.join(tmpHome, '.claude', 'projects');
58
+ const slugDir = path.join(projectsDir, `fake-project-slug-${path.basename(cwd)}`);
59
+ fs.mkdirSync(slugDir, { recursive: true });
60
+ fs.writeFileSync(path.join(slugDir, 'transcript.jsonl'), JSON.stringify({ cwd }) + '\n');
61
+ }
62
+
63
+ function makeFixtureProject(prefix) {
64
+ const cwd = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
65
+ registerActiveProject(cwd);
66
+ return cwd;
67
+ }
68
+
69
+ function writeCanonicalPrd(cwd, epicId, slug) {
70
+ const epicPrdsDir = path.join(cwd, 'session-manager-operations', 'scheduler', 'epics', epicId, 'prds');
71
+ fs.mkdirSync(epicPrdsDir, { recursive: true });
72
+ fs.writeFileSync(
73
+ path.join(epicPrdsDir, `${slug}.md`),
74
+ `---\ntitle: Timing fixture\ncwd: ${cwd}\nestimateMinutes: 10\nsourcePromptId: ${epicId}\ncreatedVia: scheduler-api\nissuedAt: 2026-08-07T00:00:00.000Z\n---\n\n# Goal\n\nSomething.\n`,
75
+ 'utf8',
76
+ );
77
+ }
78
+
79
+ function warnCallsMatching(spy) {
80
+ return spy.mock.calls
81
+ .map(([payload]) => payload)
82
+ .filter((payload) => payload?.level === 'warn' && payload?.scope === 'scheduler' && /reconcile\(\) pass took/.test(payload?.message ?? ''));
83
+ }
84
+
85
+ test('a normal-speed reconcile() pass logs nothing', async () => {
86
+ const cwd = makeFixtureProject('sm-reconcile-timing-fast-proj-');
87
+ writeCanonicalPrd(cwd, 'epic-fast', '1600-fast');
88
+
89
+ const writeLineSpy = vi.spyOn(logs, 'writeLine');
90
+ const state = { jobs: [], invalidJobs: [], paused: null };
91
+ await scheduler.reconcile(state);
92
+
93
+ expect(warnCallsMatching(writeLineSpy)).toHaveLength(0);
94
+ });
95
+
96
+ test('a slow reconcile() pass emits exactly one warn with the per-phase breakdown', async () => {
97
+ const cwd = makeFixtureProject('sm-reconcile-timing-slow-proj-');
98
+ writeCanonicalPrd(cwd, 'epic-slow', '1601-slow');
99
+
100
+ const realHistoryTerminalBySlug = queueHistory.historyTerminalBySlug.bind(queueHistory);
101
+ vi.spyOn(queueHistory, 'historyTerminalBySlug').mockImplementation(async (...args) => {
102
+ await new Promise((resolve) => { setTimeout(resolve, RECONCILE_SLOW_PASS_MS + 200); });
103
+ return realHistoryTerminalBySlug(...args);
104
+ });
105
+ const writeLineSpy = vi.spyOn(logs, 'writeLine');
106
+
107
+ const state = { jobs: [], invalidJobs: [], paused: null };
108
+ await scheduler.reconcile(state);
109
+
110
+ const warnCalls = warnCallsMatching(writeLineSpy);
111
+ expect(warnCalls).toHaveLength(1);
112
+ const { meta } = warnCalls[0];
113
+ expect(meta.totalMs).toBeGreaterThan(RECONCILE_SLOW_PASS_MS);
114
+ expect(meta.phaseMs).toHaveProperty('flatPrdSweep');
115
+ expect(meta.phaseMs).toHaveProperty('prdDirResolve');
116
+ expect(meta.phaseMs).toHaveProperty('parseLoop');
117
+ expect(meta.phaseMs).toHaveProperty('historyLookup');
118
+ expect(meta.phaseMs).toHaveProperty('queueWrite');
119
+ expect(meta.phaseMs.historyLookup).toBeGreaterThanOrEqual(RECONCILE_SLOW_PASS_MS);
120
+ expect(typeof meta.prdFileCount).toBe('number');
121
+ expect(typeof meta.resolvedDirCount).toBe('number');
122
+ expect(meta.prdFileCount).toBeGreaterThanOrEqual(1);
123
+ }, 15_000);
124
+
125
+ test('reconcile() surfaces its own error and does not swallow it while timing', async () => {
126
+ vi.spyOn(queueHistory, 'partitionJobs').mockImplementation(() => {
127
+ throw new Error('boom: injected failure for timing-swallow test');
128
+ });
129
+
130
+ const cwd = makeFixtureProject('sm-reconcile-timing-throw-proj-');
131
+ writeCanonicalPrd(cwd, 'epic-throw', '1602-throw');
132
+
133
+ const state = { jobs: [], invalidJobs: [], paused: null };
134
+ await expect(scheduler.reconcile(state)).rejects.toThrow('boom: injected failure for timing-swallow test');
135
+ });
@@ -0,0 +1,181 @@
1
+ /**
2
+ * runVerify-landed-commit-outranks.test.cjs — ground truth (exit 0 + a commit
3
+ * landed THIS run + no explicit FAIL sentinel) outranks the transcript-
4
+ * pattern heuristics (final-20% is_error, FAIL/Traceback,
5
+ * ModuleNotFoundError), so a self-recovered tool error no longer parks a
6
+ * green, committed run.
7
+ *
8
+ * Fixture: the REAL run log for 1218-fo-01-move-scripts-lib-into-src-main-lib
9
+ * (session-manager-operations/reviews/2026-09-13-scheduler-stability-
10
+ * investigation.md), copied verbatim from
11
+ * ~/.claude/session-manager/scheduled-plans/runs/2026-09-13T21-52-51-192Z/ —
12
+ * a `git apply` "patch does not apply" tool error at event 179/209 (inside
13
+ * the final 20% of the transcript) that the agent retried and resolved 3
14
+ * events later, in a run that landed commit d1edf15 and was STILL parked
15
+ * needs_review (verdicts.json: verdict transcript_errors, reason
16
+ * "is_error=true in final 20% of transcript (event 179/209)"), spawning a
17
+ * $3.74 fix-plan investigation into work that needed no fix.
18
+ *
19
+ * The real run's own SCHEDULER_VERDICT sentinel is an explicit FAIL — an
20
+ * honest, UNRELATED report that test:unit was red for proven pre-existing/
21
+ * environmental reasons (shared /tmp contention), not a regression from this
22
+ * PRD's move. Per this PRD's own AC, an explicit FAIL sentinel must keep
23
+ * today's behavior byte-for-byte, so scenarios (a)/(b)/(d) below use a
24
+ * variant of the fixture with that trailing SCHEDULER_VERDICT line stripped
25
+ * (sentinel === null) — the majority real-world park shape this rule targets
26
+ * (the findings report measured 77 of 212 parks as transcript_errors with NO
27
+ * sentinel at all, vs. this one specific row's own additional, unrelated
28
+ * FAIL claim). Scenario (c) reuses the pristine, byte-for-byte-unmodified
29
+ * fixture to prove the real, still-present FAIL sentinel is honored exactly
30
+ * as before this change.
31
+ *
32
+ * Run standalone: timeout 120 npx vitest run src/main/__tests__/runVerify-landed-commit-outranks.test.cjs
33
+ */
34
+
35
+ import { test } from 'vitest';
36
+ const assert = require('node:assert/strict');
37
+ const os = require('node:os');
38
+ const fs = require('node:fs');
39
+ const path = require('node:path');
40
+ const { execFileSync } = require('node:child_process');
41
+ const { verifyRun } = require('../runVerify.cjs');
42
+
43
+ const SLUG = '1218-fo-01-move-scripts-lib-into-src-main-lib';
44
+ const FIXTURE_PATH = path.join(__dirname, 'fixtures', `${SLUG}.log`);
45
+ const LANDED_COMMIT = 'd1edf15be50a783f08a55e6de328780f0feb3cb0';
46
+
47
+ function makeTmpDir() {
48
+ return fs.mkdtempSync(path.join(os.tmpdir(), 'run-verify-landed-commit-outranks-test-'));
49
+ }
50
+
51
+ function rmdir(dir) {
52
+ try { fs.rmSync(dir, { recursive: true, force: true }); } catch { /* */ }
53
+ }
54
+
55
+ function makeTmpGitRepo() {
56
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'run-verify-landed-commit-outranks-repo-'));
57
+ execFileSync('git', ['init', '-q'], { cwd: dir });
58
+ execFileSync('git', ['config', 'user.email', 'test@example.com'], { cwd: dir });
59
+ execFileSync('git', ['config', 'user.name', 'Test'], { cwd: dir });
60
+ fs.writeFileSync(path.join(dir, 'README.md'), 'init\n');
61
+ execFileSync('git', ['add', 'README.md'], { cwd: dir });
62
+ execFileSync('git', ['commit', '-q', '-m', 'init'], { cwd: dir });
63
+ return dir;
64
+ }
65
+
66
+ function writePrd(dir, slug) {
67
+ const text = '---\ntitle: Test PRD\ncwd: /tmp\nestimateMinutes: 30\n---\n# Move scripts/lib into src/main/lib\n';
68
+ fs.writeFileSync(path.join(dir, `${slug}.md`), text);
69
+ return path.join(dir, `${slug}.md`);
70
+ }
71
+
72
+ /**
73
+ * Load the real fixture log, optionally stripping the trailing
74
+ * `SCHEDULER_VERDICT: FAIL ...` line from the final `{"type":"result",...}`
75
+ * event's `result` field so the sentinel scan sees no sentinel at all.
76
+ */
77
+ function loadFixture({ stripFailSentinel }) {
78
+ const raw = fs.readFileSync(FIXTURE_PATH, 'utf8');
79
+ if (!stripFailSentinel) return raw;
80
+ const lines = raw.split('\n');
81
+ const idx = lines.findIndex((l) => l.includes('"type":"result"'));
82
+ assert.ok(idx !== -1, 'fixture must contain a type:result line');
83
+ const obj = JSON.parse(lines[idx]);
84
+ assert.ok(/SCHEDULER_VERDICT:\s*FAIL/.test(obj.result), 'fixture result must carry the real FAIL sentinel before stripping');
85
+ obj.result = obj.result.replace(/\n?SCHEDULER_VERDICT:\s*FAIL[^\n]*$/, '');
86
+ assert.ok(!/SCHEDULER_VERDICT/.test(obj.result), 'sentinel must be fully removed from the stripped variant');
87
+ lines[idx] = JSON.stringify(obj);
88
+ return lines.join('\n');
89
+ }
90
+
91
+ function writeLog(dir, slug, text) {
92
+ fs.writeFileSync(path.join(dir, `${slug}.log`), text);
93
+ }
94
+
95
+ // (a) exit 0 + commit landed THIS run + no sentinel at all → clean, demoted to annotation.
96
+ test('exit 0 + landed commit this run + no FAIL sentinel → clean with annotation (1218-fo-01)', async () => {
97
+ const tmp = makeTmpDir();
98
+ const repo = makeTmpGitRepo();
99
+ try {
100
+ writeLog(tmp, SLUG, loadFixture({ stripFailSentinel: true }));
101
+ const prdPath = writePrd(tmp, SLUG);
102
+ const verdict = await verifyRun({
103
+ runDir: tmp,
104
+ prdPath,
105
+ queueEntry: { slug: SLUG, status: 'running', cwd: repo },
106
+ allJobs: [],
107
+ committedDuringRun: false,
108
+ jobLandedCommitThisRun: LANDED_COMMIT,
109
+ exitCode: 0,
110
+ });
111
+ assert.equal(verdict.verdict, 'clean', `expected clean, got ${verdict.verdict}: ${verdict.reason}`);
112
+ assert.equal(verdict.downgradeTo, null);
113
+ const sidecar = JSON.parse(fs.readFileSync(path.join(tmp, `${SLUG}.verdicts.json`), 'utf8'));
114
+ assert.ok(Array.isArray(sidecar.annotations) && sidecar.annotations.length > 0, 'the demoted is_error hit must be recorded as an annotation');
115
+ assert.ok(sidecar.annotations.some((a) => a.verdict === 'transcript_errors'), 'annotation must carry the original transcript_errors verdict');
116
+ } finally { rmdir(tmp); rmdir(repo); }
117
+ });
118
+
119
+ // (b) the same (stripped-sentinel) log, but exitCode !== 0 → keeps today's behavior.
120
+ test('non-zero exit → transcript_errors (evidence never applies to a failing run)', async () => {
121
+ const tmp = makeTmpDir();
122
+ const repo = makeTmpGitRepo();
123
+ try {
124
+ writeLog(tmp, SLUG, loadFixture({ stripFailSentinel: true }));
125
+ const prdPath = writePrd(tmp, SLUG);
126
+ const verdict = await verifyRun({
127
+ runDir: tmp,
128
+ prdPath,
129
+ queueEntry: { slug: SLUG, status: 'running', cwd: repo },
130
+ allJobs: [],
131
+ committedDuringRun: false,
132
+ jobLandedCommitThisRun: LANDED_COMMIT,
133
+ exitCode: 1,
134
+ });
135
+ assert.equal(verdict.verdict, 'transcript_errors', `expected transcript_errors, got ${verdict.verdict}: ${verdict.reason}`);
136
+ assert.equal(verdict.downgradeTo, 'needs_review');
137
+ } finally { rmdir(tmp); rmdir(repo); }
138
+ });
139
+
140
+ // (c) the pristine, byte-for-byte-unmodified fixture — its own real FAIL sentinel — keeps today's behavior.
141
+ test('explicit FAIL sentinel (the real, unmodified fixture) → transcript_errors byte-for-byte', async () => {
142
+ const tmp = makeTmpDir();
143
+ const repo = makeTmpGitRepo();
144
+ try {
145
+ writeLog(tmp, SLUG, loadFixture({ stripFailSentinel: false }));
146
+ const prdPath = writePrd(tmp, SLUG);
147
+ const verdict = await verifyRun({
148
+ runDir: tmp,
149
+ prdPath,
150
+ queueEntry: { slug: SLUG, status: 'running', cwd: repo },
151
+ allJobs: [],
152
+ committedDuringRun: false,
153
+ jobLandedCommitThisRun: LANDED_COMMIT,
154
+ exitCode: 0,
155
+ });
156
+ assert.equal(verdict.verdict, 'transcript_errors', `expected transcript_errors, got ${verdict.verdict}: ${verdict.reason}`);
157
+ assert.equal(verdict.downgradeTo, 'needs_review');
158
+ assert.equal(verdict.sentinel, 'fail');
159
+ } finally { rmdir(tmp); rmdir(repo); }
160
+ });
161
+
162
+ // (d) no landed-commit evidence at all → transcript_errors (evidence gate requires attribution).
163
+ test('no landed commit evidence → transcript_errors (evidence gate requires attribution)', async () => {
164
+ const tmp = makeTmpDir();
165
+ const repo = makeTmpGitRepo();
166
+ try {
167
+ writeLog(tmp, SLUG, loadFixture({ stripFailSentinel: true }));
168
+ const prdPath = writePrd(tmp, SLUG);
169
+ const verdict = await verifyRun({
170
+ runDir: tmp,
171
+ prdPath,
172
+ queueEntry: { slug: SLUG, status: 'running', cwd: repo },
173
+ allJobs: [],
174
+ committedDuringRun: false,
175
+ jobLandedCommitThisRun: null,
176
+ exitCode: 0,
177
+ });
178
+ assert.equal(verdict.verdict, 'transcript_errors', `expected transcript_errors, got ${verdict.verdict}: ${verdict.reason}`);
179
+ assert.equal(verdict.downgradeTo, 'needs_review');
180
+ } finally { rmdir(tmp); rmdir(repo); }
181
+ });
@@ -1140,7 +1140,7 @@ function noOpRunEvents(resultText) {
1140
1140
  test('no_verdict_sentinel: no tool_result issues, no sentinel, no commit → needs_review', async () => {
1141
1141
  const tmp = makeTmpDir();
1142
1142
  try {
1143
- const slug = '406-browser-capture-panel-ui';
1143
+ const slug = '406-capture-panel-ui';
1144
1144
  writeLog(tmp, slug, noOpRunEvents('I need clarification on which capture API to target before proceeding.'));
1145
1145
  const prdPath = writePrd(tmp, slug, '# Browser capture panel UI');
1146
1146
  const verdict = await verifyRun({
@@ -1160,7 +1160,7 @@ test('no_verdict_sentinel: no tool_result issues, no sentinel, no commit → nee
1160
1160
  test('no_verdict_sentinel guard: same run but committedDuringRun:true → stays clean', async () => {
1161
1161
  const tmp = makeTmpDir();
1162
1162
  try {
1163
- const slug = '406-browser-capture-panel-ui-committed';
1163
+ const slug = '406-capture-panel-ui-committed';
1164
1164
  writeLog(tmp, slug, noOpRunEvents('I need clarification on which capture API to target before proceeding.'));
1165
1165
  const prdPath = writePrd(tmp, slug, '# Browser capture panel UI');
1166
1166
  const verdict = await verifyRun({
@@ -1180,7 +1180,7 @@ test('no_verdict_sentinel guard: same run but committedDuringRun:true → stays
1180
1180
  test('no_verdict_sentinel guard: same run but with SCHEDULER_VERDICT: PASS sentinel + a commit → stays clean', async () => {
1181
1181
  const tmp = makeTmpDir();
1182
1182
  try {
1183
- const slug = '406-browser-capture-panel-ui-sentinel';
1183
+ const slug = '406-capture-panel-ui-sentinel';
1184
1184
  writeLog(tmp, slug, noOpRunEvents('All acceptance criteria verified.\nSCHEDULER_VERDICT: PASS'));
1185
1185
  const prdPath = writePrd(tmp, slug, '# Browser capture panel UI');
1186
1186
  const verdict = await verifyRun({
@@ -1634,7 +1634,7 @@ test('isMergeMainSlug matches the NN-prXXX-merge-main and NN-fix-prXXX-merge-mai
1634
1634
  assert.equal(isMergeMainSlug('565-pr188-merge-main'), true);
1635
1635
  assert.equal(isMergeMainSlug('571-fix-pr141-merge-main'), true);
1636
1636
  assert.equal(isMergeMainSlug('523-fix-bounded-fix-plan-retry'), false);
1637
- assert.equal(isMergeMainSlug('406-browser-capture-panel-ui'), false);
1637
+ assert.equal(isMergeMainSlug('406-capture-panel-ui'), false);
1638
1638
  assert.equal(isMergeMainSlug(''), false);
1639
1639
  assert.equal(isMergeMainSlug(undefined), false);
1640
1640
  });
@@ -1727,7 +1727,7 @@ test('merge-main exemption: gh call throws → falls through to pass_no_commit,
1727
1727
  test('merge-main exemption: non-merge-main slug never attempts a gh call', async () => {
1728
1728
  const tmp = makeTmpDir();
1729
1729
  try {
1730
- const slug = '406-browser-capture-panel-ui';
1730
+ const slug = '406-capture-panel-ui';
1731
1731
  writeLog(tmp, slug, noOpRunEvents('All acceptance criteria verified.\nSCHEDULER_VERDICT: PASS'));
1732
1732
  const prdPath = writePrd(tmp, slug, '# Browser capture panel UI');
1733
1733
  let ghCalls = 0;
@@ -6,7 +6,7 @@
6
6
  * is types-only (erased at build time, nothing to import at test runtime), so
7
7
  * each consumer keeps its own hand-written mirror of the status enum:
8
8
  * - src/preload/api.d.ts's `ScheduleJobStatus` union
9
- * - src/renderer/components/ui/StatusBadge.tsx's `JobStatus` union
9
+ * - src/renderer/lib/jobStatus.ts's `JobStatus` union
10
10
  * - src/renderer/components/SchedulePanel.tsx's `FilterStatus` union
11
11
  * (which also carries the UI-only 'all' meta-value)
12
12
  *
@@ -52,8 +52,8 @@ test('src/preload/api.d.ts ScheduleJobStatus matches JOB_STATUSES', () => {
52
52
  expect(sorted(values)).toEqual(sorted(JOB_STATUSES));
53
53
  });
54
54
 
55
- test('src/renderer/components/ui/StatusBadge.tsx JobStatus matches JOB_STATUSES', () => {
56
- const source = readSource('src/renderer/components/ui/StatusBadge.tsx');
55
+ test('src/renderer/lib/jobStatus.ts JobStatus matches JOB_STATUSES', () => {
56
+ const source = readSource('src/renderer/lib/jobStatus.ts');
57
57
  const values = extractQuotedList(source, /export type JobStatus = ([^\n]+)/);
58
58
  expect(sorted(values)).toEqual(sorted(JOB_STATUSES));
59
59
  });