claude-code-session-manager 0.98.0 → 0.101.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 (441) hide show
  1. package/README.md +3 -3
  2. package/bin/cli.cjs +16 -4
  3. package/bin/node-floor.cjs +41 -0
  4. package/dist/assets/DataModel-CXaH2GED.js +1 -0
  5. package/dist/assets/{History-Bfdoywlh.js → History-Cerii-HA.js} +2 -2
  6. package/dist/assets/{Hooks-CKDJjTJV.js → Hooks-aXMb5LDy.js} +3 -3
  7. package/dist/assets/{Library-7akfgy67.js → Library-WrJFKEOJ.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-DDesiqet.js → MarkdownEditor-D1BMCXS7.js} +1 -1
  9. package/dist/assets/{McpServers-D4lZtXof.js → McpServers-Cf0davLd.js} +2 -2
  10. package/dist/assets/{Memory-CeI8G1w5.js → Memory-Dn6NHWPe.js} +6 -6
  11. package/dist/assets/Permissions-CTVuFPjg.js +3 -0
  12. package/dist/assets/{Plugins-P4eCXy_S.js → Plugins-C2TyF8Mn.js} +2 -2
  13. package/dist/assets/ProvenanceBadge-F_jwHSwE.js +1 -0
  14. package/dist/assets/{SaveBar-l9jnEMES.js → SaveBar-DA1KVJST.js} +1 -1
  15. package/dist/assets/Scheduler-Bm6Q_hlu.js +16 -0
  16. package/dist/assets/{ScopeSwitcher-CjJi8ST5.js → ScopeSwitcher-BygZlJca.js} +1 -1
  17. package/dist/assets/Settings-BYCiv5KV.js +3 -0
  18. package/dist/assets/{SkillReferenceGraph-CjbO01Sg.js → SkillReferenceGraph-CcLnl9np.js} +1 -1
  19. package/dist/assets/{Skills-BxZ9_fCR.js → Skills-ChffsdTD.js} +2 -2
  20. package/dist/assets/{SystemPrompt-D8vfFb8b.js → SystemPrompt-dNkfHE_M.js} +1 -1
  21. package/dist/assets/{TagLibrary-2vzUo7YL.js → TagLibrary-jVlEuB0m.js} +1 -1
  22. package/dist/assets/{TiptapBody-B8ZfbK90.js → TiptapBody-6_cdwcP0.js} +1 -1
  23. package/dist/assets/{Toggle-BbW4nGGf.js → Toggle-CVXqGpAP.js} +1 -1
  24. package/dist/assets/ViewTabs-DO1YVbpc.js +1 -0
  25. package/dist/assets/{index-CKH5Uxik.css → index-D89EUizB.css} +1 -1
  26. package/dist/assets/{index-Cd98Q1lD.js → index-DbCQPWWY.js} +747 -752
  27. package/dist/assets/{settingsSchema-IhUfucCF.js → settingsSchema-Wrvn-Yz1.js} +1 -1
  28. package/dist/index.html +2 -2
  29. package/package.json +25 -29
  30. package/plugins/CLAUDE.md +6 -6
  31. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +21 -19
  32. package/plugins/session-manager-dev/skills/builder/SKILL.md +3 -3
  33. package/plugins/session-manager-dev/skills/develop/SKILL.md +254 -510
  34. package/plugins/session-manager-dev/skills/develop/standards.md +17 -22
  35. package/scripts/README.md +3 -11
  36. package/scripts/audit-ops-hygiene.cjs +3 -3
  37. package/scripts/hooks/lib/guard-prd-writes-policy.cjs +1 -1
  38. package/scripts/hooks/lib/guard-self-schedule-policy.cjs +1 -1
  39. package/scripts/mint-epic.cjs +2 -3
  40. package/scripts/scheduler-mcp-server.cjs +64 -7
  41. package/src/main/agentLibrary.cjs +22 -2
  42. package/src/main/build-info.json +4 -4
  43. package/src/main/chatRunner.cjs +4 -3
  44. package/src/main/config.cjs +41 -38
  45. package/src/main/docEdit.cjs +4 -1
  46. package/src/main/files.cjs +2 -5
  47. package/src/main/health.cjs +1 -1
  48. package/src/main/index.cjs +40 -11
  49. package/src/main/ipcSchemas.cjs +35 -34
  50. package/src/main/lib/agentPersonaSchema.cjs +13 -2
  51. package/src/main/lib/atomicFs.cjs +116 -0
  52. package/src/main/lib/branchSweep.cjs +13 -12
  53. package/src/main/lib/buildTarget.cjs +3 -3
  54. package/src/main/lib/builtinMacros.cjs +39 -0
  55. package/src/main/lib/claudeCliCaps.cjs +129 -0
  56. package/src/main/lib/credentials.cjs +2 -4
  57. package/src/main/lib/crossProjectFeedback.cjs +3 -3
  58. package/src/main/lib/cwdClassify.cjs +15 -3
  59. package/src/main/lib/definitionOfDone.cjs +207 -125
  60. package/src/main/lib/dodDrainHook.cjs +11 -5
  61. package/src/main/lib/epicMint.cjs +2 -4
  62. package/src/main/lib/epicStatusMirror.cjs +8 -10
  63. package/src/main/lib/epicWorktreeProjectConfig.cjs +2 -4
  64. package/src/main/lib/gateAuthority.cjs +96 -0
  65. package/src/main/lib/gitExec.cjs +69 -0
  66. package/src/main/lib/gitWorktree.cjs +656 -92
  67. package/src/main/lib/instanceLock.cjs +5 -8
  68. package/src/main/lib/launchFailure.cjs +2 -3
  69. package/src/main/lib/macroLibrary.cjs +480 -0
  70. package/src/main/lib/mcpToolCatalog.cjs +49 -17
  71. package/src/main/lib/needsReviewLedger.cjs +1 -1
  72. package/src/main/lib/opsErrorLog.cjs +11 -1
  73. package/src/main/lib/opsOwnership.cjs +5 -9
  74. package/src/main/lib/pidAlive.cjs +32 -0
  75. package/src/main/lib/prdCreate.cjs +121 -12
  76. package/src/main/lib/prdDisposition.cjs +2 -2
  77. package/src/main/lib/prdGateFiles.cjs +284 -0
  78. package/src/main/lib/prdLocations.cjs +61 -0
  79. package/src/main/lib/prdMigration.cjs +2 -3
  80. package/src/main/lib/prdSizing.cjs +2 -1
  81. package/src/main/lib/projectHomeAdminRoutes.cjs +136 -21
  82. package/src/main/lib/promptSessionSchema.cjs +10 -2
  83. package/src/main/lib/rcaReport.cjs +4 -6
  84. package/src/main/lib/reaperHelpers.cjs +8 -1
  85. package/src/main/lib/reservationExpiry.cjs +6 -1
  86. package/src/main/lib/reviewNotice.cjs +263 -0
  87. package/src/main/lib/runClaudeP.cjs +4 -1
  88. package/src/main/lib/schedulerPaths.cjs +22 -2
  89. package/src/main/lib/sessionSlots.cjs +2 -4
  90. package/src/main/lib/shippedPersonaSeeds.cjs +226 -0
  91. package/src/main/lib/smfileHeaders.cjs +66 -0
  92. package/src/main/lib/timeoutShim.cjs +123 -0
  93. package/src/main/lib/timeoutShimScript.cjs +275 -0
  94. package/src/main/lib/upgradeDrain.cjs +4 -10
  95. package/src/main/lib/watchdogHelpers.cjs +12 -25
  96. package/src/main/lib/workTypeLibrary.cjs +7 -1
  97. package/src/main/projectPages.cjs +43 -13
  98. package/src/main/promptSessionEvents.cjs +37 -13
  99. package/src/main/scheduler.cjs +910 -198
  100. package/src/main/seedAgentPersonas.cjs +279 -24
  101. package/src/main/supervisor.cjs +4 -1
  102. package/src/main/templates/PRD_AUTHORING.md +126 -406
  103. package/src/preload/__tests__/preload-surface.test.cjs +68 -0
  104. package/src/preload/api.d.ts +53 -94
  105. package/src/preload/index.cjs +14 -9
  106. package/src/seed/agents/architect.md +2 -0
  107. package/src/seed/agents/demo-video-builder.md +84 -0
  108. package/src/seed/agents/dev-lead.md +18 -18
  109. package/src/seed/agents/project-home-builder.md +66 -35
  110. package/src/seed/agents/validator.md +10 -4
  111. package/dist/assets/DataModel-DCEgMTjH.js +0 -1
  112. package/dist/assets/HostBilko-CDVHE0hT.js +0 -1
  113. package/dist/assets/Permissions-BGleLja4.js +0 -3
  114. package/dist/assets/ProvenanceBadge-BFczzI1B.js +0 -1
  115. package/dist/assets/Scheduler-C_phpeUS.js +0 -16
  116. package/dist/assets/Settings-BIwZ3FGQ.js +0 -3
  117. package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
  118. package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
  119. package/src/main/__tests__/agentLibrary.test.cjs +0 -276
  120. package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
  121. package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
  122. package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
  123. package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
  124. package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
  125. package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
  126. package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
  127. package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
  128. package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
  129. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
  130. package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
  131. package/src/main/__tests__/chat-queue.test.cjs +0 -97
  132. package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
  133. package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
  134. package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
  135. package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
  136. package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
  137. package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
  138. package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
  139. package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
  140. package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
  141. package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
  142. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
  143. package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
  144. package/src/main/__tests__/docEdit.test.cjs +0 -244
  145. package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
  146. package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
  147. package/src/main/__tests__/dod-report.test.cjs +0 -304
  148. package/src/main/__tests__/dod-reverify.test.cjs +0 -285
  149. package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
  150. package/src/main/__tests__/epicMint.test.cjs +0 -332
  151. package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
  152. package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
  153. package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
  154. package/src/main/__tests__/exchanges.test.cjs +0 -122
  155. package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
  156. package/src/main/__tests__/extractJson.test.cjs +0 -51
  157. package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
  158. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
  159. package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
  160. package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
  161. package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
  162. package/src/main/__tests__/health-credentials.test.cjs +0 -81
  163. package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
  164. package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
  165. package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
  166. package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
  167. package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
  168. package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
  169. package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
  170. package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
  171. package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
  172. package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
  173. package/src/main/__tests__/historyDashboard.test.cjs +0 -163
  174. package/src/main/__tests__/historyRollup.test.cjs +0 -333
  175. package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
  176. package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
  177. package/src/main/__tests__/kg-augment.test.cjs +0 -195
  178. package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
  179. package/src/main/__tests__/machineProfile.test.cjs +0 -152
  180. package/src/main/__tests__/mcpStatus.test.cjs +0 -61
  181. package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
  182. package/src/main/__tests__/memoryStale.test.cjs +0 -88
  183. package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
  184. package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
  185. package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
  186. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
  187. package/src/main/__tests__/personaMerge.test.cjs +0 -169
  188. package/src/main/__tests__/planValidator.test.cjs +0 -125
  189. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
  190. package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
  191. package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
  192. package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
  193. package/src/main/__tests__/prdAgentType.test.cjs +0 -103
  194. package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
  195. package/src/main/__tests__/prdCreate.test.cjs +0 -979
  196. package/src/main/__tests__/prdCreateAdoption.test.cjs +0 -205
  197. package/src/main/__tests__/prdCreateDisposition.test.cjs +0 -201
  198. package/src/main/__tests__/prdCreatePlanId.test.cjs +0 -132
  199. package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +0 -117
  200. package/src/main/__tests__/prdFrontmatterDependsOn.test.cjs +0 -136
  201. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +0 -125
  202. package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +0 -108
  203. package/src/main/__tests__/prdLocations.test.cjs +0 -195
  204. package/src/main/__tests__/prdLocationsArchived.test.cjs +0 -201
  205. package/src/main/__tests__/prdMigration.test.cjs +0 -349
  206. package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +0 -91
  207. package/src/main/__tests__/prdParserHighWater.test.cjs +0 -74
  208. package/src/main/__tests__/prdParserSourcePromptId.test.cjs +0 -65
  209. package/src/main/__tests__/prdSetDisposition.test.cjs +0 -222
  210. package/src/main/__tests__/prdSizing.test.cjs +0 -106
  211. package/src/main/__tests__/prdSourcePromptIdBackfill.test.cjs +0 -118
  212. package/src/main/__tests__/prdUpdateDependsOn.test.cjs +0 -160
  213. package/src/main/__tests__/proc-role-env.test.cjs +0 -125
  214. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +0 -304
  215. package/src/main/__tests__/procname-sm-processes.test.cjs +0 -127
  216. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +0 -177
  217. package/src/main/__tests__/projectPages.test.cjs +0 -137
  218. package/src/main/__tests__/promptSessionEvents.test.cjs +0 -234
  219. package/src/main/__tests__/promptSessionSchema.test.cjs +0 -101
  220. package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
  221. package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +0 -159
  222. package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +0 -282
  223. package/src/main/__tests__/pty-session-open-telemetry.test.cjs +0 -96
  224. package/src/main/__tests__/pty-write-result.test.cjs +0 -46
  225. package/src/main/__tests__/queue-health-verdict.test.cjs +0 -170
  226. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +0 -286
  227. package/src/main/__tests__/queue-starvation-per-project.test.cjs +0 -147
  228. package/src/main/__tests__/queueHistory.test.cjs +0 -355
  229. package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +0 -65
  230. package/src/main/__tests__/queueOpsArchiveDestination.test.cjs +0 -65
  231. package/src/main/__tests__/queueOpsAutoArchive.test.cjs +0 -211
  232. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +0 -128
  233. package/src/main/__tests__/rcaReport.test.cjs +0 -266
  234. package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +0 -119
  235. package/src/main/__tests__/reconcileTiming.test.cjs +0 -135
  236. package/src/main/__tests__/runLogRetention.test.cjs +0 -489
  237. package/src/main/__tests__/runVerify-atomic-verdicts.test.cjs +0 -26
  238. package/src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs +0 -58
  239. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +0 -191
  240. package/src/main/__tests__/runVerify-policy-denial.test.cjs +0 -89
  241. package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +0 -225
  242. package/src/main/__tests__/runVerify.test.cjs +0 -1784
  243. package/src/main/__tests__/scheduleJobSchema.test.cjs +0 -127
  244. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +0 -65
  245. package/src/main/__tests__/scheduleJobTransitions.test.cjs +0 -277
  246. package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +0 -59
  247. package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +0 -72
  248. package/src/main/__tests__/scheduler-admin-routes.test.cjs +0 -199
  249. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +0 -143
  250. package/src/main/__tests__/scheduler-already-satisfied-on-main.test.cjs +0 -105
  251. package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +0 -102
  252. package/src/main/__tests__/scheduler-archived-twin-guard.test.cjs +0 -155
  253. package/src/main/__tests__/scheduler-autofix-outcome.test.cjs +0 -188
  254. package/src/main/__tests__/scheduler-autofix-select.test.cjs +0 -439
  255. package/src/main/__tests__/scheduler-autopromote.test.cjs +0 -51
  256. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +0 -101
  257. package/src/main/__tests__/scheduler-blocked-by-foreign-wip.test.cjs +0 -107
  258. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +0 -153
  259. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +0 -121
  260. package/src/main/__tests__/scheduler-clear-queue-history.test.cjs +0 -134
  261. package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +0 -244
  262. package/src/main/__tests__/scheduler-committed-in-window.test.cjs +0 -182
  263. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +0 -43
  264. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +0 -168
  265. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +0 -58
  266. package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +0 -81
  267. package/src/main/__tests__/scheduler-epic-digest.test.cjs +0 -227
  268. package/src/main/__tests__/scheduler-failed-autoreset.test.cjs +0 -121
  269. package/src/main/__tests__/scheduler-finalize-dispatch-guards.test.cjs +0 -229
  270. package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +0 -75
  271. package/src/main/__tests__/scheduler-fix-plan-path.test.cjs +0 -119
  272. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +0 -46
  273. package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +0 -78
  274. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +0 -119
  275. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +0 -390
  276. package/src/main/__tests__/scheduler-heal-refusal.test.cjs +0 -61
  277. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +0 -80
  278. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +0 -323
  279. package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +0 -41
  280. package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +0 -63
  281. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +0 -123
  282. package/src/main/__tests__/scheduler-job-budget.test.cjs +0 -172
  283. package/src/main/__tests__/scheduler-job-overrun.test.cjs +0 -175
  284. package/src/main/__tests__/scheduler-launch-failure.test.cjs +0 -199
  285. package/src/main/__tests__/scheduler-leftover-fields.test.cjs +0 -52
  286. package/src/main/__tests__/scheduler-leftover-quarantine.test.cjs +0 -199
  287. package/src/main/__tests__/scheduler-looks-done.test.cjs +0 -537
  288. package/src/main/__tests__/scheduler-manual-pause.test.cjs +0 -118
  289. package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +0 -245
  290. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +0 -46
  291. package/src/main/__tests__/scheduler-mutate-reentrancy.test.cjs +0 -117
  292. package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
  293. package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
  294. package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
  295. package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
  296. package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
  297. package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
  298. package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
  299. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
  300. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
  301. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
  302. package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
  303. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
  304. package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
  305. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
  306. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
  307. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
  308. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
  309. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
  310. package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
  311. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
  312. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
  313. package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
  314. package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
  315. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
  316. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
  317. package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
  318. package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
  319. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
  320. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
  321. package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
  322. package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
  323. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
  324. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
  325. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
  326. package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
  327. package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
  328. package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
  329. package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
  330. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
  331. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
  332. package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
  333. package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
  334. package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
  335. package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
  336. package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
  337. package/src/main/__tests__/seedStatus.test.cjs +0 -100
  338. package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
  339. package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
  340. package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
  341. package/src/main/__tests__/telemetryContract.test.cjs +0 -930
  342. package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
  343. package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
  344. package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
  345. package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
  346. package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
  347. package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
  348. package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
  349. package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
  350. package/src/main/__tests__/validationSentinels.test.cjs +0 -84
  351. package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
  352. package/src/main/bilkoHost.cjs +0 -314
  353. package/src/main/bilkoHostCore.cjs +0 -89
  354. package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
  355. package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
  356. package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
  357. package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
  358. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
  359. package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
  360. package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
  361. package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
  362. package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
  363. package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
  364. package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
  365. package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
  366. package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
  367. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
  368. package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
  369. package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
  370. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
  371. package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
  372. package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
  373. package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
  374. package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
  375. package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
  376. package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
  377. package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
  378. package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
  379. package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
  380. package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
  381. package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
  382. package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
  383. package/src/main/lib/__tests__/gateFixtures.json +0 -20
  384. package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
  385. package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
  386. package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
  387. package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
  388. package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
  389. package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
  390. package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
  391. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
  392. package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
  393. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
  394. package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
  395. package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
  396. package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
  397. package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
  398. package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
  399. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
  400. package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
  401. package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
  402. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
  403. package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
  404. package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
  405. package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
  406. package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
  407. package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
  408. package/src/main/lib/__tests__/procName.test.cjs +0 -92
  409. package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
  410. package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
  411. package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
  412. package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
  413. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
  414. package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
  415. package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
  416. package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
  417. package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
  418. package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
  419. package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
  420. package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
  421. package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
  422. package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
  423. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
  424. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
  425. package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
  426. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
  427. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
  428. package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
  429. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
  430. package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
  431. package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
  432. package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
  433. package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
  434. package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
  435. package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
  436. package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
  437. package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
  438. package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
  439. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -441
  440. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
  441. package/src/main/lib/kgExchangePairing.cjs +0 -75
