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
@@ -0,0 +1,355 @@
1
+ /**
2
+ * v0.8.0: Spec Kit tasks 导入 — 将 Spec Kit 的 spec/tasks 结构转换为 Goal Session 子目标。
3
+ *
4
+ * - 解析 Spec Kit JSON(parseSpecKitJson)
5
+ * - 幂等导入:按 external_ref 去重,重复导入不产生重复 subgoal
6
+ * - 依赖解析:将 task.depends_on(external_ref 引用)解析为本地 subgoal id
7
+ * - 验收标准映射:input.acceptance → GoalStatus.acceptance_criteria
8
+ *
9
+ * 不引入第三方依赖,仅用 Node.js 内置模块。
10
+ */
11
+
12
+ import { PatchWardenError } from "../errors.js";
13
+ import { addSubgoal, type GoalStatus } from "./goalStatus.js";
14
+ import { mutateGoalStatus } from "./goalStore.js";
15
+
16
+ // ── 类型定义 ──────────────────────────────────────────────────────
17
+
18
+ export interface SpecKitTask {
19
+ id: string;
20
+ desc: string;
21
+ files?: string[];
22
+ /** 引用其他 task 的 id(external_ref) */
23
+ depends_on?: string[];
24
+ }
25
+
26
+ export interface SpecKitInput {
27
+ spec: string;
28
+ tasks: SpecKitTask[];
29
+ acceptance?: string[];
30
+ }
31
+
32
+ export interface SpecKitImportResult {
33
+ goal_id: string;
34
+ spec_name: string;
35
+ created_count: number;
36
+ existing_count: number;
37
+ subgoal_ids: string[];
38
+ }
39
+
40
+ // ── JSON 解析 ─────────────────────────────────────────────────────
41
+
42
+ /**
43
+ * 解析 Spec Kit JSON 文本并验证结构。
44
+ *
45
+ * 必须包含:
46
+ * - spec: 非空字符串
47
+ * - tasks: 数组,每个元素含 id(字符串)和 desc(字符串)
48
+ *
49
+ * 可选:
50
+ * - acceptance: 字符串数组
51
+ *
52
+ * 解析失败或结构不合法时抛出 PatchWardenError("invalid_spec_kit_json" | "invalid_spec_kit_input")。
53
+ */
54
+ export function parseSpecKitJson(jsonText: string): SpecKitInput {
55
+ let parsed: unknown;
56
+ try {
57
+ parsed = JSON.parse(jsonText);
58
+ } catch (e) {
59
+ throw new PatchWardenError(
60
+ "invalid_spec_kit_json",
61
+ `Failed to parse Spec Kit JSON: ${e instanceof Error ? e.message : String(e)}`,
62
+ "Ensure the input is valid JSON.",
63
+ true,
64
+ {}
65
+ );
66
+ }
67
+
68
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
69
+ throw new PatchWardenError(
70
+ "invalid_spec_kit_input",
71
+ "Spec Kit JSON must be a JSON object",
72
+ 'Ensure the top-level value is an object with "spec" and "tasks" fields.',
73
+ true,
74
+ { actual_type: Array.isArray(parsed) ? "array" : typeof parsed }
75
+ );
76
+ }
77
+
78
+ const obj = parsed as Record<string, unknown>;
79
+
80
+ if (typeof obj.spec !== "string" || obj.spec === "") {
81
+ throw new PatchWardenError(
82
+ "invalid_spec_kit_input",
83
+ 'Spec Kit JSON must contain a non-empty "spec" string field',
84
+ 'Add a "spec" field with a non-empty string value describing the specification name.',
85
+ true,
86
+ { spec: obj.spec }
87
+ );
88
+ }
89
+
90
+ if (!Array.isArray(obj.tasks)) {
91
+ throw new PatchWardenError(
92
+ "invalid_spec_kit_input",
93
+ 'Spec Kit JSON must contain a "tasks" array field',
94
+ 'Add a "tasks" field with an array of task objects (each with "id" and "desc").',
95
+ true,
96
+ { tasks_type: Array.isArray(obj.tasks) ? "array" : typeof obj.tasks }
97
+ );
98
+ }
99
+
100
+ const tasks: SpecKitTask[] = [];
101
+ for (let i = 0; i < obj.tasks.length; i++) {
102
+ const raw = obj.tasks[i];
103
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
104
+ throw new PatchWardenError(
105
+ "invalid_spec_kit_input",
106
+ `Spec Kit task at index ${i} must be an object`,
107
+ "Each task must be an object with at least \"id\" and \"desc\" string fields.",
108
+ true,
109
+ { task_index: i, actual_type: typeof raw }
110
+ );
111
+ }
112
+ const t = raw as Record<string, unknown>;
113
+ if (typeof t.id !== "string" || t.id === "") {
114
+ throw new PatchWardenError(
115
+ "invalid_spec_kit_input",
116
+ `Spec Kit task at index ${i} must have a non-empty "id" string`,
117
+ "Ensure each task has a unique non-empty string \"id\".",
118
+ true,
119
+ { task_index: i, id: t.id }
120
+ );
121
+ }
122
+ if (typeof t.desc !== "string") {
123
+ throw new PatchWardenError(
124
+ "invalid_spec_kit_input",
125
+ `Spec Kit task at index ${i} (id="${t.id}") must have a "desc" string`,
126
+ "Ensure each task has a \"desc\" string describing the task.",
127
+ true,
128
+ { task_index: i, id: t.id, desc_type: typeof t.desc }
129
+ );
130
+ }
131
+
132
+ const task: SpecKitTask = { id: t.id, desc: t.desc };
133
+ if (Array.isArray(t.files)) {
134
+ task.files = t.files.filter((f): f is string => typeof f === "string");
135
+ }
136
+ if (Array.isArray(t.depends_on)) {
137
+ task.depends_on = t.depends_on.filter(
138
+ (d): d is string => typeof d === "string"
139
+ );
140
+ }
141
+ tasks.push(task);
142
+ }
143
+
144
+ const result: SpecKitInput = { spec: obj.spec, tasks };
145
+ if (Array.isArray(obj.acceptance)) {
146
+ result.acceptance = obj.acceptance.filter(
147
+ (a): a is string => typeof a === "string"
148
+ );
149
+ }
150
+
151
+ return result;
152
+ }
153
+
154
+ // ── 导入实现 ──────────────────────────────────────────────────────
155
+
156
+ /**
157
+ * 将 Spec Kit tasks 导入到指定 Goal Session,创建对应的 subgoal。
158
+ *
159
+ * 行为:
160
+ * - 验证 input(spec 非空、tasks 为数组)
161
+ * - 幂等去重:按 external_ref(task.id)匹配现有 subgoal,已存在则跳过
162
+ * - 依赖解析:将 task.depends_on(external_ref)解析为本地 subgoal id
163
+ * - 先创建无依赖的 subgoal,再处理有依赖的,支持多级依赖
164
+ * - 依赖的 task 在同一批次中:用刚创建的 subgoal_id
165
+ * - 依赖的 task 不存在:忽略该依赖并记录 warning(console.warn)
166
+ * - 验收标准:input.acceptance 非空时写入 GoalStatus.acceptance_criteria
167
+ * - 持久化:用 writeGoalStatus 原子写入
168
+ *
169
+ * @param goalId 目标 Goal 标识
170
+ * @param input Spec Kit 输入(spec + tasks + acceptance)
171
+ * @param options 可选配置:workspaceRoot 用于测试
172
+ * @returns SpecKitImportResult
173
+ */
174
+ export function importSpecKitTasks(
175
+ goalId: string,
176
+ input: SpecKitInput,
177
+ options?: { workspaceRoot?: string }
178
+ ): SpecKitImportResult {
179
+ // ── 验证 ───────────────────────────────────────────────────
180
+ if (!input || typeof input !== "object") {
181
+ throw new PatchWardenError(
182
+ "invalid_spec_kit_input",
183
+ "Spec Kit input must be an object",
184
+ "Pass a SpecKitInput object with spec, tasks, and optional acceptance fields.",
185
+ true,
186
+ {}
187
+ );
188
+ }
189
+ if (typeof input.spec !== "string" || input.spec === "") {
190
+ throw new PatchWardenError(
191
+ "invalid_spec_kit_input",
192
+ 'Spec Kit input must contain a non-empty "spec" string',
193
+ "Ensure input.spec is a non-empty string.",
194
+ true,
195
+ { spec: input.spec }
196
+ );
197
+ }
198
+ if (!Array.isArray(input.tasks)) {
199
+ throw new PatchWardenError(
200
+ "invalid_spec_kit_input",
201
+ 'Spec Kit input must contain a "tasks" array',
202
+ "Ensure input.tasks is an array of SpecKitTask objects.",
203
+ true,
204
+ { tasks_type: Array.isArray(input.tasks) ? "array" : typeof input.tasks }
205
+ );
206
+ }
207
+
208
+ const workspaceRoot = options?.workspaceRoot;
209
+
210
+ return mutateGoalStatus(goalId, (currentGoalStatus) => {
211
+ let goalStatus: GoalStatus = currentGoalStatus;
212
+
213
+ // ── 构建 external_ref → subgoal_id 映射(现有 subgoal)─────
214
+ const refToSubgoalId = new Map<string, string>();
215
+ for (const sg of goalStatus.subgoals) {
216
+ if (sg.external_ref) {
217
+ refToSubgoalId.set(sg.external_ref, sg.id);
218
+ }
219
+ }
220
+
221
+ // ── 幂等去重:区分新 task 和已存在的 task ─────────────────
222
+ const newTasks: SpecKitTask[] = [];
223
+ let existingCount = 0;
224
+ for (const task of input.tasks) {
225
+ if (refToSubgoalId.has(task.id)) {
226
+ existingCount++;
227
+ } else {
228
+ newTasks.push(task);
229
+ }
230
+ }
231
+
232
+ // 新批次中所有 task id 集合(用于判断依赖是否在同批次)
233
+ const newTaskIds = new Set(newTasks.map((t) => t.id));
234
+
235
+ // ── 按依赖顺序创建 subgoal(拓扑排序,迭代多轮)────────────
236
+ const createdIds: string[] = [];
237
+ let remaining = [...newTasks];
238
+
239
+ // 辅助:创建单个 subgoal 并设置 external_ref / scope_hints
240
+ const createSubgoalForTask = (
241
+ gs: GoalStatus,
242
+ task: SpecKitTask,
243
+ resolvedDeps: string[]
244
+ ): { goalStatus: GoalStatus; subgoalId: string } => {
245
+ const { goalStatus: newGs, subgoalId } = addSubgoal(gs, task.desc, resolvedDeps);
246
+
247
+ // 新 subgoal 是数组最后一个元素,追加 external_ref 和 scope_hints
248
+ const subgoals = [...newGs.subgoals];
249
+ const lastIdx = subgoals.length - 1;
250
+ const updated: typeof subgoals[number] = {
251
+ ...subgoals[lastIdx],
252
+ external_ref: task.id,
253
+ };
254
+ if (task.files && task.files.length > 0) {
255
+ updated.scope_hints = [...task.files];
256
+ }
257
+ subgoals[lastIdx] = updated;
258
+
259
+ return { goalStatus: { ...newGs, subgoals }, subgoalId };
260
+ };
261
+
262
+ while (remaining.length > 0) {
263
+ const stillRemaining: SpecKitTask[] = [];
264
+ let progress = false;
265
+
266
+ // 正向遍历:保持输入顺序,先创建的 task 可被同批次后续 task 依赖
267
+ for (const task of remaining) {
268
+ const deps = task.depends_on ?? [];
269
+
270
+ const resolvedDeps: string[] = [];
271
+ let hasUnresolvedInBatch = false;
272
+
273
+ for (const dep of deps) {
274
+ if (refToSubgoalId.has(dep)) {
275
+ // 依赖已存在(已有 subgoal 或本批次已创建)
276
+ resolvedDeps.push(refToSubgoalId.get(dep)!);
277
+ } else if (newTaskIds.has(dep)) {
278
+ // 依赖同批次中尚未创建的 task
279
+ hasUnresolvedInBatch = true;
280
+ } else {
281
+ // 依赖的 task 不存在任何地方:忽略并记录 warning
282
+ console.warn(
283
+ `[specKitImport] Task "${task.id}" depends on non-existent task "${dep}"; ignoring this dependency.`
284
+ );
285
+ }
286
+ }
287
+
288
+ if (!hasUnresolvedInBatch) {
289
+ const { goalStatus: newGs, subgoalId } = createSubgoalForTask(
290
+ goalStatus,
291
+ task,
292
+ resolvedDeps
293
+ );
294
+ goalStatus = newGs;
295
+ refToSubgoalId.set(task.id, subgoalId);
296
+ createdIds.push(subgoalId);
297
+ progress = true;
298
+ } else {
299
+ stillRemaining.push(task);
300
+ }
301
+ }
302
+
303
+ remaining = stillRemaining;
304
+
305
+ if (!progress && remaining.length > 0) {
306
+ // 检测到循环依赖:强制创建剩余 task,忽略未解析的同批次依赖
307
+ for (const task of remaining) {
308
+ const deps = task.depends_on ?? [];
309
+ const resolvedDeps: string[] = [];
310
+ for (const dep of deps) {
311
+ if (refToSubgoalId.has(dep)) {
312
+ resolvedDeps.push(refToSubgoalId.get(dep)!);
313
+ } else if (newTaskIds.has(dep)) {
314
+ console.warn(
315
+ `[specKitImport] Circular dependency detected: task "${task.id}" depends on "${dep}" which could not be resolved; ignoring this dependency.`
316
+ );
317
+ } else {
318
+ console.warn(
319
+ `[specKitImport] Task "${task.id}" depends on non-existent task "${dep}"; ignoring this dependency.`
320
+ );
321
+ }
322
+ }
323
+ const { goalStatus: newGs, subgoalId } = createSubgoalForTask(
324
+ goalStatus,
325
+ task,
326
+ resolvedDeps
327
+ );
328
+ goalStatus = newGs;
329
+ refToSubgoalId.set(task.id, subgoalId);
330
+ createdIds.push(subgoalId);
331
+ }
332
+ break;
333
+ }
334
+ }
335
+
336
+ // ── 验收标准 ───────────────────────────────────────────────
337
+ if (input.acceptance && input.acceptance.length > 0) {
338
+ goalStatus = {
339
+ ...goalStatus,
340
+ acceptance_criteria: [...input.acceptance],
341
+ };
342
+ }
343
+
344
+ return {
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
+ },
353
+ };
354
+ }, workspaceRoot);
355
+ }
@@ -9,8 +9,9 @@
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
+ import { logger } from "../logging.js";
14
15
 
