claude-code-session-manager 0.98.0 → 0.100.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 (431) 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-DCEgMTjH.js → DataModel-HdR0AxFq.js} +1 -1
  5. package/dist/assets/{History-Bfdoywlh.js → History-Dr4Jr_5z.js} +2 -2
  6. package/dist/assets/{Hooks-CKDJjTJV.js → Hooks-NhrvH3JP.js} +3 -3
  7. package/dist/assets/{Library-7akfgy67.js → Library-Dv_EtIMr.js} +1 -1
  8. package/dist/assets/{MarkdownEditor-DDesiqet.js → MarkdownEditor-DTKDWV_n.js} +1 -1
  9. package/dist/assets/{McpServers-D4lZtXof.js → McpServers-M034G9B3.js} +2 -2
  10. package/dist/assets/{Memory-CeI8G1w5.js → Memory-DzF1B4gx.js} +6 -6
  11. package/dist/assets/{Permissions-BGleLja4.js → Permissions-DrgbSGWl.js} +3 -3
  12. package/dist/assets/{Plugins-P4eCXy_S.js → Plugins-Bd3ef35w.js} +2 -2
  13. package/dist/assets/{ProvenanceBadge-BFczzI1B.js → ProvenanceBadge-DcBHT1Iy.js} +1 -1
  14. package/dist/assets/{SaveBar-l9jnEMES.js → SaveBar-rimJem-Y.js} +1 -1
  15. package/dist/assets/Scheduler-DjgFJl2w.js +16 -0
  16. package/dist/assets/{ScopeSwitcher-CjJi8ST5.js → ScopeSwitcher-CnAzTBrl.js} +1 -1
  17. package/dist/assets/Settings-Dc1F_sSg.js +3 -0
  18. package/dist/assets/{SkillReferenceGraph-CjbO01Sg.js → SkillReferenceGraph-B9aZDecM.js} +1 -1
  19. package/dist/assets/{Skills-BxZ9_fCR.js → Skills-DRXd18gu.js} +2 -2
  20. package/dist/assets/{SystemPrompt-D8vfFb8b.js → SystemPrompt-ahy5-Fwe.js} +1 -1
  21. package/dist/assets/{TagLibrary-2vzUo7YL.js → TagLibrary-C_tToDeL.js} +1 -1
  22. package/dist/assets/{TiptapBody-B8ZfbK90.js → TiptapBody-DvdefS6K.js} +1 -1
  23. package/dist/assets/{Toggle-BbW4nGGf.js → Toggle-6Jl5Njc6.js} +1 -1
  24. package/dist/assets/{index-CKH5Uxik.css → index-Bta-hwud.css} +1 -1
  25. package/dist/assets/{index-Cd98Q1lD.js → index-vx8O73l8.js} +497 -502
  26. package/dist/assets/{settingsSchema-IhUfucCF.js → settingsSchema-D6iRguNV.js} +1 -1
  27. package/dist/index.html +2 -2
  28. package/package.json +25 -29
  29. package/plugins/CLAUDE.md +6 -6
  30. package/plugins/session-manager-dev/skills/builder/4-manual/SKILL.md +21 -19
  31. package/plugins/session-manager-dev/skills/builder/SKILL.md +3 -3
  32. package/plugins/session-manager-dev/skills/develop/SKILL.md +254 -510
  33. package/plugins/session-manager-dev/skills/develop/standards.md +17 -22
  34. package/scripts/README.md +3 -11
  35. package/scripts/audit-ops-hygiene.cjs +3 -3
  36. package/scripts/hooks/lib/guard-prd-writes-policy.cjs +1 -1
  37. package/scripts/hooks/lib/guard-self-schedule-policy.cjs +1 -1
  38. package/scripts/mint-epic.cjs +2 -3
  39. package/scripts/scheduler-mcp-server.cjs +36 -7
  40. package/src/main/agentLibrary.cjs +22 -2
  41. package/src/main/build-info.json +4 -4
  42. package/src/main/config.cjs +41 -38
  43. package/src/main/docEdit.cjs +4 -1
  44. package/src/main/files.cjs +2 -5
  45. package/src/main/health.cjs +1 -1
  46. package/src/main/index.cjs +23 -10
  47. package/src/main/ipcSchemas.cjs +28 -35
  48. package/src/main/lib/agentPersonaSchema.cjs +13 -2
  49. package/src/main/lib/atomicFs.cjs +116 -0
  50. package/src/main/lib/branchSweep.cjs +13 -12
  51. package/src/main/lib/buildTarget.cjs +3 -3
  52. package/src/main/lib/claudeCliCaps.cjs +129 -0
  53. package/src/main/lib/credentials.cjs +2 -4
  54. package/src/main/lib/crossProjectFeedback.cjs +3 -3
  55. package/src/main/lib/cwdClassify.cjs +15 -3
  56. package/src/main/lib/definitionOfDone.cjs +207 -125
  57. package/src/main/lib/dodDrainHook.cjs +11 -5
  58. package/src/main/lib/epicMint.cjs +2 -4
  59. package/src/main/lib/epicStatusMirror.cjs +8 -10
  60. package/src/main/lib/epicWorktreeProjectConfig.cjs +2 -4
  61. package/src/main/lib/gateAuthority.cjs +96 -0
  62. package/src/main/lib/gitExec.cjs +69 -0
  63. package/src/main/lib/gitWorktree.cjs +656 -92
  64. package/src/main/lib/instanceLock.cjs +5 -8
  65. package/src/main/lib/launchFailure.cjs +2 -3
  66. package/src/main/lib/macroLibrary.cjs +386 -0
  67. package/src/main/lib/mcpToolCatalog.cjs +26 -17
  68. package/src/main/lib/needsReviewLedger.cjs +1 -1
  69. package/src/main/lib/opsErrorLog.cjs +11 -1
  70. package/src/main/lib/opsOwnership.cjs +0 -5
  71. package/src/main/lib/pidAlive.cjs +32 -0
  72. package/src/main/lib/prdCreate.cjs +121 -12
  73. package/src/main/lib/prdDisposition.cjs +2 -2
  74. package/src/main/lib/prdGateFiles.cjs +284 -0
  75. package/src/main/lib/prdLocations.cjs +61 -0
  76. package/src/main/lib/prdMigration.cjs +2 -3
  77. package/src/main/lib/prdSizing.cjs +2 -1
  78. package/src/main/lib/promptSessionSchema.cjs +10 -2
  79. package/src/main/lib/rcaReport.cjs +4 -6
  80. package/src/main/lib/reaperHelpers.cjs +8 -1
  81. package/src/main/lib/reservationExpiry.cjs +6 -1
  82. package/src/main/lib/reviewNotice.cjs +263 -0
  83. package/src/main/lib/runClaudeP.cjs +4 -1
  84. package/src/main/lib/schedulerPaths.cjs +22 -2
  85. package/src/main/lib/sessionSlots.cjs +2 -4
  86. package/src/main/lib/shippedPersonaSeeds.cjs +179 -0
  87. package/src/main/lib/timeoutShim.cjs +123 -0
  88. package/src/main/lib/timeoutShimScript.cjs +275 -0
  89. package/src/main/lib/upgradeDrain.cjs +4 -10
  90. package/src/main/lib/watchdogHelpers.cjs +12 -25
  91. package/src/main/lib/workTypeLibrary.cjs +7 -1
  92. package/src/main/promptSessionEvents.cjs +37 -13
  93. package/src/main/scheduler.cjs +910 -198
  94. package/src/main/seedAgentPersonas.cjs +278 -23
  95. package/src/main/supervisor.cjs +4 -1
  96. package/src/main/templates/PRD_AUTHORING.md +126 -406
  97. package/src/preload/__tests__/preload-surface.test.cjs +68 -0
  98. package/src/preload/api.d.ts +41 -94
  99. package/src/preload/index.cjs +14 -9
  100. package/src/seed/agents/architect.md +1 -0
  101. package/src/seed/agents/dev-lead.md +17 -18
  102. package/src/seed/agents/project-home-builder.md +1 -0
  103. package/src/seed/agents/validator.md +10 -4
  104. package/dist/assets/HostBilko-CDVHE0hT.js +0 -1
  105. package/dist/assets/Scheduler-C_phpeUS.js +0 -16
  106. package/dist/assets/Settings-BIwZ3FGQ.js +0 -3
  107. package/src/main/__tests__/activeIndexMerge.test.cjs +0 -235
  108. package/src/main/__tests__/agentEffortResolve.test.cjs +0 -117
  109. package/src/main/__tests__/agentLibrary.test.cjs +0 -276
  110. package/src/main/__tests__/agentModelResolve.test.cjs +0 -311
  111. package/src/main/__tests__/agentOverlayWrite.test.cjs +0 -95
  112. package/src/main/__tests__/bilkoHost-deriveSlug.test.cjs +0 -26
  113. package/src/main/__tests__/bilkoHost-integration.test.cjs +0 -118
  114. package/src/main/__tests__/bilkoHostCore.test.cjs +0 -72
  115. package/src/main/__tests__/broadcastCoalescer.test.cjs +0 -122
  116. package/src/main/__tests__/chat-cancel-terminal.test.cjs +0 -120
  117. package/src/main/__tests__/chat-dead-channels.test.cjs +0 -63
  118. package/src/main/__tests__/chat-exit-close-race.test.cjs +0 -146
  119. package/src/main/__tests__/chat-mcp-consent-notice.test.cjs +0 -139
  120. package/src/main/__tests__/chat-preamble-anchors.test.cjs +0 -100
  121. package/src/main/__tests__/chat-queue.test.cjs +0 -97
  122. package/src/main/__tests__/chat-stop-signal.test.cjs +0 -89
  123. package/src/main/__tests__/chatRunner-epic-worktree-execcwd.test.cjs +0 -125
  124. package/src/main/__tests__/chatRunner-session-flag-retry.test.cjs +0 -252
  125. package/src/main/__tests__/classifyPromptTicket.test.cjs +0 -101
  126. package/src/main/__tests__/classifyTranscriptLine.test.cjs +0 -201
  127. package/src/main/__tests__/computeDepHistorySatisfaction.test.cjs +0 -66
  128. package/src/main/__tests__/config-readText-bounded.test.cjs +0 -84
  129. package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +0 -59
  130. package/src/main/__tests__/crossProjectFeedback.test.cjs +0 -334
  131. package/src/main/__tests__/crossProjectFeedbackRoutes.test.cjs +0 -161
  132. package/src/main/__tests__/dep-orphan-archive-health.test.cjs +0 -77
  133. package/src/main/__tests__/develop-skill-failure-modes.test.cjs +0 -70
  134. package/src/main/__tests__/docEdit.test.cjs +0 -244
  135. package/src/main/__tests__/dod-batchkey.test.cjs +0 -183
  136. package/src/main/__tests__/dod-drain-hook.test.cjs +0 -302
  137. package/src/main/__tests__/dod-report.test.cjs +0 -304
  138. package/src/main/__tests__/dod-reverify.test.cjs +0 -285
  139. package/src/main/__tests__/epicContextDigest.test.cjs +0 -174
  140. package/src/main/__tests__/epicMint.test.cjs +0 -332
  141. package/src/main/__tests__/epicMintTelemetryTap.test.cjs +0 -64
  142. package/src/main/__tests__/epicStatusMirror.test.cjs +0 -110
  143. package/src/main/__tests__/epicValidationHook.test.cjs +0 -291
  144. package/src/main/__tests__/exchanges.test.cjs +0 -122
  145. package/src/main/__tests__/exchangesPromptId.test.cjs +0 -61
  146. package/src/main/__tests__/extractJson.test.cjs +0 -51
  147. package/src/main/__tests__/files-reject-credentials.test.cjs +0 -40
  148. package/src/main/__tests__/fixtures/1218-fo-01-move-scripts-lib-into-src-main-lib.log +0 -556
  149. package/src/main/__tests__/flatPrdTickSweep.test.cjs +0 -110
  150. package/src/main/__tests__/health-build-freshness.test.cjs +0 -39
  151. package/src/main/__tests__/health-claude-md-budget.test.cjs +0 -57
  152. package/src/main/__tests__/health-credentials.test.cjs +0 -81
  153. package/src/main/__tests__/health-delegation-chain.test.cjs +0 -124
  154. package/src/main/__tests__/health-per-project-stall.test.cjs +0 -84
  155. package/src/main/__tests__/health-prd-migration.test.cjs +0 -37
  156. package/src/main/__tests__/health-queue-dispatch.test.cjs +0 -135
  157. package/src/main/__tests__/health-starve-escalation.test.cjs +0 -94
  158. package/src/main/__tests__/health-tick-liveness.test.cjs +0 -179
  159. package/src/main/__tests__/health-usage-poller.test.cjs +0 -144
  160. package/src/main/__tests__/health-worktree-cap-blocked.test.cjs +0 -65
  161. package/src/main/__tests__/heapSnapshot.test.cjs +0 -121
  162. package/src/main/__tests__/historyAggregatorIntraday.test.cjs +0 -313
  163. package/src/main/__tests__/historyDashboard.test.cjs +0 -163
  164. package/src/main/__tests__/historyRollup.test.cjs +0 -333
  165. package/src/main/__tests__/intradayRefresh.test.cjs +0 -39
  166. package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +0 -39
  167. package/src/main/__tests__/kg-augment.test.cjs +0 -195
  168. package/src/main/__tests__/loadGateDetailTick.test.cjs +0 -31
  169. package/src/main/__tests__/machineProfile.test.cjs +0 -152
  170. package/src/main/__tests__/mcpStatus.test.cjs +0 -61
  171. package/src/main/__tests__/memoryAggregate.test.cjs +0 -109
  172. package/src/main/__tests__/memoryStale.test.cjs +0 -88
  173. package/src/main/__tests__/needsReviewLedger.test.cjs +0 -162
  174. package/src/main/__tests__/openExternalApp-spawn-error.test.cjs +0 -25
  175. package/src/main/__tests__/opsErrorLog.test.cjs +0 -109
  176. package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +0 -173
  177. package/src/main/__tests__/personaMerge.test.cjs +0 -169
  178. package/src/main/__tests__/planValidator.test.cjs +0 -125
  179. package/src/main/__tests__/pollLoop-dispatch-on-failure.test.cjs +0 -176
  180. package/src/main/__tests__/prd-group-allocator.test.cjs +0 -119
  181. package/src/main/__tests__/prdAdminRouteParity.test.cjs +0 -70
  182. package/src/main/__tests__/prdAdminRoutes.test.cjs +0 -718
  183. package/src/main/__tests__/prdAgentType.test.cjs +0 -103
  184. package/src/main/__tests__/prdAuthoringSeed.test.cjs +0 -39
  185. package/src/main/__tests__/prdCreate.test.cjs +0 -979
  186. package/src/main/__tests__/prdCreateAdoption.test.cjs +0 -205
  187. package/src/main/__tests__/prdCreateDisposition.test.cjs +0 -201
  188. package/src/main/__tests__/prdCreatePlanId.test.cjs +0 -132
  189. package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +0 -117
  190. package/src/main/__tests__/prdFrontmatterDependsOn.test.cjs +0 -136
  191. package/src/main/__tests__/prdFrontmatterDisposition.test.cjs +0 -125
  192. package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +0 -108
  193. package/src/main/__tests__/prdLocations.test.cjs +0 -195
  194. package/src/main/__tests__/prdLocationsArchived.test.cjs +0 -201
  195. package/src/main/__tests__/prdMigration.test.cjs +0 -349
  196. package/src/main/__tests__/prdMigrationLegacyAdopt.test.cjs +0 -91
  197. package/src/main/__tests__/prdParserHighWater.test.cjs +0 -74
  198. package/src/main/__tests__/prdParserSourcePromptId.test.cjs +0 -65
  199. package/src/main/__tests__/prdSetDisposition.test.cjs +0 -222
  200. package/src/main/__tests__/prdSizing.test.cjs +0 -106
  201. package/src/main/__tests__/prdSourcePromptIdBackfill.test.cjs +0 -118
  202. package/src/main/__tests__/prdUpdateDependsOn.test.cjs +0 -160
  203. package/src/main/__tests__/proc-role-env.test.cjs +0 -125
  204. package/src/main/__tests__/procname-claude-spawn-sites.test.cjs +0 -304
  205. package/src/main/__tests__/procname-sm-processes.test.cjs +0 -127
  206. package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +0 -177
  207. package/src/main/__tests__/projectPages.test.cjs +0 -137
  208. package/src/main/__tests__/promptSessionEvents.test.cjs +0 -234
  209. package/src/main/__tests__/promptSessionSchema.test.cjs +0 -101
  210. package/src/main/__tests__/promptSessionTranscript.test.cjs +0 -0
  211. package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +0 -159
  212. package/src/main/__tests__/pty-epic-worktree-spawn-cwd.test.cjs +0 -282
  213. package/src/main/__tests__/pty-session-open-telemetry.test.cjs +0 -96
  214. package/src/main/__tests__/pty-write-result.test.cjs +0 -46
  215. package/src/main/__tests__/queue-health-verdict.test.cjs +0 -170
  216. package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +0 -286
  217. package/src/main/__tests__/queue-starvation-per-project.test.cjs +0 -147
  218. package/src/main/__tests__/queueHistory.test.cjs +0 -355
  219. package/src/main/__tests__/queueOps-interactive-ac-lint.test.cjs +0 -65
  220. package/src/main/__tests__/queueOpsArchiveDestination.test.cjs +0 -65
  221. package/src/main/__tests__/queueOpsAutoArchive.test.cjs +0 -211
  222. package/src/main/__tests__/rateLimitPollerStreak.test.cjs +0 -128
  223. package/src/main/__tests__/rcaReport.test.cjs +0 -266
  224. package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +0 -119
  225. package/src/main/__tests__/reconcileTiming.test.cjs +0 -135
  226. package/src/main/__tests__/runLogRetention.test.cjs +0 -489
  227. package/src/main/__tests__/runVerify-atomic-verdicts.test.cjs +0 -26
  228. package/src/main/__tests__/runVerify-blocked-by-foreign-wip.test.cjs +0 -58
  229. package/src/main/__tests__/runVerify-landed-commit-outranks.test.cjs +0 -191
  230. package/src/main/__tests__/runVerify-policy-denial.test.cjs +0 -89
  231. package/src/main/__tests__/runVerify-transcript-commit-evidence.test.cjs +0 -225
  232. package/src/main/__tests__/runVerify.test.cjs +0 -1784
  233. package/src/main/__tests__/scheduleJobSchema.test.cjs +0 -127
  234. package/src/main/__tests__/scheduleJobStatusDrift.test.cjs +0 -65
  235. package/src/main/__tests__/scheduleJobTransitions.test.cjs +0 -277
  236. package/src/main/__tests__/scheduleJobTransitionsGrep.test.cjs +0 -59
  237. package/src/main/__tests__/scheduleJobTransitionsTelemetryTap.test.cjs +0 -72
  238. package/src/main/__tests__/scheduler-admin-routes.test.cjs +0 -199
  239. package/src/main/__tests__/scheduler-adopted-run-supervision.test.cjs +0 -143
  240. package/src/main/__tests__/scheduler-already-satisfied-on-main.test.cjs +0 -105
  241. package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +0 -102
  242. package/src/main/__tests__/scheduler-archived-twin-guard.test.cjs +0 -155
  243. package/src/main/__tests__/scheduler-autofix-outcome.test.cjs +0 -188
  244. package/src/main/__tests__/scheduler-autofix-select.test.cjs +0 -439
  245. package/src/main/__tests__/scheduler-autopromote.test.cjs +0 -51
  246. package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +0 -101
  247. package/src/main/__tests__/scheduler-blocked-by-foreign-wip.test.cjs +0 -107
  248. package/src/main/__tests__/scheduler-boot-orphans.test.cjs +0 -153
  249. package/src/main/__tests__/scheduler-broadcast-reconcile.test.cjs +0 -121
  250. package/src/main/__tests__/scheduler-clear-queue-history.test.cjs +0 -134
  251. package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +0 -244
  252. package/src/main/__tests__/scheduler-committed-in-window.test.cjs +0 -182
  253. package/src/main/__tests__/scheduler-cross-project-batch.test.cjs +0 -43
  254. package/src/main/__tests__/scheduler-default-eligible-heal.test.cjs +0 -168
  255. package/src/main/__tests__/scheduler-dispatch-loop.test.cjs +0 -58
  256. package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +0 -81
  257. package/src/main/__tests__/scheduler-epic-digest.test.cjs +0 -227
  258. package/src/main/__tests__/scheduler-failed-autoreset.test.cjs +0 -121
  259. package/src/main/__tests__/scheduler-finalize-dispatch-guards.test.cjs +0 -229
  260. package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +0 -75
  261. package/src/main/__tests__/scheduler-fix-plan-path.test.cjs +0 -119
  262. package/src/main/__tests__/scheduler-force-tick-outcome.test.cjs +0 -46
  263. package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +0 -78
  264. package/src/main/__tests__/scheduler-gate-shadow.test.cjs +0 -119
  265. package/src/main/__tests__/scheduler-guard-verdict-autoresolve.test.cjs +0 -390
  266. package/src/main/__tests__/scheduler-heal-refusal.test.cjs +0 -61
  267. package/src/main/__tests__/scheduler-heartbeat-payload.test.cjs +0 -80
  268. package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +0 -323
  269. package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +0 -41
  270. package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +0 -63
  271. package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +0 -123
  272. package/src/main/__tests__/scheduler-job-budget.test.cjs +0 -172
  273. package/src/main/__tests__/scheduler-job-overrun.test.cjs +0 -175
  274. package/src/main/__tests__/scheduler-launch-failure.test.cjs +0 -199
  275. package/src/main/__tests__/scheduler-leftover-fields.test.cjs +0 -52
  276. package/src/main/__tests__/scheduler-leftover-quarantine.test.cjs +0 -199
  277. package/src/main/__tests__/scheduler-looks-done.test.cjs +0 -537
  278. package/src/main/__tests__/scheduler-manual-pause.test.cjs +0 -118
  279. package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +0 -245
  280. package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +0 -46
  281. package/src/main/__tests__/scheduler-mutate-reentrancy.test.cjs +0 -117
  282. package/src/main/__tests__/scheduler-needs-review-autoresolve.test.cjs +0 -197
  283. package/src/main/__tests__/scheduler-never-stop.test.cjs +0 -157
  284. package/src/main/__tests__/scheduler-no-dead-end-status.test.cjs +0 -152
  285. package/src/main/__tests__/scheduler-no-orphan-run-dir.test.cjs +0 -81
  286. package/src/main/__tests__/scheduler-notify-originating-tab-transcript.test.cjs +0 -87
  287. package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +0 -343
  288. package/src/main/__tests__/scheduler-periodic-reverify-guard.test.cjs +0 -217
  289. package/src/main/__tests__/scheduler-porcelain-rename.test.cjs +0 -164
  290. package/src/main/__tests__/scheduler-prd-missing-skip.test.cjs +0 -161
  291. package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +0 -178
  292. package/src/main/__tests__/scheduler-quarantine-autoresolve.test.cjs +0 -165
  293. package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +0 -257
  294. package/src/main/__tests__/scheduler-rate-limit-cooldown-freshness.test.cjs +0 -123
  295. package/src/main/__tests__/scheduler-rate-limit-pause.test.cjs +0 -152
  296. package/src/main/__tests__/scheduler-rate-limit-spin-guard.test.cjs +0 -156
  297. package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +0 -754
  298. package/src/main/__tests__/scheduler-reaper-helpers-basics.test.cjs +0 -87
  299. package/src/main/__tests__/scheduler-reconcile-cwd-preserve.test.cjs +0 -100
  300. package/src/main/__tests__/scheduler-reconcile-history-backfill.test.cjs +0 -105
  301. package/src/main/__tests__/scheduler-reconcile-invalid-repair.test.cjs +0 -203
  302. package/src/main/__tests__/scheduler-reconcile-quarantine.test.cjs +0 -247
  303. package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +0 -77
  304. package/src/main/__tests__/scheduler-resume-recovery.test.cjs +0 -254
  305. package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +0 -115
  306. package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +0 -380
  307. package/src/main/__tests__/scheduler-sigterm-commit.test.cjs +0 -43
  308. package/src/main/__tests__/scheduler-stall-per-project.test.cjs +0 -126
  309. package/src/main/__tests__/scheduler-starve-escalation.test.cjs +0 -154
  310. package/src/main/__tests__/scheduler-stranded-autofix-park.test.cjs +0 -252
  311. package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +0 -185
  312. package/src/main/__tests__/scheduler-stuck-failed-escalation.test.cjs +0 -136
  313. package/src/main/__tests__/scheduler-supervisor-record.test.cjs +0 -81
  314. package/src/main/__tests__/scheduler-tick-cancel-token.test.cjs +0 -54
  315. package/src/main/__tests__/scheduler-tick-wedge.test.cjs +0 -172
  316. package/src/main/__tests__/scheduler-transient-failure.test.cjs +0 -141
  317. package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +0 -62
  318. package/src/main/__tests__/scheduler-utilization-hold.test.cjs +0 -89
  319. package/src/main/__tests__/scheduler-verify-prd-path.test.cjs +0 -109
  320. package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +0 -234
  321. package/src/main/__tests__/scheduler-worktree-exec-cwd.test.cjs +0 -120
  322. package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +0 -105
  323. package/src/main/__tests__/schedulerBatchRootBlocker.test.cjs +0 -117
  324. package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +0 -110
  325. package/src/main/__tests__/seedAgentPersonas.test.cjs +0 -184
  326. package/src/main/__tests__/seedSchedulerMcp.test.cjs +0 -211
  327. package/src/main/__tests__/seedStatus.test.cjs +0 -100
  328. package/src/main/__tests__/seedValidatorPersona.test.cjs +0 -116
  329. package/src/main/__tests__/stop-signal-anchor.test.cjs +0 -75
  330. package/src/main/__tests__/telemetryClient.test.cjs +0 -1055
  331. package/src/main/__tests__/telemetryContract.test.cjs +0 -930
  332. package/src/main/__tests__/telemetrySettings.test.cjs +0 -210
  333. package/src/main/__tests__/transcripts-batch-flush.test.cjs +0 -249
  334. package/src/main/__tests__/transcripts-doFlush-array.test.cjs +0 -124
  335. package/src/main/__tests__/transcripts-paged-reads.test.cjs +0 -241
  336. package/src/main/__tests__/transcripts-worktree-epic-path.test.cjs +0 -154
  337. package/src/main/__tests__/transcriptsUsageFor.test.cjs +0 -206
  338. package/src/main/__tests__/uniquePrdNumbers.test.cjs +0 -153
  339. package/src/main/__tests__/usageSingleFlight.test.cjs +0 -169
  340. package/src/main/__tests__/validationSentinels.test.cjs +0 -84
  341. package/src/main/__tests__/workTypeLibrary.test.cjs +0 -89
  342. package/src/main/bilkoHost.cjs +0 -314
  343. package/src/main/bilkoHostCore.cjs +0 -89
  344. package/src/main/lib/__tests__/active-sessions.test.cjs +0 -251
  345. package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +0 -179
  346. package/src/main/lib/__tests__/agentPersonaSchema.test.cjs +0 -67
  347. package/src/main/lib/__tests__/auditLog.test.cjs +0 -38
  348. package/src/main/lib/__tests__/bootSelfHeal.test.cjs +0 -107
  349. package/src/main/lib/__tests__/branchSweep.test.cjs +0 -164
  350. package/src/main/lib/__tests__/buildIdentity.test.cjs +0 -121
  351. package/src/main/lib/__tests__/buildTarget.test.cjs +0 -52
  352. package/src/main/lib/__tests__/childWithLog.test.cjs +0 -321
  353. package/src/main/lib/__tests__/coldBootPromptSessionsWrite.test.cjs +0 -87
  354. package/src/main/lib/__tests__/crashTelemetry.test.cjs +0 -103
  355. package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +0 -115
  356. package/src/main/lib/__tests__/cwdClassify.test.cjs +0 -111
  357. package/src/main/lib/__tests__/definitionOfDoneSequence.test.cjs +0 -95
  358. package/src/main/lib/__tests__/delegationReadiness.test.cjs +0 -1175
  359. package/src/main/lib/__tests__/dispatchLoop.test.cjs +0 -63
  360. package/src/main/lib/__tests__/effectiveModelInfo.test.cjs +0 -244
  361. package/src/main/lib/__tests__/ephemeralCwd.test.cjs +0 -91
  362. package/src/main/lib/__tests__/epicDelegationStats.test.cjs +0 -137
  363. package/src/main/lib/__tests__/epicSpawnCwd.test.cjs +0 -283
  364. package/src/main/lib/__tests__/epicSpawnPlan.test.cjs +0 -196
  365. package/src/main/lib/__tests__/epicTranscriptPath.test.cjs +0 -163
  366. package/src/main/lib/__tests__/epicWorktreeBoot.test.cjs +0 -136
  367. package/src/main/lib/__tests__/epicWorktreeMerge.test.cjs +0 -130
  368. package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +0 -117
  369. package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +0 -137
  370. package/src/main/lib/__tests__/fixChainDepth.test.cjs +0 -40
  371. package/src/main/lib/__tests__/fixtures/204-mercury-steam-horse.log.txt +0 -13
  372. package/src/main/lib/__tests__/fixtures/scheduler-machine.json.corrupt-1789147548 +0 -34
  373. package/src/main/lib/__tests__/gateFixtures.json +0 -20
  374. package/src/main/lib/__tests__/gitCacheBound.test.cjs +0 -69
  375. package/src/main/lib/__tests__/gitWorktree.test.cjs +0 -1478
  376. package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +0 -107
  377. package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +0 -153
  378. package/src/main/lib/__tests__/guardShims.test.cjs +0 -158
  379. package/src/main/lib/__tests__/importReferences.spec.cjs +0 -56
  380. package/src/main/lib/__tests__/instanceLock.test.cjs +0 -173
  381. package/src/main/lib/__tests__/jobSupervisorRecord.test.cjs +0 -78
  382. package/src/main/lib/__tests__/jobWorktree.test.cjs +0 -199
  383. package/src/main/lib/__tests__/jobWorktreeBootLive.test.cjs +0 -82
  384. package/src/main/lib/__tests__/landedSinceRun.test.cjs +0 -133
  385. package/src/main/lib/__tests__/launchFailure.test.cjs +0 -220
  386. package/src/main/lib/__tests__/loadGate.test.cjs +0 -303
  387. package/src/main/lib/__tests__/localAdminHttp.test.cjs +0 -214
  388. package/src/main/lib/__tests__/loopDelay.test.cjs +0 -68
  389. package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +0 -107
  390. package/src/main/lib/__tests__/modelCatalog.test.cjs +0 -202
  391. package/src/main/lib/__tests__/opsOwnership.test.cjs +0 -113
  392. package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +0 -328
  393. package/src/main/lib/__tests__/opsRootNestedWrite.test.cjs +0 -51
  394. package/src/main/lib/__tests__/opsRootResolve.test.cjs +0 -149
  395. package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +0 -82
  396. package/src/main/lib/__tests__/prdDisposition.test.cjs +0 -224
  397. package/src/main/lib/__tests__/procIdentity.test.cjs +0 -119
  398. package/src/main/lib/__tests__/procName.test.cjs +0 -92
  399. package/src/main/lib/__tests__/projectBriefCore.test.cjs +0 -216
  400. package/src/main/lib/__tests__/projectRootResolve.test.cjs +0 -148
  401. package/src/main/lib/__tests__/queueHealth.test.cjs +0 -58
  402. package/src/main/lib/__tests__/queueStoreAtomicWrite.test.cjs +0 -88
  403. package/src/main/lib/__tests__/queueStoreMachineStateRecovery.test.cjs +0 -190
  404. package/src/main/lib/__tests__/quietMachineLease.test.cjs +0 -39
  405. package/src/main/lib/__tests__/rateLimitWindow.test.cjs +0 -88
  406. package/src/main/lib/__tests__/reaperHelpers.test.cjs +0 -577
  407. package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +0 -312
  408. package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +0 -213
  409. package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +0 -125
  410. package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +0 -127
  411. package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +0 -109
  412. package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +0 -71
  413. package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +0 -216
  414. package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +0 -183
  415. package/src/main/lib/__tests__/schedulerPaths.test.cjs +0 -226
  416. package/src/main/lib/__tests__/schedulerPathsWorktree.test.cjs +0 -133
  417. package/src/main/lib/__tests__/schedulerRuntimeState.test.cjs +0 -56
  418. package/src/main/lib/__tests__/sessionSlots.test.cjs +0 -144
  419. package/src/main/lib/__tests__/telemetryBacklog.test.cjs +0 -626
  420. package/src/main/lib/__tests__/telemetryBoot.test.cjs +0 -134
  421. package/src/main/lib/__tests__/telemetryConsent.test.cjs +0 -136
  422. package/src/main/lib/__tests__/telemetryCounters.test.cjs +0 -57
  423. package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +0 -98
  424. package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +0 -200
  425. package/src/main/lib/__tests__/toolUseClassify.test.cjs +0 -53
  426. package/src/main/lib/__tests__/updateCheck.test.cjs +0 -63
  427. package/src/main/lib/__tests__/upgradeDrain.test.cjs +0 -130
  428. package/src/main/lib/__tests__/usageCircuit.test.cjs +0 -354
  429. package/src/main/lib/__tests__/watchdog-helpers.test.cjs +0 -441
  430. package/src/main/lib/__tests__/watchdog-relaunch.test.cjs +0 -266
  431. package/src/main/lib/kgExchangePairing.cjs +0 -75
