patchwarden 1.5.1 → 1.6.1

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 (573) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +152 -25
  3. package/README.md +120 -23
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentStore.js +42 -34
  6. package/dist/config.d.ts +15 -0
  7. package/dist/config.js +66 -13
  8. package/dist/control/fileManager.d.ts +2 -0
  9. package/dist/control/fileManager.js +39 -0
  10. package/dist/control/middleware/auth.d.ts +6 -0
  11. package/dist/control/middleware/auth.js +14 -5
  12. package/dist/control/middleware/static.js +16 -3
  13. package/dist/control/routeTable.d.ts +24 -0
  14. package/dist/control/routeTable.js +263 -0
  15. package/dist/control/routes/audit.js +17 -7
  16. package/dist/control/routes/evidence.js +1 -1
  17. package/dist/control/routes/lineage.js +1 -1
  18. package/dist/control/routes/process.d.ts +7 -0
  19. package/dist/control/routes/process.js +329 -42
  20. package/dist/control/routes/sessions.d.ts +9 -1
  21. package/dist/control/routes/sessions.js +84 -21
  22. package/dist/control/routes/status.d.ts +46 -1
  23. package/dist/control/routes/status.js +72 -15
  24. package/dist/control/routes/taskActions.js +24 -60
  25. package/dist/control/routes/tasks.d.ts +4 -0
  26. package/dist/control/routes/tasks.js +61 -62
  27. package/dist/control/routes/workspace.js +50 -23
  28. package/dist/control/runtime.d.ts +3 -2
  29. package/dist/control/runtime.js +63 -30
  30. package/dist/control/server.js +36 -365
  31. package/dist/control/shared.d.ts +25 -1
  32. package/dist/control/shared.js +64 -11
  33. package/dist/direct/directAudit.js +234 -225
  34. package/dist/direct/directGuards.d.ts +2 -1
  35. package/dist/direct/directGuards.js +42 -29
  36. package/dist/direct/directPatch.d.ts +6 -1
  37. package/dist/direct/directPatch.js +55 -8
  38. package/dist/direct/directSessionStore.d.ts +16 -6
  39. package/dist/direct/directSessionStore.js +127 -35
  40. package/dist/direct/directVerification.js +8 -16
  41. package/dist/doctor.js +62 -52
  42. package/dist/goal/acceptanceEngine.d.ts +1 -1
  43. package/dist/goal/goalProgress.js +75 -69
  44. package/dist/goal/goalReport.js +5 -12
  45. package/dist/goal/goalStatus.d.ts +2 -0
  46. package/dist/goal/goalStatus.js +10 -3
  47. package/dist/goal/goalStore.d.ts +9 -2
  48. package/dist/goal/goalStore.js +152 -45
  49. package/dist/goal/handoffExport.js +6 -6
  50. package/dist/goal/specKitImport.js +97 -96
  51. package/dist/goal/subgoalSync.js +11 -9
  52. package/dist/goal/worktreeManager.d.ts +2 -0
  53. package/dist/goal/worktreeManager.js +209 -136
  54. package/dist/httpServer.js +111 -32
  55. package/dist/logging.js +5 -3
  56. package/dist/policy/projectPolicy.js +5 -2
  57. package/dist/release/releaseGate.d.ts +1 -1
  58. package/dist/release/releaseGate.js +78 -25
  59. package/dist/runner/agentInvocation.d.ts +14 -0
  60. package/dist/runner/agentInvocation.js +96 -4
  61. package/dist/runner/changeCapture.d.ts +13 -5
  62. package/dist/runner/changeCapture.js +243 -61
  63. package/dist/runner/postTaskCleanup.js +48 -19
  64. package/dist/runner/processSecurity.d.ts +56 -0
  65. package/dist/runner/processSecurity.js +373 -0
  66. package/dist/runner/runTask.d.ts +1 -1
  67. package/dist/runner/runTask.js +115 -87
  68. package/dist/runner/simpleProcess.d.ts +3 -0
  69. package/dist/runner/simpleProcess.js +50 -36
  70. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  71. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  72. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  73. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  74. package/dist/runner/taskStatusStore.d.ts +11 -0
  75. package/dist/runner/taskStatusStore.js +24 -0
  76. package/dist/runner/watch.d.ts +14 -1
  77. package/dist/runner/watch.js +256 -48
  78. package/dist/security/contentRedaction.js +2 -2
  79. package/dist/security/discoveryTokenStore.d.ts +3 -2
  80. package/dist/security/discoveryTokenStore.js +17 -2
  81. package/dist/security/loopbackHost.d.ts +2 -0
  82. package/dist/security/loopbackHost.js +16 -0
  83. package/dist/security/pathGuard.js +32 -17
  84. package/dist/security/planGuard.js +2 -1
  85. package/dist/security/runtimeGuard.js +9 -6
  86. package/dist/security/secretComparison.d.ts +3 -0
  87. package/dist/security/secretComparison.js +11 -0
  88. package/dist/security/sensitiveGuard.d.ts +5 -0
  89. package/dist/security/sensitiveGuard.js +27 -8
  90. package/dist/security/toolInvocationGuard.d.ts +2 -2
  91. package/dist/security/toolInvocationGuard.js +1 -1
  92. package/dist/security/workspaceRootGuard.d.ts +8 -0
  93. package/dist/security/workspaceRootGuard.js +47 -0
  94. package/dist/smoke-test.js +24 -23
  95. package/dist/tools/catalog/index.d.ts +4 -0
  96. package/dist/tools/catalog/index.js +4 -0
  97. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  98. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  99. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  100. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  101. package/dist/tools/definitions/toolDefs.js +1177 -0
  102. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  103. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  104. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +1 -1
  105. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
  106. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  107. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
  108. package/dist/tools/diagnostics/index.d.ts +6 -0
  109. package/dist/tools/diagnostics/index.js +6 -0
  110. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  111. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  112. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  113. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  114. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  115. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  116. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  117. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  118. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  119. package/dist/tools/direct/index.d.ts +3 -0
  120. package/dist/tools/direct/index.js +3 -0
  121. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  122. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  123. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  124. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  125. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  126. package/dist/tools/discovery/index.d.ts +3 -0
  127. package/dist/tools/discovery/index.js +3 -0
  128. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  129. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  130. package/dist/tools/dispatch/coreDispatch.js +32 -31
  131. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  132. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  133. package/dist/tools/dispatch/directDispatch.js +11 -10
  134. package/dist/tools/dispatch/goalDispatch.js +5 -4
  135. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  136. package/dist/tools/dispatch/validation.d.ts +7 -0
  137. package/dist/tools/dispatch/validation.js +71 -0
  138. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  139. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  140. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  141. package/dist/tools/goals/index.d.ts +3 -0
  142. package/dist/tools/goals/index.js +3 -0
  143. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  144. package/dist/tools/registry.d.ts +7 -11
  145. package/dist/tools/registry.js +17 -1178
  146. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  147. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  148. package/dist/tools/release/index.d.ts +2 -0
  149. package/dist/tools/release/index.js +2 -0
  150. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  151. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  152. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  153. package/dist/tools/tasks/cancelTask.js +85 -0
  154. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
  155. package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
  156. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  157. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  158. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  159. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  160. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  161. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  162. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  163. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  164. package/dist/tools/tasks/index.d.ts +18 -0
  165. package/dist/tools/tasks/index.js +18 -0
  166. package/dist/tools/tasks/killTask.d.ts +1 -0
  167. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  168. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  169. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  170. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  171. package/dist/tools/tasks/retryTask.js +60 -0
  172. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  173. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +58 -30
  174. package/dist/tools/tasks/runVerification.js +38 -0
  175. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  176. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  177. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  178. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  179. package/dist/tools/workspace/applyPatch.js +53 -0
  180. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  181. package/dist/tools/workspace/index.d.ts +10 -0
  182. package/dist/tools/workspace/index.js +10 -0
  183. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  184. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  185. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  186. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  187. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  188. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  189. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  190. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  191. package/dist/tools/workspace/syncFile.js +134 -0
  192. package/dist/utils/atomicFile.d.ts +7 -0
  193. package/dist/utils/atomicFile.js +48 -0
  194. package/dist/utils/boundedFile.d.ts +8 -0
  195. package/dist/utils/boundedFile.js +93 -0
  196. package/dist/utils/lockedJsonFile.d.ts +15 -0
  197. package/dist/utils/lockedJsonFile.js +224 -0
  198. package/dist/utils/platform.d.ts +11 -0
  199. package/dist/utils/platform.js +26 -0
  200. package/dist/utils/stableJson.d.ts +2 -0
  201. package/dist/utils/stableJson.js +12 -0
  202. package/dist/version.d.ts +3 -2
  203. package/dist/version.js +3 -2
  204. package/dist/watcherStatus.d.ts +2 -2
  205. package/dist/watcherStatus.js +18 -0
  206. package/docs/CODE_WIKI.md +808 -143
  207. package/docs/architecture.md +15 -55
  208. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  209. package/docs/control-center/README.md +4 -4
  210. package/docs/dashboard-overview.md +4 -4
  211. package/docs/desktop-app.md +175 -0
  212. package/docs/evidence-pack-schema.md +3 -3
  213. package/docs/open-source-application.md +17 -11
  214. package/docs/release-checklist.md +1 -1
  215. package/docs/release-evidence.md +39 -29
  216. package/docs/task-safe-review-workflow.md +5 -5
  217. package/docs/threat-model.md +19 -1
  218. package/examples/config.example.json +4 -2
  219. package/package.json +27 -10
  220. package/scripts/README.md +15 -6
  221. package/scripts/build.js +11 -0
  222. package/scripts/checks/brand-check.js +3 -0
  223. package/scripts/checks/build-output-check.js +37 -0
  224. package/scripts/checks/control-center-smoke.js +91 -6
  225. package/scripts/checks/control-smoke.js +19 -8
  226. package/scripts/checks/http-mcp-smoke.js +82 -4
  227. package/scripts/checks/lifecycle-smoke.js +10 -10
  228. package/scripts/checks/mcp-manifest-check.js +1 -1
  229. package/scripts/checks/mcp-smoke.js +27 -3
  230. package/scripts/checks/package-manifest-check.js +20 -7
  231. package/scripts/checks/unit-tests.js +3 -5
  232. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  233. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  234. package/scripts/control/manage-patchwarden.ps1 +69 -4
  235. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  236. package/scripts/control/run-background-supervisor.ps1 +41 -0
  237. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  238. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  239. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  240. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  241. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  242. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  243. package/scripts/lib/clean-generated-output.js +11 -0
  244. package/scripts/lib/file-discovery.js +12 -0
  245. package/scripts/release/desktop-preflight.js +211 -0
  246. package/scripts/release/pack-clean.js +59 -14
  247. package/src/assessments/agentAssessor.ts +26 -12
  248. package/src/assessments/assessmentStore.ts +52 -40
  249. package/src/config.ts +70 -13
  250. package/src/control/fileManager.ts +40 -0
  251. package/src/control/middleware/auth.ts +22 -4
  252. package/src/control/middleware/static.ts +16 -3
  253. package/src/control/routeTable.ts +301 -0
  254. package/src/control/routes/audit.ts +14 -6
  255. package/src/control/routes/evidence.ts +1 -1
  256. package/src/control/routes/lineage.ts +1 -1
  257. package/src/control/routes/process.ts +341 -40
  258. package/src/control/routes/sessions.ts +88 -25
  259. package/src/control/routes/status.ts +97 -17
  260. package/src/control/routes/taskActions.ts +24 -58
  261. package/src/control/routes/tasks.ts +66 -58
  262. package/src/control/routes/workspace.ts +56 -25
  263. package/src/control/runtime.ts +69 -33
  264. package/src/control/server.ts +36 -342
  265. package/src/control/shared.ts +77 -11
  266. package/src/direct/directAudit.ts +17 -9
  267. package/src/direct/directGuards.ts +72 -44
  268. package/src/direct/directPatch.ts +95 -8
  269. package/src/direct/directSessionStore.ts +211 -55
  270. package/src/direct/directVerification.ts +9 -23
  271. package/src/doctor.ts +73 -59
  272. package/src/goal/acceptanceEngine.ts +1 -1
  273. package/src/goal/goalProgress.ts +17 -13
  274. package/src/goal/goalReport.ts +5 -13
  275. package/src/goal/goalStatus.ts +19 -3
  276. package/src/goal/goalStore.ts +241 -54
  277. package/src/goal/handoffExport.ts +6 -6
  278. package/src/goal/specKitImport.ts +12 -12
  279. package/src/goal/subgoalSync.ts +11 -8
  280. package/src/goal/worktreeManager.ts +139 -22
  281. package/src/httpServer.ts +113 -30
  282. package/src/logging.ts +10 -3
  283. package/src/policy/projectPolicy.ts +5 -2
  284. package/src/release/releaseGate.ts +74 -27
  285. package/src/runner/agentInvocation.ts +126 -4
  286. package/src/runner/changeCapture.ts +291 -57
  287. package/src/runner/postTaskCleanup.ts +41 -17
  288. package/src/runner/processSecurity.ts +433 -0
  289. package/src/runner/runTask.ts +138 -91
  290. package/src/runner/simpleProcess.ts +66 -31
  291. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  292. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  293. package/src/runner/taskStatusStore.ts +54 -0
  294. package/src/runner/watch.ts +281 -51
  295. package/src/security/contentRedaction.ts +2 -2
  296. package/src/security/discoveryTokenStore.ts +18 -4
  297. package/src/security/loopbackHost.ts +19 -0
  298. package/src/security/pathGuard.ts +35 -16
  299. package/src/security/planGuard.ts +2 -1
  300. package/src/security/runtimeGuard.ts +9 -6
  301. package/src/security/secretComparison.ts +13 -0
  302. package/src/security/sensitiveGuard.ts +27 -8
  303. package/src/security/toolInvocationGuard.ts +2 -2
  304. package/src/security/workspaceRootGuard.ts +53 -0
  305. package/src/smoke-test.ts +25 -24
  306. package/src/tools/catalog/index.ts +4 -0
  307. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  308. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  309. package/src/tools/definitions/toolDefs.ts +1264 -0
  310. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  311. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +99 -32
  312. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
  313. package/src/tools/diagnostics/index.ts +6 -0
  314. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  315. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  316. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  317. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  318. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  319. package/src/tools/direct/index.ts +3 -0
  320. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  321. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  322. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  323. package/src/tools/discovery/index.ts +3 -0
  324. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  325. package/src/tools/dispatch/coreDispatch.ts +36 -31
  326. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  327. package/src/tools/dispatch/directDispatch.ts +11 -10
  328. package/src/tools/dispatch/goalDispatch.ts +5 -4
  329. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  330. package/src/tools/dispatch/validation.ts +78 -0
  331. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  332. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  333. package/src/tools/goals/index.ts +3 -0
  334. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  335. package/src/tools/registry.ts +17 -1261
  336. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  337. package/src/tools/release/index.ts +2 -0
  338. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  339. package/src/tools/tasks/cancelTask.ts +109 -0
  340. package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
  341. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  342. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  343. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  344. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  345. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  346. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  347. package/src/tools/tasks/index.ts +18 -0
  348. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  349. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  350. package/src/tools/tasks/retryTask.ts +69 -0
  351. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +69 -34
  352. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  353. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  354. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  355. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  356. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  357. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  358. package/src/tools/workspace/index.ts +10 -0
  359. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  360. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  361. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  362. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  363. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  364. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  365. package/src/tools/workspace/syncFile.ts +292 -0
  366. package/src/utils/atomicFile.ts +58 -0
  367. package/src/utils/boundedFile.ts +116 -0
  368. package/src/utils/lockedJsonFile.ts +251 -0
  369. package/src/utils/platform.ts +31 -0
  370. package/src/utils/stableJson.ts +12 -0
  371. package/src/version.ts +3 -2
  372. package/src/watcherStatus.ts +19 -1
  373. package/ui/desktop-bootstrap.js +11 -0
  374. package/ui/desktop-bridge.js +36 -0
  375. package/ui/desktop.css +160 -0
  376. package/ui/getting-started.js +93 -0
  377. package/ui/i18n.js +484 -0
  378. package/ui/log-parser.js +30 -0
  379. package/ui/pages/audit.html +12 -0
  380. package/ui/pages/dashboard.html +90 -147
  381. package/ui/pages/direct-sessions.html +123 -26
  382. package/ui/pages/getting-started.html +43 -0
  383. package/ui/pages/logs.html +105 -22
  384. package/ui/pages/settings.html +101 -0
  385. package/ui/pages/task-detail.html +12 -0
  386. package/ui/pages/tasks.html +54 -8
  387. package/ui/pages/workspace.html +38 -1
  388. package/ui/settings.js +245 -0
  389. package/PatchWarden-Control.cmd +0 -6
  390. package/Restart-PatchWarden-Control.cmd +0 -6
  391. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  392. package/dist/test/unit/acceptance-engine.test.js +0 -228
  393. package/dist/test/unit/agent-router.test.d.ts +0 -1
  394. package/dist/test/unit/agent-router.test.js +0 -287
  395. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  396. package/dist/test/unit/android-doctor.test.js +0 -118
  397. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  398. package/dist/test/unit/apply-patch.test.js +0 -225
  399. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  400. package/dist/test/unit/audit-checks.test.js +0 -350
  401. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  402. package/dist/test/unit/chinese-path.test.js +0 -91
  403. package/dist/test/unit/command-guard.test.d.ts +0 -1
  404. package/dist/test/unit/command-guard.test.js +0 -160
  405. package/dist/test/unit/create-task.test.d.ts +0 -1
  406. package/dist/test/unit/create-task.test.js +0 -197
  407. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  408. package/dist/test/unit/diagnose-task.test.js +0 -457
  409. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  410. package/dist/test/unit/direct-guards.test.js +0 -328
  411. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  412. package/dist/test/unit/discovery-token-store.test.js +0 -139
  413. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  414. package/dist/test/unit/evidence-pack.test.js +0 -224
  415. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  416. package/dist/test/unit/get-task-status.test.js +0 -174
  417. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  418. package/dist/test/unit/get-task-summary.test.js +0 -146
  419. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  420. package/dist/test/unit/goal-graph.test.js +0 -298
  421. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  422. package/dist/test/unit/goal-progress.test.js +0 -381
  423. package/dist/test/unit/goal-report.test.d.ts +0 -1
  424. package/dist/test/unit/goal-report.test.js +0 -159
  425. package/dist/test/unit/goal-status.test.d.ts +0 -1
  426. package/dist/test/unit/goal-status.test.js +0 -215
  427. package/dist/test/unit/goal-store.test.d.ts +0 -1
  428. package/dist/test/unit/goal-store.test.js +0 -253
  429. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  430. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  431. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  432. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  433. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  434. package/dist/test/unit/handoff-export.test.js +0 -263
  435. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  436. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  437. package/dist/test/unit/logging.test.d.ts +0 -1
  438. package/dist/test/unit/logging.test.js +0 -397
  439. package/dist/test/unit/path-guard.test.d.ts +0 -1
  440. package/dist/test/unit/path-guard.test.js +0 -133
  441. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  442. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  443. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  444. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  445. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  446. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  447. package/dist/test/unit/release-gate.test.d.ts +0 -1
  448. package/dist/test/unit/release-gate.test.js +0 -242
  449. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  450. package/dist/test/unit/run-task-loop.test.js +0 -380
  451. package/dist/test/unit/safe-status.test.d.ts +0 -1
  452. package/dist/test/unit/safe-status.test.js +0 -165
  453. package/dist/test/unit/safe-views.test.d.ts +0 -1
  454. package/dist/test/unit/safe-views.test.js +0 -171
  455. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  456. package/dist/test/unit/schema-drift-check.test.js +0 -176
  457. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  458. package/dist/test/unit/sensitive-guard.test.js +0 -104
  459. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  460. package/dist/test/unit/spec-kit-import.test.js +0 -341
  461. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  462. package/dist/test/unit/subgoal-sync.test.js +0 -183
  463. package/dist/test/unit/sync-file.test.d.ts +0 -1
  464. package/dist/test/unit/sync-file.test.js +0 -154
  465. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  466. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  467. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  468. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  469. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  470. package/dist/test/unit/toolSearch.test.js +0 -571
  471. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  472. package/dist/test/unit/wait-for-task.test.js +0 -144
  473. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  474. package/dist/test/unit/watcher-status.test.js +0 -169
  475. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  476. package/dist/test/unit/worktree-manager.test.js +0 -176
  477. package/dist/tools/applyPatch.js +0 -41
  478. package/dist/tools/cancelTask.d.ts +0 -30
  479. package/dist/tools/cancelTask.js +0 -64
  480. package/dist/tools/finalizeDirectSession.js +0 -84
  481. package/dist/tools/goalSubgoalTask.js +0 -110
  482. package/dist/tools/killTask.d.ts +0 -15
  483. package/dist/tools/retryTask.js +0 -45
  484. package/dist/tools/runVerification.js +0 -36
  485. package/dist/tools/syncFile.js +0 -65
  486. package/docs/control-center/control-center-daily-driver.md +0 -211
  487. package/docs/control-center/control-center-mvp.md +0 -205
  488. package/docs/control-center/control-center-phase2.md +0 -159
  489. package/docs/execution-plan-2026-07-09.md +0 -315
  490. package/docs/release-v0.6.0.md +0 -71
  491. package/docs/release-v0.6.1.md +0 -75
  492. package/docs/release-v0.6.4.md +0 -45
  493. package/docs/roadmap-execution-and-acceptance.md +0 -365
  494. package/src/test/unit/acceptance-engine.test.ts +0 -261
  495. package/src/test/unit/agent-router.test.ts +0 -342
  496. package/src/test/unit/android-doctor.test.ts +0 -158
  497. package/src/test/unit/apply-patch.test.ts +0 -293
  498. package/src/test/unit/audit-checks.test.ts +0 -567
  499. package/src/test/unit/chinese-path.test.ts +0 -106
  500. package/src/test/unit/command-guard.test.ts +0 -221
  501. package/src/test/unit/create-task.test.ts +0 -255
  502. package/src/test/unit/diagnose-task.test.ts +0 -544
  503. package/src/test/unit/direct-guards.test.ts +0 -467
  504. package/src/test/unit/discovery-token-store.test.ts +0 -181
  505. package/src/test/unit/evidence-pack.test.ts +0 -251
  506. package/src/test/unit/get-task-status.test.ts +0 -203
  507. package/src/test/unit/get-task-summary.test.ts +0 -173
  508. package/src/test/unit/goal-graph.test.ts +0 -347
  509. package/src/test/unit/goal-progress.test.ts +0 -538
  510. package/src/test/unit/goal-report.test.ts +0 -189
  511. package/src/test/unit/goal-status.test.ts +0 -270
  512. package/src/test/unit/goal-store.test.ts +0 -318
  513. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  514. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  515. package/src/test/unit/handoff-export.test.ts +0 -295
  516. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  517. package/src/test/unit/logging.test.ts +0 -497
  518. package/src/test/unit/path-guard.test.ts +0 -180
  519. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  520. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  521. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  522. package/src/test/unit/release-gate.test.ts +0 -314
  523. package/src/test/unit/run-task-loop.test.ts +0 -425
  524. package/src/test/unit/safe-status.test.ts +0 -187
  525. package/src/test/unit/safe-views.test.ts +0 -184
  526. package/src/test/unit/schema-drift-check.test.ts +0 -260
  527. package/src/test/unit/sensitive-guard.test.ts +0 -124
  528. package/src/test/unit/spec-kit-import.test.ts +0 -429
  529. package/src/test/unit/subgoal-sync.test.ts +0 -236
  530. package/src/test/unit/sync-file.test.ts +0 -231
  531. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  532. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  533. package/src/test/unit/toolSearch.test.ts +0 -756
  534. package/src/test/unit/wait-for-task.test.ts +0 -176
  535. package/src/test/unit/watcher-status.test.ts +0 -190
  536. package/src/test/unit/worktree-manager.test.ts +0 -247
  537. package/src/tools/cancelTask.ts +0 -70
  538. package/src/tools/retryTask.ts +0 -53
  539. package/src/tools/syncFile.ts +0 -122
  540. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  541. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  542. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  543. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  544. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  545. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  546. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  547. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  548. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  549. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  550. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  551. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  552. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  553. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  554. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  555. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  556. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  557. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  558. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  559. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  560. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  561. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  562. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  563. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  564. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  565. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  566. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  567. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  568. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  569. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  570. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  571. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  572. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  573. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -13,7 +13,7 @@