15
16
  // ── 同步函数 ──────────────────────────────────────────────────────
16
17
 
@@ -38,17 +39,21 @@ export function syncSubgoalOnTaskDone(
38
39
  if (!goalId || !subgoalId) return;
39
40
 
40
41
  try {
41
- const goalStatus = readGoalStatus(goalId, workspaceRoot);
42
- const subgoal = goalStatus.subgoals.find((s) => s.id === subgoalId);
43
- if (!subgoal) return;
44
- if (subgoal.status !== "running") return;
45
-
46
- const updated = updateSubgoalStatus(goalStatus, subgoalId, "done_by_agent");
47
- 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);
48
52
  } catch (err) {
49
53
  // subgoal 同步失败不应影响任务完成流程,只记录到 stderr
50
- console.error(
51
- `[goal] syncSubgoalOnTaskDone failed for task ${taskId}: ${err instanceof Error ? err.message : String(err)}`
54
+ logger.error(
55
+ `[goal] syncSubgoalOnTaskDone failed for task ${taskId}`,
56
+ { error: err instanceof Error ? err.message : String(err) },
52
57
  );
53
58
  }
54
59
  }
@@ -23,17 +23,18 @@ import {
23
23
  existsSync,
24
24
  mkdirSync,
25
25
  readFileSync,
26
- renameSync,
27
- writeFileSync,
28
26
  rmSync,
29
27
  } from "node:fs";