@@ -11,8 +11,8 @@
11
11
  * anymore — both just point the headless executor at STANDARDS_PATH with an
12
12
  * instruction to read it before starting. There's nothing to go stale: the
13
13
  * executor always reads the live file at run time, same one-concept-one-
14
- * implementation reasoning that keeps the two PRD-creation paths in sync
15
- * (see SKILL.md).
14
+ * implementation reasoning that keeps the two PRD-creation paths (admin
15
+ * HTTP route, chat:create-prd IPC) in sync.
16
16
  */
17
17
  'use strict';
18
18
 
@@ -32,6 +32,8 @@ const { resolveDepSlug, findNearMatches } = require('./depSlugResolve.cjs');
32
32
  const { isFixPlanSlug } = require('./fixPlanSlug.cjs');
33
33
  const { isIncomplete, resolveChainTerminals, isValidPlanId, mintPlanId, resolveInheritedPlanId } = require('./prdDisposition.cjs');
34
34
  const { sizingWarnings } = require('./prdSizing.cjs');
35
+ const { validateGate, validateFiles, renderFilesSection, renderGateSection } = require('./prdGateFiles.cjs');
36
+ const { resolveGate, parseChain, isNoneGate } = require('./definitionOfDone.cjs');
35
37
 
36
38
  // A caller-supplied slug that already starts with its own `NN-` (e.g.
