patchwarden 1.5.0 → 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 (597) hide show
  1. package/PatchWarden.cmd +32 -2
  2. package/README.en.md +194 -26
  3. package/README.md +160 -22
  4. package/dist/assessments/agentAssessor.d.ts +1 -1
  5. package/dist/assessments/agentAssessor.js +24 -15
  6. package/dist/assessments/assessmentStore.d.ts +1 -1
  7. package/dist/assessments/assessmentStore.js +44 -36
  8. package/dist/assessments/confirmCli.js +5 -4
  9. package/dist/config.d.ts +22 -0
  10. package/dist/config.js +98 -11
  11. package/dist/control/fileManager.d.ts +2 -0
  12. package/dist/control/fileManager.js +39 -0
  13. package/dist/control/middleware/auth.d.ts +16 -0
  14. package/dist/control/middleware/auth.js +17 -0
  15. package/dist/control/middleware/static.d.ts +3 -0
  16. package/dist/control/middleware/static.js +78 -0
  17. package/dist/control/routeTable.d.ts +24 -0
  18. package/dist/control/routeTable.js +263 -0
  19. package/dist/control/routes/audit.d.ts +15 -0
  20. package/dist/control/routes/audit.js +287 -0
  21. package/dist/control/routes/evidence.d.ts +4 -0
  22. package/dist/control/routes/evidence.js +96 -0
  23. package/dist/control/routes/lineage.d.ts +3 -0
  24. package/dist/control/routes/lineage.js +71 -0
  25. package/dist/control/routes/policy.d.ts +3 -0
  26. package/dist/control/routes/policy.js +81 -0
  27. package/dist/control/routes/process.d.ts +12 -0
  28. package/dist/control/routes/process.js +487 -0
  29. package/dist/control/routes/sessions.d.ts +30 -0
  30. package/dist/control/routes/sessions.js +287 -0
  31. package/dist/control/routes/status.d.ts +51 -0
  32. package/dist/control/routes/status.js +307 -0
  33. package/dist/control/routes/taskActions.d.ts +21 -0
  34. package/dist/control/routes/taskActions.js +197 -0
  35. package/dist/control/routes/tasks.d.ts +24 -0
  36. package/dist/control/routes/tasks.js +309 -0
  37. package/dist/control/routes/workspace.d.ts +15 -0
  38. package/dist/control/routes/workspace.js +220 -0
  39. package/dist/control/runtime.d.ts +92 -0
  40. package/dist/control/runtime.js +425 -0
  41. package/dist/control/server.d.ts +13 -0
  42. package/dist/control/server.js +150 -0
  43. package/dist/control/shared.d.ts +59 -0
  44. package/dist/control/shared.js +341 -0
  45. package/dist/controlCenter.d.ts +6 -0
  46. package/dist/controlCenter.js +7 -2197
  47. package/dist/direct/directAudit.js +234 -225
  48. package/dist/direct/directGuards.d.ts +2 -1
  49. package/dist/direct/directGuards.js +72 -37
  50. package/dist/direct/directPatch.d.ts +6 -1
  51. package/dist/direct/directPatch.js +55 -8
  52. package/dist/direct/directSessionStore.d.ts +16 -6
  53. package/dist/direct/directSessionStore.js +127 -35
  54. package/dist/direct/directVerification.js +8 -16
  55. package/dist/doctor.d.ts +18 -1
  56. package/dist/doctor.js +614 -373
  57. package/dist/goal/acceptanceEngine.d.ts +1 -1
  58. package/dist/goal/goalProgress.js +75 -69
  59. package/dist/goal/goalReport.d.ts +54 -0
  60. package/dist/goal/goalReport.js +197 -0
  61. package/dist/goal/goalStatus.d.ts +8 -0
  62. package/dist/goal/goalStatus.js +10 -3
  63. package/dist/goal/goalStore.d.ts +9 -2
  64. package/dist/goal/goalStore.js +152 -45
  65. package/dist/goal/handoffExport.js +6 -6
  66. package/dist/goal/specKitImport.d.ts +63 -0
  67. package/dist/goal/specKitImport.js +221 -0
  68. package/dist/goal/subgoalSync.js +13 -10
  69. package/dist/goal/worktreeManager.d.ts +2 -0
  70. package/dist/goal/worktreeManager.js +209 -136
  71. package/dist/httpServer.js +126 -44
  72. package/dist/index.js +7 -4
  73. package/dist/logging.d.ts +7 -1
  74. package/dist/logging.js +13 -3
  75. package/dist/policy/projectPolicy.js +5 -2
  76. package/dist/release/releaseGate.d.ts +1 -1
  77. package/dist/release/releaseGate.js +78 -25
  78. package/dist/runner/agentInvocation.d.ts +14 -0
  79. package/dist/runner/agentInvocation.js +96 -4
  80. package/dist/runner/changeCapture.d.ts +14 -6
  81. package/dist/runner/changeCapture.js +287 -81
  82. package/dist/runner/cli.js +7 -6
  83. package/dist/runner/postTaskCleanup.js +60 -7
  84. package/dist/runner/processSecurity.d.ts +56 -0
  85. package/dist/runner/processSecurity.js +373 -0
  86. package/dist/runner/runTask.d.ts +1 -1
  87. package/dist/runner/runTask.js +339 -287
  88. package/dist/runner/simpleProcess.d.ts +3 -0
  89. package/dist/runner/simpleProcess.js +52 -38
  90. package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
  91. package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
  92. package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
  93. package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
  94. package/dist/runner/taskStatusStore.d.ts +11 -0
  95. package/dist/runner/taskStatusStore.js +24 -0
  96. package/dist/runner/watch.d.ts +14 -1
  97. package/dist/runner/watch.js +265 -54
  98. package/dist/security/contentRedaction.d.ts +6 -0
  99. package/dist/security/contentRedaction.js +24 -2
  100. package/dist/security/discoveryTokenStore.d.ts +3 -2
  101. package/dist/security/discoveryTokenStore.js +17 -2
  102. package/dist/security/loopbackHost.d.ts +2 -0
  103. package/dist/security/loopbackHost.js +16 -0
  104. package/dist/security/pathGuard.js +32 -17
  105. package/dist/security/planGuard.js +2 -1
  106. package/dist/security/runtimeGuard.js +9 -6
  107. package/dist/security/secretComparison.d.ts +3 -0
  108. package/dist/security/secretComparison.js +11 -0
  109. package/dist/security/sensitiveGuard.d.ts +5 -0
  110. package/dist/security/sensitiveGuard.js +27 -8
  111. package/dist/security/toolInvocationGuard.d.ts +2 -2
  112. package/dist/security/toolInvocationGuard.js +1 -1
  113. package/dist/security/workspaceRootGuard.d.ts +8 -0
  114. package/dist/security/workspaceRootGuard.js +47 -0
  115. package/dist/smoke-test.js +280 -273
  116. package/dist/tools/catalog/index.d.ts +4 -0
  117. package/dist/tools/catalog/index.js +4 -0
  118. package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
  119. package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
  120. package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
  121. package/dist/tools/definitions/toolDefs.d.ts +18 -0
  122. package/dist/tools/definitions/toolDefs.js +1177 -0
  123. package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
  124. package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
  125. package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
  126. package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
  127. package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
  128. package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
  129. package/dist/tools/diagnostics/index.d.ts +6 -0
  130. package/dist/tools/diagnostics/index.js +6 -0
  131. package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
  132. package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
  133. package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
  134. package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
  135. package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
  136. package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
  137. package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
  138. package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
  139. package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
  140. package/dist/tools/direct/finalizeDirectSession.js +86 -0
  141. package/dist/tools/direct/index.d.ts +3 -0
  142. package/dist/tools/direct/index.js +3 -0
  143. package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
  144. package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
  145. package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
  146. package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
  147. package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
  148. package/dist/tools/discovery/index.d.ts +3 -0
  149. package/dist/tools/discovery/index.js +3 -0
  150. package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
  151. package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
  152. package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
  153. package/dist/tools/dispatch/coreDispatch.js +283 -0
  154. package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
  155. package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
  156. package/dist/tools/dispatch/directDispatch.d.ts +8 -0
  157. package/dist/tools/dispatch/directDispatch.js +116 -0
  158. package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
  159. package/dist/tools/dispatch/goalDispatch.js +92 -0
  160. package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
  161. package/dist/tools/dispatch/releaseDispatch.js +46 -0
  162. package/dist/tools/dispatch/types.d.ts +23 -0
  163. package/dist/tools/dispatch/types.js +15 -0
  164. package/dist/tools/dispatch/validation.d.ts +7 -0
  165. package/dist/tools/dispatch/validation.js +71 -0
  166. package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
  167. package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
  168. package/dist/tools/goals/goalSubgoalTask.js +118 -0
  169. package/dist/tools/goals/index.d.ts +3 -0
  170. package/dist/tools/goals/index.js +3 -0
  171. package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
  172. package/dist/tools/registry.d.ts +10 -14
  173. package/dist/tools/registry.js +47 -1651
  174. package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
  175. package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
  176. package/dist/tools/release/index.d.ts +2 -0
  177. package/dist/tools/release/index.js +2 -0
  178. package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
  179. package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
  180. package/dist/tools/tasks/cancelTask.d.ts +11 -0
  181. package/dist/tools/tasks/cancelTask.js +85 -0
  182. package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
  183. package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
  184. package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
  185. package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
  186. package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
  187. package/dist/tools/tasks/evidencePack.js +387 -0
  188. package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
  189. package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
  190. package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
  191. package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
  192. package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
  193. package/dist/tools/tasks/index.d.ts +18 -0
  194. package/dist/tools/tasks/index.js +18 -0
  195. package/dist/tools/tasks/killTask.d.ts +1 -0
  196. package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
  197. package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
  198. package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
  199. package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
  200. package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
  201. package/dist/tools/tasks/retryTask.js +60 -0
  202. package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
  203. package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
  204. package/dist/tools/tasks/runVerification.js +38 -0
  205. package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
  206. package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
  207. package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
  208. package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
  209. package/dist/tools/workspace/applyPatch.js +53 -0
  210. package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
  211. package/dist/tools/workspace/index.d.ts +10 -0
  212. package/dist/tools/workspace/index.js +10 -0
  213. package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
  214. package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
  215. package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
  216. package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
  217. package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
  218. package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
  219. package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
  220. package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
  221. package/dist/tools/workspace/syncFile.js +134 -0
  222. package/dist/utils/atomicFile.d.ts +7 -0
  223. package/dist/utils/atomicFile.js +48 -0
  224. package/dist/utils/boundedFile.d.ts +8 -0
  225. package/dist/utils/boundedFile.js +93 -0
  226. package/dist/utils/lockedJsonFile.d.ts +15 -0
  227. package/dist/utils/lockedJsonFile.js +224 -0
  228. package/dist/utils/platform.d.ts +11 -0
  229. package/dist/utils/platform.js +26 -0
  230. package/dist/utils/stableJson.d.ts +2 -0
  231. package/dist/utils/stableJson.js +12 -0
  232. package/dist/version.d.ts +3 -2
  233. package/dist/version.js +3 -2
  234. package/dist/watcherStatus.d.ts +2 -2
  235. package/dist/watcherStatus.js +18 -0
  236. package/docs/CODE_WIKI.md +1648 -0
  237. package/docs/agentseal-integration.md +150 -0
  238. package/docs/architecture.md +23 -0
  239. package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
  240. package/docs/assets/patchwarden-oss-demo.gif +0 -0
  241. package/docs/control-center/README.md +4 -4
  242. package/docs/dashboard-overview.md +86 -0
  243. package/docs/demo.md +8 -0
  244. package/docs/desktop-app.md +175 -0
  245. package/docs/direct-session-workflow.md +98 -0
  246. package/docs/evidence-pack-schema.md +215 -0
  247. package/docs/lineage-evidence-pack-workflow.md +127 -0
  248. package/docs/mcp-inspector-testing.md +200 -0
  249. package/docs/open-source-application.md +168 -0
  250. package/docs/opencode-worker.md +151 -0
  251. package/docs/openhands-worker.md +181 -0
  252. package/docs/release-checklist.md +1 -1
  253. package/docs/release-evidence.md +82 -0
  254. package/docs/spec-kit-integration.md +131 -0
  255. package/docs/task-safe-review-workflow.md +98 -0
  256. package/docs/threat-model.md +97 -0
  257. package/docs/user-feedback.md +40 -0
  258. package/docs/why-patchwarden.md +110 -0
  259. package/examples/config.example.json +4 -2
  260. package/package.json +28 -11
  261. package/scripts/README.md +15 -6
  262. package/scripts/build.js +11 -0
  263. package/scripts/checks/brand-check.js +3 -0
  264. package/scripts/checks/build-output-check.js +37 -0
  265. package/scripts/checks/control-center-smoke.js +447 -6
  266. package/scripts/checks/control-smoke.js +19 -8
  267. package/scripts/checks/http-mcp-smoke.js +82 -4
  268. package/scripts/checks/lifecycle-smoke.js +39 -33
  269. package/scripts/checks/mcp-manifest-check.js +1 -1
  270. package/scripts/checks/mcp-smoke.js +30 -4
  271. package/scripts/checks/package-manifest-check.js +20 -7
  272. package/scripts/checks/unit-tests.js +3 -5
  273. package/scripts/checks/watcher-supervisor-smoke.js +9 -0
  274. package/scripts/control/get-patchwarden-health.ps1 +1 -1
  275. package/scripts/control/manage-patchwarden.ps1 +69 -4
  276. package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
  277. package/scripts/control/run-background-supervisor.ps1 +41 -0
  278. package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
  279. package/scripts/generate-demo-gif.py +320 -0
  280. package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
  281. package/scripts/launchers/PatchWarden-Control.cmd +6 -0
  282. package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
  283. package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
  284. package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
  285. package/scripts/lib/clean-generated-output.js +11 -0
  286. package/scripts/lib/file-discovery.js +12 -0
  287. package/scripts/release/desktop-preflight.js +211 -0
  288. package/scripts/release/pack-clean.js +59 -14
  289. package/src/assessments/agentAssessor.ts +29 -15
  290. package/src/assessments/assessmentStore.ts +55 -43
  291. package/src/assessments/confirmCli.ts +5 -4
  292. package/src/config.ts +105 -11
  293. package/src/control/fileManager.ts +40 -0
  294. package/src/control/middleware/auth.ts +35 -0
  295. package/src/control/middleware/static.ts +84 -0
  296. package/src/control/routeTable.ts +301 -0
  297. package/src/control/routes/audit.ts +329 -0
  298. package/src/control/routes/evidence.ts +107 -0
  299. package/src/control/routes/lineage.ts +92 -0
  300. package/src/control/routes/policy.ts +81 -0
  301. package/src/control/routes/process.ts +505 -0
  302. package/src/control/routes/sessions.ts +314 -0
  303. package/src/control/routes/status.ts +405 -0
  304. package/src/control/routes/taskActions.ts +214 -0
  305. package/src/control/routes/tasks.ts +331 -0
  306. package/src/control/routes/workspace.ts +234 -0
  307. package/src/control/runtime.ts +508 -0
  308. package/src/control/server.ts +165 -0
  309. package/src/control/shared.ts +360 -0
  310. package/src/controlCenter.ts +7 -2347
  311. package/src/direct/directAudit.ts +17 -9
  312. package/src/direct/directGuards.ts +100 -51
  313. package/src/direct/directPatch.ts +95 -8
  314. package/src/direct/directSessionStore.ts +211 -55
  315. package/src/direct/directVerification.ts +9 -23
  316. package/src/doctor.ts +766 -492
  317. package/src/goal/acceptanceEngine.ts +1 -1
  318. package/src/goal/goalProgress.ts +17 -13
  319. package/src/goal/goalReport.ts +263 -0
  320. package/src/goal/goalStatus.ts +25 -3
  321. package/src/goal/goalStore.ts +241 -54
  322. package/src/goal/handoffExport.ts +6 -6
  323. package/src/goal/specKitImport.ts +355 -0
  324. package/src/goal/subgoalSync.ts +15 -10
  325. package/src/goal/worktreeManager.ts +139 -22
  326. package/src/httpServer.ts +130 -44
  327. package/src/index.ts +7 -4
  328. package/src/logging.ts +20 -4
  329. package/src/policy/projectPolicy.ts +5 -2
  330. package/src/release/releaseGate.ts +74 -27
  331. package/src/runner/agentInvocation.ts +126 -4
  332. package/src/runner/changeCapture.ts +342 -80
  333. package/src/runner/cli.ts +7 -6
  334. package/src/runner/postTaskCleanup.ts +55 -7
  335. package/src/runner/processSecurity.ts +433 -0
  336. package/src/runner/runTask.ts +441 -292
  337. package/src/runner/simpleProcess.ts +68 -33
  338. package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
  339. package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
  340. package/src/runner/taskStatusStore.ts +54 -0
  341. package/src/runner/watch.ts +290 -57
  342. package/src/security/contentRedaction.ts +31 -2
  343. package/src/security/discoveryTokenStore.ts +18 -4
  344. package/src/security/loopbackHost.ts +19 -0
  345. package/src/security/pathGuard.ts +35 -16
  346. package/src/security/planGuard.ts +2 -1
  347. package/src/security/runtimeGuard.ts +9 -6
  348. package/src/security/secretComparison.ts +13 -0
  349. package/src/security/sensitiveGuard.ts +27 -8
  350. package/src/security/toolInvocationGuard.ts +2 -2
  351. package/src/security/workspaceRootGuard.ts +53 -0
  352. package/src/smoke-test.ts +276 -273
  353. package/src/tools/catalog/index.ts +4 -0
  354. package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
  355. package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
  356. package/src/tools/definitions/toolDefs.ts +1264 -0
  357. package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
  358. package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
  359. package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
  360. package/src/tools/diagnostics/index.ts +6 -0
  361. package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
  362. package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
  363. package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
  364. package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
  365. package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
  366. package/src/tools/direct/index.ts +3 -0
  367. package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
  368. package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
  369. package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
  370. package/src/tools/discovery/index.ts +3 -0
  371. package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
  372. package/src/tools/dispatch/coreDispatch.ts +379 -0
  373. package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
  374. package/src/tools/dispatch/directDispatch.ts +168 -0
  375. package/src/tools/dispatch/goalDispatch.ts +128 -0
  376. package/src/tools/dispatch/releaseDispatch.ts +66 -0
  377. package/src/tools/dispatch/types.ts +24 -0
  378. package/src/tools/dispatch/validation.ts +78 -0
  379. package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
  380. package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
  381. package/src/tools/goals/index.ts +3 -0
  382. package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
  383. package/src/tools/registry.ts +54 -1858
  384. package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
  385. package/src/tools/release/index.ts +2 -0
  386. package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
  387. package/src/tools/tasks/cancelTask.ts +109 -0
  388. package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
  389. package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
  390. package/src/tools/tasks/evidencePack.ts +504 -0
  391. package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
  392. package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
  393. package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
  394. package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
  395. package/src/tools/tasks/index.ts +18 -0
  396. package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
  397. package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
  398. package/src/tools/tasks/retryTask.ts +69 -0
  399. package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
  400. package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
  401. package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
  402. package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
  403. package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
  404. package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
  405. package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
  406. package/src/tools/workspace/index.ts +10 -0
  407. package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
  408. package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
  409. package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
  410. package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
  411. package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
  412. package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
  413. package/src/tools/workspace/syncFile.ts +292 -0
  414. package/src/utils/atomicFile.ts +58 -0
  415. package/src/utils/boundedFile.ts +116 -0
  416. package/src/utils/lockedJsonFile.ts +251 -0
  417. package/src/utils/platform.ts +31 -0
  418. package/src/utils/stableJson.ts +12 -0
  419. package/src/version.ts +3 -2
  420. package/src/watcherStatus.ts +19 -1
  421. package/ui/desktop-bootstrap.js +11 -0
  422. package/ui/desktop-bridge.js +36 -0
  423. package/ui/desktop.css +160 -0
  424. package/ui/getting-started.js +93 -0
  425. package/ui/i18n.js +484 -0
  426. package/ui/log-parser.js +30 -0
  427. package/ui/pages/audit.html +204 -3
  428. package/ui/pages/dashboard.html +937 -79
  429. package/ui/pages/direct-sessions.html +609 -60
  430. package/ui/pages/getting-started.html +43 -0
  431. package/ui/pages/logs.html +105 -22
  432. package/ui/pages/settings.html +101 -0
  433. package/ui/pages/task-detail.html +468 -438
  434. package/ui/pages/tasks.html +648 -65
  435. package/ui/pages/workspace.html +38 -1
  436. package/ui/settings.js +245 -0
  437. package/PatchWarden-Control.cmd +0 -6
  438. package/Restart-PatchWarden-Control.cmd +0 -6
  439. package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
  440. package/dist/test/unit/acceptance-engine.test.js +0 -228
  441. package/dist/test/unit/agent-router.test.d.ts +0 -1
  442. package/dist/test/unit/agent-router.test.js +0 -287
  443. package/dist/test/unit/android-doctor.test.d.ts +0 -1
  444. package/dist/test/unit/android-doctor.test.js +0 -118
  445. package/dist/test/unit/audit-checks.test.d.ts +0 -1
  446. package/dist/test/unit/audit-checks.test.js +0 -350
  447. package/dist/test/unit/chinese-path.test.d.ts +0 -1
  448. package/dist/test/unit/chinese-path.test.js +0 -91
  449. package/dist/test/unit/command-guard.test.d.ts +0 -1
  450. package/dist/test/unit/command-guard.test.js +0 -160
  451. package/dist/test/unit/diagnose-task.test.d.ts +0 -1
  452. package/dist/test/unit/diagnose-task.test.js +0 -457
  453. package/dist/test/unit/direct-guards.test.d.ts +0 -1
  454. package/dist/test/unit/direct-guards.test.js +0 -213
  455. package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
  456. package/dist/test/unit/discovery-token-store.test.js +0 -139
  457. package/dist/test/unit/evidence-pack.test.d.ts +0 -1
  458. package/dist/test/unit/evidence-pack.test.js +0 -130
  459. package/dist/test/unit/goal-graph.test.d.ts +0 -1
  460. package/dist/test/unit/goal-graph.test.js +0 -298
  461. package/dist/test/unit/goal-progress.test.d.ts +0 -1
  462. package/dist/test/unit/goal-progress.test.js +0 -381
  463. package/dist/test/unit/goal-status.test.d.ts +0 -1
  464. package/dist/test/unit/goal-status.test.js +0 -215
  465. package/dist/test/unit/goal-store.test.d.ts +0 -1
  466. package/dist/test/unit/goal-store.test.js +0 -253
  467. package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
  468. package/dist/test/unit/goal-subgoal-task.test.js +0 -55
  469. package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
  470. package/dist/test/unit/goal-tools-registry.test.js +0 -190
  471. package/dist/test/unit/handoff-export.test.d.ts +0 -1
  472. package/dist/test/unit/handoff-export.test.js +0 -263
  473. package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
  474. package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
  475. package/dist/test/unit/logging.test.d.ts +0 -1
  476. package/dist/test/unit/logging.test.js +0 -397
  477. package/dist/test/unit/path-guard.test.d.ts +0 -1
  478. package/dist/test/unit/path-guard.test.js +0 -109
  479. package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
  480. package/dist/test/unit/post-task-cleanup.test.js +0 -48
  481. package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
  482. package/dist/test/unit/project-policy-release-mode.test.js +0 -125
  483. package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
  484. package/dist/test/unit/reconcile-tasks.test.js +0 -456
  485. package/dist/test/unit/release-gate.test.d.ts +0 -1
  486. package/dist/test/unit/release-gate.test.js +0 -242
  487. package/dist/test/unit/run-task-loop.test.d.ts +0 -1
  488. package/dist/test/unit/run-task-loop.test.js +0 -380
  489. package/dist/test/unit/safe-status.test.d.ts +0 -1
  490. package/dist/test/unit/safe-status.test.js +0 -165
  491. package/dist/test/unit/safe-views.test.d.ts +0 -1
  492. package/dist/test/unit/safe-views.test.js +0 -171
  493. package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
  494. package/dist/test/unit/schema-drift-check.test.js +0 -176
  495. package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
  496. package/dist/test/unit/sensitive-guard.test.js +0 -104
  497. package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
  498. package/dist/test/unit/subgoal-sync.test.js +0 -183
  499. package/dist/test/unit/sync-file.test.d.ts +0 -1
  500. package/dist/test/unit/sync-file.test.js +0 -154
  501. package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
  502. package/dist/test/unit/tool-invocation-guard.test.js +0 -432
  503. package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
  504. package/dist/test/unit/tool-usage-stats.test.js +0 -300
  505. package/dist/test/unit/toolSearch.test.d.ts +0 -1
  506. package/dist/test/unit/toolSearch.test.js +0 -571
  507. package/dist/test/unit/watcher-status.test.d.ts +0 -1
  508. package/dist/test/unit/watcher-status.test.js +0 -169
  509. package/dist/test/unit/worktree-manager.test.d.ts +0 -1
  510. package/dist/test/unit/worktree-manager.test.js +0 -176
  511. package/dist/tools/applyPatch.js +0 -41
  512. package/dist/tools/cancelTask.d.ts +0 -30
  513. package/dist/tools/cancelTask.js +0 -64
  514. package/dist/tools/evidencePack.js +0 -168
  515. package/dist/tools/finalizeDirectSession.js +0 -84
  516. package/dist/tools/goalSubgoalTask.js +0 -110
  517. package/dist/tools/killTask.d.ts +0 -15
  518. package/dist/tools/retryTask.js +0 -45
  519. package/dist/tools/runVerification.js +0 -36
  520. package/dist/tools/syncFile.js +0 -65
  521. package/docs/control-center/control-center-daily-driver.md +0 -211
  522. package/docs/control-center/control-center-mvp.md +0 -205
  523. package/docs/control-center/control-center-phase2.md +0 -159
  524. package/docs/release-v0.6.0.md +0 -71
  525. package/docs/release-v0.6.1.md +0 -75
  526. package/docs/release-v0.6.4.md +0 -45
  527. package/src/test/unit/acceptance-engine.test.ts +0 -261
  528. package/src/test/unit/agent-router.test.ts +0 -342
  529. package/src/test/unit/android-doctor.test.ts +0 -158
  530. package/src/test/unit/audit-checks.test.ts +0 -567
  531. package/src/test/unit/chinese-path.test.ts +0 -106
  532. package/src/test/unit/command-guard.test.ts +0 -221
  533. package/src/test/unit/diagnose-task.test.ts +0 -544
  534. package/src/test/unit/direct-guards.test.ts +0 -297
  535. package/src/test/unit/discovery-token-store.test.ts +0 -181
  536. package/src/test/unit/evidence-pack.test.ts +0 -142
  537. package/src/test/unit/goal-graph.test.ts +0 -347
  538. package/src/test/unit/goal-progress.test.ts +0 -538
  539. package/src/test/unit/goal-status.test.ts +0 -270
  540. package/src/test/unit/goal-store.test.ts +0 -318
  541. package/src/test/unit/goal-subgoal-task.test.ts +0 -72
  542. package/src/test/unit/goal-tools-registry.test.ts +0 -243
  543. package/src/test/unit/handoff-export.test.ts +0 -295
  544. package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
  545. package/src/test/unit/logging.test.ts +0 -497
  546. package/src/test/unit/path-guard.test.ts +0 -150
  547. package/src/test/unit/post-task-cleanup.test.ts +0 -53
  548. package/src/test/unit/project-policy-release-mode.test.ts +0 -156
  549. package/src/test/unit/reconcile-tasks.test.ts +0 -551
  550. package/src/test/unit/release-gate.test.ts +0 -314
  551. package/src/test/unit/run-task-loop.test.ts +0 -425
  552. package/src/test/unit/safe-status.test.ts +0 -187
  553. package/src/test/unit/safe-views.test.ts +0 -184
  554. package/src/test/unit/schema-drift-check.test.ts +0 -260
  555. package/src/test/unit/sensitive-guard.test.ts +0 -124
  556. package/src/test/unit/subgoal-sync.test.ts +0 -236
  557. package/src/test/unit/sync-file.test.ts +0 -231
  558. package/src/test/unit/tool-invocation-guard.test.ts +0 -542
  559. package/src/test/unit/tool-usage-stats.test.ts +0 -384
  560. package/src/test/unit/toolSearch.test.ts +0 -756
  561. package/src/test/unit/watcher-status.test.ts +0 -190
  562. package/src/test/unit/worktree-manager.test.ts +0 -247
  563. package/src/tools/cancelTask.ts +0 -70
  564. package/src/tools/evidencePack.ts +0 -205
  565. package/src/tools/retryTask.ts +0 -53
  566. package/src/tools/syncFile.ts +0 -122
  567. /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
  568. /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
  569. /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
  570. /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
  571. /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
  572. /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
  573. /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
  574. /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
  575. /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
  576. /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
  577. /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
  578. /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
  579. /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
  580. /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
  581. /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
  582. /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
  583. /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
  584. /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
  585. /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
  586. /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
  587. /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
  588. /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
  589. /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
  590. /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
  591. /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
  592. /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
  593. /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
  594. /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
  595. /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
  596. /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
  597. /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