30
- import { join } from "node:path";
28
+ import { join, normalize } from "node:path";
31
29
  import { execFileSync } from "node:child_process";
32
30
  import { randomBytes } from "node:crypto";
33
31
  import { getConfig } from "../config.js";
34
- import { guardWorkspacePath } from "../security/pathGuard.js";
32
+ import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
35
33
  import { guardSensitivePath } from "../security/sensitiveGuard.js";
36
34
  import { PatchWardenError } from "../errors.js";
35
+ import { buildGitEnvironment, resolveTrustedExecutable } from "../runner/processSecurity.js";
36
+ import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
37
+ import { withFileLockSync } from "../utils/lockedJsonFile.js";
37
38
 
38
39
  // ── 类型定义 ──────────────────────────────────────────────────────
39
40
 
@@ -56,9 +57,12 @@ export const WorktreeDir = "_workspacetrees";
56
57
 
57
58
  /** 归档目录名(discard 后保留状态),位于 .patchwarden 下,始终为安全路径。 */
58
59
  const WORKTREE_ARCHIVE_DIR = ".patchwarden/worktree-archive";
60
+ const WORKTREE_LOCK_DIR = ".patchwarden/worktree-locks";
59
61
 
60
62
  const GIT_TIMEOUT_MS = 30000;