37
39
  // "254-perf-x") used to silently become the double-prefixed row
@@ -68,18 +70,19 @@ function deriveSlugFromTitle(title) {
68
70
 
69
71
  /**
70
72
  * Build the full PRD markdown body (frontmatter + required sections + a
71
- * pointer at the engineering standards file), matching the structure
72
- * `/develop`'s SKILL.md documents: frontmatter, then Goal / Acceptance
73
- * criteria / Implementation notes / Out of scope / Engineering standards,
74
- * in order.
73
+ * pointer at the engineering standards file), matching the body shape
74
+ * PRD_AUTHORING.md §13 documents: frontmatter, then Goal / Acceptance
75
+ * criteria / Files / Implementation notes / Out of scope / Gate /
76
+ * Engineering standards, in order.
75
77
  */
76
78
  function buildPrdBody(input) {
77
79
  const {
78
80
  title, cwd, estimateMinutes, goal, acceptanceCriteria,
79
81
  implementationNotes, outOfScope, sourcePromptId, sourceTabId, tag, agentType, dependsOn, quietMachine, disposition, deliverable, artifactPaths, planId,
82
+ gate, files,
80
83
  } = input;
81
84
 
82
- // No `parallelGroup` frontmatter key by convention (SKILL.md) — the NN-
85
+ // No `parallelGroup` frontmatter key (PRD_AUTHORING.md §5) — the NN-
83
86
  // filename prefix is the single source of truth for grouping; adding a
84
87
  // second one here would let the two drift out of sync.
85
88
  const fmLines = ['---', `title: ${title}`, `cwd: ${cwd}`, `estimateMinutes: ${estimateMinutes}`];
@@ -140,13 +143,22 @@ function buildPrdBody(input) {
140
143
  'do not re-read the whole file every run.',
141
144
  ].join('\n');
142
145
 
146
+ // `# Files` sits right after Acceptance criteria; `# Gate` sits after Out
147
+ // of scope, before Engineering standards (gate-and-files-in-PRD-body unit).
148
+ // Each section appears only when its field is given. Only the MCP tool
149
+ // refuses a call without them; createPrd() (so the HTTP route and the IPC
150
+ // handler too) just warns when either is absent.
143
151
  const bodyLines = [
144
152
  '# Goal', '', goal, '',
145
153
  '# Acceptance criteria', '', acLines, '',
154
+ ];
155
+ if (files && files.length) bodyLines.push(...renderFilesSection(files));
156
+ bodyLines.push(
146
157
  '# Implementation notes', '', implementationNotes, '',
147
158
  '# Out of scope', '', oosLines, '',
148
- '## Engineering standards', '', standardsPointer, '',
149
- ];
159
+ );
160
+ if (gate && gate.length) bodyLines.push(...renderGateSection(gate));
161
+ bodyLines.push('## Engineering standards', '', standardsPointer, '');
150
162
 
151
163
  return `${fmLines.join('\n')}${bodyLines.join('\n')}`;
152
164
  }
