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
|
@@ -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,18 @@
|
|
|
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";
|
|
12
|
+
// Session history writes are metadata-only and may queue behind concurrent
|
|
13
|
+
// Direct workers. Keep the wait bounded, while workspace mutation locks remain
|
|
14
|
+
// fail-fast so an active patch is never hidden by retrying.
|
|
15
|
+
const DIRECT_SESSION_RECORD_LOCK_WAIT_MS = 10_000;
|
|
9
16
|
// ── ID generation ──────────────────────────────────────────────────
|
|
10
17
|
export function generateDirectSessionId() {
|
|
11
18
|
const timestamp = new Date()
|
|
@@ -16,21 +23,32 @@ export function generateDirectSessionId() {
|
|
|
16
23
|
const randomHex = randomBytes(16).toString("hex");
|
|
17
24
|
return `direct_${timestamp}_${randomHex}`;
|
|
18
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Direct session IDs are directory names. Keep one shared, bounded grammar so
|
|
28
|
+
* MCP tools and Control Center routes cannot disagree about traversal input.
|
|
29
|
+
*/
|
|
30
|
+
export function isValidDirectSessionId(sessionId) {
|
|
31
|
+
return /^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(sessionId);
|
|
32
|
+
}
|
|
33
|
+
function guardDirectSessionId(sessionId) {
|
|
34
|
+
if (!isValidDirectSessionId(sessionId)) {
|
|
35
|
+
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 });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
19
38
|
// ── Directory management ───────────────────────────────────────────
|
|
20
|
-
export function createDirectSessionDir(sessionId) {
|
|
21
|
-
|
|
39
|
+
export function createDirectSessionDir(sessionId, config = getConfig()) {
|
|
40
|
+
guardDirectSessionId(sessionId);
|
|
22
41
|
const sessionsDir = getDirectSessionsDir(config);
|
|
23
42
|
guardPath(sessionsDir, config.workspaceRoot, config.directSessionsDir);
|
|
24
43
|
mkdirSync(sessionsDir, { recursive: true });
|
|
25
|
-
const dir =
|
|
26
|
-
guardPath(dir, config.workspaceRoot, config.directSessionsDir);
|
|
44
|
+
const dir = getDirectSessionDir(sessionId, config);
|
|
27
45
|
mkdirSync(dir, { recursive: true });
|
|
28
46
|
return dir;
|
|
29
47
|
}
|
|
30
|
-
export function getDirectSessionDir(sessionId) {
|
|
31
|
-
|
|
48
|
+
export function getDirectSessionDir(sessionId, config = getConfig()) {
|
|
49
|
+
guardDirectSessionId(sessionId);
|
|
32
50
|
const sessionsDir = getDirectSessionsDir(config);
|
|
33
|
-
return resolve(sessionsDir, sessionId);
|
|
51
|
+
return guardPath(resolve(sessionsDir, sessionId), config.workspaceRoot, config.directSessionsDir);
|
|
34
52
|
}
|
|
35
53
|
// ── CRUD ───────────────────────────────────────────────────────────
|
|
36
54
|
export function createDirectSession(input) {
|
|
@@ -48,7 +66,11 @@ export function createDirectSession(input) {
|
|
|
48
66
|
const expiresAt = new Date(now.getTime() + config.directSessionTtlSeconds * 1000).toISOString();
|
|
49
67
|
const record = {
|
|
50
68
|
session_id: sessionId,
|
|
51
|
-
title: input.title || ""
|
|
69
|
+
title: redactSensitiveContent(input.title || "")
|
|
70
|
+
.content
|
|
71
|
+
.replace(/[\r\n\t]+/g, " ")
|
|
72
|
+
.trim()
|
|
73
|
+
.slice(0, 500),
|
|
52
74
|
repo_path: input.repo_path,
|
|
53
75
|
resolved_repo_path: input.resolved_repo_path,
|
|
54
76
|
created_at: now.toISOString(),
|
|
@@ -66,36 +88,55 @@ export function createDirectSession(input) {
|
|
|
66
88
|
audited: false,
|
|
67
89
|
change_artifacts: null,
|
|
68
90
|
};
|
|
69
|
-
|
|
91
|
+
atomicWriteJsonFileSync(join(dir, "session.json"), record);
|
|
70
92
|
return record;
|
|
71
93
|
}
|
|
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);
|
|
94
|
+
export function readDirectSession(sessionId, config = getConfig()) {
|
|
95
|
+
const file = getDirectSessionFile(sessionId, config);
|
|
78
96
|
if (!existsSync(file)) {
|
|
79
97
|
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
98
|
}
|
|
81
|
-
|
|
99
|
+
let value;
|
|
100
|
+
try {
|
|
101
|
+
value = readJsonObjectFileSync(file);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
throw invalidDirectSessionRecord(sessionId, "session.json is not valid JSON");
|
|
105
|
+
}
|
|
106
|
+
return validateDirectSessionRecord(sessionId, value);
|
|
82
107
|
}
|
|
83
108
|
export function updateDirectSession(sessionId, updates) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
109
|
+
return mutateDirectSessionRecord(sessionId, (session) => ({
|
|
110
|
+
...session,
|
|
111
|
+
...updates,
|
|
112
|
+
session_id: session.session_id,
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
export function appendDirectSessionOperation(sessionId, operation, config = getConfig()) {
|
|
116
|
+
return mutateDirectSessionRecord(sessionId, (session) => ({
|
|
117
|
+
...session,
|
|
118
|
+
operations: [
|
|
119
|
+
...session.operations,
|
|
120
|
+
{
|
|
121
|
+
...operation,
|
|
122
|
+
index: session.operations.reduce((max, entry) => Math.max(max, entry.index), -1) + 1,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
}), config);
|
|
89
126
|
}
|
|
90
|
-
export function
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
127
|
+
export function withDirectSessionMutationLock(sessionId, action, config = getConfig()) {
|
|
128
|
+
const lockTarget = join(getDirectSessionDir(sessionId, config), "workspace-mutation");
|
|
129
|
+
return withFileLockSync(lockTarget, action, directSessionBusyOptions(sessionId));
|
|
130
|
+
}
|
|
131
|
+
export function withDirectSessionMutationLockAsync(sessionId, action, config = getConfig()) {
|
|
132
|
+
const lockTarget = join(getDirectSessionDir(sessionId, config), "workspace-mutation");
|
|
133
|
+
return withFileLock(lockTarget, action, directSessionBusyOptions(sessionId));
|
|
94
134
|
}
|
|
95
135
|
export function appendDirectSessionVerificationRun(sessionId, run) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
return mutateDirectSessionRecord(sessionId, (session) => ({
|
|
137
|
+
...session,
|
|
138
|
+
verification_runs: [...session.verification_runs, run],
|
|
139
|
+
}));
|
|
99
140
|
}
|
|
100
141
|
export function validateDirectSessionFreshness(sessionId) {
|
|
101
142
|
let session;
|
|
@@ -124,20 +165,22 @@ export function validateDirectSessionFreshness(sessionId) {
|
|
|
124
165
|
return { valid: true, failure_reason: null, session };
|
|
125
166
|
}
|
|
126
167
|
// ── Finalization ───────────────────────────────────────────────────
|
|
127
|
-
export function finalizeDirectSessionRecord(sessionId,
|
|
168
|
+
export function finalizeDirectSessionRecord(sessionId, changeArtifacts) {
|
|
128
169
|
const dir = getDirectSessionDir(sessionId);
|
|
129
170
|
// Write change artifacts to session directory
|
|
130
|
-
|
|
131
|
-
|
|
171
|
+
atomicWriteJsonFileSync(join(dir, "changed-files.json"), changeArtifacts.changed_files);
|
|
172
|
+
atomicWriteFileSync(join(dir, "diff.patch"), changeArtifacts.diff);
|
|
132
173
|
const summary = {
|
|
133
174
|
changed_files_total: changeArtifacts.changed_files.length,
|
|
134
175
|
additions: changeArtifacts.additions,
|
|
135
176
|
deletions: changeArtifacts.deletions,
|
|
136
177
|
patch_mode: changeArtifacts.patch_mode,
|
|
178
|
+
diff_redacted: changeArtifacts.diff_redacted === true,
|
|
179
|
+
diff_redaction_categories: changeArtifacts.diff_redaction_categories ?? [],
|
|
137
180
|
artifact_hygiene: changeArtifacts.artifact_hygiene,
|
|
138
181
|
};
|
|
139
|
-
|
|
140
|
-
|
|
182
|
+
atomicWriteJsonFileSync(join(dir, "summary.json"), summary);
|
|
183
|
+
atomicWriteFileSync(join(dir, "summary.md"), formatSummaryMd(changeArtifacts));
|
|
141
184
|
return updateDirectSession(sessionId, {
|
|
142
185
|
finalized: true,
|
|
143
186
|
finalized_at: new Date().toISOString(),
|
|
@@ -166,6 +209,55 @@ function extractReason(error) {
|
|
|
166
209
|
return error.message.slice(0, 80);
|
|
167
210
|
return String(error).slice(0, 80);
|
|
168
211
|
}
|
|
212
|
+
function invalidDirectSessionRecord(sessionId, detail) {
|
|
213
|
+
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 });
|
|
214
|
+
}
|
|
215
|
+
function getDirectSessionFile(sessionId, config = getConfig()) {
|
|
216
|
+
const dir = getDirectSessionDir(sessionId, config);
|
|
217
|
+
return guardPath(join(dir, "session.json"), config.workspaceRoot, config.directSessionsDir);
|
|
218
|
+
}
|
|
219
|
+
function mutateDirectSessionRecord(sessionId, mutation, config = getConfig()) {
|
|
220
|
+
const file = getDirectSessionFile(sessionId, config);
|
|
221
|
+
if (!existsSync(file)) {
|
|
222
|
+
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 });
|
|
223
|
+
}
|
|
224
|
+
return mutateLockedJsonFileSync(file, (current) => {
|
|
225
|
+
const session = validateDirectSessionRecord(sessionId, current);
|
|
226
|
+
const next = mutation(session);
|
|
227
|
+
return { next, result: next };
|
|
228
|
+
}, {
|
|
229
|
+
waitMs: DIRECT_SESSION_RECORD_LOCK_WAIT_MS,
|
|
230
|
+
busyError: () => new PatchWardenError("direct_session_busy", `Direct session "${sessionId}" is currently being updated.`, "Retry after the current Direct operation completes.", true, { session_id: sessionId }),
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function directSessionBusyOptions(sessionId) {
|
|
234
|
+
return {
|
|
235
|
+
waitMs: 0,
|
|
236
|
+
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 }),
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function validateDirectSessionRecord(sessionId, value) {
|
|
240
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
241
|
+
throw invalidDirectSessionRecord(sessionId, "session.json must contain an object");
|
|
242
|
+
}
|
|
243
|
+
const record = value;
|
|
244
|
+
if (record.session_id !== sessionId) {
|
|
245
|
+
throw invalidDirectSessionRecord(sessionId, "session_id does not match its directory");
|
|
246
|
+
}
|
|
247
|
+
if (typeof record.resolved_repo_path !== "string" || record.resolved_repo_path.trim() === "") {
|
|
248
|
+
throw invalidDirectSessionRecord(sessionId, "resolved_repo_path is missing");
|
|
249
|
+
}
|
|
250
|
+
if (typeof record.expires_at !== "string" || !Number.isFinite(Date.parse(record.expires_at))) {
|
|
251
|
+
throw invalidDirectSessionRecord(sessionId, "expires_at is invalid");
|
|
252
|
+
}
|
|
253
|
+
if (typeof record.finalized !== "boolean") {
|
|
254
|
+
throw invalidDirectSessionRecord(sessionId, "finalized must be a boolean");
|
|
255
|
+
}
|
|
256
|
+
if (!Array.isArray(record.operations) || !Array.isArray(record.verification_runs)) {
|
|
257
|
+
throw invalidDirectSessionRecord(sessionId, "operation or verification history is invalid");
|
|
258
|
+
}
|
|
259
|
+
return record;
|
|
260
|
+
}
|
|
169
261
|
function formatSummaryMd(artifacts) {
|
|
170
262
|
const lines = [
|
|
171
263
|
"# Direct Session Change Summary",
|
|
@@ -174,6 +266,7 @@ function formatSummaryMd(artifacts) {
|
|
|
174
266
|
`**Additions:** ${artifacts.additions}`,
|
|
175
267
|
`**Deletions:** ${artifacts.deletions}`,
|
|
176
268
|
`**Patch mode:** ${artifacts.patch_mode}`,
|
|
269
|
+
`**Diff redacted:** ${artifacts.diff_redacted === true ? "yes" : "no"}`,
|
|
177
270
|
"",
|
|
178
271
|
"## Artifact Hygiene",
|
|
179
272
|
"",
|
|
@@ -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,37 @@
|
|
|
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";
|
|
28
|
+
import { isDangerousAllowedTestCommand } from "./diagnostics/allowedTestCommandSafety.js";
|
|
26
29
|
// ── Helpers ────────────────────────────────────────────────────────
|
|
27
|
-
function cmd(
|
|
30
|
+
function cmd(command, args = []) {
|
|
28
31
|
try {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
const result = runSimpleProcessSync({
|
|
33
|
+
command,
|
|
34
|
+
args: [...args],
|
|
35
|
+
cwd: process.cwd(),
|
|
36
|
+
timeoutMs: 5000,
|
|
37
|
+
maxStdoutBytes: 64 * 1024,
|
|
38
|
+
maxStderrBytes: 64 * 1024,
|
|
39
|
+
});
|
|
40
|
+
if (result.exitCode !== 0)
|
|
41
|
+
return "";
|
|
42
|
+
return `${result.stdout}${result.stderr}`.trim();
|
|
34
43
|
}
|
|
35
44
|
catch {
|
|
36
45
|
return "";
|
|
@@ -75,7 +84,7 @@ const checkNpmAvailable = {
|
|
|
75
84
|
id: "npm-available",
|
|
76
85
|
description: "npm available",
|
|
77
86
|
run() {
|
|
78
|
-
const npmVer = cmd("npm --version");
|
|
87
|
+
const npmVer = cmd(process.platform === "win32" ? "npm.cmd" : "npm", ["--version"]);
|
|
79
88
|
return [checkResult("npm available", npmVer !== "", npmVer || "npm not found in PATH")];
|
|
80
89
|
},
|
|
81
90
|
};
|
|
@@ -101,7 +110,7 @@ const checkGitAvailable = {
|
|
|
101
110
|
id: "git-available",
|
|
102
111
|
description: "Git available",
|
|
103
112
|
run() {
|
|
104
|
-
const gitVer = cmd("git --version");
|
|
113
|
+
const gitVer = cmd("git", ["--version"]);
|
|
105
114
|
return [warnCheckResult("Git available", gitVer !== "", gitVer || "git not found — runner git.diff will not work")];
|
|
106
115
|
},
|
|
107
116
|
};
|
|
@@ -154,17 +163,9 @@ const checkWorkspaceRoot = {
|
|
|
154
163
|
}
|
|
155
164
|
catch { }
|
|
156
165
|
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
|
-
}
|
|
166
|
+
const unsafeLabel = unsafeWorkspaceRootLabel(ws);
|
|
167
|
+
if (unsafeLabel) {
|
|
168
|
+
results.push(warnResult(`workspaceRoot is ${unsafeLabel}: ${ws} — consider narrowing to a project directory`));
|
|
168
169
|
}
|
|
169
170
|
return results;
|
|
170
171
|
},
|
|
@@ -258,7 +259,9 @@ const checkServerVersion = {
|
|
|
258
259
|
const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
|
|
259
260
|
return [
|
|
260
261
|
checkResult("Server version matches package.json", packageJson.version === PATCHWARDEN_VERSION, `${PATCHWARDEN_VERSION} vs ${packageJson.version}`),
|
|
261
|
-
|
|
262
|
+
process.env.PATCHWARDEN_DESKTOP_RUNTIME === "1"
|
|
263
|
+
? okResult("Manifest preflight covered by compiled desktop runtime checks")
|
|
264
|
+
: checkResult("Manifest preflight script exists", existsSync(resolve(process.cwd(), "scripts/checks/mcp-manifest-check.js")), "scripts/checks/mcp-manifest-check.js"),
|
|
262
265
|
];
|
|
263
266
|
},
|
|
264
267
|
};
|
|
@@ -375,23 +378,23 @@ const checkToolModules = {
|
|
|
375
378
|
description: "Tool module checks",
|
|
376
379
|
run() {
|
|
377
380
|
const newTools = [
|
|
378
|
-
"listTasks",
|
|
379
|
-
"listAgents",
|
|
380
|
-
"cancelTask",
|
|
381
|
-
"killTask",
|
|
382
|
-
"retryTask",
|
|
383
|
-
"getTaskProgress",
|
|
384
|
-
"getTaskSummary",
|
|
385
|
-
"waitForTask",
|
|
386
|
-
"getTaskStdoutTail",
|
|
387
|
-
"healthCheck",
|
|
388
|
-
"auditTask",
|
|
381
|
+
{ name: "listTasks", subdir: "tasks" },
|
|
382
|
+
{ name: "listAgents", subdir: "workspace" },
|
|
383
|
+
{ name: "cancelTask", subdir: "tasks" },
|
|
384
|
+
{ name: "killTask", subdir: "tasks" },
|
|
385
|
+
{ name: "retryTask", subdir: "tasks" },
|
|
386
|
+
{ name: "getTaskProgress", subdir: "tasks" },
|
|
387
|
+
{ name: "getTaskSummary", subdir: "tasks" },
|
|
388
|
+
{ name: "waitForTask", subdir: "tasks" },
|
|
389
|
+
{ name: "getTaskStdoutTail", subdir: "tasks" },
|
|
390
|
+
{ name: "healthCheck", subdir: "diagnostics" },
|
|
391
|
+
{ name: "auditTask", subdir: "diagnostics" },
|
|
389
392
|
];
|
|
390
393
|
const results = [];
|
|
391
394
|
for (const t of newTools) {
|
|
392
|
-
const compiled = resolve(process.cwd(), "dist/tools", `${t}.js`);
|
|
395
|
+
const compiled = resolve(process.cwd(), "dist/tools", t.subdir, `${t.name}.js`);
|
|
393
396
|
const exists = existsSync(compiled);
|
|
394
|
-
results.push(checkResult(`Tool module: ${t}`, exists, exists ? "compiled" : "missing"));
|
|
397
|
+
results.push(checkResult(`Tool module: ${t.name}`, exists, exists ? "compiled" : "missing"));
|
|
395
398
|
}
|
|
396
399
|
return results;
|
|
397
400
|
},
|
|
@@ -401,18 +404,18 @@ const checkDirectToolModules = {
|
|
|
401
404
|
description: "Direct tool module checks",
|
|
402
405
|
run() {
|
|
403
406
|
const directToolModules = [
|
|
404
|
-
"createDirectSession",
|
|
405
|
-
"searchWorkspace",
|
|
406
|
-
"applyPatch",
|
|
407
|
-
"runVerification",
|
|
408
|
-
"finalizeDirectSession",
|
|
409
|
-
"auditSession",
|
|
407
|
+
{ name: "createDirectSession", subdir: "direct" },
|
|
408
|
+
{ name: "searchWorkspace", subdir: "workspace" },
|
|
409
|
+
{ name: "applyPatch", subdir: "workspace" },
|
|
410
|
+
{ name: "runVerification", subdir: "tasks" },
|
|
411
|
+
{ name: "finalizeDirectSession", subdir: "direct" },
|
|
412
|
+
{ name: "auditSession", subdir: "diagnostics" },
|
|
410
413
|
];
|
|
411
414
|
const results = [];
|
|
412
415
|
for (const t of directToolModules) {
|
|
413
|
-
const compiled = resolve(process.cwd(), "dist/tools", `${t}.js`);
|
|
416
|
+
const compiled = resolve(process.cwd(), "dist/tools", t.subdir, `${t.name}.js`);
|
|
414
417
|
const exists = existsSync(compiled);
|
|
415
|
-
results.push(checkResult(`Direct tool module: ${t}`, exists, exists ? "compiled" : "missing"));
|
|
418
|
+
results.push(checkResult(`Direct tool module: ${t.name}`, exists, exists ? "compiled" : "missing"));
|
|
416
419
|
}
|
|
417
420
|
return results;
|
|
418
421
|
},
|
|
@@ -514,8 +517,7 @@ const checkAgentCommands = {
|
|
|
514
517
|
if (!context.config)
|
|
515
518
|
return [];
|
|
516
519
|
const results = [];
|
|
517
|
-
const
|
|
518
|
-
for (const [name, agentCfg] of Object.entries(agents)) {
|
|
520
|
+
for (const [name, agentCfg] of Object.entries(context.config.agents)) {
|
|
519
521
|
const cmdName = agentCfg.command;
|
|
520
522
|
const looksLikePath = isAbsolute(cmdName) || cmdName.includes("/") || cmdName.includes("\\");
|
|
521
523
|
if (looksLikePath) {
|
|
@@ -523,11 +525,20 @@ const checkAgentCommands = {
|
|
|
523
525
|
results.push(warnCheckResult(`Agent "${name}" command available`, agentExists, agentExists ? `Found: ${cmdName}` : `"${cmdName}" does not exist — agent tasks will fail`));
|
|
524
526
|
continue;
|
|
525
527
|
}
|
|
526
|
-
//
|
|
528
|
+
// Use an argumentized lookup utility; never interpolate config into a shell.
|
|
527
529
|
const isWin = process.platform === "win32";
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
530
|
+
let found = "";
|
|
531
|
+
if (isWin) {
|
|
532
|
+
try {
|
|
533
|
+
found = resolveAgentLaunch(name, cmdName, process.platform, process.env.PATH || "", existsSync, context.config.workspaceRoot, typeof agentCfg.adapter === "string" ? agentCfg.adapter : name).command;
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
found = "";
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
else {
|
|
540
|
+
found = cmd("which", [cmdName]);
|
|
541
|
+
}
|
|
531
542
|
results.push(warnCheckResult(`Agent "${name}" command available`, found !== "", found ? `Found: ${found.split("\n")[0]}` : `"${cmdName}" not in PATH — agent tasks will fail`));
|
|
532
543
|
}
|
|
533
544
|
return results;
|
|
@@ -542,12 +553,9 @@ const checkAllowedTestCommandsSafety = {
|
|
|
542
553
|
const results = [];
|
|
543
554
|
const testCmds = context.config.allowedTestCommands || [];
|
|
544
555
|
results.push(checkResult("allowedTestCommands is non-empty", testCmds.length > 0, testCmds.length > 0 ? `${testCmds.length} commands` : "No test commands configured"));
|
|
545
|
-
const dangerous = ["rm -rf", "del /s", "format", "shutdown", "curl |", "wget |"];
|
|
546
556
|
for (const cmdStr of testCmds) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
results.push(warnResult(`allowedTestCommands contains dangerous pattern: "${cmdStr}"`));
|
|
550
|
-
}
|
|
557
|
+
if (isDangerousAllowedTestCommand(cmdStr)) {
|
|
558
|
+
results.push(warnResult(`allowedTestCommands contains dangerous pattern: "${cmdStr}"`));
|
|
551
559
|
}
|
|
552
560
|
}
|
|
553
561
|
return results;
|