@@ -18,37 +18,53 @@
18
18
  * 所有文件系统函数都接受可选的 workspaceRoot 参数用于测试;
19
19
  * 默认从 getConfig().workspaceRoot 读取。
20
20
  */
21
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, rmSync, } from "node:fs";
22
- import { join } from "node:path";
21
+ import { existsSync, mkdirSync, readFileSync, rmSync, } from "node:fs";
22
+ import { join, normalize } from "node:path";
23
23
  import { execFileSync } from "node:child_process";
24
24
  import { randomBytes } from "node:crypto";
25
25
  import { getConfig } from "../config.js";
26
- import { guardWorkspacePath } from "../security/pathGuard.js";
26
+ import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
27
27
  import { guardSensitivePath } from "../security/sensitiveGuard.js";
28
28
  import { PatchWardenError } from "../errors.js";
29
+ import { buildGitEnvironment, resolveTrustedExecutable } from "../runner/processSecurity.js";
30
+ import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
31
+ import { withFileLockSync } from "../utils/lockedJsonFile.js";
29
32
  // ── 常量 ──────────────────────────────────────────────────────────
30
33
  /** worktree 根目录名,放在 workspaceRoot 下。 */
31
34
  export const WorktreeDir = "_workspacetrees";
32
35
  /** 归档目录名(discard 后保留状态),位于 .patchwarden 下,始终为安全路径。 */