@@ -347,6 +359,67 @@ async function createPrd(input, remote) {
347
359
  }
348
360
  }
349
361
 
362
+ // Fake-heading guard (gate-and-files-in-PRD-body unit): a caller-supplied
363
+ // free-text field that itself contains a "# Gate" or "# Files" heading line
364
+ // would, after buildPrdBody splices in the real section below, read as TWO
365
+ // Gate/Files sections — readExplicitGate takes the FIRST fence/heading it
366
+ // finds, so the fake one would silently win and the executor would see the
367
+ // wrong gate. Checked before NN allocation so a refusal burns no number.
368
+ const HEADING_LINE_RE = /^ {0,3}#{1,6}[ \t]+(gate|files)[ \t]*#*[ \t]*$/i;
369
+ function headingGuardError(fieldLabel, text) {
370
+ if (typeof text !== 'string') return null;
371
+ for (const line of text.split(/\r\n|\r|\n/)) {
372
+ if (HEADING_LINE_RE.test(line)) {
373
+ return `The ${fieldLabel} text has a "# Gate" or "# Files" heading. The API writes those sections ` +
374
+ 'from the gate and files fields. Remove the heading and call again.';
375
+ }
376
+ }
377
+ return null;
378
+ }
379
+ for (const [fieldLabel, text] of [['goal', input.goal], ['implementation notes', input.implementationNotes]]) {
380
+ const headingError = headingGuardError(fieldLabel, text);
381
+ if (headingError) return { ok: false, status: 400, error: headingError };
382
+ }
383
+ if (Array.isArray(input.acceptanceCriteria)) {
384
+ for (const entry of input.acceptanceCriteria) {
385
+ const headingError = headingGuardError('acceptance criteria', entry);
386
+ if (headingError) return { ok: false, status: 400, error: headingError };
387
+ }
388
+ }
389
+ if (Array.isArray(input.outOfScope)) {
390
+ for (const entry of input.outOfScope) {
391
+ const headingError = headingGuardError('out of scope', entry);
392
+ if (headingError) return { ok: false, status: 400, error: headingError };
393
+ }
394
+ }
395
+
396
+ // Gate + files validation (gate-and-files-in-PRD-body unit): fail-closed,
397
+ // checked BEFORE NN allocation so a refusal burns no number — same posture
398
+ // as the artifactPaths checks above. The actual shape rules live in
399
+ // prdGateFiles.cjs (pure, no I/O); this is just the accumulate-and-return glue.
400
+ const extraWarnings = [];
401
+ let gateCommands;
402
+ if (input.gate !== undefined) {
403
+ const gateValidation = validateGate(input.gate);
404
+ if (!gateValidation.ok) {
405
+ return { ok: false, status: 400, error: gateValidation.error };
406
+ }
407
+ gateCommands = gateValidation.commands;
408
+ extraWarnings.push(...gateValidation.warnings);
409
+ } else {
410
+ extraWarnings.push('No gate: the scheduler cannot re-check this PRD.');
411
+ }
412
+ let filesList;
413
+ if (input.files !== undefined) {
414
+ const filesValidation = validateFiles(input.files);
415
+ if (!filesValidation.ok) {
416
+ return { ok: false, status: 400, error: filesValidation.error };
417
+ }
418
+ filesList = filesValidation.files;
419
+ } else {
420
+ extraWarnings.push('No files list: the executor is not told which files it may change.');
421
+ }
422
+
350
423
  const nn = await remote.allocateParallelGroup(input.cwd);