@@ -10,10 +10,6 @@
10
10
 
11
11
  - State the time and space complexity of any non-trivial algorithm in a comment.
12
12
  - Flag any nested loop over user-scaled data as a complexity hazard.
13
- - Prefer O(n) solutions over O(n log n) only when n is provably small or constant.
14
- - Lay out hot data contiguously and traverse it in memory order.
15
- - Prefer arrays of structs or structs of arrays based on actual access patterns.
16
- - Avoid pointer-chasing in inner loops on large datasets.
17
13
 
18
14
  ## Debugging approach
19
15
 
@@ -70,22 +66,21 @@ a higher-priority source exists:
70
66
 
71
67
  ## Execution discipline (headless runs)
72
68
 
73
- Data-driven from 400+ scheduler runs: long hangs (not bad code) are the dominant real failure, and "exited clean but left a red test" is the top verifier downgrade. These rules run at execution time — they are inlined into every PRD because the headless executor reads nothing else.
69
+ Data from 400+ scheduler runs: long hangs, not bad code, cause most failures, and "exited clean but left a red test" is the top verifier downgrade. The PRD body carries a one-line pointer to this file, never a copy. The dev-lead persona and the scheduler's finish protocol carry the per-run rules; this file gives the reasons behind them. Run order: orient → red → build → finish protocol (review → verify the `# Gate` commands → commit exact paths → verdict line).
74
70
 
