patchwarden 1.5.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PatchWarden.cmd +32 -2
- package/README.en.md +158 -26
- package/README.md +124 -24
- package/dist/assessments/agentAssessor.js +21 -12
- package/dist/assessments/assessmentDiagnostics.d.ts +29 -0
- package/dist/assessments/assessmentDiagnostics.js +132 -0
- package/dist/assessments/assessmentStore.d.ts +39 -0
- package/dist/assessments/assessmentStore.js +207 -45
- package/dist/assessments/securitySnapshot.d.ts +50 -0
- package/dist/assessments/securitySnapshot.js +158 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.js +66 -13
- package/dist/control/fileManager.d.ts +2 -0
- package/dist/control/fileManager.js +39 -0
- package/dist/control/middleware/auth.d.ts +6 -0
- package/dist/control/middleware/auth.js +14 -5
- package/dist/control/middleware/static.js +16 -3
- package/dist/control/routeTable.d.ts +24 -0
- package/dist/control/routeTable.js +263 -0
- package/dist/control/routes/audit.js +17 -7
- package/dist/control/routes/evidence.js +1 -1
- package/dist/control/routes/lineage.js +1 -1
- package/dist/control/routes/process.d.ts +7 -0
- package/dist/control/routes/process.js +329 -42
- package/dist/control/routes/sessions.d.ts +9 -1
- package/dist/control/routes/sessions.js +84 -21
- package/dist/control/routes/status.d.ts +60 -1
- package/dist/control/routes/status.js +94 -15
- package/dist/control/routes/taskActions.js +24 -60
- package/dist/control/routes/tasks.d.ts +4 -0
- package/dist/control/routes/tasks.js +61 -62
- package/dist/control/routes/workspace.js +50 -23
- package/dist/control/runtime.d.ts +3 -2
- package/dist/control/runtime.js +65 -40
- package/dist/control/server.js +37 -365
- package/dist/control/shared.d.ts +25 -1
- package/dist/control/shared.js +64 -11
- package/dist/diagnostics/allowedTestCommandSafety.d.ts +1 -0
- package/dist/diagnostics/allowedTestCommandSafety.js +11 -0
- package/dist/direct/directAudit.js +234 -225
- package/dist/direct/directGuards.d.ts +2 -1
- package/dist/direct/directGuards.js +42 -29
- 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 +128 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.js +65 -57
- package/dist/goal/acceptanceEngine.d.ts +1 -1
- package/dist/goal/goalProgress.js +75 -69
- package/dist/goal/goalReport.js +5 -12
- package/dist/goal/goalStatus.d.ts +2 -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.js +97 -96
- package/dist/goal/subgoalSync.js +11 -9
- package/dist/goal/worktreeManager.d.ts +2 -0
- package/dist/goal/worktreeManager.js +209 -136
- package/dist/httpServer.js +111 -32
- package/dist/logging.js +5 -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 +13 -5
- package/dist/runner/changeCapture.js +243 -61
- package/dist/runner/postTaskCleanup.js +48 -19
- package/dist/runner/processSecurity.d.ts +56 -0
- package/dist/runner/processSecurity.js +373 -0
- package/dist/runner/runTask.d.ts +2 -1
- package/dist/runner/runTask.js +162 -93
- package/dist/runner/simpleProcess.d.ts +3 -0
- package/dist/runner/simpleProcess.js +50 -36
- 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} +3 -1
- package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
- package/dist/runner/taskStatusStore.d.ts +11 -0
- package/dist/runner/taskStatusStore.js +35 -0
- package/dist/runner/watch.d.ts +14 -1
- package/dist/runner/watch.js +320 -49
- package/dist/security/contentRedaction.js +2 -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 +27 -26
- 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} +6 -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} +2 -1
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +134 -36
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +18 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +36 -8
- 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} +12 -8
- package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
- 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.js → direct/createDirectSession.js} +6 -6
- package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
- 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.js +32 -31
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
- package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
- package/dist/tools/dispatch/directDispatch.js +11 -10
- package/dist/tools/dispatch/goalDispatch.js +5 -4
- package/dist/tools/dispatch/releaseDispatch.js +3 -2
- 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} +1 -1
- 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 +7 -11
- package/dist/tools/registry.js +17 -1178
- 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 +86 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
- package/dist/tools/{createTask.js → tasks/createTask.js} +110 -34
- package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
- package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +12 -16
- package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
- 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} +11 -6
- package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
- package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +35 -36
- 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} +5 -4
- package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +3 -2
- package/dist/tools/tasks/reconcileTasks.js +496 -0
- 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} +63 -41
- package/dist/tools/tasks/runVerification.js +38 -0
- package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
- package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +2 -2
- package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
- package/dist/tools/tasks/taskStates.d.ts +6 -0
- package/dist/tools/tasks/taskStates.js +52 -0
- package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +3 -11
- 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/workspace/listAgents.d.ts +23 -0
- package/dist/tools/workspace/listAgents.js +56 -0
- 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 +67 -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 +808 -143
- package/docs/architecture.md +15 -55
- package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
- package/docs/control-center/README.md +4 -4
- package/docs/dashboard-overview.md +4 -4
- package/docs/desktop-app.md +175 -0
- package/docs/evidence-pack-schema.md +3 -3
- package/docs/open-source-application.md +16 -11
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +35 -39
- package/docs/task-safe-review-workflow.md +5 -5
- package/docs/threat-model.md +19 -1
- package/examples/config.example.json +4 -2
- package/package.json +27 -10
- 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 +102 -10
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +15 -12
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +58 -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 +38 -1
- 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 +191 -33
- package/scripts/e2e/demo-runtime.mjs +153 -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 +26 -12
- package/src/assessments/assessmentDiagnostics.ts +172 -0
- package/src/assessments/assessmentStore.ts +288 -51
- package/src/assessments/securitySnapshot.ts +231 -0
- package/src/config.ts +70 -13
- package/src/control/fileManager.ts +40 -0
- package/src/control/middleware/auth.ts +22 -4
- package/src/control/middleware/static.ts +16 -3
- package/src/control/routeTable.ts +301 -0
- package/src/control/routes/audit.ts +14 -6
- package/src/control/routes/evidence.ts +1 -1
- package/src/control/routes/lineage.ts +1 -1
- package/src/control/routes/process.ts +341 -40
- package/src/control/routes/sessions.ts +88 -25
- package/src/control/routes/status.ts +134 -17
- package/src/control/routes/taskActions.ts +24 -58
- package/src/control/routes/tasks.ts +66 -58
- package/src/control/routes/workspace.ts +56 -25
- package/src/control/runtime.ts +71 -43
- package/src/control/server.ts +37 -342
- package/src/control/shared.ts +77 -11
- package/src/diagnostics/allowedTestCommandSafety.ts +12 -0
- package/src/direct/directAudit.ts +17 -9
- package/src/direct/directGuards.ts +72 -44
- package/src/direct/directPatch.ts +95 -8
- package/src/direct/directSessionStore.ts +213 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +76 -64
- package/src/goal/acceptanceEngine.ts +1 -1
- package/src/goal/goalProgress.ts +17 -13
- package/src/goal/goalReport.ts +5 -13
- package/src/goal/goalStatus.ts +19 -3
- package/src/goal/goalStore.ts +241 -54
- package/src/goal/handoffExport.ts +6 -6
- package/src/goal/specKitImport.ts +12 -12
- package/src/goal/subgoalSync.ts +11 -8
- package/src/goal/worktreeManager.ts +139 -22
- package/src/httpServer.ts +113 -30
- package/src/logging.ts +10 -3
- 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 +291 -57
- package/src/runner/postTaskCleanup.ts +41 -17
- package/src/runner/processSecurity.ts +433 -0
- package/src/runner/runTask.ts +197 -97
- package/src/runner/simpleProcess.ts +66 -31
- package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
- package/src/{taskRuntime.ts → runner/taskRuntime.ts} +6 -3
- package/src/runner/taskStatusStore.ts +69 -0
- package/src/runner/watch.ts +348 -52
- package/src/security/contentRedaction.ts +2 -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 +27 -26
- 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} +7 -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} +136 -39
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +37 -9
- 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} +27 -17
- package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
- package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
- package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
- 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 +36 -31
- package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
- package/src/tools/dispatch/directDispatch.ts +11 -10
- package/src/tools/dispatch/goalDispatch.ts +5 -4
- package/src/tools/dispatch/releaseDispatch.ts +3 -2
- 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} +41 -26
- package/src/tools/goals/index.ts +3 -0
- package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
- package/src/tools/registry.ts +17 -1261
- 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 +110 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +161 -33
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +12 -16
- package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
- package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
- package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +13 -6
- package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
- package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +46 -44
- package/src/tools/tasks/index.ts +18 -0
- package/src/tools/{listTasks.ts → tasks/listTasks.ts} +6 -5
- package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +228 -77
- package/src/tools/tasks/retryTask.ts +69 -0
- package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +73 -46
- 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/tasks/taskStates.ts +54 -0
- package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +3 -12
- 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/workspace/listAgents.ts +75 -0
- 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 +74 -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 +12 -0
- package/ui/pages/dashboard.html +94 -147
- package/ui/pages/direct-sessions.html +123 -26
- package/ui/pages/getting-started.html +43 -0
- package/ui/pages/logs.html +134 -22
- package/ui/pages/settings.html +101 -0
- package/ui/pages/task-detail.html +12 -0
- package/ui/pages/tasks.html +54 -8
- package/ui/pages/workspace.html +38 -1
- package/ui/settings.js +254 -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/apply-patch.test.d.ts +0 -1
- package/dist/test/unit/apply-patch.test.js +0 -225
- 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/create-task.test.d.ts +0 -1
- package/dist/test/unit/create-task.test.js +0 -197
- 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 -328
- 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 -224
- package/dist/test/unit/get-task-status.test.d.ts +0 -1
- package/dist/test/unit/get-task-status.test.js +0 -174
- package/dist/test/unit/get-task-summary.test.d.ts +0 -1
- package/dist/test/unit/get-task-summary.test.js +0 -146
- 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-report.test.d.ts +0 -1
- package/dist/test/unit/goal-report.test.js +0 -159
- 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 -192
- 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 -133
- 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/spec-kit-import.test.d.ts +0 -1
- package/dist/test/unit/spec-kit-import.test.js +0 -341
- 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/wait-for-task.test.d.ts +0 -1
- package/dist/test/unit/wait-for-task.test.js +0 -144
- 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/finalizeDirectSession.js +0 -84
- package/dist/tools/goalSubgoalTask.js +0 -110
- package/dist/tools/killTask.d.ts +0 -15
- package/dist/tools/listAgents.d.ts +0 -12
- package/dist/tools/listAgents.js +0 -42
- package/dist/tools/reconcileTasks.js +0 -352
- 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/execution-plan-2026-07-09.md +0 -315
- 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/docs/roadmap-execution-and-acceptance.md +0 -365
- 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/apply-patch.test.ts +0 -293
- 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/create-task.test.ts +0 -255
- package/src/test/unit/diagnose-task.test.ts +0 -544
- package/src/test/unit/direct-guards.test.ts +0 -467
- package/src/test/unit/discovery-token-store.test.ts +0 -181
- package/src/test/unit/evidence-pack.test.ts +0 -251
- package/src/test/unit/get-task-status.test.ts +0 -203
- package/src/test/unit/get-task-summary.test.ts +0 -173
- 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-report.test.ts +0 -189
- 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 -245
- 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 -180
- 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/spec-kit-import.test.ts +0 -429
- 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/wait-for-task.test.ts +0 -176
- 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/listAgents.ts +0 -54
- 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/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +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/{evidencePack.d.ts → tasks/evidencePack.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/{retryTask.d.ts → tasks/retryTask.d.ts} +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/{waitForTask.d.ts → tasks/waitForTask.d.ts} +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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 3. 存在 warn 级检查项或文档不同步 → NEEDS_FIX
|
|
13
13
|
* 4. 所有检查通过 → ACCEPTED
|
|
14
14
|
*/
|
|
15
|
-
import type { AcceptanceStatus } from "../tools/createTask.js";
|
|
15
|
+
import type { AcceptanceStatus } from "../tools/tasks/createTask.js";
|
|
16
16
|
export type AcceptanceVerdict = "ACCEPTED" | "NEEDS_FIX" | "REJECTED" | "BLOCKED_BY_APPROVAL";
|
|
17
17
|
export interface AcceptanceCheck {
|
|
18
18
|
name: string;
|
|
@@ -14,7 +14,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
14
14
|
import { isAbsolute, join } from "node:path";
|
|
15
15
|
import { getConfig } from "../config.js";
|
|
16
16
|
import { PatchWardenError } from "../errors.js";
|
|
17
|
-
import {
|
|
17
|
+
import { mutateGoalStatus, readGoalStatus } from "./goalStore.js";
|
|
18
18
|
import { updateSubgoalStatus } from "./goalStatus.js";
|
|
19
19
|
import { getBlockedSubgoals } from "./goalGraph.js";
|
|
20
20
|
// ── 辅助:解析 workspaceRoot ──────────────────────────────────────
|
|
@@ -66,42 +66,45 @@ function readTaskStatus(taskId, workspaceRoot) {
|
|
|
66
66
|
* @throws {PatchWardenError} reason="goal_not_found" | "subgoal_not_found" | "subgoal_not_ready" | "invalid_status_transition"
|
|
67
67
|
*/
|
|
68
68
|
export function acceptSubgoal(goalId, subgoalId, workspaceRoot) {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
69
|
+
return mutateGoalStatus(goalId, (goalStatus) => {
|
|
70
|
+
const subgoal = goalStatus.subgoals.find((s) => s.id === subgoalId);
|
|
71
|
+
if (!subgoal) {
|
|
72
|
+
throw new PatchWardenError("subgoal_not_found", `Subgoal "${subgoalId}" not found in goal "${goalId}"`, "Ensure the subgoal id exists before accepting.", true, { subgoal_id: subgoalId, goal_id: goalId });
|
|
73
|
+
}
|
|
74
|
+
// 校验 1:task_ids 不能为空
|
|
75
|
+
if (subgoal.task_ids.length === 0) {
|
|
76
|
+
throw new PatchWardenError("subgoal_not_ready", `Subgoal "${subgoalId}" has no associated tasks; cannot accept without task verification.`, "Link tasks to the subgoal and run audit_task on each before accepting.", true, { subgoal_id: subgoalId, goal_id: goalId, task_ids: [] });
|
|
77
|
+
}
|
|
78
|
+
// 校验 2:所有 task 必须为 accepted
|
|
79
|
+
const unacceptedTasks = [];
|
|
80
|
+
for (const taskId of subgoal.task_ids) {
|
|
81
|
+
const taskStatus = readTaskStatus(taskId, workspaceRoot);
|
|
82
|
+
if (taskStatus !== "accepted") {
|
|
83
|
+
unacceptedTasks.push({
|
|
84
|
+
task_id: taskId,
|
|
85
|
+
current_status: taskStatus ?? "missing",
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (unacceptedTasks.length > 0) {
|
|
90
|
+
throw new PatchWardenError("subgoal_not_ready", `Subgoal "${subgoalId}" cannot be accepted: ${unacceptedTasks.length} task(s) are not in "accepted" status.`, "Run audit_task on each unaccepted task first.", true, {
|
|
91
|
+
subgoal_id: subgoalId,
|
|
92
|
+
goal_id: goalId,
|
|
93
|
+
unaccepted_tasks: unacceptedTasks,
|
|
86
94
|
});
|
|
87
95
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
subgoal_id: subgoalId,
|
|
102
|
-
status: "accepted",
|
|
103
|
-
accepted_at: updatedSubgoal.accepted_at,
|
|
104
|
-
};
|
|
96
|
+
// 校验 3:通过状态机转换(要求 subgoal 当前为 done_by_agent)
|
|
97
|
+
const updatedGoalStatus = updateSubgoalStatus(goalStatus, subgoalId, "accepted");
|
|
98
|
+
const updatedSubgoal = updatedGoalStatus.subgoals.find((s) => s.id === subgoalId);
|
|
99
|
+
return {
|
|
100
|
+
next: updatedGoalStatus,
|
|
101
|
+
result: {
|
|
102
|
+
subgoal_id: subgoalId,
|
|
103
|
+
status: "accepted",
|
|
104
|
+
accepted_at: updatedSubgoal.accepted_at,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}, workspaceRoot);
|
|
105
108
|
}
|
|
106
109
|
/**
|
|
107
110
|
* 拒绝子目标:从任意非终态(ready/running/done_by_agent/needs_fix)拒绝 subgoal。
|
|
@@ -117,40 +120,43 @@ export function acceptSubgoal(goalId, subgoalId, workspaceRoot) {
|
|
|
117
120
|
* @throws {PatchWardenError} reason="goal_not_found" | "subgoal_not_found" | "invalid_status_transition"
|
|
118
121
|
*/
|
|
119
122
|
export function rejectSubgoal(goalId, subgoalId, reason, workspaceRoot) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
123
|
+
return mutateGoalStatus(goalId, (goalStatus) => {
|
|
124
|
+
const index = goalStatus.subgoals.findIndex((s) => s.id === subgoalId);
|
|
125
|
+
if (index === -1) {
|
|
126
|
+
throw new PatchWardenError("subgoal_not_found", `Subgoal "${subgoalId}" not found in goal "${goalId}"`, "Ensure the subgoal id exists before rejecting.", true, { subgoal_id: subgoalId, goal_id: goalId });
|
|
127
|
+
}
|
|
128
|
+
const current = goalStatus.subgoals[index];
|
|
129
|
+
// 终态检查:accepted 和 rejected 不允许再 reject
|
|
130
|
+
if (current.status === "accepted" || current.status === "rejected") {
|
|
131
|
+
throw new PatchWardenError("invalid_status_transition", `Cannot reject subgoal "${subgoalId}" in terminal state "${current.status}".`, "Reject is only allowed from non-terminal states (ready, running, done_by_agent, needs_fix).", true, {
|
|
132
|
+
subgoal_id: subgoalId,
|
|
133
|
+
goal_id: goalId,
|
|
134
|
+
from_status: current.status,
|
|
135
|
+
to_status: "rejected",
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
// 手动更新 status 和 rejected_reason(绕过状态机)
|
|
139
|
+
const updatedSubgoal = {
|
|
140
|
+
...current,
|
|
141
|
+
status: "rejected",
|
|
142
|
+
rejected_reason: reason,
|
|
143
|
+
};
|
|
144
|
+
const newSubgoals = [...goalStatus.subgoals];
|
|
145
|
+
newSubgoals[index] = updatedSubgoal;
|
|
146
|
+
const updatedGoalStatus = {
|
|
147
|
+
...goalStatus,
|
|
148
|
+
subgoals: newSubgoals,
|
|
149
|
+
updated_at: new Date().toISOString(),
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
next: updatedGoalStatus,
|
|
153
|
+
result: {
|
|
154
|
+
subgoal_id: subgoalId,
|
|
155
|
+
status: "rejected",
|
|
156
|
+
rejected_reason: reason,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}, workspaceRoot);
|
|
154
160
|
}
|
|
155
161
|
/**
|
|
156
162
|
* 汇总 Goal 进度:统计各状态数量、完成率、阻塞子目标、风险项。
|
package/dist/goal/goalReport.js
CHANGED
|
@@ -5,25 +5,18 @@
|
|
|
5
5
|
* 生成 REPORT.md 和 report.json 到 {workspaceRoot}/.patchwarden/goals/{goalId}/report/。
|
|
6
6
|
* 所有写入操作使用原子写(.tmp + rename),所有字符串内容经 redactSensitiveValue 脱敏。
|
|
7
7
|
*/
|
|
8
|
-
import { mkdirSync
|
|
8
|
+
import { mkdirSync } from "node:fs";
|
|
9
9
|
import { join, resolve } from "node:path";
|
|
10
10
|
import { getConfig } from "../config.js";
|
|
11
11
|
import { redactSensitiveValue } from "../security/contentRedaction.js";
|
|
12
|
-
import { listEvidencePacks } from "../tools/evidencePack.js";
|
|
12
|
+
import { listEvidencePacks } from "../tools/tasks/evidencePack.js";
|
|
13
|
+
import { atomicWriteFileSync } from "../utils/atomicFile.js";
|
|
13
14
|
import { summarizeGoalProgress } from "./goalProgress.js";
|
|
14
15
|
import { readGoalStatus } from "./goalStore.js";
|
|
15
16
|
// ── 辅助函数 ──────────────────────────────────────────────────────
|
|
16
17
|
function resolveWorkspaceRoot(workspaceRoot) {
|
|
17
18
|
return workspaceRoot ?? getConfig().workspaceRoot;
|
|
18
19
|
}
|
|
19
|
-
/**
|
|
20
|
-
* 原子写文件:先写到 .tmp 文件,再 renameSync 到目标路径。
|
|
21
|
-
*/
|
|
22
|
-
function atomicWriteFile(filePath, content) {
|
|
23
|
-
const tmpPath = filePath + ".tmp";
|
|
24
|
-
writeFileSync(tmpPath, content, "utf-8");
|
|
25
|
-
renameSync(tmpPath, filePath);
|
|
26
|
-
}
|
|
27
20
|
/**
|
|
28
21
|
* 将百分比字符串(如 "100%")解析为数字。
|
|
29
22
|
*/
|
|
@@ -196,9 +189,9 @@ export function exportGoalReport(goalId, options) {
|
|
|
196
189
|
// 脱敏所有字符串内容
|
|
197
190
|
const safeReport = redactSensitiveValue(rawReport).value;
|
|
198
191
|
// 原子写 JSON(机器可读)
|
|
199
|
-
|
|
192
|
+
atomicWriteFileSync(reportJsonPath, JSON.stringify(safeReport, null, 2) + "\n");
|
|
200
193
|
// 原子写 Markdown(人类可读,从脱敏后的对象生成)
|
|
201
194
|
const markdown = buildReportMarkdown(safeReport, { noSubgoals, incomplete });
|
|
202
|
-
|
|
195
|
+
atomicWriteFileSync(reportMdPath, markdown);
|
|
203
196
|
return safeReport;
|
|
204
197
|
}
|
|
@@ -23,6 +23,8 @@ export interface Subgoal {
|
|
|
23
23
|
external_ref?: string;
|
|
24
24
|
/** Spec Kit task.files 提示(作用域文件路径列表) */
|
|
25
25
|
scope_hints?: string[];
|
|
26
|
+
/** Managed git worktree associated with an isolated subgoal task. */
|
|
27
|
+
worktree_id?: string;
|
|
26
28
|
}
|
|
27
29
|
export interface GoalStatus {
|
|
28
30
|
goal_id: string;
|
package/dist/goal/goalStatus.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* 所有更新函数返回新的 GoalStatus 对象,不修改原对象。
|
|
12
12
|
*/
|
|
13
13
|
import { PatchWardenError } from "../errors.js";
|
|
14
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
14
15
|
// ── 合法状态转换表 ────────────────────────────────────────────────
|
|
15
16
|
const LEGAL_TRANSITIONS = {
|
|
16
17
|
ready: ["running"],
|
|
@@ -29,7 +30,7 @@ export function createInitialGoalStatus(goalId, title, repoPath) {
|
|
|
29
30
|
const now = new Date().toISOString();
|
|
30
31
|
return {
|
|
31
32
|
goal_id: goalId,
|
|
32
|
-
title,
|
|
33
|
+
title: redactGoalText(title, 500),
|
|
33
34
|
status: "active",
|
|
34
35
|
repo_path: repoPath,
|
|
35
36
|
created_at: now,
|
|
@@ -44,6 +45,9 @@ export function createInitialGoalStatus(goalId, title, repoPath) {
|
|
|
44
45
|
* 不可变更新:返回新的 GoalStatus 对象。
|
|
45
46
|
*/
|
|
46
47
|
export function addSubgoal(goalStatus, title, dependsOn = []) {
|
|
48
|
+
if (goalStatus.status !== "active") {
|
|
49
|
+
throw new PatchWardenError("goal_not_active", `Goal "${goalStatus.goal_id}" is ${goalStatus.status} and cannot accept new subgoals.`, "Create a new Goal Session for additional work.", true, { goal_id: goalStatus.goal_id, status: goalStatus.status });
|
|
50
|
+
}
|
|
47
51
|
const existingIds = new Set(goalStatus.subgoals.map((s) => s.id));
|
|
48
52
|
for (const depId of dependsOn) {
|
|
49
53
|
if (!existingIds.has(depId)) {
|
|
@@ -54,7 +58,7 @@ export function addSubgoal(goalStatus, title, dependsOn = []) {
|
|
|
54
58
|
const subgoalId = `subgoal-${String(nextNumber).padStart(3, "0")}`;
|
|
55
59
|
const newSubgoal = {
|
|
56
60
|
id: subgoalId,
|
|
57
|
-
title,
|
|
61
|
+
title: redactGoalText(title, 500),
|
|
58
62
|
status: "ready",
|
|
59
63
|
depends_on: [...dependsOn],
|
|
60
64
|
task_ids: [],
|
|
@@ -99,7 +103,7 @@ export function updateSubgoalStatus(goalStatus, subgoalId, newStatus, options) {
|
|
|
99
103
|
if (!options || !options.rejected_reason) {
|
|
100
104
|
throw new PatchWardenError("invalid_status_transition", `Rejecting subgoal "${subgoalId}" requires rejected_reason`, "Provide options.rejected_reason when transitioning to \"rejected\".", true, { subgoal_id: subgoalId, from_status: current.status, to_status: newStatus });
|
|
101
105
|
}
|
|
102
|
-
updatedSubgoal.rejected_reason = options.rejected_reason;
|
|
106
|
+
updatedSubgoal.rejected_reason = redactGoalText(options.rejected_reason, 2000);
|
|
103
107
|
}
|
|
104
108
|
const newSubgoals = [...goalStatus.subgoals];
|
|
105
109
|
newSubgoals[index] = updatedSubgoal;
|
|
@@ -109,6 +113,9 @@ export function updateSubgoalStatus(goalStatus, subgoalId, newStatus, options) {
|
|
|
109
113
|
updated_at: new Date().toISOString(),
|
|
110
114
|
};
|
|
111
115
|
}
|
|
116
|
+
function redactGoalText(value, maxChars) {
|
|
117
|
+
return redactSensitiveContent(String(value || "")).content.slice(0, maxChars);
|
|
118
|
+
}
|
|
112
119
|
/**
|
|
113
120
|
* 将 taskId 关联到指定 subgoal(去重)。
|
|
114
121
|
* 不可变更新:返回新的 GoalStatus 对象。
|
package/dist/goal/goalStore.d.ts
CHANGED
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
* 默认从 getConfig().workspaceRoot 读取。
|
|
14
14
|
*/
|
|
15
15
|
import { type GoalStatus, type Subgoal } from "./goalStatus.js";
|
|
16
|
+
/** Validate the storage key before it can participate in path construction. */
|
|
17
|
+
export declare function assertValidGoalId(goalId: string): void;
|
|
16
18
|
/**
|
|
17
19
|
* 生成 `goal_{YYYYMMDD}_{slug}` 格式的 goal id。
|
|
18
20
|
* 冲突时追加 `_2`、`_3`... 直到唯一。
|
|
@@ -68,8 +70,7 @@ export declare function readGoal(goalId: string, workspaceRoot?: string): {
|
|
|
68
70
|
subgoals: Subgoal[];
|
|
69
71
|
};
|
|
70
72
|
/**
|
|
71
|
-
*
|
|
72
|
-
* JSON 序列化用 JSON.stringify(status, null, 2) + 末尾换行。
|
|
73
|
+
* Replace goal_status.json under the shared cross-process mutation lock.
|
|
73
74
|
*/
|
|
74
75
|
export declare function writeGoalStatus(goalId: string, status: GoalStatus, workspaceRoot?: string): void;
|
|
75
76
|
/**
|
|
@@ -77,3 +78,9 @@ export declare function writeGoalStatus(goalId: string, status: GoalStatus, work
|
|
|
77
78
|
* 如果文件不存在,抛出 PatchWardenError("goal_not_found")。
|
|
78
79
|
*/
|
|
79
80
|
export declare function readGoalStatus(goalId: string, workspaceRoot?: string): GoalStatus;
|
|
81
|
+
export interface GoalStatusMutation<R> {
|
|
82
|
+
next?: GoalStatus;
|
|
83
|
+
result: R;
|
|
84
|
+
}
|
|
85
|
+
export declare function mutateGoalStatus<R>(goalId: string, mutation: (current: GoalStatus) => GoalStatusMutation<R>, workspaceRoot?: string): R;
|
|
86
|
+
export declare function mutateGoalStatusAsync<R>(goalId: string, mutation: (current: GoalStatus) => Promise<GoalStatusMutation<R>>, workspaceRoot?: string): Promise<R>;
|
package/dist/goal/goalStore.js
CHANGED
|
@@ -12,16 +12,29 @@
|
|
|
12
12
|
* 所有文件系统函数都接受可选的 workspaceRoot 参数用于测试;
|
|
13
13
|
* 默认从 getConfig().workspaceRoot 读取。
|
|
14
14
|
*/
|
|
15
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync,
|
|
15
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, } from "node:fs";
|
|
16
16
|
import { join } from "node:path";
|
|
17
17
|
import { getConfig } from "../config.js";
|
|
18
|
-
import { guardWorkspacePath } from "../security/pathGuard.js";
|
|
18
|
+
import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
|
|
19
19
|
import { PatchWardenError } from "../errors.js";
|
|
20
|
+
import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
21
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
22
|
+
import { mutateLockedJsonFileSync, readJsonObjectFileSync, withFileLock, withFileLockSync, } from "../utils/lockedJsonFile.js";
|
|
20
23
|
import { createInitialGoalStatus, } from "./goalStatus.js";
|
|
21
24
|
// ── 辅助:解析 workspaceRoot ──────────────────────────────────────
|
|
22
25
|
function resolveWorkspaceRoot(workspaceRoot) {
|
|
23
26
|
return workspaceRoot ?? getConfig().workspaceRoot;
|
|
24
27
|
}
|
|
28
|
+
const GOALS_PREFIX = join(".patchwarden", "goals");
|
|
29
|
+
const GOAL_ID_MAX_LENGTH = 128;
|
|
30
|
+
const GOAL_ID_PATTERN = /^goal_[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
31
|
+
/** Validate the storage key before it can participate in path construction. */
|
|
32
|
+
export function assertValidGoalId(goalId) {
|
|
33
|
+
if (goalId.length > GOAL_ID_MAX_LENGTH ||
|
|
34
|
+
!GOAL_ID_PATTERN.test(goalId)) {
|
|
35
|
+
throw new PatchWardenError("invalid_goal_id", `Invalid goal id. Expected "goal_" followed by at most ${GOAL_ID_MAX_LENGTH - 5} ASCII letters, digits, underscores, or hyphens.`, "Use the goal_id returned by create_goal or list_goals.", true, { goal_id_length: goalId.length });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
25
38
|
// ── Goal ID 生成 ──────────────────────────────────────────────────
|
|
26
39
|
/**
|
|
27
40
|
* 从 title 生成 slug:小写、非字母数字字符替换为 `_`、合并连续 `_`、去除首尾 `_`、截断到 30 字符。
|
|
@@ -64,13 +77,16 @@ export function generateGoalId(title, existingIds) {
|
|
|
64
77
|
* 不自动创建目录。
|
|
65
78
|
*/
|
|
66
79
|
export function getGoalsDir(workspaceRoot) {
|
|
67
|
-
|
|
80
|
+
const wsRoot = resolveWorkspaceRoot(workspaceRoot);
|
|
81
|
+
return guardPath(join(wsRoot, GOALS_PREFIX), wsRoot, GOALS_PREFIX);
|
|
68
82
|
}
|
|
69
83
|
/**
|
|
70
84
|
* 返回 `{getGoalsDir()}/{goalId}` 路径。
|
|
71
85
|
*/
|
|
72
86
|
export function getGoalDir(goalId, workspaceRoot) {
|
|
73
|
-
|
|
87
|
+
assertValidGoalId(goalId);
|
|
88
|
+
const wsRoot = resolveWorkspaceRoot(workspaceRoot);
|
|
89
|
+
return guardPath(join(getGoalsDir(wsRoot), goalId), wsRoot, GOALS_PREFIX);
|
|
74
90
|
}
|
|
75
91
|
// ── CRUD ──────────────────────────────────────────────────────────
|
|
76
92
|
/**
|
|
@@ -82,11 +98,21 @@ export function getGoalDir(goalId, workspaceRoot) {
|
|
|
82
98
|
* 返回 { goal_id, goal_dir }。
|
|
83
99
|
*/
|
|
84
100
|
export function createGoal(repoPath, title, description, workspaceRoot) {
|
|
101
|
+
const safeTitle = redactSensitiveContent(title)
|
|
102
|
+
.content
|
|
103
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
104
|
+
.trim()
|
|
105
|
+
.slice(0, 500) || "Untitled goal";
|
|
106
|
+
const safeDescription = redactSensitiveContent(description).content;
|
|
107
|
+
if (Buffer.byteLength(safeDescription, "utf-8") > 1024 * 1024) {
|
|
108
|
+
throw new PatchWardenError("goal_description_too_large", "Goal description exceeds the 1 MiB limit.", "Split the work into smaller Goal Sessions.");
|
|
109
|
+
}
|
|
85
110
|
const wsRoot = resolveWorkspaceRoot(workspaceRoot);
|
|
86
111
|
const guardedRepo = guardWorkspacePath(repoPath, wsRoot);
|
|
87
112
|
const goalsDir = getGoalsDir(workspaceRoot);
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
mkdirSync(goalsDir, { recursive: true });
|
|
114
|
+
return withFileLockSync(join(goalsDir, "goal-creation"), () => {
|
|
115
|
+
const existingIds = [];
|
|
90
116
|
for (const entry of readdirSync(goalsDir)) {
|
|
91
117
|
const entryPath = join(goalsDir, entry);
|
|
92
118
|
try {
|
|
@@ -95,35 +121,32 @@ export function createGoal(repoPath, title, description, workspaceRoot) {
|
|
|
95
121
|
}
|
|
96
122
|
}
|
|
97
123
|
catch {
|
|
98
|
-
//
|
|
124
|
+
// Skip entries that cannot be inspected.
|
|
99
125
|
}
|
|
100
126
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
"
|
|
117
|
-
"",
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const status = createInitialGoalStatus(goalId, title, guardedRepo);
|
|
125
|
-
writeFileSync(join(goalDir, "goal_status.json"), JSON.stringify(status, null, 2) + "\n", "utf-8");
|
|
126
|
-
return { goal_id: goalId, goal_dir: goalDir };
|
|
127
|
+
const goalId = generateGoalId(safeTitle, existingIds);
|
|
128
|
+
const goalDir = getGoalDir(goalId, wsRoot);
|
|
129
|
+
mkdirSync(goalDir);
|
|
130
|
+
mkdirSync(join(goalDir, "tasks"));
|
|
131
|
+
mkdirSync(join(goalDir, "artifacts"));
|
|
132
|
+
const now = new Date().toISOString();
|
|
133
|
+
const goalMd = [
|
|
134
|
+
`# ${safeTitle}`,
|
|
135
|
+
"",
|
|
136
|
+
safeDescription,
|
|
137
|
+
"",
|
|
138
|
+
`- Created: ${now}`,
|
|
139
|
+
`- Repo: ${guardedRepo}`,
|
|
140
|
+
"- Status: active",
|
|
141
|
+
"",
|
|
142
|
+
].join("\n");
|
|
143
|
+
atomicWriteFileSync(join(goalDir, "GOAL.md"), goalMd);
|
|
144
|
+
const goalsMd = `# Subgoals: ${safeTitle}\n\n_No subgoals yet._\n`;
|
|
145
|
+
atomicWriteFileSync(join(goalDir, "GOALS.md"), goalsMd);
|
|
146
|
+
const status = createInitialGoalStatus(goalId, safeTitle, guardedRepo);
|
|
147
|
+
atomicWriteJsonFileSync(join(goalDir, "goal_status.json"), status);
|
|
148
|
+
return { goal_id: goalId, goal_dir: goalDir };
|
|
149
|
+
}, goalBusyOptions("creation"));
|
|
127
150
|
}
|
|
128
151
|
/**
|
|
129
152
|
* 列出所有 goal 的摘要信息,按 updated_at 降序排列。
|
|
@@ -136,8 +159,8 @@ export function listGoals(workspaceRoot) {
|
|
|
136
159
|
}
|
|
137
160
|
const results = [];
|
|
138
161
|
for (const entry of readdirSync(goalsDir)) {
|
|
139
|
-
const entryPath = join(goalsDir, entry);
|
|
140
162
|
try {
|
|
163
|
+
const entryPath = getGoalDir(entry, workspaceRoot);
|
|
141
164
|
if (!statSync(entryPath).isDirectory()) {
|
|
142
165
|
continue;
|
|
143
166
|
}
|
|
@@ -172,7 +195,8 @@ export function readGoal(goalId, workspaceRoot) {
|
|
|
172
195
|
let goalDescription = "";
|
|
173
196
|
const goalMdPath = join(goalDir, "GOAL.md");
|
|
174
197
|
if (existsSync(goalMdPath)) {
|
|
175
|
-
|
|
198
|
+
const safeGoalMdPath = guardPath(goalMdPath, resolveWorkspaceRoot(workspaceRoot), GOALS_PREFIX);
|
|
199
|
+
goalDescription = readFileSync(safeGoalMdPath, "utf-8");
|
|
176
200
|
}
|
|
177
201
|
return {
|
|
178
202
|
goal_id: status.goal_id,
|
|
@@ -186,26 +210,109 @@ export function readGoal(goalId, workspaceRoot) {
|
|
|
186
210
|
};
|
|
187
211
|
}
|
|
188
212
|
/**
|
|
189
|
-
*
|
|
190
|
-
* JSON 序列化用 JSON.stringify(status, null, 2) + 末尾换行。
|
|
213
|
+
* Replace goal_status.json under the shared cross-process mutation lock.
|
|
191
214
|
*/
|
|
192
215
|
export function writeGoalStatus(goalId, status, workspaceRoot) {
|
|
193
216
|
const goalDir = getGoalDir(goalId, workspaceRoot);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
217
|
+
if (!existsSync(goalDir)) {
|
|
218
|
+
throw new PatchWardenError("goal_not_found", `Goal directory not found: "${goalDir}"`, "Ensure the goal has been created before writing its status.", true, { goal_id: goalId, goal_dir: goalDir });
|
|
219
|
+
}
|
|
220
|
+
const statusPath = getGoalStatusPath(goalId, workspaceRoot);
|
|
221
|
+
if (!existsSync(statusPath)) {
|
|
222
|
+
withFileLockSync(statusPath, () => {
|
|
223
|
+
atomicWriteJsonFileSync(statusPath, normalizeGoalLifecycle(validateGoalStatus(goalId, status, workspaceRoot)));
|
|
224
|
+
}, goalBusyOptions(goalId));
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
mutateGoalStatus(goalId, () => ({ next: status, result: undefined }), workspaceRoot);
|
|
198
228
|
}
|
|
199
229
|
/**
|
|
200
230
|
* 读取 goal_status.json 并 JSON.parse。
|
|
201
231
|
* 如果文件不存在,抛出 PatchWardenError("goal_not_found")。
|
|
202
232
|
*/
|
|
203
233
|
export function readGoalStatus(goalId, workspaceRoot) {
|
|
204
|
-
const
|
|
205
|
-
const statusPath = join(goalDir, "goal_status.json");
|
|
234
|
+
const statusPath = getGoalStatusPath(goalId, workspaceRoot);
|
|
206
235
|
if (!existsSync(statusPath)) {
|
|
207
236
|
throw new PatchWardenError("goal_not_found", `goal_status.json not found for goal "${goalId}" at "${statusPath}"`, "Ensure the goal has been created via createGoal before reading its status.", true, { goal_id: goalId, status_path: statusPath });
|
|
208
237
|
}
|
|
209
|
-
|
|
210
|
-
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
parsed = readJsonObjectFileSync(statusPath);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
throw invalidGoalStatus(goalId, "goal_status.json is not valid JSON");
|
|
244
|
+
}
|
|
245
|
+
return validateGoalStatus(goalId, parsed, workspaceRoot);
|
|
246
|
+
}
|
|
247
|
+
export function mutateGoalStatus(goalId, mutation, workspaceRoot) {
|
|
248
|
+
const statusPath = getGoalStatusPath(goalId, workspaceRoot);
|
|
249
|
+
ensureGoalStatusExists(goalId, statusPath);
|
|
250
|
+
return mutateLockedJsonFileSync(statusPath, (raw) => {
|
|
251
|
+
const current = validateGoalStatus(goalId, raw, workspaceRoot);
|
|
252
|
+
const outcome = mutation(current);
|
|
253
|
+
if (!outcome.next)
|
|
254
|
+
return outcome;
|
|
255
|
+
return {
|
|
256
|
+
next: normalizeGoalLifecycle(validateGoalStatus(goalId, outcome.next, workspaceRoot)),
|
|
257
|
+
result: outcome.result,
|
|
258
|
+
};
|
|
259
|
+
}, goalBusyOptions(goalId));
|
|
260
|
+
}
|
|
261
|
+
export async function mutateGoalStatusAsync(goalId, mutation, workspaceRoot) {
|
|
262
|
+
const statusPath = getGoalStatusPath(goalId, workspaceRoot);
|
|
263
|
+
ensureGoalStatusExists(goalId, statusPath);
|
|
264
|
+
return withFileLock(statusPath, async () => {
|
|
265
|
+
const current = validateGoalStatus(goalId, readJsonObjectFileSync(statusPath), workspaceRoot);
|
|
266
|
+
const outcome = await mutation(current);
|
|
267
|
+
if (outcome.next) {
|
|
268
|
+
atomicWriteJsonFileSync(statusPath, normalizeGoalLifecycle(validateGoalStatus(goalId, outcome.next, workspaceRoot)));
|
|
269
|
+
}
|
|
270
|
+
return outcome.result;
|
|
271
|
+
}, goalBusyOptions(goalId));
|
|
272
|
+
}
|
|
273
|
+
function getGoalStatusPath(goalId, workspaceRoot) {
|
|
274
|
+
const wsRoot = resolveWorkspaceRoot(workspaceRoot);
|
|
275
|
+
return guardPath(join(getGoalDir(goalId, wsRoot), "goal_status.json"), wsRoot, GOALS_PREFIX);
|
|
276
|
+
}
|
|
277
|
+
function ensureGoalStatusExists(goalId, statusPath) {
|
|
278
|
+
if (!existsSync(statusPath)) {
|
|
279
|
+
throw new PatchWardenError("goal_not_found", `goal_status.json not found for goal "${goalId}" at "${statusPath}"`, "Ensure the goal has been created via createGoal before updating it.", true, { goal_id: goalId, status_path: statusPath });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
function validateGoalStatus(goalId, value, workspaceRoot) {
|
|
283
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
284
|
+
throw invalidGoalStatus(goalId, "expected a JSON object");
|
|
285
|
+
}
|
|
286
|
+
const candidate = value;
|
|
287
|
+
if (candidate.goal_id !== goalId) {
|
|
288
|
+
throw invalidGoalStatus(goalId, "goal_id does not match its directory");
|
|
289
|
+
}
|
|
290
|
+
if (typeof candidate.title !== "string" || typeof candidate.repo_path !== "string") {
|
|
291
|
+
throw invalidGoalStatus(goalId, "title or repo_path is missing");
|
|
292
|
+
}
|
|
293
|
+
if (candidate.status !== "active" && candidate.status !== "completed" && candidate.status !== "abandoned") {
|
|
294
|
+
throw invalidGoalStatus(goalId, "status is invalid");
|
|
295
|
+
}
|
|
296
|
+
if (!Array.isArray(candidate.subgoals)) {
|
|
297
|
+
throw invalidGoalStatus(goalId, "subgoals must be an array");
|
|
298
|
+
}
|
|
299
|
+
guardWorkspacePath(candidate.repo_path, resolveWorkspaceRoot(workspaceRoot));
|
|
300
|
+
return candidate;
|
|
301
|
+
}
|
|
302
|
+
function normalizeGoalLifecycle(status) {
|
|
303
|
+
if (status.status === "active" &&
|
|
304
|
+
status.subgoals.length > 0 &&
|
|
305
|
+
status.subgoals.every((subgoal) => subgoal.status === "accepted")) {
|
|
306
|
+
return { ...status, status: "completed", updated_at: new Date().toISOString() };
|
|
307
|
+
}
|
|
308
|
+
return status;
|
|
309
|
+
}
|
|
310
|
+
function invalidGoalStatus(goalId, detail) {
|
|
311
|
+
return new PatchWardenError("invalid_goal_status", `Invalid goal status for "${goalId}": ${detail}.`, "Recreate the Goal Session or restore a valid goal_status.json artifact.", true, { goal_id: goalId });
|
|
312
|
+
}
|
|
313
|
+
function goalBusyOptions(goalId) {
|
|
314
|
+
return {
|
|
315
|
+
waitMs: 0,
|
|
316
|
+
busyError: () => new PatchWardenError("goal_busy", `Goal "${goalId}" is currently being updated.`, "Retry after the active Goal operation finishes.", true, { goal_id: goalId }),
|
|
317
|
+
};
|
|
211
318
|
}
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* 以避免与 goalStore 形成循环依赖。
|
|
8
8
|
*/
|
|
9
9
|
import { suggestNextSubgoal } from "./goalGraph.js";
|
|
10
|
-
import {
|
|
11
|
-
import { mkdirSync
|
|
12
|
-
import { join
|
|
10
|
+
import { getGoalDir } from "./goalStore.js";
|
|
11
|
+
import { mkdirSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { atomicWriteFileSync } from "../utils/atomicFile.js";
|
|
13
14
|
// ── Markdown 生成 ────────────────────────────────────────────────
|
|
14
15
|
/**
|
|
15
16
|
* 生成 Goal Session 交接文档(Markdown 字符串)。
|
|
@@ -170,11 +171,10 @@ export function generateHandoff(goalId, goalStatus, recentDiff, recentTestResult
|
|
|
170
171
|
*/
|
|
171
172
|
export function exportHandoff(goalId, goalStatus, workspaceRoot) {
|
|
172
173
|
const content = generateHandoff(goalId, goalStatus);
|
|
173
|
-
const
|
|
174
|
-
const goalDir = resolve(root, ".patchwarden", "goals", goalId);
|
|
174
|
+
const goalDir = getGoalDir(goalId, workspaceRoot);
|
|
175
175
|
mkdirSync(goalDir, { recursive: true });
|
|
176
176
|
const handoffPath = join(goalDir, "handoff.md");
|
|
177
|
-
|
|
177
|
+
atomicWriteFileSync(handoffPath, content);
|
|
178
178
|
const preview = content.length > 500 ? content.slice(0, 500) + "..." : content;
|
|
179
179
|
return {
|
|
180
180
|
handoff_path: handoffPath,
|