351
424
  const filenameSlug = `${nn}-${slug}`;
352
425
 
@@ -463,7 +536,32 @@ async function createPrd(input, remote) {
463
536
  }
464
537
  if (!planId) planId = mintPlanId();
465
538
 
466
- const body = buildPrdBody({ ...input, planId });
539
+ const body = buildPrdBody({ ...input, planId, gate: gateCommands, files: filesList });
540
+
541
+ // Read-back check (gate-and-files-in-PRD-body unit): the whole point of a
542
+ // declared gate is that the scheduler's own reader (resolveGate/parseChain
543
+ // in definitionOfDone.cjs) gets back exactly what was declared. A stray
544
+ // ```gate fence elsewhere in the body (goal/notes/criteria/out-of-scope)
545
+ // would otherwise win — readExplicitGate takes the FIRST fence in the
546
+ // text — and the scheduler would silently re-run the wrong gate. Caught
547
+ // here, before the write, rather than discovered later from a job log.
548
+ if (gateCommands) {
549
+ const expectedSource = gateCommands.length === 1 && isNoneGate(gateCommands[0]) ? 'none' : 'explicit';
550
+ const expectedSequence = expectedSource === 'none' ? [] : gateCommands.flatMap(parseChain);
551
+ const resolved = resolveGate(body);
552
+ const matches = resolved.source === expectedSource
553
+ && JSON.stringify(resolved.sequence) === JSON.stringify(expectedSequence);
554
+ if (!matches) {
555
+ return {
556
+ ok: false,
557
+ status: 400,
558
+ error: 'The PRD text already holds a gate fence (three backticks + gate), so the scheduler would read '
559
+ + 'the wrong gate. Remove it from the goal, implementation notes, acceptance criteria, out of scope '
560
+ + 'or files, and call again.',
561
+ };
562
+ }
563
+ }
564
+
467
565
  const writeResult = await remote.writePrd(filenameSlug, body, input.cwd);