61
63
  const GIT_BRANCH_TIMEOUT_MS = 15000;
64
+ const WORKTREE_ID_MAX_LENGTH = 32;
65
+ const WORKTREE_ID_PATTERN = /^wt_[a-z0-9]{1,16}_[a-f0-9]{12}$/;
62
66
 
63
67
  // ── 辅助:解析 workspaceRoot ──────────────────────────────────────
64
68
 
@@ -66,20 +70,43 @@ function resolveWorkspaceRoot(workspaceRoot?: string): string {
66
70
  return workspaceRoot ?? getConfig().workspaceRoot;
67
71
  }
68
72
 
73
+ /** Accept only IDs emitted by generateWorktreeId(). */
74
+ export function assertValidWorktreeId(worktreeId: string): void {
75
+ if (
76
+ worktreeId.length > WORKTREE_ID_MAX_LENGTH ||
77
+ !WORKTREE_ID_PATTERN.test(worktreeId)
78
+ ) {
79
+ throw new PatchWardenError(
80
+ "invalid_worktree_id",
81
+ "Invalid worktree id. Expected the wt_<base36 timestamp>_<12 hex chars> format.",
82
+ "Use the worktree_id returned by create_subgoal_task.",
83
+ true,
84
+ { worktree_id_length: worktreeId.length }
85
+ );
86
+ }
87
+ }
88
+
69
89
  // ── 目录路径解析 ──────────────────────────────────────────────────