33
36
  const WORKTREE_ARCHIVE_DIR = ".patchwarden/worktree-archive";
37
+ const WORKTREE_LOCK_DIR = ".patchwarden/worktree-locks";
34
38
  const GIT_TIMEOUT_MS = 30000;
35
39
  const GIT_BRANCH_TIMEOUT_MS = 15000;
40
+ const WORKTREE_ID_MAX_LENGTH = 32;
41
+ const WORKTREE_ID_PATTERN = /^wt_[a-z0-9]{1,16}_[a-f0-9]{12}$/;
36
42
  // ── 辅助:解析 workspaceRoot ──────────────────────────────────────
37
43
  function resolveWorkspaceRoot(workspaceRoot) {
38
44
  return workspaceRoot ?? getConfig().workspaceRoot;
39
45
  }
46
+ /** Accept only IDs emitted by generateWorktreeId(). */
47
+ export function assertValidWorktreeId(worktreeId) {
48
+ if (worktreeId.length > WORKTREE_ID_MAX_LENGTH ||
49
+ !WORKTREE_ID_PATTERN.test(worktreeId)) {
50
+ throw new PatchWardenError("invalid_worktree_id", "Invalid worktree id. Expected the wt_<base36 timestamp>_<12 hex chars> format.", "Use the worktree_id returned by create_subgoal_task.", true, { worktree_id_length: worktreeId.length });
51
+ }
52
+ }
40
53
  // ── 目录路径解析 ──────────────────────────────────────────────────