13
13
  * 4. 所有检查通过 → ACCEPTED
14
14
  */
15
15
 
16
- import type { AcceptanceStatus } from "../tools/createTask.js";
16
+ import type { AcceptanceStatus } from "../tools/tasks/createTask.js";
17
17
 
18
18
  // ── 类型定义 ──────────────────────────────────────────────────────
19
19
 
@@ -15,7 +15,7 @@ import { existsSync, readFileSync } from "node:fs";
15
15
  import { isAbsolute, join } from "node:path";
16
16
  import { getConfig } from "../config.js";
17
17
  import { PatchWardenError } from "../errors.js";
18
- import { readGoalStatus, writeGoalStatus } from "./goalStore.js";
18
+ import { mutateGoalStatus, readGoalStatus } from "./goalStore.js";
19
19
  import { updateSubgoalStatus, type GoalStatus, type Subgoal } from "./goalStatus.js";
20
20
  import { getBlockedSubgoals } from "./goalGraph.js";
21
21
 
@@ -93,7 +93,7 @@ export function acceptSubgoal(
93
93
  subgoalId: string,
94
94
  workspaceRoot?: string
95
95
  ): { subgoal_id: string; status: "accepted"; accepted_at: string } {
96
- const goalStatus = readGoalStatus(goalId, workspaceRoot);
96
+ return mutateGoalStatus(goalId, (goalStatus) => {
97
97
 
98
98
  const subgoal = goalStatus.subgoals.find((s) => s.id === subgoalId);
99
99
  if (!subgoal) {
@@ -146,14 +146,16 @@ export function acceptSubgoal(
146
146
  // 校验 3:通过状态机转换(要求 subgoal 当前为 done_by_agent)
147
147
  const updatedGoalStatus = updateSubgoalStatus(goalStatus, subgoalId, "accepted");
148
148
 
149
- writeGoalStatus(goalId, updatedGoalStatus, workspaceRoot);
150
-
151
149
  const updatedSubgoal = updatedGoalStatus.subgoals.find((s) => s.id === subgoalId)!;
152
150
  return {
153
- subgoal_id: subgoalId,
154
- status: "accepted",
155
- accepted_at: updatedSubgoal.accepted_at!,
151
+ next: updatedGoalStatus,
152
+ result: {
153
+ subgoal_id: subgoalId,
154
+ status: "accepted" as const,
155
+ accepted_at: updatedSubgoal.accepted_at!,
156
+ },
156
157
  };
158
+ }, workspaceRoot);
157
159
  }
158
160
 
159
161
  /**
@@ -175,7 +177,7 @@ export function rejectSubgoal(
175
177
  reason: string,
176
178
  workspaceRoot?: string
177
179
  ): { subgoal_id: string; status: "rejected"; rejected_reason: string } {
178
- const goalStatus = readGoalStatus(goalId, workspaceRoot);
180
+ return mutateGoalStatus(goalId, (goalStatus) => {
179
181
 
180
182
  const index = goalStatus.subgoals.findIndex((s) => s.id === subgoalId);
181
183
  if (index === -1) {
@@ -222,13 +224,15 @@ export function rejectSubgoal(
222
224
  updated_at: new Date().toISOString(),
223
225
  };
224
226
 
225
- writeGoalStatus(goalId, updatedGoalStatus, workspaceRoot);
226
-
227
227
  return {
228
- subgoal_id: subgoalId,
229
- status: "rejected",
230
- rejected_reason: reason,
228
+ next: updatedGoalStatus,
229
+ result: {
230
+ subgoal_id: subgoalId,
231
+ status: "rejected" as const,
232
+ rejected_reason: reason,
233
+ },
231
234
  };
235
+ }, workspaceRoot);
232
236
  }
233
237
 
234
238
  /**
@@ -6,11 +6,12 @@
6
6
  * 所有写入操作使用原子写(.tmp + rename),所有字符串内容经 redactSensitiveValue 脱敏。
7
7
  */
8
8
 
9
- import { mkdirSync, renameSync, writeFileSync } from "node:fs";
9
+ import { mkdirSync } from "node:fs";
10
10
  import { join, resolve } from "node:path";
11
11
  import { getConfig } from "../config.js";
12
12
  import { redactSensitiveValue } from "../security/contentRedaction.js";
13
- import { listEvidencePacks } from "../tools/evidencePack.js";
13
+ import { listEvidencePacks } from "../tools/tasks/evidencePack.js";
14
+ import { atomicWriteFileSync } from "../utils/atomicFile.js";
14
15
  import { summarizeGoalProgress, type GoalProgressSummary } from "./goalProgress.js";
15
16
  import { readGoalStatus } from "./goalStore.js";
16
17
 
@@ -45,15 +46,6 @@ function resolveWorkspaceRoot(workspaceRoot?: string): string {
45
46
  return workspaceRoot ?? getConfig().workspaceRoot;
46
47
  }
47
48
 
48
- /**
49
- * 原子写文件:先写到 .tmp 文件,再 renameSync 到目标路径。
50
- */
51
- function atomicWriteFile(filePath: string, content: string): void {
52
- const tmpPath = filePath + ".tmp";
53
- writeFileSync(tmpPath, content, "utf-8");
54
- renameSync(tmpPath, filePath);
55
- }
56
-
57
49
  /**
58
50
  * 将百分比字符串(如 "100%")解析为数字。
59
51
  */
@@ -261,11 +253,11 @@ export function exportGoalReport(
261
253
  const safeReport = redactSensitiveValue(rawReport).value as SafeGoalReport;
262
254
 
263
255
  // 原子写 JSON(机器可读)
264
- atomicWriteFile(reportJsonPath, JSON.stringify(safeReport, null, 2) + "\n");
256
+ atomicWriteFileSync(reportJsonPath, JSON.stringify(safeReport, null, 2) + "\n");
265
257
 
266
258
  // 原子写 Markdown(人类可读,从脱敏后的对象生成)
267
259
  const markdown = buildReportMarkdown(safeReport, { noSubgoals, incomplete });
268
- atomicWriteFile(reportMdPath, markdown);
260
+ atomicWriteFileSync(reportMdPath, markdown);
269
261
 
270
262
  return safeReport;
271
263
  }
@@ -12,6 +12,7 @@
12
12
  */
13
13
 
14
14
  import { PatchWardenError } from "../errors.js";
15
+ import { redactSensitiveContent } from "../security/contentRedaction.js";
15
16
 
16
17
  // ── 类型定义 ──────────────────────────────────────────────────────
17
18
 
@@ -35,6 +36,8 @@ export interface Subgoal {
35
36
  external_ref?: string;
36
37
  /** Spec Kit task.files 提示(作用域文件路径列表) */
37
38
  scope_hints?: string[];
39
+ /** Managed git worktree associated with an isolated subgoal task. */
40
+ worktree_id?: string;
38
41
  }
39
42
 
40
43
  export interface GoalStatus {
@@ -74,7 +77,7 @@ export function createInitialGoalStatus(
74
77
  const now = new Date().toISOString();
75
78
  return {
76
79
  goal_id: goalId,
77
- title,
80
+ title: redactGoalText(title, 500),
78
81
  status: "active",
79
82
  repo_path: repoPath,
80
83
  created_at: now,
@@ -94,6 +97,15 @@ export function addSubgoal(
94
97
  title: string,
95
98
  dependsOn: string[] = []
96
99
  ): { goalStatus: GoalStatus; subgoalId: string } {
100
+ if (goalStatus.status !== "active") {
101
+ throw new PatchWardenError(
102
+ "goal_not_active",
103
+ `Goal "${goalStatus.goal_id}" is ${goalStatus.status} and cannot accept new subgoals.`,
104
+ "Create a new Goal Session for additional work.",
105
+ true,
106
+ { goal_id: goalStatus.goal_id, status: goalStatus.status },
107
+ );
108
+ }
97
109
  const existingIds = new Set(goalStatus.subgoals.map((s) => s.id));
98
110
  for (const depId of dependsOn) {
99
111
  if (!existingIds.has(depId)) {
@@ -111,7 +123,7 @@ export function addSubgoal(
111
123
  const subgoalId = `subgoal-${String(nextNumber).padStart(3, "0")}`;
112
124
  const newSubgoal: Subgoal = {
113
125
  id: subgoalId,
114
- title,
126
+ title: redactGoalText(title, 500),
115
127
  status: "ready",
116
128
  depends_on: [...dependsOn],
117
129
  task_ids: [],
@@ -185,7 +197,7 @@ export function updateSubgoalStatus(
185
197
  { subgoal_id: subgoalId, from_status: current.status, to_status: newStatus }
186
198
  );
187
199
  }
188
- updatedSubgoal.rejected_reason = options.rejected_reason;
200
+ updatedSubgoal.rejected_reason = redactGoalText(options.rejected_reason, 2000);
189
201
  }
190
202
 
191
203
  const newSubgoals = [...goalStatus.subgoals];
@@ -198,6 +210,10 @@ export function updateSubgoalStatus(
198
210
  };
199
211
  }
200
212
 
213
+ function redactGoalText(value: string, maxChars: number): string {
214
+ return redactSensitiveContent(String(value || "")).content.slice(0, maxChars);
215
+ }
216
+
201
217
  /**
202
218
  * 将 taskId 关联到指定 subgoal(去重)。
203
219
  * 不可变更新:返回新的 GoalStatus 对象。
@@ -18,14 +18,20 @@ import {
18
18
  mkdirSync,
19
19
  readdirSync,
20
20
  readFileSync,
21
- renameSync,
22
- writeFileSync,
23
21
  statSync,
24
22
  } from "node:fs";
25
23
  import { join } from "node:path";
26
24
  import { getConfig } from "../config.js";
27
- import { guardWorkspacePath } from "../security/pathGuard.js";
25
+ import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
28
26
  import { PatchWardenError } from "../errors.js";
27
+ import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
28
+ import { redactSensitiveContent } from "../security/contentRedaction.js";
29
+ import {
30
+ mutateLockedJsonFileSync,
31
+ readJsonObjectFileSync,
32
+ withFileLock,
33
+ withFileLockSync,
34
+ } from "../utils/lockedJsonFile.js";
29
35
  import {
30
36
  type GoalStatus,
31
37
  type Subgoal,
@@ -38,6 +44,26 @@ function resolveWorkspaceRoot(workspaceRoot?: string): string {
38
44
  return workspaceRoot ?? getConfig().workspaceRoot;
39
45
  }
40
46
 
47
+ const GOALS_PREFIX = join(".patchwarden", "goals");
48
+ const GOAL_ID_MAX_LENGTH = 128;
49
+ const GOAL_ID_PATTERN = /^goal_[A-Za-z0-9][A-Za-z0-9_-]*$/;
50
+
51
+ /** Validate the storage key before it can participate in path construction. */
52
+ export function assertValidGoalId(goalId: string): void {
53
+ if (
54
+ goalId.length > GOAL_ID_MAX_LENGTH ||
55
+ !GOAL_ID_PATTERN.test(goalId)
56
+ ) {
57
+ throw new PatchWardenError(
58
+ "invalid_goal_id",
59
+ `Invalid goal id. Expected "goal_" followed by at most ${GOAL_ID_MAX_LENGTH - 5} ASCII letters, digits, underscores, or hyphens.`,
60
+ "Use the goal_id returned by create_goal or list_goals.",
61
+ true,
62
+ { goal_id_length: goalId.length }
63
+ );
64
+ }
65
+ }
66
+
41
67
  // ── Goal ID 生成 ──────────────────────────────────────────────────
42
68
 
43
69
  /**
@@ -86,14 +112,17 @@ export function generateGoalId(title: string, existingIds: string[]): string {
86
112
  * 不自动创建目录。
87
113
  */
88
114
  export function getGoalsDir(workspaceRoot?: string): string {
89
- return join(resolveWorkspaceRoot(workspaceRoot), ".patchwarden", "goals");
115
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
116
+ return guardPath(join(wsRoot, GOALS_PREFIX), wsRoot, GOALS_PREFIX);
90
117
  }
91
118
 
92
119
  /**
93
120
  * 返回 `{getGoalsDir()}/{goalId}` 路径。
94
121
  */
95
122
  export function getGoalDir(goalId: string, workspaceRoot?: string): string {
96
- return join(getGoalsDir(workspaceRoot), goalId);
123
+ assertValidGoalId(goalId);
124
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
125
+ return guardPath(join(getGoalsDir(wsRoot), goalId), wsRoot, GOALS_PREFIX);
97
126
  }
98
127
 
99
128
  // ── CRUD ──────────────────────────────────────────────────────────
@@ -112,12 +141,26 @@ export function createGoal(
112
141
  description: string,
113
142
  workspaceRoot?: string
114
143
  ): { goal_id: string; goal_dir: string } {
144
+ const safeTitle = redactSensitiveContent(title)
145
+ .content
146
+ .replace(/[\r\n\t]+/g, " ")
147
+ .trim()
148
+ .slice(0, 500) || "Untitled goal";
149
+ const safeDescription = redactSensitiveContent(description).content;
150
+ if (Buffer.byteLength(safeDescription, "utf-8") > 1024 * 1024) {
151
+ throw new PatchWardenError(
152
+ "goal_description_too_large",
153
+ "Goal description exceeds the 1 MiB limit.",
154
+ "Split the work into smaller Goal Sessions.",
155
+ );
156
+ }
115
157
  const wsRoot = resolveWorkspaceRoot(workspaceRoot);
116
158
  const guardedRepo = guardWorkspacePath(repoPath, wsRoot);
117
159
 
118
160
  const goalsDir = getGoalsDir(workspaceRoot);
119
- const existingIds: string[] = [];
120
- if (existsSync(goalsDir)) {
161
+ mkdirSync(goalsDir, { recursive: true });
162
+ return withFileLockSync(join(goalsDir, "goal-creation"), () => {
163
+ const existingIds: string[] = [];
121
164
  for (const entry of readdirSync(goalsDir)) {
122
165
  const entryPath = join(goalsDir, entry);
123
166
  try {
@@ -125,45 +168,38 @@ export function createGoal(
125
168
  existingIds.push(entry);
126
169
  }
127
170
  } catch {
128
- // 跳过无法 stat 的条目
171
+ // Skip entries that cannot be inspected.
129
172
  }
130
173
  }
131
- }
132
174
 
133
- const goalId = generateGoalId(title, existingIds);
134
- const goalDir = join(goalsDir, goalId);
135
-
136
- // 创建目录结构
137
- mkdirSync(join(goalDir, "tasks"), { recursive: true });
138
- mkdirSync(join(goalDir, "artifacts"), { recursive: true });
139
-
140
- // 写入 GOAL.md
141
- const now = new Date().toISOString();
142
- const goalMd = [
143
- `# ${title}`,
144
- "",
145
- description,
146
- "",
147
- `- Created: ${now}`,
148
- `- Repo: ${guardedRepo}`,
149
- "- Status: active",
150
- "",
151
- ].join("\n");
152
- writeFileSync(join(goalDir, "GOAL.md"), goalMd, "utf-8");
153
-
154
- // 写入 GOALS.md
155
- const goalsMd = `# Subgoals: ${title}\n\n_No subgoals yet._\n`;
156
- writeFileSync(join(goalDir, "GOALS.md"), goalsMd, "utf-8");
157
-
158
- // 写入 goal_status.json
159
- const status = createInitialGoalStatus(goalId, title, guardedRepo);
160
- writeFileSync(
161
- join(goalDir, "goal_status.json"),
162
- JSON.stringify(status, null, 2) + "\n",
163
- "utf-8"
164
- );
175
+ const goalId = generateGoalId(safeTitle, existingIds);
176
+ const goalDir = getGoalDir(goalId, wsRoot);
177
+
178
+ mkdirSync(goalDir);
179
+ mkdirSync(join(goalDir, "tasks"));
180
+ mkdirSync(join(goalDir, "artifacts"));
181
+
182
+ const now = new Date().toISOString();
183
+ const goalMd = [
184
+ `# ${safeTitle}`,
185
+ "",
186
+ safeDescription,
187
+ "",
188
+ `- Created: ${now}`,
189
+ `- Repo: ${guardedRepo}`,
190
+ "- Status: active",
191
+ "",
192
+ ].join("\n");
193
+ atomicWriteFileSync(join(goalDir, "GOAL.md"), goalMd);
194
+
195
+ const goalsMd = `# Subgoals: ${safeTitle}\n\n_No subgoals yet._\n`;
196
+ atomicWriteFileSync(join(goalDir, "GOALS.md"), goalsMd);
197
+
198
+ const status = createInitialGoalStatus(goalId, safeTitle, guardedRepo);
199
+ atomicWriteJsonFileSync(join(goalDir, "goal_status.json"), status);
165
200
 
166
- return { goal_id: goalId, goal_dir: goalDir };
201
+ return { goal_id: goalId, goal_dir: goalDir };
202
+ }, goalBusyOptions("creation"));
167
203
  }
168
204
 
169
205
  /**
@@ -197,8 +233,8 @@ export function listGoals(
197
233
  }> = [];
198
234
 
199
235
  for (const entry of readdirSync(goalsDir)) {
200
- const entryPath = join(goalsDir, entry);
201
236
  try {
237
+ const entryPath = getGoalDir(entry, workspaceRoot);
202
238
  if (!statSync(entryPath).isDirectory()) {
203
239
  continue;
204
240
  }
@@ -254,7 +290,12 @@ export function readGoal(
254
290
  let goalDescription = "";
255
291
  const goalMdPath = join(goalDir, "GOAL.md");
256
292
  if (existsSync(goalMdPath)) {
257
- goalDescription = readFileSync(goalMdPath, "utf-8");
293
+ const safeGoalMdPath = guardPath(
294
+ goalMdPath,
295
+ resolveWorkspaceRoot(workspaceRoot),
296
+ GOALS_PREFIX,
297
+ );
298
+ goalDescription = readFileSync(safeGoalMdPath, "utf-8");
258
299
  }
259
300
 
260
301
  return {
@@ -270,8 +311,7 @@ export function readGoal(
270
311
  }
271
312
 
272
313
  /**
273
- * 原子写入 goal_status.json:先写到 .tmp 文件,再 renameSync。
274
- * JSON 序列化用 JSON.stringify(status, null, 2) + 末尾换行。
314
+ * Replace goal_status.json under the shared cross-process mutation lock.
275
315
  */
276
316
  export function writeGoalStatus(
277
317
  goalId: string,
@@ -279,10 +319,26 @@ export function writeGoalStatus(
279
319
  workspaceRoot?: string
280
320
  ): void {
281
321
  const goalDir = getGoalDir(goalId, workspaceRoot);
282
- const finalPath = join(goalDir, "goal_status.json");
283
- const tmpPath = join(goalDir, "goal_status.json.tmp");
284
- writeFileSync(tmpPath, JSON.stringify(status, null, 2) + "\n", "utf-8");
285
- renameSync(tmpPath, finalPath);
322
+ if (!existsSync(goalDir)) {
323
+ throw new PatchWardenError(
324
+ "goal_not_found",
325
+ `Goal directory not found: "${goalDir}"`,
326
+ "Ensure the goal has been created before writing its status.",
327
+ true,
328
+ { goal_id: goalId, goal_dir: goalDir },
329
+ );
330
+ }
331
+ const statusPath = getGoalStatusPath(goalId, workspaceRoot);
332
+ if (!existsSync(statusPath)) {
333
+ withFileLockSync(statusPath, () => {
334
+ atomicWriteJsonFileSync(
335
+ statusPath,
336
+ normalizeGoalLifecycle(validateGoalStatus(goalId, status, workspaceRoot)),
337
+ );
338
+ }, goalBusyOptions(goalId));
339
+ return;
340
+ }
341
+ mutateGoalStatus(goalId, () => ({ next: status, result: undefined }), workspaceRoot);
286
342
  }
287
343
 
288
344
  /**
@@ -290,8 +346,7 @@ export function writeGoalStatus(
290
346
  * 如果文件不存在,抛出 PatchWardenError("goal_not_found")。
291
347
  */
292
348
  export function readGoalStatus(goalId: string, workspaceRoot?: string): GoalStatus {
293
- const goalDir = getGoalDir(goalId, workspaceRoot);
294
- const statusPath = join(goalDir, "goal_status.json");
349
+ const statusPath = getGoalStatusPath(goalId, workspaceRoot);
295
350
  if (!existsSync(statusPath)) {
296
351
  throw new PatchWardenError(
297
352
  "goal_not_found",
@@ -301,6 +356,138 @@ export function readGoalStatus(goalId: string, workspaceRoot?: string): GoalStat
301
356
  { goal_id: goalId, status_path: statusPath }
302
357
  );
303
358
  }
304
- const raw = readFileSync(statusPath, "utf-8");
305
- return JSON.parse(raw) as GoalStatus;
359
+ let parsed: unknown;
360
+ try {
361
+ parsed = readJsonObjectFileSync(statusPath);
362
+ } catch {
363
+ throw invalidGoalStatus(goalId, "goal_status.json is not valid JSON");
364
+ }
365
+ return validateGoalStatus(goalId, parsed, workspaceRoot);
366
+ }
367
+
368
+ export interface GoalStatusMutation<R> {
369
+ next?: GoalStatus;
370
+ result: R;
371
+ }
372
+
373
+ export function mutateGoalStatus<R>(
374
+ goalId: string,
375
+ mutation: (current: GoalStatus) => GoalStatusMutation<R>,
376
+ workspaceRoot?: string,
377
+ ): R {
378
+ const statusPath = getGoalStatusPath(goalId, workspaceRoot);
379
+ ensureGoalStatusExists(goalId, statusPath);
380
+ return mutateLockedJsonFileSync<GoalStatus, R>(statusPath, (raw) => {
381
+ const current = validateGoalStatus(goalId, raw, workspaceRoot);
382
+ const outcome = mutation(current);
383
+ if (!outcome.next) return outcome;
384
+ return {
385
+ next: normalizeGoalLifecycle(validateGoalStatus(goalId, outcome.next, workspaceRoot)),
386
+ result: outcome.result,
387
+ };
388
+ }, goalBusyOptions(goalId));
389
+ }
390
+
391
+ export async function mutateGoalStatusAsync<R>(
392
+ goalId: string,
393
+ mutation: (current: GoalStatus) => Promise<GoalStatusMutation<R>>,
394
+ workspaceRoot?: string,
395
+ ): Promise<R> {
396
+ const statusPath = getGoalStatusPath(goalId, workspaceRoot);
397
+ ensureGoalStatusExists(goalId, statusPath);
398
+ return withFileLock(statusPath, async () => {
399
+ const current = validateGoalStatus(
400
+ goalId,
401
+ readJsonObjectFileSync(statusPath),
402
+ workspaceRoot,
403
+ );
404
+ const outcome = await mutation(current);
405
+ if (outcome.next) {
406
+ atomicWriteJsonFileSync(
407
+ statusPath,
408
+ normalizeGoalLifecycle(validateGoalStatus(goalId, outcome.next, workspaceRoot)),
409
+ );
410
+ }
411
+ return outcome.result;
412
+ }, goalBusyOptions(goalId));
413
+ }
414
+
415
+ function getGoalStatusPath(goalId: string, workspaceRoot?: string): string {
416
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
417
+ return guardPath(
418
+ join(getGoalDir(goalId, wsRoot), "goal_status.json"),
419
+ wsRoot,
420
+ GOALS_PREFIX,
421
+ );
422
+ }
423
+
424
+ function ensureGoalStatusExists(goalId: string, statusPath: string): void {
425
+ if (!existsSync(statusPath)) {
426
+ throw new PatchWardenError(
427
+ "goal_not_found",
428
+ `goal_status.json not found for goal "${goalId}" at "${statusPath}"`,
429
+ "Ensure the goal has been created via createGoal before updating it.",
430
+ true,
431
+ { goal_id: goalId, status_path: statusPath },
432
+ );
433
+ }
434
+ }
435
+
436
+ function validateGoalStatus(
437
+ goalId: string,
438
+ value: unknown,
439
+ workspaceRoot?: string,
440
+ ): GoalStatus {
441
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
442
+ throw invalidGoalStatus(goalId, "expected a JSON object");
443
+ }
444
+ const candidate = value as Partial<GoalStatus>;
445
+ if (candidate.goal_id !== goalId) {
446
+ throw invalidGoalStatus(goalId, "goal_id does not match its directory");
447
+ }
448
+ if (typeof candidate.title !== "string" || typeof candidate.repo_path !== "string") {
449
+ throw invalidGoalStatus(goalId, "title or repo_path is missing");
450
+ }
451
+ if (candidate.status !== "active" && candidate.status !== "completed" && candidate.status !== "abandoned") {
452
+ throw invalidGoalStatus(goalId, "status is invalid");
453
+ }
454
+ if (!Array.isArray(candidate.subgoals)) {
455
+ throw invalidGoalStatus(goalId, "subgoals must be an array");
456
+ }
457
+ guardWorkspacePath(candidate.repo_path, resolveWorkspaceRoot(workspaceRoot));
458
+ return candidate as GoalStatus;
459
+ }
460
+
461
+ function normalizeGoalLifecycle(status: GoalStatus): GoalStatus {
462
+ if (
463
+ status.status === "active" &&
464
+ status.subgoals.length > 0 &&
465
+ status.subgoals.every((subgoal) => subgoal.status === "accepted")
466
+ ) {
467
+ return { ...status, status: "completed", updated_at: new Date().toISOString() };
468
+ }
469
+ return status;
470
+ }
471
+
472
+ function invalidGoalStatus(goalId: string, detail: string): PatchWardenError {
473
+ return new PatchWardenError(
474
+ "invalid_goal_status",
475
+ `Invalid goal status for "${goalId}": ${detail}.`,
476
+ "Recreate the Goal Session or restore a valid goal_status.json artifact.",
477
+ true,
478
+ { goal_id: goalId },
479
+ );
480
+ }
481
+
482
+ function goalBusyOptions(goalId: string) {
483
+ return {
484
+ waitMs: 0,
485
+ busyError: () => new PatchWardenError(
486
+ "goal_busy",
487
+ `Goal "${goalId}" is currently being updated.`,
488
+ "Retry after the active Goal operation finishes.",
489
+ true,
490
+ { goal_id: goalId },
491
+ ),
492
+ };
306
493
  }
@@ -9,9 +9,10 @@
9
9
 
10
10
  import type { GoalStatus } from "./goalStatus.js";
11
11
  import { suggestNextSubgoal } from "./goalGraph.js";
12
- import { getConfig } from "../config.js";
13
- import { mkdirSync, writeFileSync } from "node:fs";
14
- import { join, resolve } from "node:path";
12
+ import { getGoalDir } from "./goalStore.js";
13
+ import { mkdirSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { atomicWriteFileSync } from "../utils/atomicFile.js";
15
16
 
16
17
  // ── Markdown 生成 ────────────────────────────────────────────────
17
18
 
@@ -195,12 +196,11 @@ export function exportHandoff(
195
196
  ): { handoff_path: string; content_preview: string } {
196
197
  const content = generateHandoff(goalId, goalStatus);
197
198
 
198
- const root = workspaceRoot ?? getConfig().workspaceRoot;
199
- const goalDir = resolve(root, ".patchwarden", "goals", goalId);
199
+ const goalDir = getGoalDir(goalId, workspaceRoot);
200
200
  mkdirSync(goalDir, { recursive: true });
201
201
 
202
202
  const handoffPath = join(goalDir, "handoff.md");
203
- writeFileSync(handoffPath, content, "utf-8");
203
+ atomicWriteFileSync(handoffPath, content);
204
204
 
205
205
  const preview = content.length > 500 ? content.slice(0, 500) + "..." : content;
206
206
 
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { PatchWardenError } from "../errors.js";
13
13
  import { addSubgoal, type GoalStatus } from "./goalStatus.js";
14
- import { readGoalStatus, writeGoalStatus } from "./goalStore.js";
14
+ import { mutateGoalStatus } from "./goalStore.js";
15
15
 
16
16
  // ── 类型定义 ──────────────────────────────────────────────────────
17
17
 
@@ -207,8 +207,8 @@ export function importSpecKitTasks(
207
207
 
208
208
  const workspaceRoot = options?.workspaceRoot;
209
209
 
210
- // ── 读取现有 GoalStatus ────────────────────────────────────
211
- let goalStatus: GoalStatus = readGoalStatus(goalId, workspaceRoot);
210
+ return mutateGoalStatus(goalId, (currentGoalStatus) => {
211
+ let goalStatus: GoalStatus = currentGoalStatus;
212
212
 
213
213
  // ── 构建 external_ref → subgoal_id 映射(现有 subgoal)─────
214
214
  const refToSubgoalId = new Map<string, string>();
@@ -341,15 +341,15 @@ export function importSpecKitTasks(
341
341
  };
342
342
  }
343
343
 
344
- // ── 持久化(原子写)────────────────────────────────────────
345
- writeGoalStatus(goalId, goalStatus, workspaceRoot);
346
-
347
- // ── 返回结果 ───────────────────────────────────────────────
348
344
  return {
349
- goal_id: goalId,
350
- spec_name: input.spec,
351
- created_count: createdIds.length,
352
- existing_count: existingCount,
353
- subgoal_ids: createdIds,
345
+ next: goalStatus,
346
+ result: {
347
+ goal_id: goalId,
348
+ spec_name: input.spec,
349
+ created_count: createdIds.length,
350
+ existing_count: existingCount,
351
+ subgoal_ids: createdIds,
352
+ },
354
353
  };
354
+ }, workspaceRoot);
355
355
  }