75
- - **Bound every command.** Wrap every test/build/dev-server/deploy/poll command in a hard timeout: `timeout 300 <typecheck|unit>`, `timeout 120 <one e2e spec>`, `curl --max-time 15`. Never run a bare `playwright test`/`vite`/`pnpm dev`, a full e2e suite, or an endpoint-polling publish — those are the SIGTERM/4h-watchdog tail.
76
- - **Verify before done.** Run the acceptance test command once before declaring success. If it's red, fix it or `exit 1` with the failure — never end the run on a failing test (that trips the verifier's `transcript_errors` downgrade).
77
- - **Fail loud, fail fast.** On any step failure, print one diagnostic line and `exit 1`; don't swallow with `|| true` or spin in a silent retry. A `rateLimited` exit-1 is the scheduler's benign auto-pause (auto-resumes next window) — not a failure to engineer around.
78
- - **Stay in the AC.** Do not add work past the acceptance checklist ("while we're here" generators/fixtures are the post-AC-overrun incident). Body must be clean UTF-8 — no NUL/control bytes.
79
- - **You ARE the executor — never re-queue or self-schedule.** A headless PRD run must perform its own acceptance criteria directly. Do NOT invoke `/develop` or any queue-authoring skill from inside a run — those are interactive main-loop skills that author a *new* PRD and return, so the run exits 0 having done nothing (no commit, no sentinel → `needs_review` with `no_verdict_sentinel`). Do NOT call `ScheduleWakeup`/set a tracking loop either — the process exits when the run ends and nothing re-invokes it. This applies just as much to spawning your own review agents and waiting on them: do NOT invoke `/code-review`, `/security-review`, `requesting-code-review`, or any other skill/subagent as a background/async step and then end your turn with something like "I'll wait for the review agents to complete" — a headless run has no next turn, so that line is the run's last output, no verdict sentinel prints, and the job parks in `needs_review` even though the actual work already landed. If a PRD's acceptance criteria call for a second review pass, run it **synchronously, inline, before the finish protocol** — call the reviewer and read its result in the same turn, don't fire-and-wait. If the PRD's work looks large, decompose and execute it inline within this run; never delegate it back to the queue. (Incidents: PRD 460 invoked `/develop`, spawned a duplicate PRD 461, and exited 0 with no work. PRD 479 landed its commit correctly but then backgrounded `/code-review --fix` + `/security-review` and called `ScheduleWakeup` to "wait" for them — same class of failure, different entry point.)
80
- - **Never `git stash`, `git reset`, `git checkout -- <path>`, or `git clean` against working-tree state you did not create — there is no polite version of this.** You are normally already isolated in your own `git worktree` on branch `sm-job/<slug>` (`src/main/lib/gitWorktree.cjs` — dirty base trees no longer disable this isolation, and the scheduler salvages any uncommitted diff on teardown, so you don't need to touch shared state to preserve your own work). Confirm you're isolated with `git rev-parse --git-common-dir` (a worktree's common-dir differs from its own `.git`) or by checking your cwd, before assuming otherwise. If you find yourself in a SHARED tree with pre-existing dirty tracked files anyway, work *around* them — scope `git add`/commits to only the paths you touched — or stop and report the conflict; never discard, stash, or revert someone else's uncommitted changes to get a clean checkout. A shared tree may be feeding a **running service** reading its config straight from the working tree (the trading repo's `data/spread_config.json` is live input, not a local scratch file) — reverting it is a production change, not a local inconvenience, and read-only inspection (`git diff`, `git status`) is always fine. The scheduler also runs a shared-tree stash guard as a backstop (`scheduler.cjs` `checkSharedTreeGuard`/`evaluateSharedTreeGuard`) that auto-restores a stash your run created and flags any baseline-dirty path that went clean with no commit — but that guard exists to catch a mistake, not to license one. (Incidents: PRD 477 stashed a concurrent job's rAF-throttle-revert WIP to get its own checkout and exited without restoring it, orphaning it in `stash@{0}` with no record of whose it was. 2026-09-01: a headless executor in the trader repo ran a blanket `git stash` against an operator's uncommitted LIVE TRADING CONFIG edit and exited without restoring it — `stash@{0}` "WIP on fix/trade-log-reconciliation-out-of-band-closes", `data/spread_config.json` — the third incident of this class.)
81
- - **`gh pr edit --body` can fail on repos with legacy GitHub Projects (classic) boards** — the underlying GraphQL query fetches `repository.pullRequest.projectCards`, a field GitHub is sunsetting, and errors with `GraphQL: Projects (classic) is being deprecated ... (repository.pullRequest.projectCards)` even though the edit itself would otherwise succeed. This is a known `gh` CLI quirk, not a defect in your work. Prefer `gh api -X PATCH repos/<owner>/<repo>/pulls/<n> -f body="$(cat body.md)"` for updating a PR description headlessly — it doesn't touch the deprecated field. If you do use `gh pr edit` and it fails this way, don't leave the bare GraphQL error as the last thing in that step (it reads as an unrecovered error in the final-20%-of-transcript verifier heuristic): immediately retry with the `gh api` form and print one line noting the known-bug fallback, so the recovery is adjacent to the error.
82
- - **`gh pr checks`/`gh run watch` exit non-zero while CI is merely *pending*, not failed — don't let that surface as a bare error.** Polling `gh pr checks <n>` before checks finish returns a non-zero exit (e.g. 8) with output like `check pending 0 <url>` — this is normal, documented `gh` CLI behavior, not a failure. If you retry with a *differently-worded* command (e.g. dropping a `sleep N &&` prefix, or switching to `gh run watch <id> --exit-status`), the verifier's self-recovery detector pairs retries by exact command-description match and may not recognize the differently-worded retry as the same recovery, leaving the original pending-state error looking unrecovered in the transcript (incident: `745-pr188-ci-lint-docs-integrity`, a fully green, committed, pushed run flagged `needs_review` over exactly this). Prefer polling with the *same* command/description each time (e.g. loop `gh pr checks <n>` unchanged, or use `gh run watch <id> --exit-status` from the start rather than switching mid-poll) so a later success is recognized as recovering the earlier pending-state failure.
83
- - **Negative-assertion checks must exit 0 when clean.** A check that verifies the *absence* of something (a `grep` that should find nothing, "no leftover X", `diff` expecting no change) must return exit 0 on the clean case. A bare `grep` exits **1 on no-match** — so the *success* path surfaces as `is_error=true` and the verifier downgrades a perfect run to `needs_review`. Always invert: `if <detector>; then echo "HALT: <what was found>"; exit 1; fi; echo clean`. Never let the no-match/empty path carry the non-zero exit.
84
- - **Recover or annotate every error — don't strand a Traceback in the transcript.** The verifier downgrades an otherwise-perfect run to `needs_review` when a `Traceback`/`Error` appears with *no visible recovery within ~10 lines* (the `transcript_errors` heuristic — the single most common false-positive on green deliverables). Two executor habits cause it: (1) **throwaway probes that error** — an inline `python -c` with a quoting/f-string slip, a wrong kwarg, a bad path. When a probe errors, immediately re-run the corrected version *or* print one line `# expected/handled: <why>` right after, so recovery is adjacent. Don't move on leaving a bare error as the last thing in that step. Prefer a small temp `.py` file over a fragile multi-quote `python -c` one-liner (inline f-string errors are the top source of stranded tracebacks). (2) See the timeout rule below.
85
- - **An *expected* bounded-timeout (exit 124) must be annotated, not bare.** `timeout`-capping a genuinely long task you expect to hit the cap (a full-universe ingest, a long scan) is correct — but a bare `Exit code 124` reads as a failure to the verifier. Wrap it so the cap is a success-with-note: `timeout 120 <cmd> || { rc=$?; [ $rc -eq 124 ] && echo "hit time cap — idempotent/partial, rows persist incrementally; OK" || { echo "HALT: <cmd> failed rc=$rc"; exit 1; }; }`. (Distinguish 124 = expected cap from a real non-zero.) For work that legitimately needs longer than a safe cap, run it in the background and poll a bounded number of times rather than capping the foreground command.
86
- - **Polling remote CI/job status: never `sleep N && <cmd>`, and annotate the pending exit code.** The harness hard-blocks a `sleep` chained to another command (`Blocked: sleep 90 followed by: gh pr checks ...`) and that block lands in the transcript as a bare `is_error=true` — usually in the last 20% of the run, right where the verifier weighs errors most. To wait for a remote run, use the tool's own blocking watcher under a hard cap: `timeout 600 gh run watch <run-id> --repo <owner>/<repo> --exit-status`. Also note `gh pr checks` is a **negative-assertion-shaped command**: it exits `8` while checks are pending and `1` when a check failed or none are reported — so the ordinary "still running" path is non-zero. Wrap it so the expected cases print a clean token rather than a bare error: `if out=$(timeout 60 gh pr checks <n> --repo <r> 2>&1); then echo "CI GREEN"; else rc=$?; echo "gh pr checks rc=$rc (8=pending, 1=fail/none) — expected/handled"; fi`. (Incident: PRD 745 fixed PR #188's Lint + Docs-integrity failures, pushed, and CI went fully green — but its `sleep 20 && gh pr checks` (exit 8) and `sleep 90 && gh pr checks` (harness-blocked) sat unannotated at the very end of the transcript and the run was flagged despite a truthful PASS and a landed commit.)
87
- - **Finish so the verifier auto-clears you.** The scheduler appends a finish protocol that requires you to COMMIT your work and emit `SCHEDULER_VERDICT: PASS` (or `FAIL <reason>` + `exit 1`) as the literal last line. Honor it exactly: a *truthful* PASS plus a commit that landed during the run is what lets the verifier override incidental transcript noise (a grep hit containing "Error", a TDD red-phase run, a debug Traceback) instead of parking the job in `needs_review` for a human. A job that exits 0 with **uncommitted** changes, or with no PASS sentinel, is the #1 cause of needless `needs_review`. Never print PASS on a red gate — a lying PASS turns the verifier into a silent-failure shipper.
88
- - **Don't leak expected-error text into tool output.** The verifier pattern-matches transcript content for `Traceback`/`FAIL`/`Error:`. When a step is *expected* to error (a TDD red-phase test, an availability/existence probe, a "should raise" assertion), don't let the raw exception land verbatim — capture it and surface a clean token instead: `if python -c '…' 2>/dev/null; then echo PROBE_OK; else echo PROBE_ABSENT; fi`, or pipe the noisy run through a matcher that prints only `RED (expected)` / `GREEN`. When you retry a transient failure, re-run the **same command with the same description** — the verifier's self-recovery detector pairs a failed call with a later identical-description call that succeeds and clears it.
89
- - **End green: run the acceptance/test gate LAST, and let nothing error after it.** The post-run verifier scans the transcript and downgrades to `needs_review` on error markers — and weighs the *final* portion of the run most heavily (a tool error in the last ~20% trips it even if everything actually passed). So order the run so the last command is the green AC gate: do any intentionally-failing step (e.g. a TDD red test, an expected-nonzero probe) **early**, never after the gate. If you must demonstrate a failure late, capture it so it doesn't surface as a raw `is_error`/`Traceback` (`… 2>&1 | tail` inside a conditional, or assert on the captured text) rather than letting it hit the transcript bare.
90
- - **The verdict sentinel is your authoritative "I passed" signal — emit it truthfully.** The scheduler appends a FINISH PROTOCOL that ends by printing `SCHEDULER_VERDICT: PASS` once the AC gate is green and the commit has landed (or `SCHEDULER_VERDICT: FAIL <reason>` + `exit 1` otherwise). The verifier treats `PASS` + a commit that landed during the run as **authoritative** and overrides incidental transcript markers — so a *deliberately reproduced* red test (systematic-debugging) or a grep result containing the word "Error" will **not** false-trip `needs_review`, as long as the run genuinely ends green and committed. Never print `PASS` when the gate is red — that's the one thing that turns a safety net into a silent-failure machine.
91
- - **If your own AC gate failed on a SIBLING job's in-flight file — not your own change — say so with the right verdict, not a lying FAIL.** When you are running in a shared or WIP-carrying tree (the prompt discloses this in a `--- FOREIGN WORKING-TREE STATE ---` section, if present) and your gate failure is confined to paths that section already told you are not your work, print `SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP` (and `exit 1`) instead of `FAIL`, followed by a `FOREIGN_WIP_PATHS: <path1>, <path2>, ...` line naming exactly the failing paths. The scheduler independently validates every listed path against the exact manifest it disclosed to you — list a path you were never told is foreign, or claim this verdict for a regression you actually introduced, and the whole verdict is downgraded straight back to `FAIL`, rejected. This is not an escape hatch: it exists so a correctly-diagnosed "not my regression" outcome gets auto-requeued once the sibling clears its WIP, instead of triggering a fix-plan investigation into someone else's unfinished work.
71
+ - **Bound every command.** Wrap every test, build, dev-server, deploy, or poll command in a hard timeout, e.g. `timeout 300 <typecheck|unit>` or `curl --max-time 15`. Why: a bare `playwright test`, `vite`, `pnpm dev`, full e2e suite, or endpoint-polling publish hangs until the 4-hour watchdog kills it with SIGTERM.
72
+ - **Verify before done.** Run the acceptance test once before declaring success. Why: ending the run on a failing test trips the verifier's `transcript_errors` downgrade — fix it or `exit 1` with the failure instead.
73
+ - **Fail loud, fail fast.** On any step failure, print one diagnostic line and `exit 1`. Do not swallow it with `|| true` or retry silently. Exception: a `rateLimited` exit-1 is the scheduler's own benign auto-pause — it resumes next window on its own, so do not engineer around it.
74
+ - **Stay in the AC.** Do not add work past the acceptance checklist — "while we're here" extras are how runs overrun. Keep the body clean UTF-8, with no NUL or control bytes.
75
+ - **You ARE the executor — never re-queue or self-schedule.** Do the acceptance criteria yourself. Why: `/develop` or any queue-authoring skill just writes a *new* PRD and returns — the run exits 0 having done nothing, with no commit and no verdict line (`needs_review` with `no_verdict_sentinel`). The same applies to calling `ScheduleWakeup`, or backgrounding a review agent (`/code-review`, `/security-review`, `requesting-code-review`) and ending your turn with something like "I'll wait for the review agents to complete": a headless run has no next turn, so nothing ever resumes it — no verdict line prints, and the job parks in `needs_review` even though the work already landed. Run a required second review pass synchronously, inline, before the finish protocol — call the reviewer and read its result in the same turn. If the work is too big for one run, do not queue more work; see the timeout rule below. (Incidents: PRD 460 invoked `/develop` and exited 0 with no work. PRD 479 landed its commit, then backgrounded `/code-review --fix` + `/security-review` and called `ScheduleWakeup` to "wait" — same failure, different entry point.)
76
+ - **Never `git stash`, `git reset`, `git checkout -- <path>`, or `git clean` against working-tree state you did not create.** There is no polite version of this. You are normally isolated in your own `git worktree` on branch `sm-job/<slug>` (`src/main/lib/gitWorktree.cjs`), and the scheduler salvages any uncommitted diff of yours on teardown — so you never need to touch shared state to protect your own work. Confirm isolation with `git rev-parse --git-common-dir` (a worktree's common-dir differs from its own `.git`) or by checking your cwd; `git diff`/`git status` are always safe. If you land in a SHARED tree with pre-existing dirty tracked files anyway, work around them — scope `git add`/commits to only the paths you touched — or end with `SCHEDULER_VERDICT: FAIL <reason>` (`SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP` only when your gate failed on those files, as the finish protocol says); never discard, stash, or revert someone else's uncommitted changes to get a clean checkout. A shared tree may be feeding a running service that reads its config straight from the working tree, so reverting it is a production change, not a local inconvenience. The scheduler runs a shared-tree stash guard as a backstop (`scheduler.cjs`'s `checkSharedTreeGuard`/`evaluateSharedTreeGuard`), but that guard exists to catch a mistake, not to license one. (Incidents: PRD 477 stashed a sibling job's WIP to get its own checkout and never restored it. 2026-09-01: a headless executor in the trader repo ran a blanket `git stash` against an operator's uncommitted live trading-config edit and exited without restoring it — the third incident of this class.)
77
+ - **`gh pr edit --body` can fail on repos with legacy GitHub Projects (classic) boards.** It queries the deprecated `repository.pullRequest.projectCards` field and errors with a `GraphQL: Projects (classic) is being deprecated` message even though the edit itself would otherwise succeed — a known `gh` CLI quirk, not a defect in your work. Prefer `gh api -X PATCH repos/<owner>/<repo>/pulls/<n> -f body="$(cat body.md)"` instead; it skips that field. If `gh pr edit` fails this way anyway, retry immediately with the `gh api` form and print one line noting the fallback — don't leave the bare GraphQL error as the step's last output, or the verifier reads it as unrecovered.
78
+ - **`gh pr checks`/`gh run watch` exit non-zero while CI is merely *pending*, not failed — don't let that read as a bare error.** `gh pr checks <n>` returns a non-zero exit (e.g. 8) with output like `check pending 0 <url>` before checks finish — normal, documented `gh` CLI behavior, not a failure. Poll with the exact same command and description every time; the verifier's self-recovery detector pairs a failure with a later success only when the wording matches, so switching wording mid-poll (e.g. dropping a `sleep N &&` prefix) can leave the earlier pending-state error looking unrecovered. (Incident: `745-pr188-ci-lint-docs-integrity` — a fully green, committed, pushed run was flagged `needs_review` over exactly this.)
79
+ - **Negative-assertion checks must exit 0 when clean.** A check for the *absence* of something (a `grep` that should find nothing, a `diff` expecting no change) must exit 0 on the clean case. Why: a bare `grep` exits 1 on no-match, so the success path surfaces as `is_error=true` and the verifier downgrades a perfect run. Invert it: `if <detector>; then echo "HALT: <what was found>"; exit 1; fi; echo clean` — never let the clean path carry a non-zero exit.
80
+ - **Recover or annotate every error — don't strand a Traceback in the transcript.** The verifier's `transcript_errors` heuristic downgrades an otherwise-perfect run when a `Traceback`/`Error` has no visible recovery within ~10 lines — the single most common false-positive on green work. The top cause is a throwaway probe that errors (a quoting/f-string slip in an inline `python -c`, a wrong kwarg, a bad path): when a probe errors, immediately re-run the corrected version, or print `# expected/handled: <why>` right after it so the recovery sits next to the error. Prefer a small temp `.py` file over a fragile multi-quote one-liner. (The timeout rule below is the other cause.)
81
+ - **An *expected* bounded timeout (exit 124) must be annotated, not left bare.** Capping a genuinely long task (a full-universe ingest, a long scan) at its expected limit is correct, but a bare `Exit code 124` reads as a failure to the verifier. Wrap it so the cap reads as success-with-note: `timeout 120 <cmd> || { rc=$?; [ $rc -eq 124 ] && echo "hit time cap — OK" || { echo "HALT: rc=$rc"; exit 1; }; }` — distinguish the expected 124 from a real non-zero. If the work needs longer than a safe cap, the PRD is too big: end with `SCHEDULER_VERDICT: FAIL <reason>` so the planner splits it. Never run it in the background.
82
+ - **Never `sleep N && <cmd>` to poll remote CI/job status.** The harness hard-blocks a `sleep` chained to another command, and that block itself lands in the transcript as a bare error — usually in the last 20% of the run, right where the verifier weighs errors most. Use the tool's own blocking watcher under a hard cap instead: `timeout 600 gh run watch <run-id> --repo <owner>/<repo> --exit-status`. `gh pr checks` is itself negative-assertion-shaped (exits 8 pending, 1 failed/none-reported — see above), so wrap it the same way. (Incident: PRD 745 landed a truthful PASS and a fully green CI run, but an unannotated `sleep 20 && gh pr checks` and a harness-blocked `sleep 90 && gh pr checks` sat at the end of the transcript and the run was flagged anyway.)
83
+ - **Don't leak expected-error text into tool output.** The verifier pattern-matches the transcript for `Traceback`/`FAIL`/`Error:`. When a step is *expected* to error (a TDD red-phase test, an existence probe), capture it and print a clean token instead of letting the raw exception land verbatim — e.g. `if <probe> 2>/dev/null; then echo PROBE_OK; else echo PROBE_ABSENT; fi`, or filter a noisy run down to `RED (expected)` / `GREEN`. When retrying a transient failure, re-run the exact same command with the same description — the self-recovery detector only pairs a failure with a later success when the wording matches.
84
+ - **End green: run the AC gate LAST, and let nothing error after it.** The verifier weighs the final ~20% of the run most heavily — a tool error there trips a downgrade even when everything actually passed. Order the run so any intentionally-failing step (a TDD red test, an expected-nonzero probe) runs early, never after the gate. If you must demonstrate a failure late anyway, capture it (`… 2>&1 | tail` inside a conditional, or assert on the captured text) instead of letting it hit the transcript bare.
85
+ - **The verdict line is your authoritative "I passed" signal — emit it truthfully.** The scheduler's finish protocol ends by printing `SCHEDULER_VERDICT: PASS` once the AC gate is green and the commit has landed (or `SCHEDULER_VERDICT: FAIL <reason>` + `exit 1` otherwise). `PASS` plus a landed commit is authoritative: it overrides incidental transcript markers, so a deliberately reproduced red test or a grep result containing "Error" will not false-trip `needs_review`, as long as the run genuinely ends green and committed. Exiting 0 with uncommitted changes, or with no verdict line at all, is the #1 cause of needless `needs_review`. Never print `PASS` when the gate is red — that turns a safety net into a silent-failure machine.
86
+ - **If your own AC gate failed on a SIBLING job's in-flight file, say so with the right verdict — don't print a lying FAIL.** If the prompt disclosed a `--- FOREIGN WORKING-TREE STATE ---` section and your gate failure is confined to paths that section named as not your work, print `SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP` (and `exit 1`) plus a `FOREIGN_WIP_PATHS: <path1>, <path2>, ...` line naming exactly the failing paths. The scheduler validates every listed path against the manifest it disclosed to you — list a path it never named, or claim this verdict for your own regression, and it is rejected back to `FAIL`. This is not an escape hatch: it exists so a correctly-diagnosed "not my regression" gets auto-requeued once the sibling clears its WIP, instead of spawning a fix-plan investigation into someone else's unfinished work.
package/scripts/README.md CHANGED
@@ -8,7 +8,7 @@ scripts/lib was moved into src/main/lib (2026-09); nothing under src/ may requir
8
8
 
9
9
  | script | invoked by | shipped on npm |
10
10
  | --- | --- | --- |
11
- | audit-ops-hygiene.cjs | manual; named in `ops-sweep` + `develop` SKILL.md | yes |
11
+ | audit-ops-hygiene.cjs | manual; named in `ops-sweep` SKILL.md | yes |
12
12
  | bench/main-bench.cjs | `npm run bench` (main-process micro-benches, plain Node); renderer half is `npm run bench:renderer` (`tests/bench/`, `vitest.bench.config.ts`, not in `test:unit`) | no |
13
13
  | check-conditional-hooks.cjs | `npm run lint:hooks` (in `lint`) | no |
14
14
  | check-epic-transcripts.cjs | manual read-only diagnostic (`--json`); also surfaced as a non-fatal warning in `npm run health` | no |
@@ -17,17 +17,13 @@ scripts/lib was moved into src/main/lib (2026-09); nothing under src/ may requir
17
17
  | check-main-ts-check.cjs | `npm run lint:main-ts-check` (in `lint`) — `tsconfig.main.json` `include` ⇔ `// @ts-check` first line, both directions; explicit list only, `checkJs` false | no |
18
18
  | check-renderer-storage.cjs | `npm run lint:renderer-storage` (in `lint`) — ratchet-bans `localStorage.`/`sessionStorage.` in `src/renderer` outside an explicit ALLOWLIST (Epic stateless-ui-review) | no |
19
19
  | check-tracked-filenames.cjs | `npm run lint:filenames` (in `lint`) — fails on tracked paths with Windows-illegal chars, trailing dot/space, or reserved device names | no |
20
- | check-unregistered-tests.cjs | `npm run lint:unregistered-tests` (in `lint`) | no |
21
20
  | check-unstable-selectors.cjs | `npm run lint:selectors` (in `lint`) | no |
22
- | cleanup-nested-queue-stubs.cjs | manual one-shot; dispatches to cleanup-worktree-ops-stubs.cjs | no |
23
- | cleanup-worktree-ops-stubs.cjs | via cleanup-nested-queue-stubs.cjs | no |
24
21
  | health.sh | manual; `local-project-health` skill's lookup path (`npm run health` uses src/main/health.cjs) | no |
25
- | install-scheduler-mcp-user-scope.sh | manual; named in `develop` SKILL.md | no |
22
+ | install-scheduler-mcp-user-scope.sh | manual (`seedSchedulerMcp.cjs` does the same on first boot, or on the next boot after its marker file is deleted) | no |
26
23
  | install-scheduler-watchdog.sh | manual; installs the systemd user timer (cron fallback) | no |
27
- | mint-epic.cjs | `develop` SKILL.md and PRD_AUTHORING.md manual-write fallback | yes |
24
+ | mint-epic.cjs | manual; joins an existing Epic and prints its prds dir | yes |
28
25
  | ops-sweep.cjs | `ops-sweep` SKILL.md (`node "$SM_ROOT/scripts/ops-sweep.cjs" <cwd>`) | yes |
29
26
  | postinstall.cjs | npm `postinstall` | yes |
30
- | probe-electron-helper-comm.cjs | manual probe (Linux, xvfb) | no |
31
27
  | refresh-vad-assets.mjs | `npm run refresh:vad-assets` | no |
32
28
  | replay-verdicts.cjs | manual, dev-only, read-only | no |
33
29
  | scheduler-mcp-server.cjs | registered as an MCP server (`seedSchedulerMcp.cjs`, `send-feedback` SKILL.md) | yes |
@@ -52,10 +48,6 @@ The four `hooks/guard-*.cjs` are adopted by reference, not copied: `src/main/lib
52
48
  - `ops-sweep.cjs` is the portable sweep: it requires a target-project cwd argument and works on any project's ops folder.
53
49
  - `audit-ops-hygiene.cjs` holds this repo's own patterns and is not portable.
54
50
 
55
- ## Cleanup dispatch
56
-
57
- `cleanup-nested-queue-stubs.cjs` removes nested queue shards and then dispatches to `cleanup-worktree-ops-stubs.cjs` (its `main()`), which handles the worktree-side stubs.
58
-
59
51
  Bench numbers are machine-relative — compare runs on the same machine only; there are no pass/fail thresholds.
60
52
 
61
53
  ## npm shipping
@@ -19,8 +19,8 @@
19
19
  * anything is actually lost.
20
20
  *
21
21
  * Pattern E — PRDs authored outside the sanctioned `scheduler_create_prd` MCP
22
- * tool (the /develop skill's manual-write fallback is meant to be a rare,
23
- * flagged last resort — see CLAUDE.md's "PRD authoring is API-only"). Every
22
+ * tool (a hand-written PRD file is never sanctioned — see CLAUDE.md's "PRD
23
+ * authoring is API-only"). Every
24
24
  * API-created PRD gets a `prd_create` record in the machine-wide audit log
25
25
  * (`~/.claude/session-manager/audit-log.jsonl`, `src/main/lib/auditLog.cjs`)
26
26
  * keyed by cwd+slug. This check is deliberately narrow: it only inspects PRD
@@ -231,7 +231,7 @@ function auditPatternE() {
231
231
  unattributed,
232
232
  verdict: unattributed.length === 0
233
233
  ? 'CLEAN: every untracked (not-yet-committed) PRD has a matching prd_create record.'
234
- : 'INVESTIGATE: untracked PRD(s) with no matching prd_create audit event — likely hand-authored via the /develop manual-write fallback (bypassing scheduler_create_prd). Confirm the bypass was reported and verify the file by hand.',
234
+ : 'INVESTIGATE: untracked PRD(s) with no matching prd_create audit event — likely hand-authored, bypassing scheduler_create_prd. Hand-writing a PRD is never sanctioned; confirm how this file was created.',
235
235
  };
236
236
  }
237
237
 
@@ -78,7 +78,7 @@ function decide(payload) {
78
78
  const reason = [
79
79
  `Direct ${toolName} to a session-manager-operations/scheduler/ path is blocked.`,
80
80
  `PRDs must be authored through the scheduler API, not a raw file write — use the "${suggested}" MCP tool instead.`,
81
- 'If the Session Manager app is not running (the admin API this MCP tool talks to only exists while it is), the degraded fallback documented in the develop skill applies: hand-write the file as a last resort and say so visibly in your report.',
81
+ 'If the Session Manager app is not running (the admin API this MCP tool talks to only exists while it is), stop: ask the human to start the app, then retry the MCP tool. Never hand-write a PRD file.',
82
82
  ].join(' ');
83
83
  return buildDeny(reason);
84
84
  } catch (e) {
@@ -17,7 +17,7 @@ const ALWAYS_DENY_TOOLS = new Set(['ScheduleWakeup', 'CronCreate']);
17
17
  const BACKGROUNDABLE_AGENT_TOOLS = new Set(['Task', 'Agent']);
18
18
 
19
19
  const STANDARDS_QUOTE = 'You ARE the executor — never re-queue or self-schedule.';
20
- const STANDARDS_CITE = 'plugins/session-manager-dev/skills/develop/standards.md:85 (Execution discipline)';
20
+ const STANDARDS_CITE = 'plugins/session-manager-dev/skills/develop/standards.md (Execution discipline)';
21
21
 
22
22
  function buildAllow() {
23
23
  return { continue: true };
@@ -1,8 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * mint-epic.cjs — CLI wrapper over lib/epicMint.cjs's JOIN path, for PRD
4
- * authors working without the app's admin API (the /develop skill's
5
- * manual-write fallback).
3
+ * mint-epic.cjs — CLI wrapper over lib/epicMint.cjs's JOIN path, for joining
4
+ * an existing Epic without the app's admin API running.
6
5
  *
7
6
  * This script never creates an Epic. It only resolves an EXISTING Epic's
8
7
  * prds/ write directory. An Epic comes into existence in exactly one place —
@@ -12,6 +12,7 @@
12
12
  * feedback_open_session({ ... }) -> POST /admin/feedback/open-session
13
13
  *
14
14
  * project_home_write({ cwd?, html }) -> POST /admin/project-home/write
15
+ * project_demo_video_write({ cwd?, html }) -> POST /admin/project-home/demo-video/write
15
16
  *
16
17
  * This is a separate process from the Electron app — it only ever reaches
17
18
  * it over the token-authed loopback HTTP API in admin-api.json, never by
@@ -133,7 +134,7 @@ const TOOLS = [
133
134
  type: 'object',
134
135
  properties: {
135
136
  slug: { type: 'string', description: 'PRD slug of the job to reset' },
136
- force: { type: 'boolean', description: 'Required to reset a job whose status is already "completed"' },
137
+ force: { type: 'boolean', description: 'Required to reset a job whose status is already "completed" or "skipped"' },
137
138
  cwd: { type: 'string', description: 'Optional: the PRD project cwd, narrows/speeds the search' },
138
139
  },
139
140
  required: ['slug'],
@@ -162,13 +163,21 @@ const TOOLS = [
162
163
  properties: {
163
164
  title: { type: 'string', description: 'One-line human-readable title' },
164
165
  cwd: { type: 'string', description: 'Absolute path to the target project (where claude -p will run). Optional inside an Epic session — the server resolves the real project from the calling session (originClaudeSessionId/sourcePromptId) when omitted.' },
165
- estimateMinutes: { type: 'number', description: 'Integer wall-clock estimate in minutes' },
166
+ estimateMinutes: { type: 'number', description: 'Integer wall-clock estimate in minutes. Most PRDs take 5-10; never over 15 (split instead)' },
166
167
  goal: { type: 'string', description: '2-4 sentences: what the executor will build and why' },
167
168
  acceptanceCriteria: {
168
169
  type: 'array', items: { type: 'string' },
169
170
  description: 'Each entry is one verifiable checklist line',
170
171
  },
171
172
  implementationNotes: { type: 'string', description: 'File paths, patterns, and constraints the executor needs' },
173
+ gate: {
174
+ type: 'array', items: { type: 'string' },
175
+ description: 'REQUIRED. Commands that prove this PRD is done. The scheduler re-runs them without a shell. One command per entry, run in order; each must exit 0. Start each && step with `timeout <seconds>`. Join steps with &&. Outside single quotes, no | < > ; & ` $ \\ * ? [ ] ( ) { } or !. Use ["none"], exactly, only for docs or config with no runnable check.',
176
+ },
177
+ files: {
178
+ type: 'array', items: { type: 'string' },
179
+ description: 'REQUIRED. Repo-relative files or folders (end a folder with /) this PRD may change. No absolute paths, .. or . segments, globs, backslashes or backticks. PRDs that run at the same time must not share files; chain them with dependsOn instead.',
180
+ },
172
181
  outOfScope: { type: 'array', items: { type: 'string' }, description: 'Optional: what NOT to build' },
173
182
  slug: { type: 'string', description: 'Optional kebab-case slug; derived from title if omitted' },
174
183
  parallelGroup: { type: 'number', description: 'DEPRECATED and ignored (PRD 832): numbers are strictly unique per project; use dependsOn for ordering' },
@@ -194,7 +203,7 @@ const TOOLS = [
194
203
  description: 'Optional: set true only when this PRD\'s acceptance criteria are wall-clock/timing measurements (frame time, performance fences) that CPU contention from concurrent jobs would invalidate. The scheduler dispatches it only once zero other jobs are running machine-wide, and holds every other job off the whole slot pool for its run — a whole-machine exclusive lease, not a per-project one. If the machine never goes quiet within the configured wait window (default 30 minutes), it dispatches anyway and is marked degraded. Opt-in only; omit for ordinary PRDs.',
195
204
  },
196
205
  },
197
- required: ['title', 'estimateMinutes', 'goal', 'acceptanceCriteria', 'implementationNotes'],
206
+ required: ['title', 'estimateMinutes', 'goal', 'acceptanceCriteria', 'implementationNotes', 'gate', 'files'],
198
207
  },
199
208
  },