70
90
 
71
91
  /**
72
92
  * 返回 `<workspaceRoot>/_workspacetrees` 目录路径。不自动创建目录。
73
93
  */
74
94
  export function getWorktreesDir(workspaceRoot?: string): string {
75
- return join(resolveWorkspaceRoot(workspaceRoot), WorktreeDir);
95
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
96
+ return guardPath(join(wsRoot, WorktreeDir), wsRoot, WorktreeDir);
76
97
  }
77
98
 
78
99
  /**
79
100
  * 返回 `<getWorktreesDir()>/<worktreeId>` 路径。
80
101
  */
81
102
  export function getWorktreeDir(worktreeId: string, workspaceRoot?: string): string {
82
- return join(getWorktreesDir(workspaceRoot), worktreeId);
103
+ assertValidWorktreeId(worktreeId);
104
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
105
+ return guardPath(
106
+ join(getWorktreesDir(wsRoot), worktreeId),
107
+ wsRoot,
108
+ WorktreeDir
109
+ );
83
110
  }
84
111
 
85
112
  // ── ID 与 branch 生成 ─────────────────────────────────────────────
@@ -105,23 +132,110 @@ function sanitizeBranchSegment(value: string): string {
105
132
  return cleaned === "" ? "x" : cleaned;
106
133
  }
107
134
 
