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.
- package/PatchWarden.cmd +32 -2
- package/README.en.md +194 -26
- package/README.md +160 -22
- package/dist/assessments/agentAssessor.d.ts +1 -1
- package/dist/assessments/agentAssessor.js +24 -15
- package/dist/assessments/assessmentStore.d.ts +1 -1
- package/dist/assessments/assessmentStore.js +44 -36
- package/dist/assessments/confirmCli.js +5 -4
- package/dist/config.d.ts +22 -0
- package/dist/config.js +98 -11
- package/dist/control/fileManager.d.ts +2 -0
- package/dist/control/fileManager.js +39 -0
- package/dist/control/middleware/auth.d.ts +16 -0
- package/dist/control/middleware/auth.js +17 -0
- package/dist/control/middleware/static.d.ts +3 -0
- package/dist/control/middleware/static.js +78 -0
- package/dist/control/routeTable.d.ts +24 -0
- package/dist/control/routeTable.js +263 -0
- package/dist/control/routes/audit.d.ts +15 -0
- package/dist/control/routes/audit.js +287 -0
- package/dist/control/routes/evidence.d.ts +4 -0
- package/dist/control/routes/evidence.js +96 -0
- package/dist/control/routes/lineage.d.ts +3 -0
- package/dist/control/routes/lineage.js +71 -0
- package/dist/control/routes/policy.d.ts +3 -0
- package/dist/control/routes/policy.js +81 -0
- package/dist/control/routes/process.d.ts +12 -0
- package/dist/control/routes/process.js +487 -0
- package/dist/control/routes/sessions.d.ts +30 -0
- package/dist/control/routes/sessions.js +287 -0
- package/dist/control/routes/status.d.ts +51 -0
- package/dist/control/routes/status.js +307 -0
- package/dist/control/routes/taskActions.d.ts +21 -0
- package/dist/control/routes/taskActions.js +197 -0
- package/dist/control/routes/tasks.d.ts +24 -0
- package/dist/control/routes/tasks.js +309 -0
- package/dist/control/routes/workspace.d.ts +15 -0
- package/dist/control/routes/workspace.js +220 -0
- package/dist/control/runtime.d.ts +92 -0
- package/dist/control/runtime.js +425 -0
- package/dist/control/server.d.ts +13 -0
- package/dist/control/server.js +150 -0
- package/dist/control/shared.d.ts +59 -0
- package/dist/control/shared.js +341 -0
- package/dist/controlCenter.d.ts +6 -0
- package/dist/controlCenter.js +7 -2197
- package/dist/direct/directAudit.js +234 -225
- package/dist/direct/directGuards.d.ts +2 -1
- package/dist/direct/directGuards.js +72 -37
- package/dist/direct/directPatch.d.ts +6 -1
- package/dist/direct/directPatch.js +55 -8
- package/dist/direct/directSessionStore.d.ts +16 -6
- package/dist/direct/directSessionStore.js +127 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.d.ts +18 -1
- package/dist/doctor.js +614 -373
- package/dist/goal/acceptanceEngine.d.ts +1 -1
- package/dist/goal/goalProgress.js +75 -69
- package/dist/goal/goalReport.d.ts +54 -0
- package/dist/goal/goalReport.js +197 -0
- package/dist/goal/goalStatus.d.ts +8 -0
- package/dist/goal/goalStatus.js +10 -3
- package/dist/goal/goalStore.d.ts +9 -2
- package/dist/goal/goalStore.js +152 -45
- package/dist/goal/handoffExport.js +6 -6
- package/dist/goal/specKitImport.d.ts +63 -0
- package/dist/goal/specKitImport.js +221 -0
- package/dist/goal/subgoalSync.js +13 -10
- package/dist/goal/worktreeManager.d.ts +2 -0
- package/dist/goal/worktreeManager.js +209 -136
- package/dist/httpServer.js +126 -44
- package/dist/index.js +7 -4
- package/dist/logging.d.ts +7 -1
- package/dist/logging.js +13 -3
- package/dist/policy/projectPolicy.js +5 -2
- package/dist/release/releaseGate.d.ts +1 -1
- package/dist/release/releaseGate.js +78 -25
- package/dist/runner/agentInvocation.d.ts +14 -0
- package/dist/runner/agentInvocation.js +96 -4
- package/dist/runner/changeCapture.d.ts +14 -6
- package/dist/runner/changeCapture.js +287 -81
- package/dist/runner/cli.js +7 -6
- package/dist/runner/postTaskCleanup.js +60 -7
- package/dist/runner/processSecurity.d.ts +56 -0
- package/dist/runner/processSecurity.js +373 -0
- package/dist/runner/runTask.d.ts +1 -1
- package/dist/runner/runTask.js +339 -287
- package/dist/runner/simpleProcess.d.ts +3 -0
- package/dist/runner/simpleProcess.js +52 -38
- package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
- package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
- package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
- package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
- package/dist/runner/taskStatusStore.d.ts +11 -0
- package/dist/runner/taskStatusStore.js +24 -0
- package/dist/runner/watch.d.ts +14 -1
- package/dist/runner/watch.js +265 -54
- package/dist/security/contentRedaction.d.ts +6 -0
- package/dist/security/contentRedaction.js +24 -2
- package/dist/security/discoveryTokenStore.d.ts +3 -2
- package/dist/security/discoveryTokenStore.js +17 -2
- package/dist/security/loopbackHost.d.ts +2 -0
- package/dist/security/loopbackHost.js +16 -0
- package/dist/security/pathGuard.js +32 -17
- package/dist/security/planGuard.js +2 -1
- package/dist/security/runtimeGuard.js +9 -6
- package/dist/security/secretComparison.d.ts +3 -0
- package/dist/security/secretComparison.js +11 -0
- package/dist/security/sensitiveGuard.d.ts +5 -0
- package/dist/security/sensitiveGuard.js +27 -8
- package/dist/security/toolInvocationGuard.d.ts +2 -2
- package/dist/security/toolInvocationGuard.js +1 -1
- package/dist/security/workspaceRootGuard.d.ts +8 -0
- package/dist/security/workspaceRootGuard.js +47 -0
- package/dist/smoke-test.js +280 -273
- package/dist/tools/catalog/index.d.ts +4 -0
- package/dist/tools/catalog/index.js +4 -0
- package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
- package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
- package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
- package/dist/tools/definitions/toolDefs.d.ts +18 -0
- package/dist/tools/definitions/toolDefs.js +1177 -0
- package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
- package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
- package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
- package/dist/tools/diagnostics/index.d.ts +6 -0
- package/dist/tools/diagnostics/index.js +6 -0
- package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
- package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
- package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
- package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
- package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
- package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
- package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
- package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
- package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
- package/dist/tools/direct/finalizeDirectSession.js +86 -0
- package/dist/tools/direct/index.d.ts +3 -0
- package/dist/tools/direct/index.js +3 -0
- package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
- package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
- package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
- package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
- package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
- package/dist/tools/discovery/index.d.ts +3 -0
- package/dist/tools/discovery/index.js +3 -0
- package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
- package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
- package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
- package/dist/tools/dispatch/coreDispatch.js +283 -0
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
- package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
- package/dist/tools/dispatch/directDispatch.d.ts +8 -0
- package/dist/tools/dispatch/directDispatch.js +116 -0
- package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
- package/dist/tools/dispatch/goalDispatch.js +92 -0
- package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
- package/dist/tools/dispatch/releaseDispatch.js +46 -0
- package/dist/tools/dispatch/types.d.ts +23 -0
- package/dist/tools/dispatch/types.js +15 -0
- package/dist/tools/dispatch/validation.d.ts +7 -0
- package/dist/tools/dispatch/validation.js +71 -0
- package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
- package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
- package/dist/tools/goals/goalSubgoalTask.js +118 -0
- package/dist/tools/goals/index.d.ts +3 -0
- package/dist/tools/goals/index.js +3 -0
- package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
- package/dist/tools/registry.d.ts +10 -14
- package/dist/tools/registry.js +47 -1651
- package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
- package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
- package/dist/tools/release/index.d.ts +2 -0
- package/dist/tools/release/index.js +2 -0
- package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
- package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
- package/dist/tools/tasks/cancelTask.d.ts +11 -0
- package/dist/tools/tasks/cancelTask.js +85 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
- package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
- package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
- package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
- package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
- package/dist/tools/tasks/evidencePack.js +387 -0
- package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
- package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
- package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
- package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
- package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
- package/dist/tools/tasks/index.d.ts +18 -0
- package/dist/tools/tasks/index.js +18 -0
- package/dist/tools/tasks/killTask.d.ts +1 -0
- package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
- package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
- package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
- package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
- package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
- package/dist/tools/tasks/retryTask.js +60 -0
- package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
- package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
- package/dist/tools/tasks/runVerification.js +38 -0
- package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
- package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
- package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
- package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
- package/dist/tools/workspace/applyPatch.js +53 -0
- package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
- package/dist/tools/workspace/index.d.ts +10 -0
- package/dist/tools/workspace/index.js +10 -0
- package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
- package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
- package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
- package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
- package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
- package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
- package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
- package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
- package/dist/tools/workspace/syncFile.js +134 -0
- package/dist/utils/atomicFile.d.ts +7 -0
- package/dist/utils/atomicFile.js +48 -0
- package/dist/utils/boundedFile.d.ts +8 -0
- package/dist/utils/boundedFile.js +93 -0
- package/dist/utils/lockedJsonFile.d.ts +15 -0
- package/dist/utils/lockedJsonFile.js +224 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +26 -0
- package/dist/utils/stableJson.d.ts +2 -0
- package/dist/utils/stableJson.js +12 -0
- package/dist/version.d.ts +3 -2
- package/dist/version.js +3 -2
- package/dist/watcherStatus.d.ts +2 -2
- package/dist/watcherStatus.js +18 -0
- package/docs/CODE_WIKI.md +1648 -0
- package/docs/agentseal-integration.md +150 -0
- package/docs/architecture.md +23 -0
- package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
- package/docs/assets/patchwarden-oss-demo.gif +0 -0
- package/docs/control-center/README.md +4 -4
- package/docs/dashboard-overview.md +86 -0
- package/docs/demo.md +8 -0
- package/docs/desktop-app.md +175 -0
- package/docs/direct-session-workflow.md +98 -0
- package/docs/evidence-pack-schema.md +215 -0
- package/docs/lineage-evidence-pack-workflow.md +127 -0
- package/docs/mcp-inspector-testing.md +200 -0
- package/docs/open-source-application.md +168 -0
- package/docs/opencode-worker.md +151 -0
- package/docs/openhands-worker.md +181 -0
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +82 -0
- package/docs/spec-kit-integration.md +131 -0
- package/docs/task-safe-review-workflow.md +98 -0
- package/docs/threat-model.md +97 -0
- package/docs/user-feedback.md +40 -0
- package/docs/why-patchwarden.md +110 -0
- package/examples/config.example.json +4 -2
- package/package.json +28 -11
- package/scripts/README.md +15 -6
- package/scripts/build.js +11 -0
- package/scripts/checks/brand-check.js +3 -0
- package/scripts/checks/build-output-check.js +37 -0
- package/scripts/checks/control-center-smoke.js +447 -6
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +39 -33
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +30 -4
- package/scripts/checks/package-manifest-check.js +20 -7
- package/scripts/checks/unit-tests.js +3 -5
- package/scripts/checks/watcher-supervisor-smoke.js +9 -0
- package/scripts/control/get-patchwarden-health.ps1 +1 -1
- package/scripts/control/manage-patchwarden.ps1 +69 -4
- package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
- package/scripts/control/run-background-supervisor.ps1 +41 -0
- package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
- package/scripts/generate-demo-gif.py +320 -0
- package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
- package/scripts/launchers/PatchWarden-Control.cmd +6 -0
- package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
- package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
- package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
- package/scripts/lib/clean-generated-output.js +11 -0
- package/scripts/lib/file-discovery.js +12 -0
- package/scripts/release/desktop-preflight.js +211 -0
- package/scripts/release/pack-clean.js +59 -14
- package/src/assessments/agentAssessor.ts +29 -15
- package/src/assessments/assessmentStore.ts +55 -43
- package/src/assessments/confirmCli.ts +5 -4
- package/src/config.ts +105 -11
- package/src/control/fileManager.ts +40 -0
- package/src/control/middleware/auth.ts +35 -0
- package/src/control/middleware/static.ts +84 -0
- package/src/control/routeTable.ts +301 -0
- package/src/control/routes/audit.ts +329 -0
- package/src/control/routes/evidence.ts +107 -0
- package/src/control/routes/lineage.ts +92 -0
- package/src/control/routes/policy.ts +81 -0
- package/src/control/routes/process.ts +505 -0
- package/src/control/routes/sessions.ts +314 -0
- package/src/control/routes/status.ts +405 -0
- package/src/control/routes/taskActions.ts +214 -0
- package/src/control/routes/tasks.ts +331 -0
- package/src/control/routes/workspace.ts +234 -0
- package/src/control/runtime.ts +508 -0
- package/src/control/server.ts +165 -0
- package/src/control/shared.ts +360 -0
- package/src/controlCenter.ts +7 -2347
- package/src/direct/directAudit.ts +17 -9
- package/src/direct/directGuards.ts +100 -51
- package/src/direct/directPatch.ts +95 -8
- package/src/direct/directSessionStore.ts +211 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +766 -492
- package/src/goal/acceptanceEngine.ts +1 -1
- package/src/goal/goalProgress.ts +17 -13
- package/src/goal/goalReport.ts +263 -0
- package/src/goal/goalStatus.ts +25 -3
- package/src/goal/goalStore.ts +241 -54
- package/src/goal/handoffExport.ts +6 -6
- package/src/goal/specKitImport.ts +355 -0
- package/src/goal/subgoalSync.ts +15 -10
- package/src/goal/worktreeManager.ts +139 -22
- package/src/httpServer.ts +130 -44
- package/src/index.ts +7 -4
- package/src/logging.ts +20 -4
- package/src/policy/projectPolicy.ts +5 -2
- package/src/release/releaseGate.ts +74 -27
- package/src/runner/agentInvocation.ts +126 -4
- package/src/runner/changeCapture.ts +342 -80
- package/src/runner/cli.ts +7 -6
- package/src/runner/postTaskCleanup.ts +55 -7
- package/src/runner/processSecurity.ts +433 -0
- package/src/runner/runTask.ts +441 -292
- package/src/runner/simpleProcess.ts +68 -33
- package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
- package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
- package/src/runner/taskStatusStore.ts +54 -0
- package/src/runner/watch.ts +290 -57
- package/src/security/contentRedaction.ts +31 -2
- package/src/security/discoveryTokenStore.ts +18 -4
- package/src/security/loopbackHost.ts +19 -0
- package/src/security/pathGuard.ts +35 -16
- package/src/security/planGuard.ts +2 -1
- package/src/security/runtimeGuard.ts +9 -6
- package/src/security/secretComparison.ts +13 -0
- package/src/security/sensitiveGuard.ts +27 -8
- package/src/security/toolInvocationGuard.ts +2 -2
- package/src/security/workspaceRootGuard.ts +53 -0
- package/src/smoke-test.ts +276 -273
- package/src/tools/catalog/index.ts +4 -0
- package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
- package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
- package/src/tools/definitions/toolDefs.ts +1264 -0
- package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
- package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
- package/src/tools/diagnostics/index.ts +6 -0
- package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
- package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
- package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
- package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
- package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
- package/src/tools/direct/index.ts +3 -0
- package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
- package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
- package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
- package/src/tools/discovery/index.ts +3 -0
- package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
- package/src/tools/dispatch/coreDispatch.ts +379 -0
- package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
- package/src/tools/dispatch/directDispatch.ts +168 -0
- package/src/tools/dispatch/goalDispatch.ts +128 -0
- package/src/tools/dispatch/releaseDispatch.ts +66 -0
- package/src/tools/dispatch/types.ts +24 -0
- package/src/tools/dispatch/validation.ts +78 -0
- package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
- package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
- package/src/tools/goals/index.ts +3 -0
- package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
- package/src/tools/registry.ts +54 -1858
- package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
- package/src/tools/release/index.ts +2 -0
- package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
- package/src/tools/tasks/cancelTask.ts +109 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
- package/src/tools/tasks/evidencePack.ts +504 -0
- package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
- package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
- package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
- package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
- package/src/tools/tasks/index.ts +18 -0
- package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
- package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
- package/src/tools/tasks/retryTask.ts +69 -0
- package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
- package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
- package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
- package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
- package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
- package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
- package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
- package/src/tools/workspace/index.ts +10 -0
- package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
- package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
- package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
- package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
- package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
- package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
- package/src/tools/workspace/syncFile.ts +292 -0
- package/src/utils/atomicFile.ts +58 -0
- package/src/utils/boundedFile.ts +116 -0
- package/src/utils/lockedJsonFile.ts +251 -0
- package/src/utils/platform.ts +31 -0
- package/src/utils/stableJson.ts +12 -0
- package/src/version.ts +3 -2
- package/src/watcherStatus.ts +19 -1
- package/ui/desktop-bootstrap.js +11 -0
- package/ui/desktop-bridge.js +36 -0
- package/ui/desktop.css +160 -0
- package/ui/getting-started.js +93 -0
- package/ui/i18n.js +484 -0
- package/ui/log-parser.js +30 -0
- package/ui/pages/audit.html +204 -3
- package/ui/pages/dashboard.html +937 -79
- package/ui/pages/direct-sessions.html +609 -60
- package/ui/pages/getting-started.html +43 -0
- package/ui/pages/logs.html +105 -22
- package/ui/pages/settings.html +101 -0
- package/ui/pages/task-detail.html +468 -438
- package/ui/pages/tasks.html +648 -65
- package/ui/pages/workspace.html +38 -1
- package/ui/settings.js +245 -0
- package/PatchWarden-Control.cmd +0 -6
- package/Restart-PatchWarden-Control.cmd +0 -6
- package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
- package/dist/test/unit/acceptance-engine.test.js +0 -228
- package/dist/test/unit/agent-router.test.d.ts +0 -1
- package/dist/test/unit/agent-router.test.js +0 -287
- package/dist/test/unit/android-doctor.test.d.ts +0 -1
- package/dist/test/unit/android-doctor.test.js +0 -118
- package/dist/test/unit/audit-checks.test.d.ts +0 -1
- package/dist/test/unit/audit-checks.test.js +0 -350
- package/dist/test/unit/chinese-path.test.d.ts +0 -1
- package/dist/test/unit/chinese-path.test.js +0 -91
- package/dist/test/unit/command-guard.test.d.ts +0 -1
- package/dist/test/unit/command-guard.test.js +0 -160
- package/dist/test/unit/diagnose-task.test.d.ts +0 -1
- package/dist/test/unit/diagnose-task.test.js +0 -457
- package/dist/test/unit/direct-guards.test.d.ts +0 -1
- package/dist/test/unit/direct-guards.test.js +0 -213
- package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
- package/dist/test/unit/discovery-token-store.test.js +0 -139
- package/dist/test/unit/evidence-pack.test.d.ts +0 -1
- package/dist/test/unit/evidence-pack.test.js +0 -130
- package/dist/test/unit/goal-graph.test.d.ts +0 -1
- package/dist/test/unit/goal-graph.test.js +0 -298
- package/dist/test/unit/goal-progress.test.d.ts +0 -1
- package/dist/test/unit/goal-progress.test.js +0 -381
- package/dist/test/unit/goal-status.test.d.ts +0 -1
- package/dist/test/unit/goal-status.test.js +0 -215
- package/dist/test/unit/goal-store.test.d.ts +0 -1
- package/dist/test/unit/goal-store.test.js +0 -253
- package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
- package/dist/test/unit/goal-subgoal-task.test.js +0 -55
- package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
- package/dist/test/unit/goal-tools-registry.test.js +0 -190
- package/dist/test/unit/handoff-export.test.d.ts +0 -1
- package/dist/test/unit/handoff-export.test.js +0 -263
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
- package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
- package/dist/test/unit/logging.test.d.ts +0 -1
- package/dist/test/unit/logging.test.js +0 -397
- package/dist/test/unit/path-guard.test.d.ts +0 -1
- package/dist/test/unit/path-guard.test.js +0 -109
- package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
- package/dist/test/unit/post-task-cleanup.test.js +0 -48
- package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
- package/dist/test/unit/project-policy-release-mode.test.js +0 -125
- package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
- package/dist/test/unit/reconcile-tasks.test.js +0 -456
- package/dist/test/unit/release-gate.test.d.ts +0 -1
- package/dist/test/unit/release-gate.test.js +0 -242
- package/dist/test/unit/run-task-loop.test.d.ts +0 -1
- package/dist/test/unit/run-task-loop.test.js +0 -380
- package/dist/test/unit/safe-status.test.d.ts +0 -1
- package/dist/test/unit/safe-status.test.js +0 -165
- package/dist/test/unit/safe-views.test.d.ts +0 -1
- package/dist/test/unit/safe-views.test.js +0 -171
- package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
- package/dist/test/unit/schema-drift-check.test.js +0 -176
- package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
- package/dist/test/unit/sensitive-guard.test.js +0 -104
- package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
- package/dist/test/unit/subgoal-sync.test.js +0 -183
- package/dist/test/unit/sync-file.test.d.ts +0 -1
- package/dist/test/unit/sync-file.test.js +0 -154
- package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
- package/dist/test/unit/tool-invocation-guard.test.js +0 -432
- package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
- package/dist/test/unit/tool-usage-stats.test.js +0 -300
- package/dist/test/unit/toolSearch.test.d.ts +0 -1
- package/dist/test/unit/toolSearch.test.js +0 -571
- package/dist/test/unit/watcher-status.test.d.ts +0 -1
- package/dist/test/unit/watcher-status.test.js +0 -169
- package/dist/test/unit/worktree-manager.test.d.ts +0 -1
- package/dist/test/unit/worktree-manager.test.js +0 -176
- package/dist/tools/applyPatch.js +0 -41
- package/dist/tools/cancelTask.d.ts +0 -30
- package/dist/tools/cancelTask.js +0 -64
- package/dist/tools/evidencePack.js +0 -168
- package/dist/tools/finalizeDirectSession.js +0 -84
- package/dist/tools/goalSubgoalTask.js +0 -110
- package/dist/tools/killTask.d.ts +0 -15
- package/dist/tools/retryTask.js +0 -45
- package/dist/tools/runVerification.js +0 -36
- package/dist/tools/syncFile.js +0 -65
- package/docs/control-center/control-center-daily-driver.md +0 -211
- package/docs/control-center/control-center-mvp.md +0 -205
- package/docs/control-center/control-center-phase2.md +0 -159
- package/docs/release-v0.6.0.md +0 -71
- package/docs/release-v0.6.1.md +0 -75
- package/docs/release-v0.6.4.md +0 -45
- package/src/test/unit/acceptance-engine.test.ts +0 -261
- package/src/test/unit/agent-router.test.ts +0 -342
- package/src/test/unit/android-doctor.test.ts +0 -158
- package/src/test/unit/audit-checks.test.ts +0 -567
- package/src/test/unit/chinese-path.test.ts +0 -106
- package/src/test/unit/command-guard.test.ts +0 -221
- package/src/test/unit/diagnose-task.test.ts +0 -544
- package/src/test/unit/direct-guards.test.ts +0 -297
- package/src/test/unit/discovery-token-store.test.ts +0 -181
- package/src/test/unit/evidence-pack.test.ts +0 -142
- package/src/test/unit/goal-graph.test.ts +0 -347
- package/src/test/unit/goal-progress.test.ts +0 -538
- package/src/test/unit/goal-status.test.ts +0 -270
- package/src/test/unit/goal-store.test.ts +0 -318
- package/src/test/unit/goal-subgoal-task.test.ts +0 -72
- package/src/test/unit/goal-tools-registry.test.ts +0 -243
- package/src/test/unit/handoff-export.test.ts +0 -295
- package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
- package/src/test/unit/logging.test.ts +0 -497
- package/src/test/unit/path-guard.test.ts +0 -150
- package/src/test/unit/post-task-cleanup.test.ts +0 -53
- package/src/test/unit/project-policy-release-mode.test.ts +0 -156
- package/src/test/unit/reconcile-tasks.test.ts +0 -551
- package/src/test/unit/release-gate.test.ts +0 -314
- package/src/test/unit/run-task-loop.test.ts +0 -425
- package/src/test/unit/safe-status.test.ts +0 -187
- package/src/test/unit/safe-views.test.ts +0 -184
- package/src/test/unit/schema-drift-check.test.ts +0 -260
- package/src/test/unit/sensitive-guard.test.ts +0 -124
- package/src/test/unit/subgoal-sync.test.ts +0 -236
- package/src/test/unit/sync-file.test.ts +0 -231
- package/src/test/unit/tool-invocation-guard.test.ts +0 -542
- package/src/test/unit/tool-usage-stats.test.ts +0 -384
- package/src/test/unit/toolSearch.test.ts +0 -756
- package/src/test/unit/watcher-status.test.ts +0 -190
- package/src/test/unit/worktree-manager.test.ts +0 -247
- package/src/tools/cancelTask.ts +0 -70
- package/src/tools/evidencePack.ts +0 -205
- package/src/tools/retryTask.ts +0 -53
- package/src/tools/syncFile.ts +0 -122
- /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
- /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
- /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
- /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
- /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
- /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
- /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
- /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
- /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
- /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
- /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
- /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
- /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
- /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
- /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
- /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
- /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
- /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
- /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
- /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
- /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
- /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
- /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
- /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
- /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
- /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
- /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
- /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
- /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
|
+
}
|
package/src/goal/subgoalSync.ts
CHANGED
|
@@ -9,8 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
-
import {
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
51
|
-
`[goal] syncSubgoalOnTaskDone failed for task ${taskId}
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
246
|
-
return JSON.parse(raw) as WorktreeStatus;
|
|
362
|
+
parsed = JSON.parse(readFileSync(statusFilePath, "utf-8"));
|
|
247
363
|
} catch {
|
|
248
|
-
return
|
|
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
|
-
//
|
|
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
|
}
|