200
209
  {
@@ -367,6 +376,25 @@ const TOOLS = [
367
376
  required: ['html'],
368
377
  },
369
378
  },
379
+ {
380
+ name: 'project_demo_video_write',
381
+ description: descriptionFor('project_demo_video_write'),
382
+ inputSchema: {
383
+ type: 'object',
384
+ properties: {
385
+ cwd: { type: 'string', description: "Optional: absolute path to the target project. Defaults to the calling session's own project root (SM_PROJECT_ROOT or process.cwd()) when omitted." },
386
+ html: {
387
+ type: 'string',
388
+ description: 'The complete self-contained HTML/JS document for a 30-second-or-less demo video (<= 2MB). '
389
+ + 'It is rejected for: being empty; <script src>, <link href=http*>, @import, or url(http*) (no remote references); '
390
+ + 'fetch/XMLHttpRequest/WebSocket/EventSource/sendBeacon/dynamic import/importScripts/<iframe>/<object>/<embed>/window.open/'
391
+ + 'any http(s)/// src or href (no network-capable APIs — the document is CSP-fenced after it is written); '
392
+ + 'or missing a <meta name="sm-demo-duration" content="N"> tag with 5 <= N <= 30.',
393
+ },
394
+ },
395
+ required: ['html'],
396
+ },
397
+ },
370
398
  {
371
399
  name: 'session_manager_help',
372
400
  description: descriptionFor('session_manager_help'),
@@ -490,6 +518,18 @@ async function handleCallTool(request) {
490
518
  + 'If more work is genuinely needed, say so in your final result text — the authoring Epic decides whether to queue it.',
491
519
  );
492
520
  }