468
566
  if (!writeResult?.ok) {
469
567
  return { ok: false, status: 500, error: writeResult?.error ?? 'write failed' };
@@ -522,8 +620,17 @@ async function createPrd(input, remote) {
522
620
  note: enqueued
523
621
  ? 'PRD file written and adopted into the queue as a pending row'
524
622
  : 'PRD file written; the queue row is derived by the next scheduler reconcile pass, not created here',
525
- // Advisory only (PRD 1403) — never blocks the write. See prdSizing.cjs.
526
- warnings: sizingWarnings(input),
623
+ // gate-and-files-in-PRD-body unit: honest echo of what was actually
624
+ // validated and rendered, same spirit as `note` above — never a fabricated status.
625
+ gate: {
626
+ source: gateCommands ? (gateCommands.length === 1 && isNoneGate(gateCommands[0]) ? 'none' : 'explicit') : 'absent',
627
+ commands: gateCommands || [],
628
+ },
629
+ files: filesList || [],
630
+ // Advisory only (PRD 1403 sizing warnings + gate-and-files-in-PRD-body
631
+ // absent/none-gate warnings) — never blocks the write. See prdSizing.cjs
632
+ // and prdGateFiles.cjs.
633
+ warnings: [...extraWarnings, ...sizingWarnings(input)],
527
634
  };
528
635
  }
529
636
 
@@ -569,6 +676,8 @@ function registerAdminRoute(adminHttp, remote) {
569
676
  epicId: result.epicId ?? null,
570
677
  enqueued: result.enqueued,
571
678
  note: result.note,
679
+ gate: result.gate,
680
+ files: result.files,
572
681
  warnings: result.warnings,
573
682
  });
574
683
  });
@@ -18,7 +18,7 @@
18
18
  * an already-written PRD's disposition from the Scheduler UI. Uses
19
19
  * computeDispositionRewrite() to validate the rewrite is safe before