135
+ function samePath(left: string, right: string): boolean {
136
+ const normalizedLeft = normalize(left);
137
+ const normalizedRight = normalize(right);
138
+ return process.platform === "win32"
139
+ ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
140
+ : normalizedLeft === normalizedRight;
141
+ }
142
+
143
+ function withWorktreeRepositoryLock<R>(workspaceRoot: string, action: () => R): R {
144
+ const wsRoot = resolveWorkspaceRoot(workspaceRoot);
145
+ const lockDir = guardPath(join(wsRoot, WORKTREE_LOCK_DIR), wsRoot, WORKTREE_LOCK_DIR);
146
+ mkdirSync(lockDir, { recursive: true });
147
+ return withFileLockSync(join(lockDir, "repository-mutation"), action, {
148
+ waitMs: 0,
149
+ busyError: () => new PatchWardenError(
150
+ "worktree_busy",
151
+ "Another managed worktree operation is already changing this repository.",
152
+ "Retry after the active create, merge, or discard operation finishes.",
153
+ true,
154
+ ),
155
+ });
156
+ }
157
+
158
+ function invalidWorktreeStatus(
159
+ worktreeId: string,
160
+ message: string
161
+ ): never {
162
+ throw new PatchWardenError(
163
+ "invalid_worktree_status",
164
+ `Invalid worktree status for "${worktreeId}": ${message}`,
165
+ "Do not edit worktree_status.json manually; recreate the isolated worktree.",
166
+ true,
167
+ { worktree_id: worktreeId }
168
+ );
169
+ }
170
+
171
+ function validateWorktreeStatus(
172
+ value: unknown,
173
+ worktreeId: string,
174
+ workspaceRoot: string,
175
+ expectedPath: string
176
+ ): WorktreeStatus {
177
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
178
+ return invalidWorktreeStatus(worktreeId, "expected a JSON object");
179
+ }
180
+
181
+ const candidate = value as Record<string, unknown>;
182
+ const statusValues = new Set(["active", "merged", "discarded"]);
183
+ if (
184
+ typeof candidate.worktree_id !== "string" ||
185
+ typeof candidate.goal_id !== "string" ||
186
+ typeof candidate.subgoal_id !== "string" ||
187
+ typeof candidate.path !== "string" ||
188
+ typeof candidate.created_at !== "string" ||
189
+ typeof candidate.status !== "string" ||
190
+ !statusValues.has(candidate.status) ||
191
+ typeof candidate.branch !== "string"
192
+ ) {
193
+ return invalidWorktreeStatus(worktreeId, "missing or invalid required fields");
194
+ }
195
+
196
+ if (candidate.worktree_id !== worktreeId) {
197
+ return invalidWorktreeStatus(worktreeId, "worktree_id does not match its directory");
198
+ }
199
+
200
+ let guardedStatusPath: string;
201
+ try {
202
+ guardedStatusPath = guardPath(candidate.path, workspaceRoot, WorktreeDir);
203
+ } catch {
204
+ return invalidWorktreeStatus(worktreeId, "path is outside the managed worktree root");
205
+ }
206
+ if (!samePath(guardedStatusPath, expectedPath)) {
207
+ return invalidWorktreeStatus(worktreeId, "path does not match its directory");
208
+ }
209
+
210
+ const expectedBranch = `pw-${sanitizeBranchSegment(candidate.goal_id)}-${sanitizeBranchSegment(candidate.subgoal_id)}`;
211
+ if (candidate.branch !== expectedBranch) {
212
+ return invalidWorktreeStatus(worktreeId, "branch does not match goal and subgoal metadata");
213
+ }
214
+
215
+ if (
216
+ (candidate.merged_at !== undefined && typeof candidate.merged_at !== "string") ||
217
+ (candidate.discarded_at !== undefined && typeof candidate.discarded_at !== "string")
218
+ ) {
219
+ return invalidWorktreeStatus(worktreeId, "invalid lifecycle timestamp");
220
+ }
221
+
222
+ return candidate as unknown as WorktreeStatus;
223
+ }
224
+
108
225
  // ── 原子写 ────────────────────────────────────────────────────────
109
226
 
110
- /**
111
- * 原子写入 status 文件:先写到 `.tmp` 文件,再 renameSync。
112
- * 参考 goalStore.ts writeGoalStatus 的模式。
113
- */
114
227
  function writeStatusAtomic(statusFilePath: string, status: WorktreeStatus): void {
115
- const tmpPath = statusFilePath + ".tmp";
116
- writeFileSync(tmpPath, JSON.stringify(status, null, 2) + "\n", "utf-8");
117
- renameSync(tmpPath, statusFilePath);
228
+ atomicWriteJsonFileSync(statusFilePath, status);
118
229
  }
119
230
 