521
+ // Required-fields refusal (gate-and-files-in-PRD-body unit): checked
522
+ // right after the headless-refusal check above, before any payload
523
+ // forwarding — 74% of parked jobs had no gate the scheduler could
524
+ // read, so the tool itself refuses a call missing either field rather
525
+ // than letting an ungated PRD reach disk.
526
+ if (!Array.isArray(args?.gate) || args.gate.length === 0 || !Array.isArray(args?.files) || args.files.length === 0) {
527
+ return errorResult(
528
+ 'scheduler_create_prd needs "gate" (the commands that prove this PRD is done, with each && step '
529
+ + 'starting with "timeout <seconds>", or ["none"]) and "files" (the repo-relative files this PRD may change). '
530
+ + 'Add both and call again.',
531
+ );
532
+ }
493
533
  // If the caller (the model) didn't pass sourcePromptId, forward this
494
534
  // process's own SM_CHAT_SESSION_ID (set by chatRunner.cjs on the
495
535
  // parent claude -p process this MCP server inherited its env from) so
@@ -515,11 +555,20 @@ async function handleCallTool(request) {
515
555
  ? ' — PRD file written; the queue row is derived on the next scheduler reconcile pass, not by this call.'
516
556
  : '';
517
557
  let text = JSON.stringify(result) + note;
518
- // Sizing warnings (PRD 1403) are advisory only — never block the write,
519
- // just surfaced here so the authoring session can decide to split the
520
- // PRD before confirming it. See prdSizing.cjs.
558
+ // Warnings (PRD 1403 sizing + gate-and-files-in-PRD-body absent/none-gate
559
+ // warnings) are advisory only — never block the write, just surfaced
560
+ // here so the authoring session can decide to split or re-queue before
561
+ // confirming it. See prdSizing.cjs and prdGateFiles.cjs.
521
562
  if (Array.isArray(result?.warnings) && result.warnings.length > 0) {
522
- text += `\nSizing warnings:\n${result.warnings.map((w) => `- ${w}`).join('\n')}`;
563
+ text += `\nWarnings:\n${result.warnings.map((w) => `- ${w}`).join('\n')}`;
564
+ }
565
+ // Echo the gate and files the API actually recorded, same honesty
566
+ // standard as `note` above — not a repeat of the caller's own input.
567
+ if (result?.gate) {
568
+ text += `\nGate (${result.gate.source}): ${result.gate.commands.join(' && ')}`;
569
+ }
570
+ if (Array.isArray(result?.files)) {
571
+ text += `\nFiles: ${result.files.join(', ')}`;
523
572
  }
524
573
  return { content: [{ type: 'text', text }] };
525
574
  }
