patchwarden 1.5.1 → 1.6.2

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 (591) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +158 -26
  3. package/README.md +124 -24
  4. package/dist/assessments/agentAssessor.js +21 -12
  5. package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
  6. package/dist/assessments/assessmentDiagnostics.js +132 -0
  7. package/dist/assessments/assessmentStore.d.ts +39 -0
  8. package/dist/assessments/assessmentStore.js +207 -45
  9. package/dist/assessments/securitySnapshot.d.ts +50 -0
  10. package/dist/assessments/securitySnapshot.js +158 -0
  11. package/dist/config.d.ts +15 -0
  12. package/dist/config.js +66 -13
  13. package/dist/control/fileManager.d.ts +2 -0
  14. package/dist/control/fileManager.js +39 -0
  15. package/dist/control/middleware/auth.d.ts +6 -0
  16. package/dist/control/middleware/auth.js +14 -5
  17. package/dist/control/middleware/static.js +16 -3
  18. package/dist/control/routeTable.d.ts +24 -0
  19. package/dist/control/routeTable.js +263 -0
  20. package/dist/control/routes/audit.js +17 -7
  21. package/dist/control/routes/evidence.js +1 -1
  22. package/dist/control/routes/lineage.js +1 -1
  23. package/dist/control/routes/process.d.ts +7 -0
  24. package/dist/control/routes/process.js +329 -42
  25. package/dist/control/routes/sessions.d.ts +9 -1
  26. package/dist/control/routes/sessions.js +84 -21
  27. package/dist/control/routes/status.d.ts +60 -1
  28. package/dist/control/routes/status.js +94 -15
  29. package/dist/control/routes/taskActions.js +24 -60
  30. package/dist/control/routes/tasks.d.ts +4 -0
  31. package/dist/control/routes/tasks.js +61 -62
  32. package/dist/control/routes/workspace.js +50 -23
  33. package/dist/control/runtime.d.ts +3 -2
  34. package/dist/control/runtime.js +65 -40
  35. package/dist/control/server.js +37 -365
  36. package/dist/control/shared.d.ts +25 -1
  37. package/dist/control/shared.js +64 -11
  38. package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
  39. package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
  40. package/dist/direct/directAudit.js +234 -225
  41. package/dist/direct/directGuards.d.ts +2 -1
  42. package/dist/direct/directGuards.js +42 -29
  43. package/dist/direct/directPatch.d.ts +6 -1
  44. package/dist/direct/directPatch.js +55 -8
  45. package/dist/direct/directSessionStore.d.ts +16 -6
  46. package/dist/direct/directSessionStore.js +128 -35
  47. package/dist/direct/directVerification.js +8 -16
  48. package/dist/doctor.js +65 -57
  49. package/dist/goal/acceptanceEngine.d.ts +1 -1
  50. package/dist/goal/goalProgress.js +75 -69
  51. package/dist/goal/goalReport.js +5 -12
  52. package/dist/goal/goalStatus.d.ts +2 -0
  53. package/dist/goal/goalStatus.js +10 -3
  54. package/dist/goal/goalStore.d.ts +9 -2
  55. package/dist/goal/goalStore.js +152 -45
  56. package/dist/goal/handoffExport.js +6 -6
  57. package/dist/goal/specKitImport.js +97 -96
  58. package/dist/goal/subgoalSync.js +11 -9
  59. package/dist/goal/worktreeManager.d.ts +2 -0
  60. package/dist/goal/worktreeManager.js +209 -136
  61. package/dist/httpServer.js +111 -32
  62. package/dist/logging.js +5 -3
  63. package/dist/policy/projectPolicy.js +5 -2
  64. package/dist/release/releaseGate.d.ts +1 -1
  65. package/dist/release/releaseGate.js +78 -25
  66. package/dist/runner/agentInvocation.d.ts +14 -0
  67. package/dist/runner/agentInvocation.js +96 -4
  68. package/dist/runner/changeCapture.d.ts +13 -5
  69. package/dist/runner/changeCapture.js +243 -61
  70. package/dist/runner/postTaskCleanup.js +48 -19
  71. package/dist/runner/processSecurity.d.ts +56 -0
  72. package/dist/runner/processSecurity.js +373 -0
  73. package/dist/runner/runTask.d.ts +2 -1
  74. package/dist/runner/runTask.js +162 -93
  75. package/dist/runner/simpleProcess.d.ts +3 -0
  76. package/dist/runner/simpleProcess.js +50 -36
  77. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  78. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  79. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +3 -1
  80. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  81. package/dist/runner/taskStatusStore.d.ts +11 -0
  82. package/dist/runner/taskStatusStore.js +35 -0
  83. package/dist/runner/watch.d.ts +14 -1
  84. package/dist/runner/watch.js +320 -49
  85. package/dist/security/contentRedaction.js +2 -2
  86. package/dist/security/discoveryTokenStore.d.ts +3 -2
  87. package/dist/security/discoveryTokenStore.js +17 -2
  88. package/dist/security/loopbackHost.d.ts +2 -0
  89. package/dist/security/loopbackHost.js +16 -0
  90. package/dist/security/pathGuard.js +32 -17
  91. package/dist/security/planGuard.js +2 -1
  92. package/dist/security/runtimeGuard.js +9 -6
  93. package/dist/security/secretComparison.d.ts +3 -0
  94. package/dist/security/secretComparison.js +11 -0
  95. package/dist/security/sensitiveGuard.d.ts +5 -0
  96. package/dist/security/sensitiveGuard.js +27 -8
  97. package/dist/security/toolInvocationGuard.d.ts +2 -2
  98. package/dist/security/toolInvocationGuard.js +1 -1
  99. package/dist/security/workspaceRootGuard.d.ts +8 -0
  100. package/dist/security/workspaceRootGuard.js +47 -0
  101. package/dist/smoke-test.js +27 -26
  102. package/dist/tools/catalog/index.d.ts +4 -0
  103. package/dist/tools/catalog/index.js +4 -0
  104. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  105. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  106. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
  107. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  108. package/dist/tools/definitions/toolDefs.js +1177 -0
  109. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  110. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  111. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +2 -1
  112. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
  113. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
  114. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
  115. package/dist/tools/diagnostics/index.d.ts +6 -0
  116. package/dist/tools/diagnostics/index.js +6 -0
  117. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  118. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  119. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
  120. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
  121. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  122. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  123. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
  124. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
  125. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  126. package/dist/tools/direct/index.d.ts +3 -0
  127. package/dist/tools/direct/index.js +3 -0
  128. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  129. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  130. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  131. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  132. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  133. package/dist/tools/discovery/index.d.ts +3 -0
  134. package/dist/tools/discovery/index.js +3 -0
  135. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  136. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  137. package/dist/tools/dispatch/coreDispatch.js +32 -31
  138. package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
  139. package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
  140. package/dist/tools/dispatch/directDispatch.js +11 -10
  141. package/dist/tools/dispatch/goalDispatch.js +5 -4
  142. package/dist/tools/dispatch/releaseDispatch.js +3 -2
  143. package/dist/tools/dispatch/validation.d.ts +7 -0
  144. package/dist/tools/dispatch/validation.js +71 -0
  145. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  146. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
  147. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  148. package/dist/tools/goals/index.d.ts +3 -0
  149. package/dist/tools/goals/index.js +3 -0
  150. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  151. package/dist/tools/registry.d.ts +7 -11
  152. package/dist/tools/registry.js +17 -1178
  153. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  154. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  155. package/dist/tools/release/index.d.ts +2 -0
  156. package/dist/tools/release/index.js +2 -0
  157. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  158. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  159. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  160. package/dist/tools/tasks/cancelTask.js +86 -0
  161. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  162. package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
  163. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  164. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
  165. package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
  166. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  167. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  168. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +11 -6
  169. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  170. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
  171. package/dist/tools/tasks/index.d.ts +18 -0
  172. package/dist/tools/tasks/index.js +18 -0
  173. package/dist/tools/tasks/killTask.d.ts +1 -0
  174. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  175. package/dist/tools/{listTasks.js → tasks/listTasks.js} +5 -4
  176. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
  177. package/dist/tools/tasks/reconcileTasks.js +496 -0
  178. package/dist/tools/tasks/retryTask.js +60 -0
  179. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  180. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +63 -41
  181. package/dist/tools/tasks/runVerification.js +38 -0
  182. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  183. package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
  184. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  185. package/dist/tools/tasks/taskStates.d.ts +6 -0
  186. package/dist/tools/tasks/taskStates.js +52 -0
  187. package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
  188. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  189. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  190. package/dist/tools/workspace/applyPatch.js +53 -0
  191. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  192. package/dist/tools/workspace/index.d.ts +10 -0
  193. package/dist/tools/workspace/index.js +10 -0
  194. package/dist/tools/workspace/listAgents.d.ts +23 -0
  195. package/dist/tools/workspace/listAgents.js +56 -0
  196. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  197. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  198. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  199. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  200. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  201. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  202. package/dist/tools/workspace/syncFile.js +134 -0
  203. package/dist/utils/atomicFile.d.ts +7 -0
  204. package/dist/utils/atomicFile.js +67 -0
  205. package/dist/utils/boundedFile.d.ts +8 -0
  206. package/dist/utils/boundedFile.js +93 -0
  207. package/dist/utils/lockedJsonFile.d.ts +15 -0
  208. package/dist/utils/lockedJsonFile.js +224 -0
  209. package/dist/utils/platform.d.ts +11 -0
  210. package/dist/utils/platform.js +26 -0
  211. package/dist/utils/stableJson.d.ts +2 -0
  212. package/dist/utils/stableJson.js +12 -0
  213. package/dist/version.d.ts +3 -2
  214. package/dist/version.js +3 -2
  215. package/dist/watcherStatus.d.ts +2 -2
  216. package/dist/watcherStatus.js +18 -0
  217. package/docs/CODE_WIKI.md +808 -143
  218. package/docs/architecture.md +15 -55
  219. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  220. package/docs/control-center/README.md +4 -4
  221. package/docs/dashboard-overview.md +4 -4
  222. package/docs/desktop-app.md +175 -0
  223. package/docs/evidence-pack-schema.md +3 -3
  224. package/docs/open-source-application.md +16 -11
  225. package/docs/release-checklist.md +1 -1
  226. package/docs/release-evidence.md +35 -39
  227. package/docs/task-safe-review-workflow.md +5 -5
  228. package/docs/threat-model.md +19 -1
  229. package/examples/config.example.json +4 -2
  230. package/package.json +27 -10
  231. package/scripts/README.md +15 -6
  232. package/scripts/build.js +11 -0
  233. package/scripts/checks/brand-check.js +3 -0
  234. package/scripts/checks/build-output-check.js +37 -0
  235. package/scripts/checks/control-center-smoke.js +102 -10
  236. package/scripts/checks/control-smoke.js +19 -8
  237. package/scripts/checks/http-mcp-smoke.js +82 -4
  238. package/scripts/checks/lifecycle-smoke.js +15 -12
  239. package/scripts/checks/mcp-manifest-check.js +1 -1
  240. package/scripts/checks/mcp-smoke.js +58 -4
  241. package/scripts/checks/package-manifest-check.js +20 -7
  242. package/scripts/checks/unit-tests.js +3 -5
  243. package/scripts/checks/watcher-supervisor-smoke.js +38 -1
  244. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  245. package/scripts/control/manage-patchwarden.ps1 +69 -4
  246. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  247. package/scripts/control/run-background-supervisor.ps1 +41 -0
  248. package/scripts/control/start-patchwarden-tunnel.ps1 +191 -33
  249. package/scripts/e2e/demo-runtime.mjs +153 -0
  250. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  251. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  252. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  253. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  254. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  255. package/scripts/lib/clean-generated-output.js +11 -0
  256. package/scripts/lib/file-discovery.js +12 -0
  257. package/scripts/release/desktop-preflight.js +211 -0
  258. package/scripts/release/pack-clean.js +59 -14
  259. package/src/assessments/agentAssessor.ts +26 -12
  260. package/src/assessments/assessmentDiagnostics.ts +172 -0
  261. package/src/assessments/assessmentStore.ts +288 -51
  262. package/src/assessments/securitySnapshot.ts +231 -0
  263. package/src/config.ts +70 -13
  264. package/src/control/fileManager.ts +40 -0
  265. package/src/control/middleware/auth.ts +22 -4
  266. package/src/control/middleware/static.ts +16 -3
  267. package/src/control/routeTable.ts +301 -0
  268. package/src/control/routes/audit.ts +14 -6
  269. package/src/control/routes/evidence.ts +1 -1
  270. package/src/control/routes/lineage.ts +1 -1
  271. package/src/control/routes/process.ts +341 -40
  272. package/src/control/routes/sessions.ts +88 -25
  273. package/src/control/routes/status.ts +134 -17
  274. package/src/control/routes/taskActions.ts +24 -58
  275. package/src/control/routes/tasks.ts +66 -58
  276. package/src/control/routes/workspace.ts +56 -25
  277. package/src/control/runtime.ts +71 -43
  278. package/src/control/server.ts +37 -342
  279. package/src/control/shared.ts +77 -11
  280. package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
  281. package/src/direct/directAudit.ts +17 -9
  282. package/src/direct/directGuards.ts +72 -44
  283. package/src/direct/directPatch.ts +95 -8
  284. package/src/direct/directSessionStore.ts +213 -55
  285. package/src/direct/directVerification.ts +9 -23
  286. package/src/doctor.ts +76 -64
  287. package/src/goal/acceptanceEngine.ts +1 -1
  288. package/src/goal/goalProgress.ts +17 -13
  289. package/src/goal/goalReport.ts +5 -13
  290. package/src/goal/goalStatus.ts +19 -3
  291. package/src/goal/goalStore.ts +241 -54
  292. package/src/goal/handoffExport.ts +6 -6
  293. package/src/goal/specKitImport.ts +12 -12
  294. package/src/goal/subgoalSync.ts +11 -8
  295. package/src/goal/worktreeManager.ts +139 -22
  296. package/src/httpServer.ts +113 -30
  297. package/src/logging.ts +10 -3
  298. package/src/policy/projectPolicy.ts +5 -2
  299. package/src/release/releaseGate.ts +74 -27
  300. package/src/runner/agentInvocation.ts +126 -4
  301. package/src/runner/changeCapture.ts +291 -57
  302. package/src/runner/postTaskCleanup.ts +41 -17
  303. package/src/runner/processSecurity.ts +433 -0
  304. package/src/runner/runTask.ts +197 -97
  305. package/src/runner/simpleProcess.ts +66 -31
  306. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  307. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
  308. package/src/runner/taskStatusStore.ts +69 -0
  309. package/src/runner/watch.ts +348 -52
  310. package/src/security/contentRedaction.ts +2 -2
  311. package/src/security/discoveryTokenStore.ts +18 -4
  312. package/src/security/loopbackHost.ts +19 -0
  313. package/src/security/pathGuard.ts +35 -16
  314. package/src/security/planGuard.ts +2 -1
  315. package/src/security/runtimeGuard.ts +9 -6
  316. package/src/security/secretComparison.ts +13 -0
  317. package/src/security/sensitiveGuard.ts +27 -8
  318. package/src/security/toolInvocationGuard.ts +2 -2
  319. package/src/security/workspaceRootGuard.ts +53 -0
  320. package/src/smoke-test.ts +27 -26
  321. package/src/tools/catalog/index.ts +4 -0
  322. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  323. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
  324. package/src/tools/definitions/toolDefs.ts +1264 -0
  325. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  326. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +136 -39
  327. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
  328. package/src/tools/diagnostics/index.ts +6 -0
  329. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  330. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
  331. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  332. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
  333. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
  334. package/src/tools/direct/index.ts +3 -0
  335. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  336. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  337. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  338. package/src/tools/discovery/index.ts +3 -0
  339. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  340. package/src/tools/dispatch/coreDispatch.ts +36 -31
  341. package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
  342. package/src/tools/dispatch/directDispatch.ts +11 -10
  343. package/src/tools/dispatch/goalDispatch.ts +5 -4
  344. package/src/tools/dispatch/releaseDispatch.ts +3 -2
  345. package/src/tools/dispatch/validation.ts +78 -0
  346. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  347. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
  348. package/src/tools/goals/index.ts +3 -0
  349. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  350. package/src/tools/registry.ts +17 -1261
  351. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  352. package/src/tools/release/index.ts +2 -0
  353. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  354. package/src/tools/tasks/cancelTask.ts +110 -0
  355. package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
  356. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
  357. package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
  358. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  359. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
  360. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  361. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
  362. package/src/tools/tasks/index.ts +18 -0
  363. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
  364. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
  365. package/src/tools/tasks/retryTask.ts +69 -0
  366. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
  367. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  368. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  369. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  370. package/src/tools/tasks/taskStates.ts +54 -0
  371. package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
  372. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  373. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  374. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  375. package/src/tools/workspace/index.ts +10 -0
  376. package/src/tools/workspace/listAgents.ts +75 -0
  377. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  378. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  379. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  380. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  381. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  382. package/src/tools/workspace/syncFile.ts +292 -0
  383. package/src/utils/atomicFile.ts +74 -0
  384. package/src/utils/boundedFile.ts +116 -0
  385. package/src/utils/lockedJsonFile.ts +251 -0
  386. package/src/utils/platform.ts +31 -0
  387. package/src/utils/stableJson.ts +12 -0
  388. package/src/version.ts +3 -2
  389. package/src/watcherStatus.ts +19 -1
  390. package/ui/desktop-bootstrap.js +11 -0
  391. package/ui/desktop-bridge.js +36 -0
  392. package/ui/desktop.css +160 -0
  393. package/ui/getting-started.js +93 -0
  394. package/ui/i18n.js +484 -0
  395. package/ui/log-parser.js +30 -0
  396. package/ui/pages/audit.html +12 -0
  397. package/ui/pages/dashboard.html +94 -147
  398. package/ui/pages/direct-sessions.html +123 -26
  399. package/ui/pages/getting-started.html +43 -0
  400. package/ui/pages/logs.html +134 -22
  401. package/ui/pages/settings.html +101 -0
  402. package/ui/pages/task-detail.html +12 -0
  403. package/ui/pages/tasks.html +54 -8
  404. package/ui/pages/workspace.html +38 -1
  405. package/ui/settings.js +254 -0
  406. package/PatchWarden-Control.cmd +0 -6
  407. package/Restart-PatchWarden-Control.cmd +0 -6
  408. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  409. package/dist/test/unit/acceptance-engine.test.js +0 -228
  410. package/dist/test/unit/agent-router.test.d.ts +0 -1
  411. package/dist/test/unit/agent-router.test.js +0 -287
  412. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  413. package/dist/test/unit/android-doctor.test.js +0 -118
  414. package/dist/test/unit/apply-patch.test.d.ts +0 -1
  415. package/dist/test/unit/apply-patch.test.js +0 -225
  416. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  417. package/dist/test/unit/audit-checks.test.js +0 -350
  418. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  419. package/dist/test/unit/chinese-path.test.js +0 -91
  420. package/dist/test/unit/command-guard.test.d.ts +0 -1
  421. package/dist/test/unit/command-guard.test.js +0 -160
  422. package/dist/test/unit/create-task.test.d.ts +0 -1
  423. package/dist/test/unit/create-task.test.js +0 -197
  424. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  425. package/dist/test/unit/diagnose-task.test.js +0 -457
  426. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  427. package/dist/test/unit/direct-guards.test.js +0 -328
  428. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  429. package/dist/test/unit/discovery-token-store.test.js +0 -139
  430. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  431. package/dist/test/unit/evidence-pack.test.js +0 -224
  432. package/dist/test/unit/get-task-status.test.d.ts +0 -1
  433. package/dist/test/unit/get-task-status.test.js +0 -174
  434. package/dist/test/unit/get-task-summary.test.d.ts +0 -1
  435. package/dist/test/unit/get-task-summary.test.js +0 -146
  436. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  437. package/dist/test/unit/goal-graph.test.js +0 -298
  438. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  439. package/dist/test/unit/goal-progress.test.js +0 -381
  440. package/dist/test/unit/goal-report.test.d.ts +0 -1
  441. package/dist/test/unit/goal-report.test.js +0 -159
  442. package/dist/test/unit/goal-status.test.d.ts +0 -1
  443. package/dist/test/unit/goal-status.test.js +0 -215
  444. package/dist/test/unit/goal-store.test.d.ts +0 -1
  445. package/dist/test/unit/goal-store.test.js +0 -253
  446. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  447. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  448. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  449. package/dist/test/unit/goal-tools-registry.test.js +0 -192
  450. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  451. package/dist/test/unit/handoff-export.test.js +0 -263
  452. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  453. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  454. package/dist/test/unit/logging.test.d.ts +0 -1
  455. package/dist/test/unit/logging.test.js +0 -397
  456. package/dist/test/unit/path-guard.test.d.ts +0 -1
  457. package/dist/test/unit/path-guard.test.js +0 -133
  458. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  459. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  460. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  461. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  462. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  463. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  464. package/dist/test/unit/release-gate.test.d.ts +0 -1
  465. package/dist/test/unit/release-gate.test.js +0 -242
  466. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  467. package/dist/test/unit/run-task-loop.test.js +0 -380
  468. package/dist/test/unit/safe-status.test.d.ts +0 -1
  469. package/dist/test/unit/safe-status.test.js +0 -165
  470. package/dist/test/unit/safe-views.test.d.ts +0 -1
  471. package/dist/test/unit/safe-views.test.js +0 -171
  472. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  473. package/dist/test/unit/schema-drift-check.test.js +0 -176
  474. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  475. package/dist/test/unit/sensitive-guard.test.js +0 -104
  476. package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
  477. package/dist/test/unit/spec-kit-import.test.js +0 -341
  478. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  479. package/dist/test/unit/subgoal-sync.test.js +0 -183
  480. package/dist/test/unit/sync-file.test.d.ts +0 -1
  481. package/dist/test/unit/sync-file.test.js +0 -154
  482. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  483. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  484. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  485. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  486. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  487. package/dist/test/unit/toolSearch.test.js +0 -571
  488. package/dist/test/unit/wait-for-task.test.d.ts +0 -1
  489. package/dist/test/unit/wait-for-task.test.js +0 -144
  490. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  491. package/dist/test/unit/watcher-status.test.js +0 -169
  492. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  493. package/dist/test/unit/worktree-manager.test.js +0 -176
  494. package/dist/tools/applyPatch.js +0 -41
  495. package/dist/tools/cancelTask.d.ts +0 -30
  496. package/dist/tools/cancelTask.js +0 -64
  497. package/dist/tools/finalizeDirectSession.js +0 -84
  498. package/dist/tools/goalSubgoalTask.js +0 -110
  499. package/dist/tools/killTask.d.ts +0 -15
  500. package/dist/tools/listAgents.d.ts +0 -12
  501. package/dist/tools/listAgents.js +0 -42
  502. package/dist/tools/reconcileTasks.js +0 -352
  503. package/dist/tools/retryTask.js +0 -45
  504. package/dist/tools/runVerification.js +0 -36
  505. package/dist/tools/syncFile.js +0 -65
  506. package/docs/control-center/control-center-daily-driver.md +0 -211
  507. package/docs/control-center/control-center-mvp.md +0 -205
  508. package/docs/control-center/control-center-phase2.md +0 -159
  509. package/docs/execution-plan-2026-07-09.md +0 -315
  510. package/docs/release-v0.6.0.md +0 -71
  511. package/docs/release-v0.6.1.md +0 -75
  512. package/docs/release-v0.6.4.md +0 -45
  513. package/docs/roadmap-execution-and-acceptance.md +0 -365
  514. package/src/test/unit/acceptance-engine.test.ts +0 -261
  515. package/src/test/unit/agent-router.test.ts +0 -342
  516. package/src/test/unit/android-doctor.test.ts +0 -158
  517. package/src/test/unit/apply-patch.test.ts +0 -293
  518. package/src/test/unit/audit-checks.test.ts +0 -567
  519. package/src/test/unit/chinese-path.test.ts +0 -106
  520. package/src/test/unit/command-guard.test.ts +0 -221
  521. package/src/test/unit/create-task.test.ts +0 -255
  522. package/src/test/unit/diagnose-task.test.ts +0 -544
  523. package/src/test/unit/direct-guards.test.ts +0 -467
  524. package/src/test/unit/discovery-token-store.test.ts +0 -181
  525. package/src/test/unit/evidence-pack.test.ts +0 -251
  526. package/src/test/unit/get-task-status.test.ts +0 -203
  527. package/src/test/unit/get-task-summary.test.ts +0 -173
  528. package/src/test/unit/goal-graph.test.ts +0 -347
  529. package/src/test/unit/goal-progress.test.ts +0 -538
  530. package/src/test/unit/goal-report.test.ts +0 -189
  531. package/src/test/unit/goal-status.test.ts +0 -270
  532. package/src/test/unit/goal-store.test.ts +0 -318
  533. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  534. package/src/test/unit/goal-tools-registry.test.ts +0 -245
  535. package/src/test/unit/handoff-export.test.ts +0 -295
  536. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  537. package/src/test/unit/logging.test.ts +0 -497
  538. package/src/test/unit/path-guard.test.ts +0 -180
  539. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  540. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  541. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  542. package/src/test/unit/release-gate.test.ts +0 -314
  543. package/src/test/unit/run-task-loop.test.ts +0 -425
  544. package/src/test/unit/safe-status.test.ts +0 -187
  545. package/src/test/unit/safe-views.test.ts +0 -184
  546. package/src/test/unit/schema-drift-check.test.ts +0 -260
  547. package/src/test/unit/sensitive-guard.test.ts +0 -124
  548. package/src/test/unit/spec-kit-import.test.ts +0 -429
  549. package/src/test/unit/subgoal-sync.test.ts +0 -236
  550. package/src/test/unit/sync-file.test.ts +0 -231
  551. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  552. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  553. package/src/test/unit/toolSearch.test.ts +0 -756
  554. package/src/test/unit/wait-for-task.test.ts +0 -176
  555. package/src/test/unit/watcher-status.test.ts +0 -190
  556. package/src/test/unit/worktree-manager.test.ts +0 -247
  557. package/src/tools/cancelTask.ts +0 -70
  558. package/src/tools/listAgents.ts +0 -54
  559. package/src/tools/retryTask.ts +0 -53
  560. package/src/tools/syncFile.ts +0 -122
  561. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  562. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  563. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  564. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  565. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  566. /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
  567. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  568. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  569. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  570. /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
  571. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  572. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  573. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  574. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  575. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  576. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  577. /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
  578. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  579. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  580. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  581. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  582. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  583. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  584. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  585. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  586. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  587. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  588. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  589. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  590. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  591. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