120
231
  function runGit(args: string[], cwd: string, timeoutMs: number): void {
121
- execFileSync("git", args, {
232
+ const env = buildGitEnvironment(cwd);
233
+ execFileSync(resolveTrustedExecutable("git", cwd, { pathValue: env.PATH }), args, {
122
234
  cwd,
123
235
  timeout: timeoutMs,
124
236
  stdio: ["pipe", "pipe", "pipe"],
237
+ windowsHide: true,
238
+ env,
125
239
  });
126
240
  }
127
241
 
@@ -157,6 +271,7 @@ export function createWorktree(
157
271
  subgoalId: string,
158
272
  workspaceRoot: string
159
273
  ): { worktreeId: string; worktreePath: string; branch: string } {
274
+ return withWorktreeRepositoryLock(workspaceRoot, () => {
160
275
  const wsRoot = resolveWorkspaceRoot(workspaceRoot);
161
276
 
162
277
  const worktreeId = generateWorktreeId();
@@ -222,6 +337,7 @@ export function createWorktree(
222
337
  }
223
338
  );
224
339
  }
340
+ });
225
341
  }
226
342
 
227
343
  /**
@@ -241,12 +357,13 @@ export function readWorktreeStatus(
241
357
  const statusFilePath = join(worktreePath, "worktree_status.json");
242
358
  if (!existsSync(statusFilePath)) return null;
243
359
 
360
+ let parsed: unknown;
244
361
  try {
245
- const raw = readFileSync(statusFilePath, "utf-8");
246
- return JSON.parse(raw) as WorktreeStatus;
362
+ parsed = JSON.parse(readFileSync(statusFilePath, "utf-8"));
247
363
  } catch {
248
- return null;
364
+ return invalidWorktreeStatus(worktreeId, "file is not valid JSON");
249
365
  }
366
+ return validateWorktreeStatus(parsed, worktreeId, wsRoot, worktreePath);
250
367
  }
251
368
 
252
369
  /**
@@ -264,6 +381,7 @@ export function mergeWorktree(
264
381
  worktreeId: string,
265
382
  workspaceRoot: string
266
383
  ): { status: "merged" } {
384
+ return withWorktreeRepositoryLock(workspaceRoot, () => {
267
385
  const wsRoot = resolveWorkspaceRoot(workspaceRoot);
268
386
  const worktreePath = getWorktreeDir(worktreeId, workspaceRoot);
269
387
 
@@ -313,6 +431,7 @@ export function mergeWorktree(
313
431
  writeStatusAtomic(statusFilePath, updatedStatus);
314
432
 
315
433
  return { status: "merged" };
434
+ });
316
435
  }
317
436
 
318
437
  /**
@@ -333,6 +452,7 @@ export function discardWorktree(
333
452
  worktreeId: string,
334
453
  workspaceRoot: string
335
454
  ): { status: "discarded" } {
455
+ return withWorktreeRepositoryLock(workspaceRoot, () => {
336
456
  const wsRoot = resolveWorkspaceRoot(workspaceRoot);
337
457
  const worktreePath = getWorktreeDir(worktreeId, workspaceRoot);
338
458
 
@@ -364,10 +484,6 @@ export function discardWorktree(
364
484
  try {
365
485
  runGit(["worktree", "remove", "--force", worktreePath], wsRoot, GIT_TIMEOUT_MS);
366
486
  } catch (err) {
367
- // git remove 失败时再尝试直接删目录(best effort),然后抛错
368
- try {
369
- rmSync(worktreePath, { recursive: true, force: true });
370
- } catch { /* ignore */ }
371
487
  throw new PatchWardenError(
372
488
  "worktree_discard_failed",
373
489
  `Failed to remove worktree "${worktreeId}": ${gitErrorMessage(err)}`,
@@ -391,7 +507,7 @@ export function discardWorktree(
391
507
 
392
508
  const archiveDir = join(wsRoot, WORKTREE_ARCHIVE_DIR);
393
509
  guardWorkspacePath(archiveDir, wsRoot);
394
- // .patchwarden 路径在 sensitiveGuard 中始终视为安全(SAFE_PREFIX),无需额外校验
510
+ // The archive path is workspace-confined and contains only managed status metadata.
395
511
 
396
512
  try {
397
513
  mkdirSync(archiveDir, { recursive: true });
@@ -401,4 +517,5 @@ export function discardWorktree(
401
517
  writeStatusAtomic(archiveFilePath, archivedStatus);
402
518
 
403
519
  return { status: "discarded" };
520
+ });
404
521
  }