patchwarden 1.5.1 → 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 +152 -25
- package/README.md +120 -23
- package/dist/assessments/agentAssessor.js +21 -12
- package/dist/assessments/assessmentStore.js +42 -34
- 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 +46 -1
- package/dist/control/routes/status.js +72 -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 +63 -30
- package/dist/control/server.js +36 -365
- package/dist/control/shared.d.ts +25 -1
- package/dist/control/shared.js +64 -11
- 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 +127 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.js +62 -52
- 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 +1 -1
- package/dist/runner/runTask.js +115 -87
- 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} +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 +256 -48
- 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 +24 -23
- 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} +1 -1
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
- 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 +85 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
- package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
- 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.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} +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/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} +58 -30
- 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 +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 +17 -11
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +39 -29
- 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 +91 -6
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +10 -10
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +27 -3
- 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/{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/assessmentStore.ts +52 -40
- 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 +97 -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 +69 -33
- package/src/control/server.ts +36 -342
- package/src/control/shared.ts +77 -11
- 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 +211 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +73 -59
- 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 +138 -91
- package/src/runner/simpleProcess.ts +66 -31
- 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 +281 -51
- 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 +25 -24
- 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} +99 -32
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
- 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 +109 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
- 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} +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} +69 -34
- 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 +12 -0
- package/ui/pages/dashboard.html +90 -147
- package/ui/pages/direct-sessions.html +123 -26
- 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 +12 -0
- package/ui/pages/tasks.html +54 -8
- 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/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/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/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/{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
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { closeSync, existsSync, fstatSync, openSync, readFileSync } from "node:fs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
3
|
import { PatchWardenError } from "../errors.js";
|
|
4
|
+
import { atomicWriteFileSync } from "../utils/atomicFile.js";
|
|
5
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
5
6
|
// ── Hash helpers ───────────────────────────────────────────────────
|
|
6
7
|
export function computeFileSha256(filePath) {
|
|
7
8
|
const content = readFileSync(filePath);
|
|
@@ -21,12 +22,36 @@ export function validateExpectedSha256(filePath, expectedSha256) {
|
|
|
21
22
|
return currentHash;
|
|
22
23
|
}
|
|
23
24
|
// ── Patch application ──────────────────────────────────────────────
|
|
24
|
-
export function applyPatchOperations(filePath, operations) {
|
|
25
|
+
export function applyPatchOperations(filePath, operations, options = {}) {
|
|
25
26
|
if (!existsSync(filePath)) {
|
|
26
27
|
throw new PatchWardenError("patch_anchor_not_found", `File does not exist: "${filePath}".`, "Ensure the file exists before applying a patch. Direct mode does not support creating new files.", true, { path: filePath });
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
-
|
|
29
|
+
let descriptor = null;
|
|
30
|
+
let beforeBytes;
|
|
31
|
+
let existingMode;
|
|
32
|
+
try {
|
|
33
|
+
descriptor = openSync(filePath, "r");
|
|
34
|
+
const metadata = fstatSync(descriptor);
|
|
35
|
+
if (!metadata.isFile()) {
|
|
36
|
+
throw new PatchWardenError("direct_target_not_file", `Patch target is not a regular file: "${filePath}".`, "Choose an existing text file inside the Direct session repository.");
|
|
37
|
+
}
|
|
38
|
+
guardFileByteLimit(metadata.size, options.maxFileBytes);
|
|
39
|
+
existingMode = metadata.mode;
|
|
40
|
+
beforeBytes = readFileSync(descriptor);
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
if (descriptor !== null)
|
|
44
|
+
closeSync(descriptor);
|
|
45
|
+
}
|
|
46
|
+
guardFileByteLimit(beforeBytes.length, options.maxFileBytes);
|
|
47
|
+
const beforeSha256 = createHash("sha256").update(beforeBytes).digest("hex");
|
|
48
|
+
if (options.expectedSha256 && beforeSha256 !== options.expectedSha256) {
|
|
49
|
+
throw fileHashMismatch(options.expectedSha256, beforeSha256);
|
|
50
|
+
}
|
|
51
|
+
const beforeContent = beforeBytes.toString("utf-8");
|
|
52
|
+
if (!Buffer.from(beforeContent, "utf-8").equals(beforeBytes)) {
|
|
53
|
+
throw new PatchWardenError("unsupported_text_encoding", `File "${filePath}" is not valid UTF-8 text.`, "Use a normal task or convert the file to UTF-8 before Direct editing.", true, { path: filePath });
|
|
54
|
+
}
|
|
30
55
|
let content = beforeContent;
|
|
31
56
|
let operationsApplied = 0;
|
|
32
57
|
for (const op of operations) {
|
|
@@ -34,10 +59,19 @@ export function applyPatchOperations(filePath, operations) {
|
|
|
34
59
|
operationsApplied++;
|
|
35
60
|
}
|
|
36
61
|
const afterSha256 = computeContentSha256(content);
|
|
62
|
+
guardFileByteLimit(Buffer.byteLength(content, "utf-8"), options.maxFileBytes);
|
|
63
|
+
const sensitive = redactSensitiveContent(content);
|
|
64
|
+
if (sensitive.redacted) {
|
|
65
|
+
throw new PatchWardenError("sensitive_content_blocked", `Patch result contains credential-like content (${sensitive.redaction_categories.join(", ")}).`, "Remove the sensitive value and retry with placeholders or environment-variable references.", true, { path: filePath, redaction_categories: sensitive.redaction_categories });
|
|
66
|
+
}
|
|
37
67
|
const bytesChanged = Math.abs(Buffer.byteLength(content, "utf-8") - Buffer.byteLength(beforeContent, "utf-8"));
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
68
|
+
if (options.revalidatePath && !samePath(options.revalidatePath(), filePath)) {
|
|
69
|
+
throw new PatchWardenError("direct_path_changed_during_write", `Patch target path changed while preparing "${filePath}".`, "Retry only after concurrent filesystem changes stop.", true, { path: filePath, operation: "apply_patch" });
|
|
70
|
+
}
|
|
71
|
+
const currentSha256 = computeFileSha256(filePath);
|
|
72
|
+
if (currentSha256 !== beforeSha256)
|
|
73
|
+
throw fileHashMismatch(beforeSha256, currentSha256);
|
|
74
|
+
atomicWriteFileSync(filePath, content, { mode: existingMode });
|
|
41
75
|
return {
|
|
42
76
|
before_sha256: beforeSha256,
|
|
43
77
|
after_sha256: afterSha256,
|
|
@@ -45,6 +79,19 @@ export function applyPatchOperations(filePath, operations) {
|
|
|
45
79
|
bytes_changed: bytesChanged,
|
|
46
80
|
};
|
|
47
81
|
}
|
|
82
|
+
function guardFileByteLimit(fileBytes, maxFileBytes) {
|
|
83
|
+
if (maxFileBytes === undefined || fileBytes <= maxFileBytes)
|
|
84
|
+
return;
|
|
85
|
+
throw new PatchWardenError("file_too_large", `File size ${fileBytes} bytes exceeds maximum ${maxFileBytes} bytes.`, "Use a normal task for larger files.", true, { file_bytes: fileBytes, max_bytes: maxFileBytes });
|
|
86
|
+
}
|
|
87
|
+
function fileHashMismatch(expectedSha256, actualSha256) {
|
|
88
|
+
return new PatchWardenError("file_hash_mismatch", `File hash mismatch. Expected "${expectedSha256}" but got "${actualSha256}".`, "Re-read the file to get the current sha256, then retry the patch.", true, { expected_sha256: expectedSha256, actual_sha256: actualSha256 });
|
|
89
|
+
}
|
|
90
|
+
function samePath(left, right) {
|
|
91
|
+
return process.platform === "win32"
|
|
92
|
+
? left.toLowerCase() === right.toLowerCase()
|
|
93
|
+
: left === right;
|
|
94
|
+
}
|
|
48
95
|
function applySingleOperation(content, op) {
|
|
49
96
|
switch (op.type) {
|
|
50
97
|
case "replace_exact":
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { PatchWardenConfig } from "../config.js";
|
|
1
2
|
import { type RepoSnapshot, type ChangeArtifacts } from "../runner/changeCapture.js";
|
|
2
3
|
export interface DirectSessionOperation {
|
|
3
4
|
index: number;
|
|
4
5
|
timestamp: string;
|
|
5
6
|
path: string;
|
|
6
|
-
|
|
7
|
+
operation_type?: "patch" | "sync";
|
|
8
|
+
source_path?: string;
|
|
9
|
+
before_sha256: string | null;
|
|
7
10
|
after_sha256: string;
|
|
8
11
|
operations_applied: number;
|
|
9
12
|
bytes_changed: number;
|
|
@@ -48,12 +51,19 @@ export interface DirectSessionCreateInput {
|
|
|
48
51
|
snapshot: RepoSnapshot;
|
|
49
52
|
}
|
|
50
53
|
export declare function generateDirectSessionId(): string;
|
|
51
|
-
|
|
52
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Direct session IDs are directory names. Keep one shared, bounded grammar so
|
|
56
|
+
* MCP tools and Control Center routes cannot disagree about traversal input.
|
|
57
|
+
*/
|
|
58
|
+
export declare function isValidDirectSessionId(sessionId: string): boolean;
|
|
59
|
+
export declare function createDirectSessionDir(sessionId: string, config?: PatchWardenConfig): string;
|
|
60
|
+
export declare function getDirectSessionDir(sessionId: string, config?: PatchWardenConfig): string;
|
|
53
61
|
export declare function createDirectSession(input: DirectSessionCreateInput): DirectSessionRecord;
|
|
54
|
-
export declare function readDirectSession(sessionId: string): DirectSessionRecord;
|
|
62
|
+
export declare function readDirectSession(sessionId: string, config?: PatchWardenConfig): DirectSessionRecord;
|
|
55
63
|
export declare function updateDirectSession(sessionId: string, updates: Partial<DirectSessionRecord>): DirectSessionRecord;
|
|
56
|
-
export declare function appendDirectSessionOperation(sessionId: string, operation: DirectSessionOperation): DirectSessionRecord;
|
|
64
|
+
export declare function appendDirectSessionOperation(sessionId: string, operation: DirectSessionOperation, config?: PatchWardenConfig): DirectSessionRecord;
|
|
65
|
+
export declare function withDirectSessionMutationLock<R>(sessionId: string, action: () => R, config?: PatchWardenConfig): R;
|
|
66
|
+
export declare function withDirectSessionMutationLockAsync<R>(sessionId: string, action: () => Promise<R>, config?: PatchWardenConfig): Promise<R>;
|
|
57
67
|
export declare function appendDirectSessionVerificationRun(sessionId: string, run: DirectSessionVerificationRun): DirectSessionRecord;
|
|
58
68
|
export interface DirectSessionValidationResult {
|
|
59
69
|
valid: boolean;
|
|
@@ -61,5 +71,5 @@ export interface DirectSessionValidationResult {
|
|
|
61
71
|
session: DirectSessionRecord | null;
|
|
62
72
|
}
|
|
63
73
|
export declare function validateDirectSessionFreshness(sessionId: string): DirectSessionValidationResult;
|
|
64
|
-
export declare function finalizeDirectSessionRecord(sessionId: string,
|
|
74
|
+
export declare function finalizeDirectSessionRecord(sessionId: string, changeArtifacts: ChangeArtifacts): DirectSessionRecord;
|
|
65
75
|
export declare function computeWorkspaceFingerprint(snapshot: RepoSnapshot): string;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { mkdirSync,
|
|
1
|
+
import { mkdirSync, existsSync, } from "node:fs";
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { resolve, join } from "node:path";
|
|
4
4
|
import { getDirectSessionsDir, getConfig, getRepoDirectAllowedCommands, } from "../config.js";
|
|
5
5
|
import { guardPath } from "../security/pathGuard.js";
|
|
6
6
|
import { PatchWardenError } from "../errors.js";
|
|
7
7
|
import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "../version.js";
|
|
8
|
-
import {
|
|
8
|
+
import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
9
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
10
|
+
import { mutateLockedJsonFileSync, readJsonObjectFileSync, withFileLock, withFileLockSync, } from "../utils/lockedJsonFile.js";
|
|
11
|
+
import { getLastToolCatalogSnapshot } from "../tools/catalog/toolCatalog.js";
|
|
9
12
|
// ── ID generation ──────────────────────────────────────────────────
|
|
10
13
|
export function generateDirectSessionId() {
|
|
11
14
|
const timestamp = new Date()
|
|
@@ -16,21 +19,32 @@ export function generateDirectSessionId() {
|
|
|
16
19
|
const randomHex = randomBytes(16).toString("hex");
|
|
17
20
|
return `direct_${timestamp}_${randomHex}`;
|
|
18
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Direct session IDs are directory names. Keep one shared, bounded grammar so
|
|
24
|
+
* MCP tools and Control Center routes cannot disagree about traversal input.
|
|
25
|
+
*/
|
|
26
|
+
export function isValidDirectSessionId(sessionId) {
|
|
27
|
+
return /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(sessionId);
|
|
28
|
+
}
|
|
29
|
+
function guardDirectSessionId(sessionId) {
|
|
30
|
+
if (!isValidDirectSessionId(sessionId)) {
|
|
31
|
+
throw new PatchWardenError("invalid_session_id", `Direct session ID "${sessionId}" is invalid.`, "Use the session_id returned by create_direct_session.", true, { session_id: sessionId });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
19
34
|
// ── Directory management ───────────────────────────────────────────
|
|
20
|
-
export function createDirectSessionDir(sessionId) {
|
|
21
|
-
|
|
35
|
+
export function createDirectSessionDir(sessionId, config = getConfig()) {
|
|
36
|
+
guardDirectSessionId(sessionId);
|
|
22
37
|
const sessionsDir = getDirectSessionsDir(config);
|
|
23
38
|
guardPath(sessionsDir, config.workspaceRoot, config.directSessionsDir);
|
|
24
39
|
mkdirSync(sessionsDir, { recursive: true });
|
|
25
|
-
const dir =
|
|
26
|
-
guardPath(dir, config.workspaceRoot, config.directSessionsDir);
|
|
40
|
+
const dir = getDirectSessionDir(sessionId, config);
|
|
27
41
|
mkdirSync(dir, { recursive: true });
|
|
28
42
|
return dir;
|
|
29
43
|
}
|
|
30
|
-
export function getDirectSessionDir(sessionId) {
|
|
31
|
-
|
|
44
|
+
export function getDirectSessionDir(sessionId, config = getConfig()) {
|
|
45
|
+
guardDirectSessionId(sessionId);
|
|
32
46
|
const sessionsDir = getDirectSessionsDir(config);
|
|
33
|
-
return resolve(sessionsDir, sessionId);
|
|
47
|
+
return guardPath(resolve(sessionsDir, sessionId), config.workspaceRoot, config.directSessionsDir);
|
|
34
48
|
}
|
|
35
49
|
// ── CRUD ───────────────────────────────────────────────────────────
|
|
36
50
|
export function createDirectSession(input) {
|
|
@@ -48,7 +62,11 @@ export function createDirectSession(input) {
|
|
|
48
62
|
const expiresAt = new Date(now.getTime() + config.directSessionTtlSeconds * 1000).toISOString();
|
|
49
63
|
const record = {
|
|
50
64
|
session_id: sessionId,
|
|
51
|
-
title: input.title || ""
|
|
65
|
+
title: redactSensitiveContent(input.title || "")
|
|
66
|
+
.content
|
|
67
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
68
|
+
.trim()
|
|
69
|
+
.slice(0, 500),
|
|
52
70
|
repo_path: input.repo_path,
|
|
53
71
|
resolved_repo_path: input.resolved_repo_path,
|
|
54
72
|
created_at: now.toISOString(),
|
|
@@ -66,36 +84,55 @@ export function createDirectSession(input) {
|
|
|
66
84
|
audited: false,
|
|
67
85
|
change_artifacts: null,
|
|
68
86
|
};
|
|
69
|
-
|
|
87
|
+
atomicWriteJsonFileSync(join(dir, "session.json"), record);
|
|
70
88
|
return record;
|
|
71
89
|
}
|
|
72
|
-
export function readDirectSession(sessionId) {
|
|
73
|
-
const
|
|
74
|
-
const sessionsDir = getDirectSessionsDir(config);
|
|
75
|
-
const dir = resolve(sessionsDir, sessionId);
|
|
76
|
-
const file = join(dir, "session.json");
|
|
77
|
-
guardPath(file, config.workspaceRoot, config.directSessionsDir);
|
|
90
|
+
export function readDirectSession(sessionId, config = getConfig()) {
|
|
91
|
+
const file = getDirectSessionFile(sessionId, config);
|
|
78
92
|
if (!existsSync(file)) {
|
|
79
93
|
throw new PatchWardenError("session_not_found", `Direct session "${sessionId}" not found.`, "Call create_direct_session first to get a valid session_id.", true, { session_id: sessionId });
|
|
80
94
|
}
|
|
81
|
-
|
|
95
|
+
let value;
|
|
96
|
+
try {
|
|
97
|
+
value = readJsonObjectFileSync(file);
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
throw invalidDirectSessionRecord(sessionId, "session.json is not valid JSON");
|
|
101
|
+
}
|
|
102
|
+
return validateDirectSessionRecord(sessionId, value);
|
|
82
103
|
}
|
|
83
104
|
export function updateDirectSession(sessionId, updates) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
105
|
+
return mutateDirectSessionRecord(sessionId, (session) => ({
|
|
106
|
+
...session,
|
|
107
|
+
...updates,
|
|
108
|
+
session_id: session.session_id,
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
export function appendDirectSessionOperation(sessionId, operation, config = getConfig()) {
|
|
112
|
+
return mutateDirectSessionRecord(sessionId, (session) => ({
|
|
113
|
+
...session,
|
|
114
|
+
operations: [
|
|
115
|
+
...session.operations,
|
|
116
|
+
{
|
|
117
|
+
...operation,
|
|
118
|
+
index: session.operations.reduce((max, entry) => Math.max(max, entry.index), -1) + 1,
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
}), config);
|
|
89
122
|
}
|
|
90
|
-
export function
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
123
|
+
export function withDirectSessionMutationLock(sessionId, action, config = getConfig()) {
|
|
124
|
+
const lockTarget = join(getDirectSessionDir(sessionId, config), "workspace-mutation");
|
|
125
|
+
return withFileLockSync(lockTarget, action, directSessionBusyOptions(sessionId));
|
|
126
|
+
}
|
|
127
|
+
export function withDirectSessionMutationLockAsync(sessionId, action, config = getConfig()) {
|
|
128
|
+
const lockTarget = join(getDirectSessionDir(sessionId, config), "workspace-mutation");
|
|
129
|
+
return withFileLock(lockTarget, action, directSessionBusyOptions(sessionId));
|
|
94
130
|
}
|
|
95
131
|
export function appendDirectSessionVerificationRun(sessionId, run) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
132
|
+
return mutateDirectSessionRecord(sessionId, (session) => ({
|
|
133
|
+
...session,
|
|
134
|
+
verification_runs: [...session.verification_runs, run],
|
|
135
|
+
}));
|
|
99
136
|
}
|
|
100
137
|
export function validateDirectSessionFreshness(sessionId) {
|
|
101
138
|
let session;
|
|
@@ -124,20 +161,22 @@ export function validateDirectSessionFreshness(sessionId) {
|
|
|
124
161
|
return { valid: true, failure_reason: null, session };
|
|
125
162
|
}
|
|
126
163
|
// ── Finalization ───────────────────────────────────────────────────
|
|
127
|
-
export function finalizeDirectSessionRecord(sessionId,
|
|
164
|
+
export function finalizeDirectSessionRecord(sessionId, changeArtifacts) {
|
|
128
165
|
const dir = getDirectSessionDir(sessionId);
|
|
129
166
|
// Write change artifacts to session directory
|
|
130
|
-
|
|
131
|
-
|
|
167
|
+
atomicWriteJsonFileSync(join(dir, "changed-files.json"), changeArtifacts.changed_files);
|
|
168
|
+
atomicWriteFileSync(join(dir, "diff.patch"), changeArtifacts.diff);
|
|
132
169
|
const summary = {
|
|
133
170
|
changed_files_total: changeArtifacts.changed_files.length,
|
|
134
171
|
additions: changeArtifacts.additions,
|
|
135
172
|
deletions: changeArtifacts.deletions,
|
|
136
173
|
patch_mode: changeArtifacts.patch_mode,
|
|
174
|
+
diff_redacted: changeArtifacts.diff_redacted === true,
|
|
175
|
+
diff_redaction_categories: changeArtifacts.diff_redaction_categories ?? [],
|
|
137
176
|
artifact_hygiene: changeArtifacts.artifact_hygiene,
|
|
138
177
|
};
|
|
139
|
-
|
|
140
|
-
|
|
178
|
+
atomicWriteJsonFileSync(join(dir, "summary.json"), summary);
|
|
179
|
+
atomicWriteFileSync(join(dir, "summary.md"), formatSummaryMd(changeArtifacts));
|
|
141
180
|
return updateDirectSession(sessionId, {
|
|
142
181
|
finalized: true,
|
|
143
182
|
finalized_at: new Date().toISOString(),
|
|
@@ -166,6 +205,58 @@ function extractReason(error) {
|
|
|
166
205
|
return error.message.slice(0, 80);
|
|
167
206
|
return String(error).slice(0, 80);
|
|
168
207
|
}
|
|
208
|
+
function invalidDirectSessionRecord(sessionId, detail) {
|
|
209
|
+
return new PatchWardenError("invalid_session_record", `Direct session "${sessionId}" is invalid: ${detail}.`, "Create a new direct session and do not modify its internal session.json file.", true, { session_id: sessionId });
|
|
210
|
+
}
|
|
211
|
+
function getDirectSessionFile(sessionId, config = getConfig()) {
|
|
212
|
+
const dir = getDirectSessionDir(sessionId, config);
|
|
213
|
+
return guardPath(join(dir, "session.json"), config.workspaceRoot, config.directSessionsDir);
|
|
214
|
+
}
|
|
215
|
+
function mutateDirectSessionRecord(sessionId, mutation, config = getConfig()) {
|
|
216
|
+
const file = getDirectSessionFile(sessionId, config);
|
|
217
|
+
if (!existsSync(file)) {
|
|
218
|
+
throw new PatchWardenError("session_not_found", `Direct session "${sessionId}" not found.`, "Call create_direct_session first to get a valid session_id.", true, { session_id: sessionId });
|
|
219
|
+
}
|
|
220
|
+
return mutateLockedJsonFileSync(file, (current) => {
|
|
221
|
+
const session = validateDirectSessionRecord(sessionId, current);
|
|
222
|
+
const next = mutation(session);
|
|
223
|
+
return { next, result: next };
|
|
224
|
+
}, {
|
|
225
|
+
// Session-record appends are short serialized writes. Give Windows
|
|
226
|
+
// antivirus-delayed lock detachment enough time to finish instead of
|
|
227
|
+
// dropping concurrent operation or verification evidence.
|
|
228
|
+
waitMs: 10_000,
|
|
229
|
+
busyError: () => new PatchWardenError("direct_session_busy", `Direct session "${sessionId}" is currently being updated.`, "Retry after the current Direct operation completes.", true, { session_id: sessionId }),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function directSessionBusyOptions(sessionId) {
|
|
233
|
+
return {
|
|
234
|
+
waitMs: 0,
|
|
235
|
+
busyError: () => new PatchWardenError("direct_session_busy", `Direct session "${sessionId}" already has a workspace operation in progress.`, "Wait for the active patch, sync, verification, or finalization to finish, then retry.", true, { session_id: sessionId }),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
function validateDirectSessionRecord(sessionId, value) {
|
|
239
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
240
|
+
throw invalidDirectSessionRecord(sessionId, "session.json must contain an object");
|
|
241
|
+
}
|
|
242
|
+
const record = value;
|
|
243
|
+
if (record.session_id !== sessionId) {
|
|
244
|
+
throw invalidDirectSessionRecord(sessionId, "session_id does not match its directory");
|
|
245
|
+
}
|
|
246
|
+
if (typeof record.resolved_repo_path !== "string" || record.resolved_repo_path.trim() === "") {
|
|
247
|
+
throw invalidDirectSessionRecord(sessionId, "resolved_repo_path is missing");
|
|
248
|
+
}
|
|
249
|
+
if (typeof record.expires_at !== "string" || !Number.isFinite(Date.parse(record.expires_at))) {
|
|
250
|
+
throw invalidDirectSessionRecord(sessionId, "expires_at is invalid");
|
|
251
|
+
}
|
|
252
|
+
if (typeof record.finalized !== "boolean") {
|
|
253
|
+
throw invalidDirectSessionRecord(sessionId, "finalized must be a boolean");
|
|
254
|
+
}
|
|
255
|
+
if (!Array.isArray(record.operations) || !Array.isArray(record.verification_runs)) {
|
|
256
|
+
throw invalidDirectSessionRecord(sessionId, "operation or verification history is invalid");
|
|
257
|
+
}
|
|
258
|
+
return record;
|
|
259
|
+
}
|
|
169
260
|
function formatSummaryMd(artifacts) {
|
|
170
261
|
const lines = [
|
|
171
262
|
"# Direct Session Change Summary",
|
|
@@ -174,6 +265,7 @@ function formatSummaryMd(artifacts) {
|
|
|
174
265
|
`**Additions:** ${artifacts.additions}`,
|
|
175
266
|
`**Deletions:** ${artifacts.deletions}`,
|
|
176
267
|
`**Patch mode:** ${artifacts.patch_mode}`,
|
|
268
|
+
`**Diff redacted:** ${artifacts.diff_redacted === true ? "yes" : "no"}`,
|
|
177
269
|
"",
|
|
178
270
|
"## Artifact Hygiene",
|
|
179
271
|
"",
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
2
1
|
import { join } from "node:path";
|
|
3
2
|
import { getConfig } from "../config.js";
|
|
4
3
|
import { guardDirectCommand } from "../security/commandGuard.js";
|
|
5
4
|
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
6
5
|
import { runSimpleProcess } from "../runner/simpleProcess.js";
|
|
6
|
+
import { resolveTrustedCommandLine } from "../runner/processSecurity.js";
|
|
7
7
|
import { getDirectSessionDir } from "./directSessionStore.js";
|
|
8
|
+
import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
8
9
|
const MAX_TAIL_CHARS = 10_000;
|
|
9
10
|
const DEFAULT_TIMEOUT_SECONDS = 120;
|
|
10
11
|
export async function runDirectVerification(input) {
|
|
@@ -13,7 +14,7 @@ export async function runDirectVerification(input) {
|
|
|
13
14
|
const timeoutSeconds = input.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS;
|
|
14
15
|
const timeoutMs = timeoutSeconds * 1000;
|
|
15
16
|
// Parse command into command + args
|
|
16
|
-
const parts = parseCommand(allowedCommand);
|
|
17
|
+
const parts = parseCommand(allowedCommand, input.resolvedRepoPath);
|
|
17
18
|
if (!parts) {
|
|
18
19
|
throw new Error(`Failed to parse command: "${allowedCommand}"`);
|
|
19
20
|
}
|
|
@@ -73,27 +74,18 @@ export async function runDirectVerification(input) {
|
|
|
73
74
|
],
|
|
74
75
|
spawn_error: result.spawnError,
|
|
75
76
|
};
|
|
76
|
-
|
|
77
|
+
atomicWriteJsonFileSync(join(sessionDir, "verification.json"), verificationJson);
|
|
77
78
|
return { run, log_path: logPath };
|
|
78
79
|
}
|
|
79
80
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
80
|
-
function parseCommand(command) {
|
|
81
|
+
function parseCommand(command, cwd) {
|
|
81
82
|
const trimmed = command.trim();
|
|
82
83
|
if (!trimmed)
|
|
83
84
|
return null;
|
|
84
|
-
|
|
85
|
-
const parts = trimmed.split(/\s+/);
|
|
86
|
-
// On Windows, npm/npx are batch files (.cmd) and spawn() cannot find them
|
|
87
|
-
// without shell resolution. Wrap with cmd.exe /c for those commands.
|
|
88
|
-
if (process.platform === "win32" && (parts[0] === "npm" || parts[0] === "npx")) {
|
|
89
|
-
return {
|
|
90
|
-
command: "cmd",
|
|
91
|
-
args: ["/c", ...parts],
|
|
92
|
-
};
|
|
93
|
-
}
|
|
85
|
+
const invocation = resolveTrustedCommandLine(trimmed, cwd);
|
|
94
86
|
return {
|
|
95
|
-
command:
|
|
96
|
-
args:
|
|
87
|
+
command: invocation.command,
|
|
88
|
+
args: invocation.argsPrefix,
|
|
97
89
|
};
|
|
98
90
|
}
|
|
99
91
|
function truncateTail(content, maxChars) {
|
package/dist/doctor.js
CHANGED
|
@@ -9,28 +9,36 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, statSync, readFileSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
11
11
|
import { isAbsolute, resolve, normalize, join } from "node:path";
|
|
12
|
-
import { execSync } from "node:child_process";
|
|
13
12
|
import { createServer } from "node:net";
|
|
14
13
|
import { getConfig } from "./config.js";
|
|
15
14
|
import { guardPath, guardWorkspacePath } from "./security/pathGuard.js";
|
|
16
15
|
import { isSensitivePath } from "./security/sensitiveGuard.js";
|
|
17
16
|
import { guardPlanContent } from "./security/planGuard.js";
|
|
18
|
-
import { TASK_READ_ONLY_FILES } from "./tools/getTaskFile.js";
|
|
17
|
+
import { TASK_READ_ONLY_FILES } from "./tools/tasks/getTaskFile.js";
|
|
19
18
|
import { getToolDefs } from "./tools/registry.js";
|
|
20
|
-
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile } from "./tools/toolCatalog.js";
|
|
21
|
-
import { buildToolRegistry } from "./tools/toolRegistry.js";
|
|
22
|
-
import { runAllSchemaDriftChecks } from "./tools/schemaDriftCheck.js";
|
|
19
|
+
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile } from "./tools/catalog/toolCatalog.js";
|
|
20
|
+
import { buildToolRegistry } from "./tools/catalog/toolRegistry.js";
|
|
21
|
+
import { runAllSchemaDriftChecks } from "./tools/diagnostics/schemaDriftCheck.js";
|
|
23
22
|
import { runReleaseGateCheck } from "./release/releaseGate.js";
|
|
24
23
|
import { PATCHWARDEN_VERSION } from "./version.js";
|
|
25
24
|
import { logger } from "./logging.js";
|
|
25
|
+
import { unsafeWorkspaceRootLabel } from "./security/workspaceRootGuard.js";
|
|
26
|
+
import { runSimpleProcessSync } from "./runner/simpleProcess.js";
|
|
27
|
+
import { resolveAgentLaunch } from "./runner/agentInvocation.js";
|
|
26
28
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
27
|
-
function cmd(
|
|
29
|
+
function cmd(command, args = []) {
|
|
28
30
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
const result = runSimpleProcessSync({
|
|
32
|
+
command,
|
|
33
|
+
args: [...args],
|
|
34
|
+
cwd: process.cwd(),
|
|
35
|
+
timeoutMs: 5000,
|
|
36
|
+
maxStdoutBytes: 64 * 1024,
|
|
37
|
+
maxStderrBytes: 64 * 1024,
|
|
38
|
+
});
|
|
39
|
+
if (result.exitCode !== 0)
|
|
40
|
+
return "";
|
|
41
|
+
return `${result.stdout}${result.stderr}`.trim();
|
|
34
42
|
}
|
|
35
43
|
catch {
|
|
36
44
|
return "";
|
|
@@ -75,7 +83,7 @@ const checkNpmAvailable = {
|
|
|
75
83
|
id: "npm-available",
|
|
76
84
|
description: "npm available",
|
|
77
85
|
run() {
|
|
78
|
-
const npmVer = cmd("npm --version");
|
|
86
|
+
const npmVer = cmd(process.platform === "win32" ? "npm.cmd" : "npm", ["--version"]);
|
|
79
87
|
return [checkResult("npm available", npmVer !== "", npmVer || "npm not found in PATH")];
|
|
80
88
|
},
|
|
81
89
|
};
|
|
@@ -101,7 +109,7 @@ const checkGitAvailable = {
|
|
|
101
109
|
id: "git-available",
|
|
102
110
|
description: "Git available",
|
|
103
111
|
run() {
|
|
104
|
-
const gitVer = cmd("git --version");
|
|
112
|
+
const gitVer = cmd("git", ["--version"]);
|
|
105
113
|
return [warnCheckResult("Git available", gitVer !== "", gitVer || "git not found — runner git.diff will not work")];
|
|
106
114
|
},
|
|
107
115
|
};
|
|
@@ -154,17 +162,9 @@ const checkWorkspaceRoot = {
|
|
|
154
162
|
}
|
|
155
163
|
catch { }
|
|
156
164
|
results.push(checkResult("workspaceRoot is directory", isDir, ws));
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
{
|
|
160
|
-
{ pattern: /\\Desktop$/, label: "Desktop" },
|
|
161
|
-
{ pattern: /\\Downloads$/, label: "Downloads" },
|
|
162
|
-
{ pattern: /\\Documents$/, label: "Documents" },
|
|
163
|
-
];
|
|
164
|
-
for (const { pattern, label } of dangerousRoots) {
|
|
165
|
-
if (pattern.test(ws)) {
|
|
166
|
-
results.push(warnResult(`workspaceRoot is ${label}: ${ws} — consider narrowing to a project directory`));
|
|
167
|
-
}
|
|
165
|
+
const unsafeLabel = unsafeWorkspaceRootLabel(ws);
|
|
166
|
+
if (unsafeLabel) {
|
|
167
|
+
results.push(warnResult(`workspaceRoot is ${unsafeLabel}: ${ws} — consider narrowing to a project directory`));
|
|
168
168
|
}
|
|
169
169
|
return results;
|
|
170
170
|
},
|
|
@@ -258,7 +258,9 @@ const checkServerVersion = {
|
|
|
258
258
|
const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
|
|
259
259
|
return [
|
|
260
260
|
checkResult("Server version matches package.json", packageJson.version === PATCHWARDEN_VERSION, `${PATCHWARDEN_VERSION} vs ${packageJson.version}`),
|
|
261
|
-
|
|
261
|
+
process.env.PATCHWARDEN_DESKTOP_RUNTIME === "1"
|
|
262
|
+
? okResult("Manifest preflight covered by compiled desktop runtime checks")
|
|
263
|
+
: checkResult("Manifest preflight script exists", existsSync(resolve(process.cwd(), "scripts/checks/mcp-manifest-check.js")), "scripts/checks/mcp-manifest-check.js"),
|
|
262
264
|
];
|
|
263
265
|
},
|
|
264
266
|
};
|
|
@@ -375,23 +377,23 @@ const checkToolModules = {
|
|
|
375
377
|
description: "Tool module checks",
|
|
376
378
|
run() {
|
|
377
379
|
const newTools = [
|
|
378
|
-
"listTasks",
|
|
379
|
-
"listAgents",
|
|
380
|
-
"cancelTask",
|
|
381
|
-
"killTask",
|
|
382
|
-
"retryTask",
|
|
383
|
-
"getTaskProgress",
|
|
384
|
-
"getTaskSummary",
|
|
385
|
-
"waitForTask",
|
|
386
|
-
"getTaskStdoutTail",
|
|
387
|
-
"healthCheck",
|
|
388
|
-
"auditTask",
|
|
380
|
+
{ name: "listTasks", subdir: "tasks" },
|
|
381
|
+
{ name: "listAgents", subdir: "workspace" },
|
|
382
|
+
{ name: "cancelTask", subdir: "tasks" },
|
|
383
|
+
{ name: "killTask", subdir: "tasks" },
|
|
384
|
+
{ name: "retryTask", subdir: "tasks" },
|
|
385
|
+
{ name: "getTaskProgress", subdir: "tasks" },
|
|
386
|
+
{ name: "getTaskSummary", subdir: "tasks" },
|
|
387
|
+
{ name: "waitForTask", subdir: "tasks" },
|
|
388
|
+
{ name: "getTaskStdoutTail", subdir: "tasks" },
|
|
389
|
+
{ name: "healthCheck", subdir: "diagnostics" },
|
|
390
|
+
{ name: "auditTask", subdir: "diagnostics" },
|
|
389
391
|
];
|
|
390
392
|
const results = [];
|
|
391
393
|
for (const t of newTools) {
|
|
392
|
-
const compiled = resolve(process.cwd(), "dist/tools", `${t}.js`);
|
|
394
|
+
const compiled = resolve(process.cwd(), "dist/tools", t.subdir, `${t.name}.js`);
|
|
393
395
|
const exists = existsSync(compiled);
|
|
394
|
-
results.push(checkResult(`Tool module: ${t}`, exists, exists ? "compiled" : "missing"));
|
|
396
|
+
results.push(checkResult(`Tool module: ${t.name}`, exists, exists ? "compiled" : "missing"));
|
|
395
397
|
}
|
|
396
398
|
return results;
|
|
397
399
|
},
|
|
@@ -401,18 +403,18 @@ const checkDirectToolModules = {
|
|
|
401
403
|
description: "Direct tool module checks",
|
|
402
404
|
run() {
|
|
403
405
|
const directToolModules = [
|
|
404
|
-
"createDirectSession",
|
|
405
|
-
"searchWorkspace",
|
|
406
|
-
"applyPatch",
|
|
407
|
-
"runVerification",
|
|
408
|
-
"finalizeDirectSession",
|
|
409
|
-
"auditSession",
|
|
406
|
+
{ name: "createDirectSession", subdir: "direct" },
|
|
407
|
+
{ name: "searchWorkspace", subdir: "workspace" },
|
|
408
|
+
{ name: "applyPatch", subdir: "workspace" },
|
|
409
|
+
{ name: "runVerification", subdir: "tasks" },
|
|
410
|
+
{ name: "finalizeDirectSession", subdir: "direct" },
|
|
411
|
+
{ name: "auditSession", subdir: "diagnostics" },
|
|
410
412
|
];
|
|
411
413
|
const results = [];
|
|
412
414
|
for (const t of directToolModules) {
|
|
413
|
-
const compiled = resolve(process.cwd(), "dist/tools", `${t}.js`);
|
|
415
|
+
const compiled = resolve(process.cwd(), "dist/tools", t.subdir, `${t.name}.js`);
|
|
414
416
|
const exists = existsSync(compiled);
|
|
415
|
-
results.push(checkResult(`Direct tool module: ${t}`, exists, exists ? "compiled" : "missing"));
|
|
417
|
+
results.push(checkResult(`Direct tool module: ${t.name}`, exists, exists ? "compiled" : "missing"));
|
|
416
418
|
}
|
|
417
419
|
return results;
|
|
418
420
|
},
|
|
@@ -514,8 +516,7 @@ const checkAgentCommands = {
|
|
|
514
516
|
if (!context.config)
|
|
515
517
|
return [];
|
|
516
518
|
const results = [];
|
|
517
|
-
const
|
|
518
|
-
for (const [name, agentCfg] of Object.entries(agents)) {
|
|
519
|
+
for (const [name, agentCfg] of Object.entries(context.config.agents)) {
|
|
519
520
|
const cmdName = agentCfg.command;
|
|
520
521
|
const looksLikePath = isAbsolute(cmdName) || cmdName.includes("/") || cmdName.includes("\\");
|
|
521
522
|
if (looksLikePath) {
|
|
@@ -523,11 +524,20 @@ const checkAgentCommands = {
|
|
|
523
524
|
results.push(warnCheckResult(`Agent "${name}" command available`, agentExists, agentExists ? `Found: ${cmdName}` : `"${cmdName}" does not exist — agent tasks will fail`));
|
|
524
525
|
continue;
|
|
525
526
|
}
|
|
526
|
-
//
|
|
527
|
+
// Use an argumentized lookup utility; never interpolate config into a shell.
|
|
527
528
|
const isWin = process.platform === "win32";
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
529
|
+
let found = "";
|
|
530
|
+
if (isWin) {
|
|
531
|
+
try {
|
|
532
|
+
found = resolveAgentLaunch(name, cmdName, process.platform, process.env.PATH || "", existsSync, context.config.workspaceRoot, typeof agentCfg.adapter === "string" ? agentCfg.adapter : name).command;
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
found = "";
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
found = cmd("which", [cmdName]);
|
|
540
|
+
}
|
|
531
541
|
results.push(warnCheckResult(`Agent "${name}" command available`, found !== "", found ? `Found: ${found.split("\n")[0]}` : `"${cmdName}" not in PATH — agent tasks will fail`));
|
|
532
542
|
}
|
|
533
543
|
return results;
|
|
@@ -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;
|