41
54
  /**
42
55
  * 返回 `<workspaceRoot>/_workspacetrees` 目录路径。不自动创建目录。
43
56
  */
44
57
  export function getWorktreesDir(workspaceRoot) {
45
- return join(resolveWorkspaceRoot(workspaceRoot), WorktreeDir);
58
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
59
+ return guardPath(join(wsRoot, WorktreeDir), wsRoot, WorktreeDir);
46
60
  }
47
61
  /**
48
62
  * 返回 `<getWorktreesDir()>/<worktreeId>` 路径。
49
63
  */
50
64
  export function getWorktreeDir(worktreeId, workspaceRoot) {
51
- return join(getWorktreesDir(workspaceRoot), worktreeId);
65
+ assertValidWorktreeId(worktreeId);
66
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
67
+ return guardPath(join(getWorktreesDir(wsRoot), worktreeId), wsRoot, WorktreeDir);
52
68
  }
53
69
  // ── ID 与 branch 生成 ─────────────────────────────────────────────
54
70
  /**
@@ -70,21 +86,76 @@ function sanitizeBranchSegment(value) {
70
86
  const cleaned = value.replace(/[^a-zA-Z0-9_-]/g, "_");
71
87
  return cleaned === "" ? "x" : cleaned;
72
88
  }
89
+ function samePath(left, right) {
90
+ const normalizedLeft = normalize(left);
91
+ const normalizedRight = normalize(right);
92
+ return process.platform === "win32"
93
+ ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
94
+ : normalizedLeft === normalizedRight;
95
+ }
96
+ function withWorktreeRepositoryLock(workspaceRoot, action) {
97
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
98
+ const lockDir = guardPath(join(wsRoot, WORKTREE_LOCK_DIR), wsRoot, WORKTREE_LOCK_DIR);
99
+ mkdirSync(lockDir, { recursive: true });
100
+ return withFileLockSync(join(lockDir, "repository-mutation"), action, {
101
+ waitMs: 0,
102
+ busyError: () => new PatchWardenError("worktree_busy", "Another managed worktree operation is already changing this repository.", "Retry after the active create, merge, or discard operation finishes.", true),
103
+ });
104
+ }
105
+ function invalidWorktreeStatus(worktreeId, message) {
106
+ throw new PatchWardenError("invalid_worktree_status", `Invalid worktree status for "${worktreeId}": ${message}`, "Do not edit worktree_status.json manually; recreate the isolated worktree.", true, { worktree_id: worktreeId });
107
+ }
108
+ function validateWorktreeStatus(value, worktreeId, workspaceRoot, expectedPath) {
109
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
110
+ return invalidWorktreeStatus(worktreeId, "expected a JSON object");
111
+ }
112
+ const candidate = value;
113
+ const statusValues = new Set(["active", "merged", "discarded"]);
114
+ if (typeof candidate.worktree_id !== "string" ||
115
+ typeof candidate.goal_id !== "string" ||
116
+ typeof candidate.subgoal_id !== "string" ||
117
+ typeof candidate.path !== "string" ||
118
+ typeof candidate.created_at !== "string" ||
119
+ typeof candidate.status !== "string" ||
120
+ !statusValues.has(candidate.status) ||
121
+ typeof candidate.branch !== "string") {
122
+ return invalidWorktreeStatus(worktreeId, "missing or invalid required fields");
123
+ }
124
+ if (candidate.worktree_id !== worktreeId) {
125
+ return invalidWorktreeStatus(worktreeId, "worktree_id does not match its directory");
126
+ }
127
+ let guardedStatusPath;
128
+ try {
129
+ guardedStatusPath = guardPath(candidate.path, workspaceRoot, WorktreeDir);
130
+ }
131
+ catch {
132
+ return invalidWorktreeStatus(worktreeId, "path is outside the managed worktree root");
133
+ }
134
+ if (!samePath(guardedStatusPath, expectedPath)) {
135
+ return invalidWorktreeStatus(worktreeId, "path does not match its directory");
136
+ }
137
+ const expectedBranch = `pw-${sanitizeBranchSegment(candidate.goal_id)}-${sanitizeBranchSegment(candidate.subgoal_id)}`;
138
+ if (candidate.branch !== expectedBranch) {
139
+ return invalidWorktreeStatus(worktreeId, "branch does not match goal and subgoal metadata");
140
+ }
141
+ if ((candidate.merged_at !== undefined && typeof candidate.merged_at !== "string") ||
142
+ (candidate.discarded_at !== undefined && typeof candidate.discarded_at !== "string")) {
143
+ return invalidWorktreeStatus(worktreeId, "invalid lifecycle timestamp");
144
+ }
145
+ return candidate;
146
+ }
73
147
  // ── 原子写 ────────────────────────────────────────────────────────
74
- /**
75
- * 原子写入 status 文件:先写到 `.tmp` 文件,再 renameSync。
76
- * 参考 goalStore.ts writeGoalStatus 的模式。
77
- */
78
148
  function writeStatusAtomic(statusFilePath, status) {
79
- const tmpPath = statusFilePath + ".tmp";
80
- writeFileSync(tmpPath, JSON.stringify(status, null, 2) + "\n", "utf-8");
81
- renameSync(tmpPath, statusFilePath);
149
+ atomicWriteJsonFileSync(statusFilePath, status);
82
150
  }