20
20
  * handing the new dependsOn to remote.updatePrd (which itself refuses a
21
- * non-pending/quarantined row — see scheduler.cjs's updatePrd).
21
+ * 'running' or 'completed' row — see scheduler.cjs's updatePrd).
22
22
  *
23
23
  * `rows` throughout is the same shape listPrdsInternal() already produces:
24
24
  * { slug, status, dependsOn }. dependsOn entries are resolved against `rows`
@@ -151,7 +151,7 @@ function wouldCreateCycle(rows, slug, newDependsOn) {
151
151
  * behind another chain" action). Pure — returns the new dependsOn value on
152
152
  * success; the caller (scheduler.cjs's IPC handler) is responsible for
153
153
  * actually persisting it via remote.updatePrd, which independently refuses
154
- * a non-pending/quarantined row (defense in depth, not duplicated here).
154
+ * a 'running' or 'completed' row (defense in depth, not duplicated here).
155
155
  *
156
156
  * Refuses (ok: false) when:
157
157
  * - `slug` isn't a known row.
@@ -0,0 +1,284 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * prdGateFiles.cjs — pure validate + render helpers for a PRD's `gate` and
5
+ * `files` fields (gate-and-files-in-PRD-body unit).
6
+ *
7
+ * Why: 74% of parked jobs had no gate the scheduler could read. The planner
8
+ * now declares the gate commands and the files list when it calls
9
+ * scheduler_create_prd; prdCreate.cjs's createPrd() validates them with the
10
+ * functions here, then renders them as two PRD body sections (`# Files`,
11
+ * `# Gate`). `resolveGate`/`parseChain` in definitionOfDone.cjs already read
12
+ * a fenced ```gate block back — renderGateSection's output must round-trip
13
+ * through them unchanged (checked by createPrd(), not here).
14
+ *
15
+ * No I/O, no scheduler imports — pure string-in, string/array-out.
16
+ */
17
+
18
+ const { parseChain, explainChain, isNoneGate } = require('./definitionOfDone.cjs');
19
+
20
+ const GATE_MIN_ENTRIES = 1;
21
+ const GATE_MAX_ENTRIES = 10;
22
+ const GATE_ENTRY_MAX_CHARS = 500;
23
+ const FILES_MIN_ENTRIES = 1;
24
+ const FILES_MAX_ENTRIES = 50;
25
+ const FILES_ENTRY_MAX_CHARS = 300;
26
+
27
+ const NONE_GATE_WARNING =
28
+ 'Gate is none: the scheduler cannot re-check this PRD. Use none only for docs or config with no runnable check.';
29
+
30
+ // Same whitespace/control-char rule explainChain's rule 1 uses
31
+ // (definitionOfDone.cjs), minus tab — a tab has no legitimate place in a
32
+ // repo-relative file path.
33
+ const FILES_FOREIGN_WHITESPACE_RE = /[^\S ]|[\u0000-\u0008\u000A-\u001F\u007F]/;
34
+
35
+ // Chars stripped from a gate entry's first word before comparing it to
36
+ // "none" — quotes, plus the punctuation a typo or trailing comment tends to
37
+ // add (`none.`, `"none"`, `none!`).
38
+ const NONE_PUNCT_STRIP_RE = /^[.,;:!?()[\]{}#'"]+|[.,;:!?()[\]{}#'"]+$/g;
39
+
40
+ /** Cut a string to 80 chars for an error message, marking truncation with "…". */
41
+ function cut80(s) {
42
+ return s.length > 80 ? `${s.slice(0, 80)}…` : s;
43
+ }
44
+
45
+ /** `<field> entry <n> ("<cut entry>"): <rule>` — the shared error-text shape. */
46
+ function entryError(field, index, entry, rule) {
47
+ return `${field} entry ${index} ("${cut80(entry)}"): ${rule}`;
48
+ }
49
+
50
+ /**
51
+ * True when `entry` reads as a typo'd "none" opt-out (`none.`, `"none"`,
52
+ * `none # docs`, `NONE!`) but is not the exact opt-out itself. The exact
53
+ * opt-out (isNoneGate) is never a near-miss.
54
+ */
55
+ function isNearMissNone(entry) {
56
+ if (isNoneGate(entry)) return false;
57
+ const firstWord = entry.split(/[ \t]+/)[0] ?? '';
58
+ return /^none$/i.test(firstWord.replace(NONE_PUNCT_STRIP_RE, ''));
59
+ }
60
+
61
+ /**
62
+ * Validate a PRD's `gate` field.
63
+ *
64
+ * @param {unknown} gate
65
+ * @returns {{ok:true, commands:string[], warnings:string[]}|{ok:false, error:string}}
66
+ * commands is `['none']` for the opt-out, else the trimmed entries verbatim
67
+ * (one command chain per entry) — the exact strings renderGateSection and
68
+ * createPrd's read-back check both work from.
69
+ */
70
+ function validateGate(gate) {
71
+ if (!Array.isArray(gate) || gate.length < GATE_MIN_ENTRIES || gate.length > GATE_MAX_ENTRIES) {
72
+ return {
73
+ ok: false,
74
+ error: `gate must have ${GATE_MIN_ENTRIES} to ${GATE_MAX_ENTRIES} entries (got ${Array.isArray(gate) ? gate.length : 0}).`,
75
+ };
76
+ }
77
+
78
+ const trimmed = gate.map((e) => (typeof e === 'string' ? e.trim() : e));
79
+
80
+ // Per-entry shape rules — run uniformly over every entry, including "none"
81
+ // (it trivially satisfies all of them; the none/mixed semantics are a
82
+ // separate check below). explainChain's result is kept per entry so the
83
+ // missing-timeout warning pass below never re-parses.
84
+ const explainedByEntry = [];
85
+ for (let i = 0; i < trimmed.length; i++) {
86
+ const entry = trimmed[i];
87
+ const idx = i + 1;
88
+ if (typeof entry !== 'string' || entry.length === 0) {
89
+ return { ok: false, error: `gate entry ${idx}: must be a non-empty string.` };
90
+ }
91
+ if (entry.length > GATE_ENTRY_MAX_CHARS) {
92
+ return { ok: false, error: entryError('gate', idx, entry, `must be at most ${GATE_ENTRY_MAX_CHARS} chars.`) };
93
+ }
94
+ if (/[\r\n]/.test(entry)) {
95
+ return { ok: false, error: entryError('gate', idx, entry, 'must not contain a newline.') };
96
+ }
97
+ if (entry.includes('```')) {
98
+ return { ok: false, error: entryError('gate', idx, entry, 'must not contain a triple-backtick run (breaks the fence).') };
99
+ }
100
+ if (entry.startsWith('#')) {
101
+ return { ok: false, error: entryError('gate', idx, entry, 'must not start with "#" — the fence parser drops lines starting with "#".') };
102
+ }
103
+ if (isNearMissNone(entry)) {
104
+ return {
105
+ ok: false,
106
+ error: entryError('gate', idx, entry, 'write exactly none, with nothing else, for a PRD with no runnable check.'),
107
+ };
108
+ }
109
+ const explained = explainChain(entry);
110
+ if (!explained.ok) {
111
+ return { ok: false, error: entryError('gate', idx, entry, explained.error) };
112
+ }
113
+ explainedByEntry.push(explained);
114
+ }
115
+
116
+ // "none" semantics: alone it's the opt-out; mixed with anything else it's
117
+ // an error (one PRD cannot be both "no gate" and "here is a gate").
118
+ const noneIdx = trimmed.findIndex((e) => isNoneGate(e));
119
+ if (noneIdx !== -1) {
120
+ if (trimmed.length > 1) {
121
+ return {
122
+ ok: false,
123
+ error: entryError(
124
+ 'gate', noneIdx + 1, trimmed[noneIdx],
125
+ '"none" must be the only entry when used — remove the other entries or remove "none".',
126
+ ),
127
+ };
128
+ }
129
+ return { ok: true, commands: ['none'], warnings: [NONE_GATE_WARNING] };
130
+ }
131
+
132
+ // Missing-timeout warning (never rejects) — a step without one can hang
133
+ // the run.
134
+ const warnings = [];
135
+ explainedByEntry.forEach((explained, i) => {
136
+ explained.steps.forEach((step, stepIdx) => {
137
+ if (!explained.timeoutGiven[stepIdx]) {
138
+ warnings.push(
139
+ `gate entry ${i + 1} step "${cut80(step.raw)}" does not start with "timeout <seconds>" — a step without a timeout can hang the run.`,
140
+ );
141
+ }
142
+ });
143
+ });
144
+
145
+ return { ok: true, commands: trimmed, warnings };
146
+ }
147
+
148
+ /**
149
+ * Validate a PRD's `files` field.
150
+ *
151
+ * @param {unknown} files
152
+ * @returns {{ok:true, files:string[]}|{ok:false, error:string}}
153
+ * files is deduped (exact match, order preserved) with every leading `./`
154
+ * stripped per entry.
155
+ */
156
+ function validateFiles(files) {
157
+ if (!Array.isArray(files) || files.length < FILES_MIN_ENTRIES || files.length > FILES_MAX_ENTRIES) {
158
+ return {
159
+ ok: false,
160
+ error: `files must have ${FILES_MIN_ENTRIES} to ${FILES_MAX_ENTRIES} entries (got ${Array.isArray(files) ? files.length : 0}).`,
161
+ };
162
+ }
163
+
164
+ const seen = new Set();
165
+ const out = [];
166
+ for (let i = 0; i < files.length; i++) {
167
+ const idx = i + 1;
168
+ const original = files[i];
169
+ if (typeof original !== 'string') {
170
+ return { ok: false, error: `files entry ${idx}: must be a string.` };
171
+ }
172
+ const entry = original.trim();
173
+ if (entry.length === 0) {
174
+ return { ok: false, error: `files entry ${idx}: must not be empty.` };
175
+ }
176
+ if (entry.length > FILES_ENTRY_MAX_CHARS) {
177
+ return { ok: false, error: entryError('files', idx, entry, `must be at most ${FILES_ENTRY_MAX_CHARS} chars.`) };
178
+ }
179
+ if (/[\r\n]/.test(entry)) {
180
+ return { ok: false, error: entryError('files', idx, entry, 'must not contain a newline.') };
181
+ }
182
+ if (FILES_FOREIGN_WHITESPACE_RE.test(entry)) {
183
+ return {
184
+ ok: false,
185
+ error: entryError('files', idx, entry, 'has a character that is not a plain space (for example a tab, a no-break space, or a line break). Retype it.'),
186
+ };
187
+ }
188
+ if (entry.includes('\\')) {
189
+ return { ok: false, error: entryError('files', idx, entry, 'use "/" between folders, not "\\".') };
190
+ }
191
+ if (entry.includes('`')) {
192
+ return { ok: false, error: entryError('files', idx, entry, 'must not contain a backtick.') };
193
+ }
194
+ if (entry.startsWith('-')) {
195
+ return { ok: false, error: entryError('files', idx, entry, 'must not start with "-".') };
196
+ }
197
+ if (entry.startsWith('/') || entry.startsWith('~') || /^[A-Za-z]:/.test(entry)) {
198
+ return { ok: false, error: entryError('files', idx, entry, 'must be repo-relative — absolute paths are not allowed.') };
199
+ }
200
+ if (entry.includes('*') || entry.includes('?')) {
201
+ return { ok: false, error: entryError('files', idx, entry, 'must not contain "*" or "?" (no globs).') };
202
+ }
203
+
204
+ let normalized = entry;
205
+ while (normalized.startsWith('./')) normalized = normalized.slice(2);
206
+ if (normalized.length === 0) {
207
+ return { ok: false, error: entryError('files', idx, entry, 'is empty after removing "./".') };
208
+ }
209
+ const segments = normalized.split('/');
210
+ if (segments.includes('..')) {
211
+ return { ok: false, error: entryError('files', idx, entry, 'must not contain a ".." path segment.') };
212
+ }
213
+ if (segments.includes('.')) {
214
+ return { ok: false, error: entryError('files', idx, entry, 'must not contain a "." path segment.') };
215
+ }
216
+ if (normalized.includes('//')) {
217
+ return { ok: false, error: entryError('files', idx, entry, 'must not contain an empty path segment ("//").') };
218
+ }
219
+
220
+ if (!seen.has(normalized)) {
221
+ seen.add(normalized);
222
+ out.push(normalized);
223
+ }
224
+ }
225
+
226
+ return { ok: true, files: out };
227
+ }
228
+
229
+ /**
230
+ * Render the `# Files` body section as an array of lines (joinable with
231
+ * '\n' alongside the rest of buildPrdBody's bodyLines). `files` must already
232
+ * be validated/normalized (validateFiles's output).
233
+ *
234
+ * @param {string[]} files
235
+ * @returns {string[]}
236
+ */
237
+ function renderFilesSection(files) {
238
+ return [
239
+ '# Files', '',
240
+ 'Change only these files. If the work needs another file, change it and say why in your report.',
241
+ '',
242
+ ...files.map((f) => `- ${f}`),
243
+ '',
244
+ ];
245
+ }
246
+
247
+ /**
248
+ * Render the `# Gate` body section as an array of lines. `commandsOrNone`
249
+ * must already be validated (validateGate's `commands` output) — either
250
+ * `['none']` or the ordered command-chain strings to fence verbatim.
251
+ *
252
+ * @param {string[]} commandsOrNone
253
+ * @returns {string[]}
254
+ */
255
+ function renderGateSection(commandsOrNone) {
256
+ const isNone = commandsOrNone.length === 1 && isNoneGate(commandsOrNone[0]);
257
+ if (isNone) {
258
+ return [
259
+ '# Gate', '',
260
+ 'This PRD has no runnable check. Check each acceptance criterion by reading the files.',
261
+ '',
262
+ '```gate',
263
+ 'none',
264
+ '```',
265
+ '',
266
+ ];
267
+ }
268
+ return [
269
+ '# Gate', '',
270
+ 'Run these commands last, in order. Each must exit 0.',
271
+ '',
272
+ '```gate',
273
+ ...commandsOrNone,
274
+ '```',
275
+ '',
276
+ ];
277
+ }
278
+
279
+ module.exports = {
280
+ validateGate,
281
+ validateFiles,
282
+ renderFilesSection,
283
+ renderGateSection,
284
+ };
@@ -384,6 +384,64 @@ function deriveProjectCwdFromPrdPath(filePath) {
384
384
  return null;
385
385
  }
386
386
 
387
+ // Incident (2026-09-30): an Epic can live in a non-git PARENT folder P
388
+ // (P/session-manager-operations/scheduler/epics/<epicId>/prds/*.md) while
389
+ // its PRDs' own frontmatter `cwd` points at a SUB-REPO, e.g. P/repo. P is
390
+ // never itself a tracked project cwd (activeProjectCwds/allProjectCwds only
391
+ // know about registered project roots), so no amount of
392
+ // resolvePrdsDirs()/listEpicPrdDirs() enumeration ever visits it — only
393
+ // walking UP from the job's own cwd does. Bounded to a few hops so a
394
+ // same-machine but unrelated ancestor folder that happens to also contain a
395
+ // `session-manager-operations/` tree can't be walked into indefinitely.
396
+ const MAX_EPIC_ANCESTOR_HOPS = 3;
397
+
398
+ /**
399
+ * epicId must be an exact, plain directory-name match — never a glob, never
400
+ * string-concatenated into a path. Mirrors resolveEpicPrdWriteDir's own
401
+ * guard above.
402
+ */
403
+ function isSafeEpicId(epicId) {
404
+ return typeof epicId === 'string' && epicId.length > 0
405
+ && !epicId.includes('/') && !epicId.includes('\\') && !epicId.includes('..');
406
+ }
407
+
408
+ /**
409
+ * Walk up to MAX_EPIC_ANCESTOR_HOPS parent directories ABOVE `cwd` (never
410
+ * including `cwd` itself — that's already covered by resolveEpicsRoot/
411
+ * listEpicPrdDirs) looking for an EXACT
412
+ * `<ancestor>/session-manager-operations/scheduler/epics/<epicId>/<leafSubpath...>`
413
+ * match. Built only from `path.join` segments (never string concatenation
414
+ * with the caller-supplied epicId/cwd), with a resolved-path containment
415
+ * check as defense in depth. Returns only dirs that actually exist on disk.
416
+ */
417
+ function ancestorEpicDirs(cwd, epicId, leafSubpath) {
418
+ if (!cwd || typeof cwd !== 'string' || !path.isAbsolute(cwd)) return [];
419
+ if (!isSafeEpicId(epicId)) return [];
420
+ const out = [];
421
+ let dir = path.dirname(cwd);
422
+ for (let hop = 0; hop < MAX_EPIC_ANCESTOR_HOPS; hop++) {
423
+ const candidate = path.join(dir, OPS_ROOT_DIR, ...EPICS_SUBPATH, epicId, ...leafSubpath);
424
+ const resolvedDir = path.resolve(dir);
425
+ if (path.resolve(candidate).startsWith(resolvedDir + path.sep) && fs.existsSync(candidate)) {
426
+ out.push(candidate);
427
+ }
428
+ const parent = path.dirname(dir);
429
+ if (parent === dir) break; // reached the filesystem root
430
+ dir = parent;
431
+ }
432
+ return out;
433
+ }
434
+
435
+ /** Ancestor-folder `epics/<epicId>/prds` dirs above `cwd` (see ancestorEpicDirs). */
436
+ function ancestorEpicPrdDirs(cwd, epicId) {
437
+ return ancestorEpicDirs(cwd, epicId, ['prds']);
438
+ }
439
+
440
+ /** Ancestor-folder `epics/<epicId>/prds-archived` dirs above `cwd` (see ancestorEpicDirs). */
441
+ function ancestorEpicArchivedPrdDirs(cwd, epicId) {
442
+ return ancestorEpicDirs(cwd, epicId, ['prds-archived']);
443
+ }
444
+
387
445
  module.exports = {
388
446
  resolvePrdWriteDir,
389
447
  resolvePrdsDirs,
@@ -394,6 +452,9 @@ module.exports = {
394
452
  listArchivedPrdDirs,
395
453
  deriveEpicIdFromPrdPath,
396
454
  deriveProjectCwdFromPrdPath,
455
+ ancestorEpicPrdDirs,
456
+ ancestorEpicArchivedPrdDirs,
457
+ isSafeEpicId,
397
458
  PRD_SUBPATH,
398
459
  EPICS_SUBPATH,
399
460
  };
@@ -20,6 +20,7 @@ const { splitFrontmatter, parsePrdFile, serializePrdFile } = require('./prdFront
20
20
  const { resolvePrdWriteDir, resolvePrdsDirs } = require('./prdLocations.cjs');
21
21
  const { projectQueuePath } = require('./queueStore.cjs');
22
22
  const { expandHome } = require('./expandHome.cjs');
23
+ const atomicFs = require('./atomicFs.cjs');
23
24
 
24
25
  /**
25
26
  * Move every `.md` file in legacyPrdsDir whose frontmatter `cwd` resolves to
@@ -262,9 +263,7 @@ async function legacyAdoptExistingPrds() {
262
263
  fm.createdVia = 'legacy-adopted';
263
264
  fm.issuedAt = issuedAt;
264
265
  const newRaw = serializePrdFile(fm, body);
265
- const tmp = `${filePath}.tmp-${process.pid}`;
266
- await fsp.writeFile(tmp, newRaw, 'utf8');
267
- await fsp.rename(tmp, filePath);
266
+ await atomicFs.writeTextAtomic(filePath, newRaw);
268
267
  stamped += 1;
269
268
  } catch (e) {
270
269
  if (e?.code === 'ENOENT') continue; // raced with a concurrent mover/archiver
@@ -5,7 +5,8 @@
5
5
  * jobs finishing in <=10 min, yet nothing at the authoring gate flags an oversized PRD before
6
6
  * it lands. `sizingWarnings` is the single rule set; prdCreate.cjs's createPrd() surfaces its
7
7
  * output in the create-prd response, and scheduler-mcp-server.cjs's scheduler_create_prd
8
- * handler prints it as a "Sizing warnings:" block. Warnings never block a write.
8
+ * handler prints it as part of a "Warnings:" block (merged with the gate/files warnings
9
+ * from prdGateFiles.cjs). Warnings never block a write.
9
10
  */
10
11
  'use strict';
11
12
 
@@ -14,7 +14,7 @@
14
14
  'use strict';
15
15
 
16
16
  const { z } = require('zod');
17
- const { WorkTypeSchema } = require('./workTypeLibrary.cjs');
17
+ const { WorkTypeSchema, LEGACY_WORK_TYPES } = require('./workTypeLibrary.cjs');
18
18
 
19
19
  // Mirrors EpicTag (src/renderer/lib/tagLibrary.ts:10) — the Epic-level
20
20
  // mission tag. Epic tag and PRD tag (ipcSchemas.cjs's scheduleRetagPrd `tag`
@@ -23,6 +23,14 @@ const { WorkTypeSchema } = require('./workTypeLibrary.cjs');
23
23
  // own enum literal.
24
24
  const EpicTagSchema = WorkTypeSchema;
25
25
 
26
+ // A legacy tag value (e.g. the retired 'bilko-host-publisher') must keep
27
+ // loading on an old on-disk Epic — dropped silently (treated as absent),
28
+ // never thrown. Any other unknown value still fails via EpicTagSchema.
29
+ const LegacyAwareEpicTagSchema = z.preprocess(
30
+ (v) => (typeof v === 'string' && LEGACY_WORK_TYPES.includes(v) ? undefined : v),
31
+ EpicTagSchema.optional(),
32
+ );
33
+
26
34
  // Mirrors EpicSource (src/renderer/state/promptSessions.ts:22-27).
27
35
  const EpicSourceSchema = z.object({
28
36
  producer: z.enum(['new-epic-ui', 'scheduler-dispatch', 'cross-project-feedback']),
@@ -69,7 +77,7 @@ const PromptSessionSchema = z.object({
69
77
  createdAt: z.string(),
70
78
  completedAt: z.string().nullable(),
71
79
  resumedFromId: z.string().nullable().optional(),
72
- tag: EpicTagSchema.optional(),
80
+ tag: LegacyAwareEpicTagSchema,
73
81
  openingPrompt: z.string().nullable().optional(),
74
82
  source: EpicSourceSchema.optional(),
75
83
  agentType: z.string().optional(),
@@ -35,6 +35,7 @@ const path = require('node:path');
35
35
  const { splitFrontmatter } = require('./prdFrontmatter.cjs');
36
36
  const { readTail } = require('./fileTail.cjs');
37
37
  const { resolvePrdWriteDir } = require('./prdLocations.cjs');
38
+ const atomicFs = require('./atomicFs.cjs');
38
39
 
39
40
  // ─── VERDICT_LABELS — deliberate duplicate ──────────────────────────────────
40
41
  // Mirrors src/renderer/components/tabs/scheduler/sched-primitives.tsx:123-131.
@@ -396,15 +397,12 @@ async function writeRcaReport({ job, runDir, verdict, annotations, investigation
396
397
  job, verdict, meta, logTail, acText, failureClass, investigationText, annotations,
397
398
  });
398
399
 
399
- // Raw fs (not config.cjs's writeTextAtomic) for the same reason the rest
400
- // of this lib uses it: the run directory lives under
400
+ // atomicFs.cjs (not config.cjs's writeTextAtomic) for the same reason the
401
+ // rest of this lib uses it: the run directory lives under
401
402
  // ~/.claude/session-manager/scheduled-plans/runs/, which is Session-Manager
402
403
  // runtime state, not an OWNERS namespace under a project's operations root.
403
404
  const reportPath = path.join(runDir, `root-cause-${job.slug}.md`);
404
- fs.mkdirSync(runDir, { recursive: true });
405
- const tmp = `${reportPath}.tmp-${process.pid}`;
406
- fs.writeFileSync(tmp, markdown);
407
- fs.renameSync(tmp, reportPath);
405
+ atomicFs.writeTextAtomicSync(reportPath, markdown);
408
406
 
409
407
  const summary = rcaSummaryLine(job, verdict);
410
408
  const recoveryAction = recoveryActionFor(failureClass);