@@ -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
  }
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { readFileSync } from "node:fs";
11
11
  import { join } from "node:path";
12
- import { readGoalStatus, writeGoalStatus } from "./goalStore.js";
12
+ import { mutateGoalStatus } from "./goalStore.js";
13
13
  import { updateSubgoalStatus } from "./goalStatus.js";
14
14
  import { logger } from "../logging.js";
15
15
 
@@ -39,13 +39,16 @@ export function syncSubgoalOnTaskDone(
39
39
  if (!goalId || !subgoalId) return;
40
40
 
41
41
  try {
42
- const goalStatus = readGoalStatus(goalId, workspaceRoot);
43
- const subgoal = goalStatus.subgoals.find((s) => s.id === subgoalId);
44
- if (!subgoal) return;
45
- if (subgoal.status !== "running") return;
46
-
47
- const updated = updateSubgoalStatus(goalStatus, subgoalId, "done_by_agent");
48
- writeGoalStatus(goalId, updated, workspaceRoot);
42
+ mutateGoalStatus(goalId, (goalStatus) => {
43
+ const subgoal = goalStatus.subgoals.find((entry) => entry.id === subgoalId);
44
+ if (!subgoal || subgoal.status !== "running") {
45
+ return { result: undefined };
46
+ }
47
+ return {
48
+ next: updateSubgoalStatus(goalStatus, subgoalId, "done_by_agent"),
49
+ result: undefined,
50
+ };
51
+ }, workspaceRoot);
49
52
  } catch (err) {
50
53
  // subgoal 同步失败不应影响任务完成流程,只记录到 stderr
51
54
  logger.error(