83
151
  function runGit(args, cwd, timeoutMs) {
84
- execFileSync("git", args, {
152
+ const env = buildGitEnvironment(cwd);
153
+ execFileSync(resolveTrustedExecutable("git", cwd, { pathValue: env.PATH }), args, {
85
154
  cwd,
86
155
  timeout: timeoutMs,
87
156
  stdio: ["pipe", "pipe", "pipe"],
157
+ windowsHide: true,
158
+ env,
88
159
  });
89
160
  }
90
161
  function gitErrorMessage(err) {
@@ -114,62 +185,64 @@ function gitErrorMessage(err) {
114
185
  * @returns { worktreeId, worktreePath, branch }
115
186
  */
116
187
  export function createWorktree(goalId, subgoalId, workspaceRoot) {
117
- const wsRoot = resolveWorkspaceRoot(workspaceRoot);
118
- const worktreeId = generateWorktreeId();
119
- const worktreePath = getWorktreeDir(worktreeId, workspaceRoot);
120
- const branch = `pw-${sanitizeBranchSegment(goalId)}-${sanitizeBranchSegment(subgoalId)}`;
121
- // 安全:校验 worktreePath 在 workspaceRoot 内且非敏感路径
122
- guardWorkspacePath(worktreePath, wsRoot);
123
- guardSensitivePath(worktreePath);
124
- let worktreeCreated = false;
125
- let branchCreated = false;
126
- try {
127
- runGit(["worktree", "add", "-b", branch, worktreePath], wsRoot, GIT_TIMEOUT_MS);
128
- worktreeCreated = true;
129
- branchCreated = true;
130
- // 原子写入 worktree_status.json(worktreePath 是新目录,无旧 status,直接 tmp + rename)
131
- const statusFilePath = join(worktreePath, "worktree_status.json");
132
- const now = new Date().toISOString();
133
- const status = {
134
- worktree_id: worktreeId,
135
- goal_id: goalId,
136
- subgoal_id: subgoalId,
137
- path: worktreePath,
138
- created_at: now,
139
- status: "active",
140
- branch,
141
- };
142
- writeStatusAtomic(statusFilePath, status);
143
- return { worktreeId, worktreePath, branch };
144
- }
145
- catch (err) {
146
- // 清理半成品
147
- if (worktreeCreated) {
148
- try {
149
- rmSync(worktreePath, { recursive: true, force: true });
150
- }
151
- catch { /* ignore */ }
152
- // rmSync 可能无法清除 git 的 worktree 元数据,再尝试 git worktree remove
153
- try {
154
- runGit(["worktree", "remove", "--force", worktreePath], wsRoot, GIT_BRANCH_TIMEOUT_MS);
155
- }
156
- catch { /* ignore */ }
188
+ return withWorktreeRepositoryLock(workspaceRoot, () => {
189
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
190
+ const worktreeId = generateWorktreeId();
191
+ const worktreePath = getWorktreeDir(worktreeId, workspaceRoot);
192
+ const branch = `pw-${sanitizeBranchSegment(goalId)}-${sanitizeBranchSegment(subgoalId)}`;
193
+ // 安全:校验 worktreePath 在 workspaceRoot 内且非敏感路径
194
+ guardWorkspacePath(worktreePath, wsRoot);
195
+ guardSensitivePath(worktreePath);
196
+ let worktreeCreated = false;
197
+ let branchCreated = false;
198
+ try {
199
+ runGit(["worktree", "add", "-b", branch, worktreePath], wsRoot, GIT_TIMEOUT_MS);
200
+ worktreeCreated = true;
201
+ branchCreated = true;
202
+ // 原子写入 worktree_status.json(worktreePath 是新目录,无旧 status,直接 tmp + rename)
203
+ const statusFilePath = join(worktreePath, "worktree_status.json");
204
+ const now = new Date().toISOString();
205
+ const status = {
206
+ worktree_id: worktreeId,
207
+ goal_id: goalId,
208
+ subgoal_id: subgoalId,
209
+ path: worktreePath,
210
+ created_at: now,
211
+ status: "active",
212
+ branch,
213
+ };
214
+ writeStatusAtomic(statusFilePath, status);
215
+ return { worktreeId, worktreePath, branch };
157
216
  }
158
- if (branchCreated) {
159
- try {
160
- runGit(["branch", "-D", branch], wsRoot, GIT_BRANCH_TIMEOUT_MS);
217
+ catch (err) {
218
+ // 清理半成品
219
+ if (worktreeCreated) {
220
+ try {
221
+ rmSync(worktreePath, { recursive: true, force: true });
222
+ }
223
+ catch { /* ignore */ }
224
+ // rmSync 可能无法清除 git 的 worktree 元数据,再尝试 git worktree remove
225
+ try {
226
+ runGit(["worktree", "remove", "--force", worktreePath], wsRoot, GIT_BRANCH_TIMEOUT_MS);
227
+ }
228
+ catch { /* ignore */ }
161
229
  }
162
- catch { /* ignore — branch 可能未创建或已随 worktree remove 清理 */ }
230
+ if (branchCreated) {
231
+ try {
232
+ runGit(["branch", "-D", branch], wsRoot, GIT_BRANCH_TIMEOUT_MS);
233
+ }
234
+ catch { /* ignore — branch 可能未创建或已随 worktree remove 清理 */ }
235
+ }
236
+ if (err instanceof PatchWardenError)
237
+ throw err;
238
+ throw new PatchWardenError("worktree_create_failed", `Failed to create worktree for goal "${goalId}" / subgoal "${subgoalId}": ${gitErrorMessage(err)}`, "Ensure workspaceRoot is a git repository with at least one commit, and the worktree path is writable.", true, {
239
+ goal_id: goalId,
240
+ subgoal_id: subgoalId,
241
+ branch,
242
+ worktree_path: worktreePath,
243
+ });
163
244
  }
164
- if (err instanceof PatchWardenError)
165
- throw err;
166
- throw new PatchWardenError("worktree_create_failed", `Failed to create worktree for goal "${goalId}" / subgoal "${subgoalId}": ${gitErrorMessage(err)}`, "Ensure workspaceRoot is a git repository with at least one commit, and the worktree path is writable.", true, {
167
- goal_id: goalId,
168
- subgoal_id: subgoalId,
169
- branch,
170
- worktree_path: worktreePath,
171
- });
172
- }
245
+ });
173
246
  }
174
247
  /**
175
248
  * 读取 worktree_status.json。不存在返回 null。
@@ -183,13 +256,14 @@ export function readWorktreeStatus(worktreeId, workspaceRoot) {
183
256
  const statusFilePath = join(worktreePath, "worktree_status.json");
184
257
  if (!existsSync(statusFilePath))
185
258
  return null;
259
+ let parsed;
186
260
  try {
187
- const raw = readFileSync(statusFilePath, "utf-8");
188
- return JSON.parse(raw);
261
+ parsed = JSON.parse(readFileSync(statusFilePath, "utf-8"));
189
262
  }
190
263
  catch {
191
- return null;
264
+ return invalidWorktreeStatus(worktreeId, "file is not valid JSON");
192
265
  }
266
+ return validateWorktreeStatus(parsed, worktreeId, wsRoot, worktreePath);
193
267
  }
194
268
  /**
195
269
  * 将 worktree 的 branch 合并回主工作区(workspaceRoot)。
@@ -203,32 +277,34 @@ export function readWorktreeStatus(worktreeId, workspaceRoot) {
203
277
  * (保留供人工排查冲突)。
204
278
  */
205
279
  export function mergeWorktree(worktreeId, workspaceRoot) {
206
- const wsRoot = resolveWorkspaceRoot(workspaceRoot);
207
- const worktreePath = getWorktreeDir(worktreeId, workspaceRoot);
208
- guardWorkspacePath(worktreePath, wsRoot);
209
- guardSensitivePath(worktreePath);
210
- const status = readWorktreeStatus(worktreeId, workspaceRoot);
211
- if (!status) {
212
- throw new PatchWardenError("worktree_not_found", `Worktree "${worktreeId}" not found or has no worktree_status.json`, "Ensure the worktree id was created via createWorktree before merging.", true, { worktree_id: worktreeId });
213
- }
214
- if (status.status !== "active") {
215
- throw new PatchWardenError("invalid_worktree_state", `Worktree "${worktreeId}" is not active (current status: "${status.status}")`, "Only active worktrees can be merged.", true, { worktree_id: worktreeId, current_status: status.status });
216
- }
217
- try {
218
- runGit(["merge", status.branch], wsRoot, GIT_TIMEOUT_MS);
219
- }
220
- catch (err) {
221
- // 合并失败:不删 worktree,保留供人工排查
222
- throw new PatchWardenError("worktree_merge_failed", `Failed to merge worktree branch "${status.branch}" into workspace: ${gitErrorMessage(err)}`, "Resolve merge conflicts manually in the main workspace, then retry or discard the worktree.", true, { worktree_id: worktreeId, branch: status.branch });
223
- }
224
- const updatedStatus = {
225
- ...status,
226
- status: "merged",
227
- merged_at: new Date().toISOString(),
228
- };
229
- const statusFilePath = join(worktreePath, "worktree_status.json");
230
- writeStatusAtomic(statusFilePath, updatedStatus);
231
- return { status: "merged" };
280
+ return withWorktreeRepositoryLock(workspaceRoot, () => {
281
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
282
+ const worktreePath = getWorktreeDir(worktreeId, workspaceRoot);
283
+ guardWorkspacePath(worktreePath, wsRoot);
284
+ guardSensitivePath(worktreePath);
285
+ const status = readWorktreeStatus(worktreeId, workspaceRoot);
286
+ if (!status) {
287
+ throw new PatchWardenError("worktree_not_found", `Worktree "${worktreeId}" not found or has no worktree_status.json`, "Ensure the worktree id was created via createWorktree before merging.", true, { worktree_id: worktreeId });
288
+ }
289
+ if (status.status !== "active") {
290
+ throw new PatchWardenError("invalid_worktree_state", `Worktree "${worktreeId}" is not active (current status: "${status.status}")`, "Only active worktrees can be merged.", true, { worktree_id: worktreeId, current_status: status.status });
291
+ }
292
+ try {
293
+ runGit(["merge", status.branch], wsRoot, GIT_TIMEOUT_MS);
294
+ }
295
+ catch (err) {
296
+ // 合并失败:不删 worktree,保留供人工排查
297
+ throw new PatchWardenError("worktree_merge_failed", `Failed to merge worktree branch "${status.branch}" into workspace: ${gitErrorMessage(err)}`, "Resolve merge conflicts manually in the main workspace, then retry or discard the worktree.", true, { worktree_id: worktreeId, branch: status.branch });
298
+ }
299
+ const updatedStatus = {
300
+ ...status,
301
+ status: "merged",
302
+ merged_at: new Date().toISOString(),
303
+ };
304
+ const statusFilePath = join(worktreePath, "worktree_status.json");
305
+ writeStatusAtomic(statusFilePath, updatedStatus);
306
+ return { status: "merged" };
307
+ });
232
308
  }
233
309
  /**
234
310
  * 丢弃 worktree:移除 worktree 目录与临时 branch,归档最终状态。
@@ -245,48 +321,45 @@ export function mergeWorktree(worktreeId, workspaceRoot) {
245
321
  * 移除失败抛 PatchWardenError("worktree_discard_failed")。
246
322
  */
247
323
  export function discardWorktree(worktreeId, workspaceRoot) {
248
- const wsRoot = resolveWorkspaceRoot(workspaceRoot);
249
- const worktreePath = getWorktreeDir(worktreeId, workspaceRoot);
250
- guardWorkspacePath(worktreePath, wsRoot);
251
- guardSensitivePath(worktreePath);
252
- // 在 remove 之前读取 status(status 文件位于 worktree 目录内)
253
- const status = readWorktreeStatus(worktreeId, workspaceRoot);
254
- if (!status) {
255
- throw new PatchWardenError("worktree_not_found", `Worktree "${worktreeId}" not found or has no worktree_status.json`, "Ensure the worktree id was created via createWorktree before discarding.", true, { worktree_id: worktreeId });
256
- }
257
- if (status.status !== "active") {
258
- throw new PatchWardenError("invalid_worktree_state", `Worktree "${worktreeId}" is not active (current status: "${status.status}")`, "Only active worktrees can be discarded.", true, { worktree_id: worktreeId, current_status: status.status });
259
- }
260
- try {
261
- runGit(["worktree", "remove", "--force", worktreePath], wsRoot, GIT_TIMEOUT_MS);
262
- }
263
- catch (err) {
264
- // git remove 失败时再尝试直接删目录(best effort),然后抛错
324
+ return withWorktreeRepositoryLock(workspaceRoot, () => {
325
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
326
+ const worktreePath = getWorktreeDir(worktreeId, workspaceRoot);
327
+ guardWorkspacePath(worktreePath, wsRoot);
328
+ guardSensitivePath(worktreePath);
329
+ // 在 remove 之前读取 status(status 文件位于 worktree 目录内)
330
+ const status = readWorktreeStatus(worktreeId, workspaceRoot);
331
+ if (!status) {
332
+ throw new PatchWardenError("worktree_not_found", `Worktree "${worktreeId}" not found or has no worktree_status.json`, "Ensure the worktree id was created via createWorktree before discarding.", true, { worktree_id: worktreeId });
333
+ }
334
+ if (status.status !== "active") {
335
+ throw new PatchWardenError("invalid_worktree_state", `Worktree "${worktreeId}" is not active (current status: "${status.status}")`, "Only active worktrees can be discarded.", true, { worktree_id: worktreeId, current_status: status.status });
336
+ }
265
337
  try {
266
- rmSync(worktreePath, { recursive: true, force: true });
338
+ runGit(["worktree", "remove", "--force", worktreePath], wsRoot, GIT_TIMEOUT_MS);
339
+ }
340
+ catch (err) {
341
+ throw new PatchWardenError("worktree_discard_failed", `Failed to remove worktree "${worktreeId}": ${gitErrorMessage(err)}`, "Remove the worktree directory manually and run `git worktree prune`.", true, { worktree_id: worktreeId, worktree_path: worktreePath });
342
+ }
343
+ // 删除临时 branch(best effort — 可能已随 worktree remove 清理)
344
+ try {
345
+ runGit(["branch", "-D", status.branch], wsRoot, GIT_BRANCH_TIMEOUT_MS);
267
346
  }
268
347
  catch { /* ignore */ }
269
- throw new PatchWardenError("worktree_discard_failed", `Failed to remove worktree "${worktreeId}": ${gitErrorMessage(err)}`, "Remove the worktree directory manually and run `git worktree prune`.", true, { worktree_id: worktreeId, worktree_path: worktreePath });
270
- }
271
- // 删除临时 branch(best effort — 可能已随 worktree remove 清理)
272
- try {
273
- runGit(["branch", "-D", status.branch], wsRoot, GIT_BRANCH_TIMEOUT_MS);
274
- }
275
- catch { /* ignore */ }
276
- // 归档最终状态到 .patchwarden/worktree-archive/(worktree 目录可能已消失)
277
- const archivedStatus = {
278
- ...status,
279
- status: "discarded",
280
- discarded_at: new Date().toISOString(),
281
- };
282
- const archiveDir = join(wsRoot, WORKTREE_ARCHIVE_DIR);
283
- guardWorkspacePath(archiveDir, wsRoot);
284
- // .patchwarden 路径在 sensitiveGuard 中始终视为安全(SAFE_PREFIX),无需额外校验
285
- try {
286
- mkdirSync(archiveDir, { recursive: true });
287
- }
288
- catch { /* ignore */ }
289
- const archiveFilePath = join(archiveDir, `${worktreeId}.json`);
290
- writeStatusAtomic(archiveFilePath, archivedStatus);
291
- return { status: "discarded" };
348
+ // 归档最终状态到 .patchwarden/worktree-archive/(worktree 目录可能已消失)
349
+ const archivedStatus = {
350
+ ...status,
351
+ status: "discarded",
352
+ discarded_at: new Date().toISOString(),
353
+ };
354
+ const archiveDir = join(wsRoot, WORKTREE_ARCHIVE_DIR);
355
+ guardWorkspacePath(archiveDir, wsRoot);
356
+ // The archive path is workspace-confined and contains only managed status metadata.
357
+ try {
358
+ mkdirSync(archiveDir, { recursive: true });
359
+ }
360
+ catch { /* ignore */ }
361
+ const archiveFilePath = join(archiveDir, `${worktreeId}.json`);
362
+ writeStatusAtomic(archiveFilePath, archivedStatus);
363
+ return { status: "discarded" };
364
+ });
292
365
  }