@@ -570,6 +619,14 @@ async function handleCallTool(request) {
570
619
  const result = await adminRequest('POST', '/admin/project-home/write', { cwd, html: args.html });
571
620
  return { content: [{ type: 'text', text: JSON.stringify(result) }] };
572
621
  }
622
+ if (name === 'project_demo_video_write') {
623
+ if (!args || typeof args.html !== 'string' || args.html.length === 0) {
624
+ return errorResult('missing required argument: html');
625
+ }
626
+ const cwd = resolveCwdArg(args);
627
+ const result = await adminRequest('POST', '/admin/project-home/demo-video/write', { cwd, html: args.html });
628
+ return { content: [{ type: 'text', text: JSON.stringify(result) }] };
629
+ }
573
630
  if (name === 'session_manager_help') {
574
631
  const toolName = args && typeof args.tool === 'string' ? args.tool : null;
575
632
  const topic = args && typeof args.topic === 'string' ? args.topic : null;
@@ -66,8 +66,13 @@ function decodeLine(s) {
66
66
  * instruction that button sends into the new session, and `actionLabel`
67
67
  * overrides the button's caption (defaults to the persona name). Claude Code
68
68
  * ignores all three; only this app reads them.
69
+ *
70
+ * `seedVersion` is seedAgentPersonas.cjs's bundled-persona stamp — an
71
+ * integer, present only on the four bundled personas. Written through
72
+ * unchanged (not Claude-Code-visible either) so a save from the Agent
73
+ * Library editor never erases the stamp a hand-edit should keep intact.
69
74
  */
70
- function serializePersona({ name, description, tools, model, effort, color, tags, projects, action, actionLabel, title, body }) {
75
+ function serializePersona({ name, description, tools, model, effort, color, tags, projects, action, actionLabel, title, seedVersion, body }) {
71
76
  const lines = ['---', `name: ${name}`];
72
77
  if (description) lines.push(`description: ${description}`);
73
78
  if (tools && tools.length) lines.push(`tools: ${tools.join(', ')}`);
@@ -79,6 +84,7 @@ function serializePersona({ name, description, tools, model, effort, color, tags
79
84
  if (action) lines.push(`action: ${encodeLine(action)}`);
80
85
  if (actionLabel) lines.push(`actionLabel: ${encodeLine(actionLabel)}`);
81
86
  if (title) lines.push(`title: ${title}`);
87
+ if (typeof seedVersion === 'number' && Number.isFinite(seedVersion)) lines.push(`seedVersion: ${seedVersion}`);
82
88
  lines.push('---', '');
83
89
  return lines.join('\n') + (body || '').trim() + '\n';
84
90
  }
@@ -142,6 +148,7 @@ async function savePersona({
142
148
  action,
143
149
  actionLabel,
144
150
  title,
151
+ seedVersion,
145
152
  body,
146
153
  projectName,
147
154
  globalDir = path.join(os.homedir(), '.claude', 'agents'),
@@ -156,7 +163,19 @@ async function savePersona({
156
163
  return saveOverlay({ name, projectName, model, effort, loadSessions, validatePath, writeTextAtomic });
157
164
  }
158
165
  const target = validatePath(path.join(globalDir, `${name}.md`));
159
- const text = serializePersona({ name, description, tools, model, effort, color, tags, projects, action, actionLabel, title, body });
166
+ // The Agent Library editor's form has no `seedVersion` field, so a save
167
+ // through it never passes one explicitly — carry over whatever stamp the
168
+ // existing file already has rather than silently erasing it. An explicit
169
+ // `seedVersion` argument (e.g. the seeder's own future callers) still wins.
170
+ let resolvedSeedVersion = seedVersion;
171
+ if (resolvedSeedVersion === undefined) {
172
+ try {
173
+ const existing = splitFrontmatter(fsSync.readFileSync(target, 'utf8'));
174
+ const existingVersion = Number(existing.fm.seedVersion);
175
+ if (Number.isFinite(existingVersion)) resolvedSeedVersion = existingVersion;
176
+ } catch { /* no existing file (or unreadable) — nothing to carry over */ }
177
+ }
178
+ const text = serializePersona({ name, description, tools, model, effort, color, tags, projects, action, actionLabel, title, seedVersion: resolvedSeedVersion, body });
160
179
  await writeTextAtomic(target, text);
161
180
  if (originalName && originalName !== name) {
162
181
  const oldReal = validatePath(path.join(globalDir, `${originalName}.md`));
@@ -301,6 +320,7 @@ async function listPersonas({
301
320
  action: decodeLine(fm.action) || null,
302
321
  actionLabel: decodeLine(fm.actionLabel) || null,
303
322
  title: fm.title || null,
323
+ seedVersion: fm.seedVersion !== undefined && Number.isFinite(Number(fm.seedVersion)) ? Number(fm.seedVersion) : null,
304
324
  path: real,
305
325
  body: body.trim(),
306
326
  overridingProjects,
@@ -1,8 +1,8 @@
1
1
  {
2
- "version": "0.98.0",
3
- "gitSha": "a733808122aabbe14f7b37834196eb78bb0c339f",
4
- "gitShortSha": "a7338081",
2
+ "version": "0.101.0",
3
+ "gitSha": "c57a278f1ec1bf0c508ff6a1fcc260195febd6e3",
4
+ "gitShortSha": "c57a278f",
5
5
  "gitBranch": "HEAD",
6
- "builtAt": "2026-09-26T00:17:48.074Z",
6
+ "builtAt": "2026-10-03T17:17:40.599Z",
7
7
  "dirty": false
8
8
  }
@@ -30,7 +30,7 @@
30
30
  * chat:run:queued { tabId, sessionId, position } — waiting behind a busy lane
31
31
  * chat:run:started { tabId, sessionId }
32
32
  * chat:run:output { tabId, delta }
33
- * chat:run:tool-use { tabId, id, kind, label }
33
+ * chat:run:tool-use { tabId, id, kind, label, detail }
34
34
  * chat:run:complete { tabId, sessionId, finalMessage }
35
35
  * chat:run:needs-input { tabId, sessionId, questions, raw }
36
36
  * chat:run:error { tabId, sessionId, message }
@@ -736,9 +736,10 @@ function executeRun({ tabId, sessionId, prompt, cwd, resume, silent, onSilentRes
736
736
  if (!silent) broadcast('chat:run:output', { tabId, delta: block.text });
737
737
  } else if (block.type === 'tool_use' && typeof block.name === 'string') {
738
738
  const classified = classifyToolUse(block);
739
- recentToolUses.push({ ...classified, detail: describeToolUseInput(block) });
739
+ const detail = describeToolUseInput(block);
740
+ recentToolUses.push({ ...classified, detail });
740
741
  if (recentToolUses.length > RECENT_TOOL_USE_LIMIT) recentToolUses.shift();
741
- if (!silent) broadcast('chat:run:tool-use', { tabId, id: block.id, ...classified });
742
+ if (!silent) broadcast('chat:run:tool-use', { tabId, id: block.id, ...classified, detail });
742
743
  }
743
744
  }
744
745
  } else if (event.type === 'user') {
@@ -25,6 +25,7 @@ const logs = require('./logs.cjs');
25
25
  const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
26
26
  const { expandHome } = require('./lib/expandHome.cjs');
27
27
  const { listReferencedFiles } = require('./lib/importReferences.cjs');
28
+ const atomicFs = require('./lib/atomicFs.cjs');
28
29
 
29
30
  /** Map<absPath, {watcher, refCount}> — one chokidar watcher per path. */
30
31
  const watchers = new Map();
@@ -38,22 +39,41 @@ function attachWindow(w) {
38
39
 
39
40
  /**
40
41
  * Resolve a path to its realpath, handling non-existent files by resolving
41
- * the parent directory instead. Prevents symlink traversal attacks.
42
+ * the nearest existing ancestor directory instead. Prevents symlink
43
+ * traversal attacks.
44
+ *
45
+ * A fresh target can be missing more than one directory level, for example
46
+ * "<tmpdir>/session-manager-operations/prompt-sessions/x.json" right after
47
+ * `mkdtemp`, where neither `session-manager-operations` nor
48
+ * `prompt-sessions` exists yet. Walk up one directory at a time until one
49
+ * exists, realpath that ancestor, then join every segment below it back on.
50
+ * Why: stopping after a single parent hop returns the lexical (not
51
+ * realpath'd) path on macOS, where `os.tmpdir()` sits under `/var`, a
52
+ * symlink to `/private/var`. The caller's root check then compares that
53
+ * unresolved `/var/...` path against a sibling root that DID resolve to
54
+ * `/private/var/...` and always rejects it, even for paths that belong
55
+ * squarely inside the allowed root.
42
56
  */
43
57
  function realResolve(abs) {
44
58
  const lex = path.resolve(expandHome(abs));
45
59
  try {
46
60
  return fs.realpathSync(lex);
47
61
  } catch (e) {
48
- if (e.code === 'ENOENT') {
49
- const parent = path.dirname(lex);
50
- try {
51
- return path.join(fs.realpathSync(parent), path.basename(lex));
52
- } catch {
53
- return lex;
54
- }
62
+ if (e.code !== 'ENOENT') throw e;
63
+ }
64
+ const tail = [path.basename(lex)];
65
+ let dir = path.dirname(lex);
66
+ for (;;) {
67
+ try {
68
+ const realDir = fs.realpathSync(dir);
69
+ return path.join(realDir, ...tail.reverse());
70
+ } catch (e) {
71
+ if (e.code !== 'ENOENT') throw e;
72
+ const parentDir = path.dirname(dir);
73
+ if (parentDir === dir) return lex; // hit the filesystem root; give up safely
74
+ tail.push(path.basename(dir));
75
+ dir = parentDir;
55
76
  }
56
- throw e;
57
77
  }
58
78
  }
59
79
 
@@ -108,11 +128,16 @@ function validatePath(abs) {
108
128
  throw new Error(`Path outside allowed boundaries: ${real}`);
109
129
  }
110
130
 
111
- // Paths that are allowed as write destinations.
131
+ // Paths that are allowed as write destinations. Built from the REALPATH of
132
+ // home because validateWrite compares against a realpath — on macOS a HOME
133
+ // under os.tmpdir() (/var → /private/var) otherwise never matches.
134
+ const REAL_HOME = (() => {
135
+ try { return fs.realpathSync(os.homedir()); } catch { return os.homedir(); }
136
+ })();
112
137
  const WRITE_PREFIXES = [
113
- path.join(os.homedir(), '.claude'),
114
- path.join(os.homedir(), '.claude.json'), // global MCP servers config
115
- path.join(os.homedir(), '.config', 'claude-code'),
138
+ path.join(REAL_HOME, '.claude'),
139
+ path.join(REAL_HOME, '.claude.json'), // global MCP servers config
140
+ path.join(REAL_HOME, '.config', 'claude-code'),
116
141
  ];
117
142
 
118
143
  /**
@@ -241,21 +266,7 @@ async function writeTextAtomic(abs, text, opts = {}) {
241
266
  const real = validatePath(expandHome(abs));
242
267
  validateWrite(real);
243
268
  assertOpsWrite(real, opts.writer);
244
- const dir = path.dirname(real);
245
- await fsp.mkdir(dir, { recursive: true });
246
- const tmp = `${real}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
247
- try {
248
- await fsp.writeFile(tmp, text, opts.mode ? { encoding: 'utf8', mode: opts.mode } : 'utf8');
249
- if (opts.mode) {
250
- // chmod explicitly because some platforms ignore the mode arg on
251
- // writeFile when the file pre-exists.
252
- try { await fsp.chmod(tmp, opts.mode); } catch { /* */ }
253
- }
254
- await fsp.rename(tmp, real);
255
- } catch (e) {
256
- try { await fsp.unlink(tmp); } catch { /* tmp never created or already gone */ }
257
- throw e;
258
- }
269
+ await atomicFs.writeTextAtomic(real, text, opts);
259
270
  const stat = await fsp.stat(real);
260
271
  return { ok: true, mtimeMs: stat.mtimeMs };
261
272
  }
@@ -296,16 +307,7 @@ function writeJsonSync(abs, data, opts = {}) {
296
307
  const real = validatePath(expandHome(abs));
297
308
  validateWrite(real);
298
309
  assertOpsWrite(real, opts.writer);
299
- const dir = path.dirname(real);
300
- fs.mkdirSync(dir, { recursive: true });
301
- const tmp = `${real}.tmp-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
302
- try {
303
- fs.writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', 'utf8');
304
- fs.renameSync(tmp, real);
305
- } catch (e) {
306
- try { fs.unlinkSync(tmp); } catch { /* tmp never created or already gone */ }
307
- throw e;
308
- }
310
+ atomicFs.writeJsonAtomicSync(real, data, opts);
309
311
  const stat = fs.statSync(real);
310
312
  return { ok: true, mtimeMs: stat.mtimeMs };
311
313
  }
@@ -510,4 +512,5 @@ module.exports = {
510
512
  registerPromptSessionsRoot,
511
513
  validatePath,
512
514
  validateWrite,
515
+ realResolve,
513
516
  };
@@ -18,6 +18,7 @@ const { ipcMain } = require('electron');
18
18
  const { spawn } = require('node:child_process');
19
19
  const crypto = require('node:crypto');
20
20
  const { resolveClaudeBin, claudeSpawnTarget } = require('./lib/claudeBin.cjs');
21
+ const { headlessPermissionArgs, ensureCliCapsProbed } = require('./lib/claudeCliCaps.cjs');
21
22
  const { withProcRole } = require('./lib/cleanEnv.cjs');
22
23
  const { extractJson } = require('./lib/extractJson.cjs');
23
24
  const { assertInsideHome } = require('./lib/insideHome.cjs');
@@ -86,7 +87,8 @@ function parseDocEdit(rawStdout) {
86
87
  }
87
88
 
88
89
  /** Spawn `claude -p`, capture stdout. Resolves {ok, out, err, error} — never throws. */
89
- function runClaude(prompt, { model = 'sonnet', timeoutMs = 90_000, systemPrompt = null } = {}) {
90
+ async function runClaude(prompt, { model = 'sonnet', timeoutMs = 90_000, systemPrompt = null } = {}) {
91
+ await ensureCliCapsProbed();
90
92
  return new Promise((resolve) => {
91
93
  let bin;
92
94
  try { bin = resolveClaudeBin(); } catch (e) { resolve({ ok: false, error: `claude not found: ${e?.message}` }); return; }
@@ -94,6 +96,7 @@ function runClaude(prompt, { model = 'sonnet', timeoutMs = 90_000, systemPrompt
94
96
  '-p', prompt,
95
97
  '--model', model,
96
98
  '--dangerously-skip-permissions',
99
+ ...headlessPermissionArgs(),
97
100
  '--output-format', 'text',
98
101
  ];
99
102
  if (systemPrompt) args.push('--append-system-prompt', systemPrompt);