patchwarden 1.5.0 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PatchWarden.cmd +32 -2
- package/README.en.md +194 -26
- package/README.md +160 -22
- package/dist/assessments/agentAssessor.d.ts +1 -1
- package/dist/assessments/agentAssessor.js +24 -15
- package/dist/assessments/assessmentStore.d.ts +1 -1
- package/dist/assessments/assessmentStore.js +44 -36
- package/dist/assessments/confirmCli.js +5 -4
- package/dist/config.d.ts +22 -0
- package/dist/config.js +98 -11
- package/dist/control/fileManager.d.ts +2 -0
- package/dist/control/fileManager.js +39 -0
- package/dist/control/middleware/auth.d.ts +16 -0
- package/dist/control/middleware/auth.js +17 -0
- package/dist/control/middleware/static.d.ts +3 -0
- package/dist/control/middleware/static.js +78 -0
- package/dist/control/routeTable.d.ts +24 -0
- package/dist/control/routeTable.js +263 -0
- package/dist/control/routes/audit.d.ts +15 -0
- package/dist/control/routes/audit.js +287 -0
- package/dist/control/routes/evidence.d.ts +4 -0
- package/dist/control/routes/evidence.js +96 -0
- package/dist/control/routes/lineage.d.ts +3 -0
- package/dist/control/routes/lineage.js +71 -0
- package/dist/control/routes/policy.d.ts +3 -0
- package/dist/control/routes/policy.js +81 -0
- package/dist/control/routes/process.d.ts +12 -0
- package/dist/control/routes/process.js +487 -0
- package/dist/control/routes/sessions.d.ts +30 -0
- package/dist/control/routes/sessions.js +287 -0
- package/dist/control/routes/status.d.ts +51 -0
- package/dist/control/routes/status.js +307 -0
- package/dist/control/routes/taskActions.d.ts +21 -0
- package/dist/control/routes/taskActions.js +197 -0
- package/dist/control/routes/tasks.d.ts +24 -0
- package/dist/control/routes/tasks.js +309 -0
- package/dist/control/routes/workspace.d.ts +15 -0
- package/dist/control/routes/workspace.js +220 -0
- package/dist/control/runtime.d.ts +92 -0
- package/dist/control/runtime.js +425 -0
- package/dist/control/server.d.ts +13 -0
- package/dist/control/server.js +150 -0
- package/dist/control/shared.d.ts +59 -0
- package/dist/control/shared.js +341 -0
- package/dist/controlCenter.d.ts +6 -0
- package/dist/controlCenter.js +7 -2197
- package/dist/direct/directAudit.js +234 -225
- package/dist/direct/directGuards.d.ts +2 -1
- package/dist/direct/directGuards.js +72 -37
- package/dist/direct/directPatch.d.ts +6 -1
- package/dist/direct/directPatch.js +55 -8
- package/dist/direct/directSessionStore.d.ts +16 -6
- package/dist/direct/directSessionStore.js +127 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.d.ts +18 -1
- package/dist/doctor.js +614 -373
- package/dist/goal/acceptanceEngine.d.ts +1 -1
- package/dist/goal/goalProgress.js +75 -69
- package/dist/goal/goalReport.d.ts +54 -0
- package/dist/goal/goalReport.js +197 -0
- package/dist/goal/goalStatus.d.ts +8 -0
- package/dist/goal/goalStatus.js +10 -3
- package/dist/goal/goalStore.d.ts +9 -2
- package/dist/goal/goalStore.js +152 -45
- package/dist/goal/handoffExport.js +6 -6
- package/dist/goal/specKitImport.d.ts +63 -0
- package/dist/goal/specKitImport.js +221 -0
- package/dist/goal/subgoalSync.js +13 -10
- package/dist/goal/worktreeManager.d.ts +2 -0
- package/dist/goal/worktreeManager.js +209 -136
- package/dist/httpServer.js +126 -44
- package/dist/index.js +7 -4
- package/dist/logging.d.ts +7 -1
- package/dist/logging.js +13 -3
- package/dist/policy/projectPolicy.js +5 -2
- package/dist/release/releaseGate.d.ts +1 -1
- package/dist/release/releaseGate.js +78 -25
- package/dist/runner/agentInvocation.d.ts +14 -0
- package/dist/runner/agentInvocation.js +96 -4
- package/dist/runner/changeCapture.d.ts +14 -6
- package/dist/runner/changeCapture.js +287 -81
- package/dist/runner/cli.js +7 -6
- package/dist/runner/postTaskCleanup.js +60 -7
- package/dist/runner/processSecurity.d.ts +56 -0
- package/dist/runner/processSecurity.js +373 -0
- package/dist/runner/runTask.d.ts +1 -1
- package/dist/runner/runTask.js +339 -287
- package/dist/runner/simpleProcess.d.ts +3 -0
- package/dist/runner/simpleProcess.js +52 -38
- package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
- package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
- package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
- package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
- package/dist/runner/taskStatusStore.d.ts +11 -0
- package/dist/runner/taskStatusStore.js +24 -0
- package/dist/runner/watch.d.ts +14 -1
- package/dist/runner/watch.js +265 -54
- package/dist/security/contentRedaction.d.ts +6 -0
- package/dist/security/contentRedaction.js +24 -2
- package/dist/security/discoveryTokenStore.d.ts +3 -2
- package/dist/security/discoveryTokenStore.js +17 -2
- package/dist/security/loopbackHost.d.ts +2 -0
- package/dist/security/loopbackHost.js +16 -0
- package/dist/security/pathGuard.js +32 -17
- package/dist/security/planGuard.js +2 -1
- package/dist/security/runtimeGuard.js +9 -6
- package/dist/security/secretComparison.d.ts +3 -0
- package/dist/security/secretComparison.js +11 -0
- package/dist/security/sensitiveGuard.d.ts +5 -0
- package/dist/security/sensitiveGuard.js +27 -8
- package/dist/security/toolInvocationGuard.d.ts +2 -2
- package/dist/security/toolInvocationGuard.js +1 -1
- package/dist/security/workspaceRootGuard.d.ts +8 -0
- package/dist/security/workspaceRootGuard.js +47 -0
- package/dist/smoke-test.js +280 -273
- package/dist/tools/catalog/index.d.ts +4 -0
- package/dist/tools/catalog/index.js +4 -0
- package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
- package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
- package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +28 -15
- package/dist/tools/definitions/toolDefs.d.ts +18 -0
- package/dist/tools/definitions/toolDefs.js +1177 -0
- package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
- package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
- package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +8 -63
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +111 -37
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +10 -10
- package/dist/tools/diagnostics/index.d.ts +6 -0
- package/dist/tools/diagnostics/index.js +6 -0
- package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
- package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
- package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +14 -10
- package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +25 -15
- package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
- package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
- package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +1 -1
- package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +8 -8
- package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +2 -2
- package/dist/tools/direct/finalizeDirectSession.js +86 -0
- package/dist/tools/direct/index.d.ts +3 -0
- package/dist/tools/direct/index.js +3 -0
- package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
- package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
- package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
- package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
- package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
- package/dist/tools/discovery/index.d.ts +3 -0
- package/dist/tools/discovery/index.js +3 -0
- package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
- package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
- package/dist/tools/dispatch/coreDispatch.d.ts +9 -0
- package/dist/tools/dispatch/coreDispatch.js +283 -0
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +12 -0
- package/dist/tools/dispatch/diagnosticDispatch.js +80 -0
- package/dist/tools/dispatch/directDispatch.d.ts +8 -0
- package/dist/tools/dispatch/directDispatch.js +116 -0
- package/dist/tools/dispatch/goalDispatch.d.ts +8 -0
- package/dist/tools/dispatch/goalDispatch.js +92 -0
- package/dist/tools/dispatch/releaseDispatch.d.ts +8 -0
- package/dist/tools/dispatch/releaseDispatch.js +46 -0
- package/dist/tools/dispatch/types.d.ts +23 -0
- package/dist/tools/dispatch/types.js +15 -0
- package/dist/tools/dispatch/validation.d.ts +7 -0
- package/dist/tools/dispatch/validation.js +71 -0
- package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
- package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +2 -2
- package/dist/tools/goals/goalSubgoalTask.js +118 -0
- package/dist/tools/goals/index.d.ts +3 -0
- package/dist/tools/goals/index.js +3 -0
- package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
- package/dist/tools/registry.d.ts +10 -14
- package/dist/tools/registry.js +47 -1651
- package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
- package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
- package/dist/tools/release/index.d.ts +2 -0
- package/dist/tools/release/index.js +2 -0
- package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
- package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
- package/dist/tools/tasks/cancelTask.d.ts +11 -0
- package/dist/tools/tasks/cancelTask.js +85 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +12 -4
- package/dist/tools/{createTask.js → tasks/createTask.js} +46 -31
- package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
- package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
- package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +6 -0
- package/dist/tools/tasks/evidencePack.js +387 -0
- package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
- package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
- package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
- package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
- package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
- package/dist/tools/tasks/index.d.ts +18 -0
- package/dist/tools/tasks/index.js +18 -0
- package/dist/tools/tasks/killTask.d.ts +1 -0
- package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
- package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
- package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
- package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
- package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +2 -2
- package/dist/tools/tasks/retryTask.js +60 -0
- package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
- package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +59 -31
- package/dist/tools/tasks/runVerification.js +38 -0
- package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
- package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
- package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
- package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
- package/dist/tools/workspace/applyPatch.js +53 -0
- package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
- package/dist/tools/workspace/index.d.ts +10 -0
- package/dist/tools/workspace/index.js +10 -0
- package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
- package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
- package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
- package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
- package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
- package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
- package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
- package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
- package/dist/tools/workspace/syncFile.js +134 -0
- package/dist/utils/atomicFile.d.ts +7 -0
- package/dist/utils/atomicFile.js +48 -0
- package/dist/utils/boundedFile.d.ts +8 -0
- package/dist/utils/boundedFile.js +93 -0
- package/dist/utils/lockedJsonFile.d.ts +15 -0
- package/dist/utils/lockedJsonFile.js +224 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +26 -0
- package/dist/utils/stableJson.d.ts +2 -0
- package/dist/utils/stableJson.js +12 -0
- package/dist/version.d.ts +3 -2
- package/dist/version.js +3 -2
- package/dist/watcherStatus.d.ts +2 -2
- package/dist/watcherStatus.js +18 -0
- package/docs/CODE_WIKI.md +1648 -0
- package/docs/agentseal-integration.md +150 -0
- package/docs/architecture.md +23 -0
- package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
- package/docs/assets/patchwarden-oss-demo.gif +0 -0
- package/docs/control-center/README.md +4 -4
- package/docs/dashboard-overview.md +86 -0
- package/docs/demo.md +8 -0
- package/docs/desktop-app.md +175 -0
- package/docs/direct-session-workflow.md +98 -0
- package/docs/evidence-pack-schema.md +215 -0
- package/docs/lineage-evidence-pack-workflow.md +127 -0
- package/docs/mcp-inspector-testing.md +200 -0
- package/docs/open-source-application.md +168 -0
- package/docs/opencode-worker.md +151 -0
- package/docs/openhands-worker.md +181 -0
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +82 -0
- package/docs/spec-kit-integration.md +131 -0
- package/docs/task-safe-review-workflow.md +98 -0
- package/docs/threat-model.md +97 -0
- package/docs/user-feedback.md +40 -0
- package/docs/why-patchwarden.md +110 -0
- package/examples/config.example.json +4 -2
- package/package.json +28 -11
- package/scripts/README.md +15 -6
- package/scripts/build.js +11 -0
- package/scripts/checks/brand-check.js +3 -0
- package/scripts/checks/build-output-check.js +37 -0
- package/scripts/checks/control-center-smoke.js +447 -6
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +39 -33
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +30 -4
- package/scripts/checks/package-manifest-check.js +20 -7
- package/scripts/checks/unit-tests.js +3 -5
- package/scripts/checks/watcher-supervisor-smoke.js +9 -0
- package/scripts/control/get-patchwarden-health.ps1 +1 -1
- package/scripts/control/manage-patchwarden.ps1 +69 -4
- package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
- package/scripts/control/run-background-supervisor.ps1 +41 -0
- package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
- package/scripts/generate-demo-gif.py +320 -0
- package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
- package/scripts/launchers/PatchWarden-Control.cmd +6 -0
- package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
- package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
- package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
- package/scripts/lib/clean-generated-output.js +11 -0
- package/scripts/lib/file-discovery.js +12 -0
- package/scripts/release/desktop-preflight.js +211 -0
- package/scripts/release/pack-clean.js +59 -14
- package/src/assessments/agentAssessor.ts +29 -15
- package/src/assessments/assessmentStore.ts +55 -43
- package/src/assessments/confirmCli.ts +5 -4
- package/src/config.ts +105 -11
- package/src/control/fileManager.ts +40 -0
- package/src/control/middleware/auth.ts +35 -0
- package/src/control/middleware/static.ts +84 -0
- package/src/control/routeTable.ts +301 -0
- package/src/control/routes/audit.ts +329 -0
- package/src/control/routes/evidence.ts +107 -0
- package/src/control/routes/lineage.ts +92 -0
- package/src/control/routes/policy.ts +81 -0
- package/src/control/routes/process.ts +505 -0
- package/src/control/routes/sessions.ts +314 -0
- package/src/control/routes/status.ts +405 -0
- package/src/control/routes/taskActions.ts +214 -0
- package/src/control/routes/tasks.ts +331 -0
- package/src/control/routes/workspace.ts +234 -0
- package/src/control/runtime.ts +508 -0
- package/src/control/server.ts +165 -0
- package/src/control/shared.ts +360 -0
- package/src/controlCenter.ts +7 -2347
- package/src/direct/directAudit.ts +17 -9
- package/src/direct/directGuards.ts +100 -51
- package/src/direct/directPatch.ts +95 -8
- package/src/direct/directSessionStore.ts +211 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +766 -492
- package/src/goal/acceptanceEngine.ts +1 -1
- package/src/goal/goalProgress.ts +17 -13
- package/src/goal/goalReport.ts +263 -0
- package/src/goal/goalStatus.ts +25 -3
- package/src/goal/goalStore.ts +241 -54
- package/src/goal/handoffExport.ts +6 -6
- package/src/goal/specKitImport.ts +355 -0
- package/src/goal/subgoalSync.ts +15 -10
- package/src/goal/worktreeManager.ts +139 -22
- package/src/httpServer.ts +130 -44
- package/src/index.ts +7 -4
- package/src/logging.ts +20 -4
- package/src/policy/projectPolicy.ts +5 -2
- package/src/release/releaseGate.ts +74 -27
- package/src/runner/agentInvocation.ts +126 -4
- package/src/runner/changeCapture.ts +342 -80
- package/src/runner/cli.ts +7 -6
- package/src/runner/postTaskCleanup.ts +55 -7
- package/src/runner/processSecurity.ts +433 -0
- package/src/runner/runTask.ts +441 -292
- package/src/runner/simpleProcess.ts +68 -33
- package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
- package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
- package/src/runner/taskStatusStore.ts +54 -0
- package/src/runner/watch.ts +290 -57
- package/src/security/contentRedaction.ts +31 -2
- package/src/security/discoveryTokenStore.ts +18 -4
- package/src/security/loopbackHost.ts +19 -0
- package/src/security/pathGuard.ts +35 -16
- package/src/security/planGuard.ts +2 -1
- package/src/security/runtimeGuard.ts +9 -6
- package/src/security/secretComparison.ts +13 -0
- package/src/security/sensitiveGuard.ts +27 -8
- package/src/security/toolInvocationGuard.ts +2 -2
- package/src/security/workspaceRootGuard.ts +53 -0
- package/src/smoke-test.ts +276 -273
- package/src/tools/catalog/index.ts +4 -0
- package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
- package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +29 -16
- package/src/tools/definitions/toolDefs.ts +1264 -0
- package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
- package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +196 -89
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +11 -11
- package/src/tools/diagnostics/index.ts +6 -0
- package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
- package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +29 -19
- package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
- package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +9 -9
- package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +11 -9
- package/src/tools/direct/index.ts +3 -0
- package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
- package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
- package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
- package/src/tools/discovery/index.ts +3 -0
- package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
- package/src/tools/dispatch/coreDispatch.ts +379 -0
- package/src/tools/dispatch/diagnosticDispatch.ts +106 -0
- package/src/tools/dispatch/directDispatch.ts +168 -0
- package/src/tools/dispatch/goalDispatch.ts +128 -0
- package/src/tools/dispatch/releaseDispatch.ts +66 -0
- package/src/tools/dispatch/types.ts +24 -0
- package/src/tools/dispatch/validation.ts +78 -0
- package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
- package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +43 -28
- package/src/tools/goals/index.ts +3 -0
- package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
- package/src/tools/registry.ts +54 -1858
- package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
- package/src/tools/release/index.ts +2 -0
- package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
- package/src/tools/tasks/cancelTask.ts +109 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +56 -33
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
- package/src/tools/tasks/evidencePack.ts +504 -0
- package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
- package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
- package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
- package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
- package/src/tools/tasks/index.ts +18 -0
- package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
- package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
- package/src/tools/tasks/retryTask.ts +69 -0
- package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +70 -35
- package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
- package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
- package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
- package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
- package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
- package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
- package/src/tools/workspace/index.ts +10 -0
- package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
- package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
- package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
- package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
- package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
- package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
- package/src/tools/workspace/syncFile.ts +292 -0
- package/src/utils/atomicFile.ts +58 -0
- package/src/utils/boundedFile.ts +116 -0
- package/src/utils/lockedJsonFile.ts +251 -0
- package/src/utils/platform.ts +31 -0
- package/src/utils/stableJson.ts +12 -0
- package/src/version.ts +3 -2
- package/src/watcherStatus.ts +19 -1
- package/ui/desktop-bootstrap.js +11 -0
- package/ui/desktop-bridge.js +36 -0
- package/ui/desktop.css +160 -0
- package/ui/getting-started.js +93 -0
- package/ui/i18n.js +484 -0
- package/ui/log-parser.js +30 -0
- package/ui/pages/audit.html +204 -3
- package/ui/pages/dashboard.html +937 -79
- package/ui/pages/direct-sessions.html +609 -60
- package/ui/pages/getting-started.html +43 -0
- package/ui/pages/logs.html +105 -22
- package/ui/pages/settings.html +101 -0
- package/ui/pages/task-detail.html +468 -438
- package/ui/pages/tasks.html +648 -65
- package/ui/pages/workspace.html +38 -1
- package/ui/settings.js +245 -0
- package/PatchWarden-Control.cmd +0 -6
- package/Restart-PatchWarden-Control.cmd +0 -6
- package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
- package/dist/test/unit/acceptance-engine.test.js +0 -228
- package/dist/test/unit/agent-router.test.d.ts +0 -1
- package/dist/test/unit/agent-router.test.js +0 -287
- package/dist/test/unit/android-doctor.test.d.ts +0 -1
- package/dist/test/unit/android-doctor.test.js +0 -118
- package/dist/test/unit/audit-checks.test.d.ts +0 -1
- package/dist/test/unit/audit-checks.test.js +0 -350
- package/dist/test/unit/chinese-path.test.d.ts +0 -1
- package/dist/test/unit/chinese-path.test.js +0 -91
- package/dist/test/unit/command-guard.test.d.ts +0 -1
- package/dist/test/unit/command-guard.test.js +0 -160
- package/dist/test/unit/diagnose-task.test.d.ts +0 -1
- package/dist/test/unit/diagnose-task.test.js +0 -457
- package/dist/test/unit/direct-guards.test.d.ts +0 -1
- package/dist/test/unit/direct-guards.test.js +0 -213
- package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
- package/dist/test/unit/discovery-token-store.test.js +0 -139
- package/dist/test/unit/evidence-pack.test.d.ts +0 -1
- package/dist/test/unit/evidence-pack.test.js +0 -130
- package/dist/test/unit/goal-graph.test.d.ts +0 -1
- package/dist/test/unit/goal-graph.test.js +0 -298
- package/dist/test/unit/goal-progress.test.d.ts +0 -1
- package/dist/test/unit/goal-progress.test.js +0 -381
- package/dist/test/unit/goal-status.test.d.ts +0 -1
- package/dist/test/unit/goal-status.test.js +0 -215
- package/dist/test/unit/goal-store.test.d.ts +0 -1
- package/dist/test/unit/goal-store.test.js +0 -253
- package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
- package/dist/test/unit/goal-subgoal-task.test.js +0 -55
- package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
- package/dist/test/unit/goal-tools-registry.test.js +0 -190
- package/dist/test/unit/handoff-export.test.d.ts +0 -1
- package/dist/test/unit/handoff-export.test.js +0 -263
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
- package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
- package/dist/test/unit/logging.test.d.ts +0 -1
- package/dist/test/unit/logging.test.js +0 -397
- package/dist/test/unit/path-guard.test.d.ts +0 -1
- package/dist/test/unit/path-guard.test.js +0 -109
- package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
- package/dist/test/unit/post-task-cleanup.test.js +0 -48
- package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
- package/dist/test/unit/project-policy-release-mode.test.js +0 -125
- package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
- package/dist/test/unit/reconcile-tasks.test.js +0 -456
- package/dist/test/unit/release-gate.test.d.ts +0 -1
- package/dist/test/unit/release-gate.test.js +0 -242
- package/dist/test/unit/run-task-loop.test.d.ts +0 -1
- package/dist/test/unit/run-task-loop.test.js +0 -380
- package/dist/test/unit/safe-status.test.d.ts +0 -1
- package/dist/test/unit/safe-status.test.js +0 -165
- package/dist/test/unit/safe-views.test.d.ts +0 -1
- package/dist/test/unit/safe-views.test.js +0 -171
- package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
- package/dist/test/unit/schema-drift-check.test.js +0 -176
- package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
- package/dist/test/unit/sensitive-guard.test.js +0 -104
- package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
- package/dist/test/unit/subgoal-sync.test.js +0 -183
- package/dist/test/unit/sync-file.test.d.ts +0 -1
- package/dist/test/unit/sync-file.test.js +0 -154
- package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
- package/dist/test/unit/tool-invocation-guard.test.js +0 -432
- package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
- package/dist/test/unit/tool-usage-stats.test.js +0 -300
- package/dist/test/unit/toolSearch.test.d.ts +0 -1
- package/dist/test/unit/toolSearch.test.js +0 -571
- package/dist/test/unit/watcher-status.test.d.ts +0 -1
- package/dist/test/unit/watcher-status.test.js +0 -169
- package/dist/test/unit/worktree-manager.test.d.ts +0 -1
- package/dist/test/unit/worktree-manager.test.js +0 -176
- package/dist/tools/applyPatch.js +0 -41
- package/dist/tools/cancelTask.d.ts +0 -30
- package/dist/tools/cancelTask.js +0 -64
- package/dist/tools/evidencePack.js +0 -168
- package/dist/tools/finalizeDirectSession.js +0 -84
- package/dist/tools/goalSubgoalTask.js +0 -110
- package/dist/tools/killTask.d.ts +0 -15
- package/dist/tools/retryTask.js +0 -45
- package/dist/tools/runVerification.js +0 -36
- package/dist/tools/syncFile.js +0 -65
- package/docs/control-center/control-center-daily-driver.md +0 -211
- package/docs/control-center/control-center-mvp.md +0 -205
- package/docs/control-center/control-center-phase2.md +0 -159
- package/docs/release-v0.6.0.md +0 -71
- package/docs/release-v0.6.1.md +0 -75
- package/docs/release-v0.6.4.md +0 -45
- package/src/test/unit/acceptance-engine.test.ts +0 -261
- package/src/test/unit/agent-router.test.ts +0 -342
- package/src/test/unit/android-doctor.test.ts +0 -158
- package/src/test/unit/audit-checks.test.ts +0 -567
- package/src/test/unit/chinese-path.test.ts +0 -106
- package/src/test/unit/command-guard.test.ts +0 -221
- package/src/test/unit/diagnose-task.test.ts +0 -544
- package/src/test/unit/direct-guards.test.ts +0 -297
- package/src/test/unit/discovery-token-store.test.ts +0 -181
- package/src/test/unit/evidence-pack.test.ts +0 -142
- package/src/test/unit/goal-graph.test.ts +0 -347
- package/src/test/unit/goal-progress.test.ts +0 -538
- package/src/test/unit/goal-status.test.ts +0 -270
- package/src/test/unit/goal-store.test.ts +0 -318
- package/src/test/unit/goal-subgoal-task.test.ts +0 -72
- package/src/test/unit/goal-tools-registry.test.ts +0 -243
- package/src/test/unit/handoff-export.test.ts +0 -295
- package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
- package/src/test/unit/logging.test.ts +0 -497
- package/src/test/unit/path-guard.test.ts +0 -150
- package/src/test/unit/post-task-cleanup.test.ts +0 -53
- package/src/test/unit/project-policy-release-mode.test.ts +0 -156
- package/src/test/unit/reconcile-tasks.test.ts +0 -551
- package/src/test/unit/release-gate.test.ts +0 -314
- package/src/test/unit/run-task-loop.test.ts +0 -425
- package/src/test/unit/safe-status.test.ts +0 -187
- package/src/test/unit/safe-views.test.ts +0 -184
- package/src/test/unit/schema-drift-check.test.ts +0 -260
- package/src/test/unit/sensitive-guard.test.ts +0 -124
- package/src/test/unit/subgoal-sync.test.ts +0 -236
- package/src/test/unit/sync-file.test.ts +0 -231
- package/src/test/unit/tool-invocation-guard.test.ts +0 -542
- package/src/test/unit/tool-usage-stats.test.ts +0 -384
- package/src/test/unit/toolSearch.test.ts +0 -756
- package/src/test/unit/watcher-status.test.ts +0 -190
- package/src/test/unit/worktree-manager.test.ts +0 -247
- package/src/tools/cancelTask.ts +0 -70
- package/src/tools/evidencePack.ts +0 -205
- package/src/tools/retryTask.ts +0 -53
- package/src/tools/syncFile.ts +0 -122
- /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
- /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
- /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
- /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
- /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
- /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
- /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
- /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
- /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
- /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
- /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
- /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
- /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
- /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
- /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
- /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
- /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
- /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
- /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
- /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
- /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
- /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
- /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
- /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
- /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
- /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
- /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
- /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
- /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import { writeFileSync } from "node:fs";
|
|
2
1
|
import { join, resolve, relative, isAbsolute } from "node:path";
|
|
3
|
-
import { getConfig } from "../config.js";
|
|
4
2
|
import { isSensitivePath } from "../security/sensitiveGuard.js";
|
|
5
3
|
import { PatchWardenError } from "../errors.js";
|
|
6
4
|
import {
|
|
7
5
|
readDirectSession,
|
|
8
6
|
getDirectSessionDir,
|
|
7
|
+
updateDirectSession,
|
|
8
|
+
withDirectSessionMutationLock,
|
|
9
9
|
type DirectSessionRecord,
|
|
10
10
|
type DirectSessionVerificationRun,
|
|
11
11
|
} from "./directSessionStore.js";
|
|
12
12
|
import type { ChangedFile, ChangeArtifacts } from "../runner/changeCapture.js";
|
|
13
|
+
import { atomicWriteFileSync, atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
13
14
|
|
|
14
15
|
// ── Types ──────────────────────────────────────────────────────────
|
|
15
16
|
|
|
@@ -39,7 +40,7 @@ export interface DirectSessionAuditOutput {
|
|
|
39
40
|
// ── Main audit function ────────────────────────────────────────────
|
|
40
41
|
|
|
41
42
|
export function auditDirectSession(sessionId: string): DirectSessionAuditOutput {
|
|
42
|
-
|
|
43
|
+
return withDirectSessionMutationLock(sessionId, () => {
|
|
43
44
|
const session = readDirectSession(sessionId);
|
|
44
45
|
const sessionDir = getDirectSessionDir(sessionId);
|
|
45
46
|
|
|
@@ -98,6 +99,15 @@ export function auditDirectSession(sessionId: string): DirectSessionAuditOutput
|
|
|
98
99
|
reason_code: sensitiveFiles.length > 0 ? "sensitive_file_modified" : undefined,
|
|
99
100
|
});
|
|
100
101
|
|
|
102
|
+
checks.push({
|
|
103
|
+
name: "sensitive_content",
|
|
104
|
+
result: artifacts.diff_redacted ? "fail" : "pass",
|
|
105
|
+
detail: artifacts.diff_redacted
|
|
106
|
+
? `Credential-like content was redacted from diff evidence (${(artifacts.diff_redaction_categories ?? []).join(", ") || "sensitive_content"}).`
|
|
107
|
+
: "No credential-like content was detected in diff evidence.",
|
|
108
|
+
reason_code: artifacts.diff_redacted ? "sensitive_content_detected" : undefined,
|
|
109
|
+
});
|
|
110
|
+
|
|
101
111
|
// Check 5: node_modules modification
|
|
102
112
|
const nodeModulesChanges = findPathChanges(artifacts, "node_modules");
|
|
103
113
|
checks.push({
|
|
@@ -304,14 +314,12 @@ export function auditDirectSession(sessionId: string): DirectSessionAuditOutput
|
|
|
304
314
|
};
|
|
305
315
|
|
|
306
316
|
// Write audit.json and audit.md
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
"utf-8"
|
|
311
|
-
);
|
|
312
|
-
writeFileSync(auditPath, formatAuditMd(output, checks, session), "utf-8");
|
|
317
|
+
atomicWriteJsonFileSync(join(sessionDir, "audit.json"), output);
|
|
318
|
+
atomicWriteFileSync(auditPath, formatAuditMd(output, checks, session));
|
|
319
|
+
updateDirectSession(sessionId, { audited: true });
|
|
313
320
|
|
|
314
321
|
return output;
|
|
322
|
+
});
|
|
315
323
|
}
|
|
316
324
|
|
|
317
325
|
// ── Helper functions ───────────────────────────────────────────────
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { relative } from "node:path";
|
|
2
2
|
import { existsSync, statSync, openSync, readSync, closeSync } from "node:fs";
|
|
3
3
|
import { PatchWardenError } from "../errors.js";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {
|
|
5
|
+
hasWindowsAlternateDataStream,
|
|
6
|
+
isSensitivePath,
|
|
7
|
+
} from "../security/sensitiveGuard.js";
|
|
8
|
+
import { guardPath, guardWorkspacePath } from "../security/pathGuard.js";
|
|
9
|
+
import { getConfig, type PatchWardenConfig } from "../config.js";
|
|
7
10
|
import type { DirectSessionRecord } from "./directSessionStore.js";
|
|
8
11
|
|
|
9
12
|
// ── Session state guards ───────────────────────────────────────────
|
|
@@ -55,36 +58,34 @@ export function guardDirectPath(
|
|
|
55
58
|
resolvedRepoPath: string,
|
|
56
59
|
workspaceRoot: string
|
|
57
60
|
): string {
|
|
58
|
-
|
|
59
|
-
const resolved = resolve(resolvedRepoPath, filePath);
|
|
60
|
-
const normalizedRepo = resolve(resolvedRepoPath);
|
|
61
|
-
const normalizedWs = resolve(workspaceRoot);
|
|
62
|
-
|
|
63
|
-
// Check path is inside workspace root
|
|
64
|
-
const relToWs = relative(normalizedWs, resolved);
|
|
65
|
-
if (isAbsolute(relToWs) || relToWs.startsWith("..")) {
|
|
61
|
+
if (hasWindowsAlternateDataStream(filePath)) {
|
|
66
62
|
throw new PatchWardenError(
|
|
67
|
-
"
|
|
68
|
-
`Path "${filePath}"
|
|
69
|
-
"Use a relative path
|
|
63
|
+
"windows_ads_path_blocked",
|
|
64
|
+
`Path "${filePath}" contains a Windows alternate data stream suffix.`,
|
|
65
|
+
"Use a normal relative file path without a colon or NTFS stream name.",
|
|
70
66
|
true,
|
|
71
67
|
{ path: filePath, operation: "direct_path_access" }
|
|
72
68
|
);
|
|
73
69
|
}
|
|
74
70
|
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
71
|
+
// Resolve the repo first so a repo-level symlink/junction cannot escape the
|
|
72
|
+
// workspace. guardPath then resolves the candidate or its nearest existing
|
|
73
|
+
// parent, closing intermediate symlink/junction escapes for new files too.
|
|
74
|
+
try {
|
|
75
|
+
const guardedRepo = guardWorkspacePath(resolvedRepoPath, workspaceRoot);
|
|
76
|
+
return guardPath(filePath, guardedRepo);
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (error instanceof PatchWardenError && error.reason === "workspace_path_escape") {
|
|
79
|
+
throw new PatchWardenError(
|
|
80
|
+
"path_outside_repo",
|
|
81
|
+
`Path "${filePath}" resolves outside the session repo.`,
|
|
82
|
+
"Use a relative path inside the session's repo_path.",
|
|
83
|
+
true,
|
|
84
|
+
{ path: filePath, operation: "direct_path_access" }
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
throw error;
|
|
85
88
|
}
|
|
86
|
-
|
|
87
|
-
return resolved;
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
/**
|
|
@@ -96,13 +97,15 @@ export function guardDirectReadPath(
|
|
|
96
97
|
workspaceRoot: string
|
|
97
98
|
): string {
|
|
98
99
|
const resolved = guardDirectPath(filePath, resolvedRepoPath, workspaceRoot);
|
|
99
|
-
const
|
|
100
|
+
const policyPaths = directPolicyPaths(
|
|
101
|
+
filePath,
|
|
102
|
+
resolved,
|
|
103
|
+
resolvedRepoPath,
|
|
104
|
+
workspaceRoot
|
|
105
|
+
);
|
|
100
106
|
|
|
101
107
|
// Block internal PatchWarden paths (sessions, tasks, plans, assessments)
|
|
102
|
-
if (
|
|
103
|
-
normalized.startsWith(".patchwarden/") ||
|
|
104
|
-
normalized.includes("/.patchwarden/")
|
|
105
|
-
) {
|
|
108
|
+
if (policyPaths.some((path) => hasPathSegment(path, ".patchwarden"))) {
|
|
106
109
|
throw new PatchWardenError(
|
|
107
110
|
"internal_patchwarden_path_blocked",
|
|
108
111
|
`Access denied: "${filePath}" is inside the internal .patchwarden directory.`,
|
|
@@ -112,7 +115,7 @@ export function guardDirectReadPath(
|
|
|
112
115
|
);
|
|
113
116
|
}
|
|
114
117
|
|
|
115
|
-
if (isSensitivePath(
|
|
118
|
+
if (policyPaths.some((path) => isSensitivePath(path))) {
|
|
116
119
|
throw new PatchWardenError(
|
|
117
120
|
"sensitive_path_blocked",
|
|
118
121
|
`Access denied: "${filePath}" matches a sensitive file pattern.`,
|
|
@@ -145,13 +148,15 @@ export function guardDirectWritePath(
|
|
|
145
148
|
workspaceRoot: string
|
|
146
149
|
): string {
|
|
147
150
|
const resolved = guardDirectPath(filePath, resolvedRepoPath, workspaceRoot);
|
|
148
|
-
const
|
|
151
|
+
const policyPaths = directPolicyPaths(
|
|
152
|
+
filePath,
|
|
153
|
+
resolved,
|
|
154
|
+
resolvedRepoPath,
|
|
155
|
+
workspaceRoot
|
|
156
|
+
);
|
|
149
157
|
|
|
150
158
|
// Block internal PatchWarden paths
|
|
151
|
-
if (
|
|
152
|
-
normalized.startsWith(".patchwarden/") ||
|
|
153
|
-
normalized.includes("/.patchwarden/")
|
|
154
|
-
) {
|
|
159
|
+
if (policyPaths.some((path) => hasPathSegment(path, ".patchwarden"))) {
|
|
155
160
|
throw new PatchWardenError(
|
|
156
161
|
"internal_patchwarden_path_blocked",
|
|
157
162
|
`Modification denied: "${filePath}" is inside the internal .patchwarden directory.`,
|
|
@@ -161,7 +166,7 @@ export function guardDirectWritePath(
|
|
|
161
166
|
);
|
|
162
167
|
}
|
|
163
168
|
|
|
164
|
-
if (isSensitivePath(
|
|
169
|
+
if (policyPaths.some((path) => isSensitivePath(path))) {
|
|
165
170
|
throw new PatchWardenError(
|
|
166
171
|
"sensitive_path_blocked",
|
|
167
172
|
`Modification denied: "${filePath}" matches a sensitive file pattern.`,
|
|
@@ -172,7 +177,7 @@ export function guardDirectWritePath(
|
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
// Block node_modules
|
|
175
|
-
if (
|
|
180
|
+
if (policyPaths.some((path) => hasPathSegment(path, "node_modules"))) {
|
|
176
181
|
throw new PatchWardenError(
|
|
177
182
|
"blocked_artifact_path",
|
|
178
183
|
`Modification denied: "${filePath}" is inside node_modules.`,
|
|
@@ -183,7 +188,7 @@ export function guardDirectWritePath(
|
|
|
183
188
|
}
|
|
184
189
|
|
|
185
190
|
// Block release/
|
|
186
|
-
if (
|
|
191
|
+
if (policyPaths.some((path) => hasPathSegment(path, "release"))) {
|
|
187
192
|
throw new PatchWardenError(
|
|
188
193
|
"blocked_artifact_path",
|
|
189
194
|
`Modification denied: "${filePath}" is inside the release directory.`,
|
|
@@ -194,7 +199,7 @@ export function guardDirectWritePath(
|
|
|
194
199
|
}
|
|
195
200
|
|
|
196
201
|
// Block dist/
|
|
197
|
-
if (
|
|
202
|
+
if (policyPaths.some((path) => hasPathSegment(path, "dist"))) {
|
|
198
203
|
throw new PatchWardenError(
|
|
199
204
|
"blocked_artifact_path",
|
|
200
205
|
`Modification denied: "${filePath}" is inside the dist directory.`,
|
|
@@ -217,6 +222,27 @@ export function guardDirectWritePath(
|
|
|
217
222
|
return resolved;
|
|
218
223
|
}
|
|
219
224
|
|
|
225
|
+
function directPolicyPaths(
|
|
226
|
+
requestedPath: string,
|
|
227
|
+
resolvedPath: string,
|
|
228
|
+
resolvedRepoPath: string,
|
|
229
|
+
workspaceRoot: string
|
|
230
|
+
): string[] {
|
|
231
|
+
const guardedRepo = guardWorkspacePath(resolvedRepoPath, workspaceRoot);
|
|
232
|
+
return [
|
|
233
|
+
requestedPath.replace(/\\/g, "/"),
|
|
234
|
+
relative(guardedRepo, resolvedPath).replace(/\\/g, "/"),
|
|
235
|
+
];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function hasPathSegment(filePath: string, expectedSegment: string): boolean {
|
|
239
|
+
const expected = expectedSegment.toLowerCase();
|
|
240
|
+
return filePath
|
|
241
|
+
.split("/")
|
|
242
|
+
.filter(Boolean)
|
|
243
|
+
.some((segment) => segment.toLowerCase() === expected);
|
|
244
|
+
}
|
|
245
|
+
|
|
220
246
|
// ── Size guards ────────────────────────────────────────────────────
|
|
221
247
|
|
|
222
248
|
export function guardDirectPatchSize(patchBytes: number): void {
|
|
@@ -232,8 +258,10 @@ export function guardDirectPatchSize(patchBytes: number): void {
|
|
|
232
258
|
}
|
|
233
259
|
}
|
|
234
260
|
|
|
235
|
-
export function guardDirectFileSize(
|
|
236
|
-
|
|
261
|
+
export function guardDirectFileSize(
|
|
262
|
+
fileBytes: number,
|
|
263
|
+
config: PatchWardenConfig = getConfig(),
|
|
264
|
+
): void {
|
|
237
265
|
if (fileBytes > config.directMaxFileBytes) {
|
|
238
266
|
throw new PatchWardenError(
|
|
239
267
|
"file_too_large",
|
|
@@ -257,22 +285,43 @@ const BINARY_EXTENSIONS = new Set([
|
|
|
257
285
|
".wasm", ".node",
|
|
258
286
|
]);
|
|
259
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Maximum bytes to scan for null-byte detection when the extension is not
|
|
290
|
+
* a known binary type. 1 MB catches binary content embedded deep in files
|
|
291
|
+
* (e.g., null bytes at offset 8200) without reading excessively large files.
|
|
292
|
+
*/
|
|
293
|
+
const BINARY_SCAN_LIMIT = 1_048_576; // 1 MB
|
|
294
|
+
const BINARY_SCAN_CHUNK = 65536; // 64 KB per read
|
|
295
|
+
|
|
260
296
|
export function isBinaryFile(filePath: string): boolean {
|
|
261
297
|
const normalized = filePath.replace(/\\/g, "/").toLowerCase();
|
|
262
298
|
for (const ext of BINARY_EXTENSIONS) {
|
|
263
299
|
if (normalized.endsWith(ext)) return true;
|
|
264
300
|
}
|
|
265
301
|
|
|
266
|
-
//
|
|
302
|
+
// Scan file content for null bytes in chunks up to BINARY_SCAN_LIMIT.
|
|
303
|
+
// Previously only the first 8 KB were checked, allowing null bytes at
|
|
304
|
+
// offset 8193+ to bypass detection.
|
|
267
305
|
try {
|
|
306
|
+
const stat = statSync(filePath);
|
|
307
|
+
const scanSize = Math.min(stat.size, BINARY_SCAN_LIMIT);
|
|
268
308
|
const fd = openSync(filePath, "r");
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
309
|
+
try {
|
|
310
|
+
const chunk = Buffer.alloc(BINARY_SCAN_CHUNK);
|
|
311
|
+
let scanned = 0;
|
|
312
|
+
while (scanned < scanSize) {
|
|
313
|
+
const toRead = Math.min(BINARY_SCAN_CHUNK, scanSize - scanned);
|
|
314
|
+
const bytesRead = readSync(fd, chunk, 0, toRead, scanned);
|
|
315
|
+
if (bytesRead === 0) break;
|
|
316
|
+
for (let i = 0; i < bytesRead; i++) {
|
|
317
|
+
if (chunk[i] === 0) return true;
|
|
318
|
+
}
|
|
319
|
+
scanned += bytesRead;
|
|
320
|
+
}
|
|
321
|
+
return false;
|
|
322
|
+
} finally {
|
|
323
|
+
closeSync(fd);
|
|
274
324
|
}
|
|
275
|
-
return false;
|
|
276
325
|
} catch {
|
|
277
326
|
return false;
|
|
278
327
|
}
|
|
@@ -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, resolve } 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
|
|
|
6
7
|
// ── Types ──────────────────────────────────────────────────────────
|
|
7
8
|
|
|
@@ -36,6 +37,12 @@ export function computeContentSha256(content: string): string {
|
|
|
36
37
|
return createHash("sha256").update(content, "utf-8").digest("hex");
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
export interface ApplyPatchOptions {
|
|
41
|
+
expectedSha256?: string;
|
|
42
|
+
maxFileBytes?: number;
|
|
43
|
+
revalidatePath?: () => string;
|
|
44
|
+
}
|
|
45
|
+
|
|
39
46
|
export function validateExpectedSha256(
|
|
40
47
|
filePath: string,
|
|
41
48
|
expectedSha256: string
|
|
@@ -68,7 +75,8 @@ export function validateExpectedSha256(
|
|
|
68
75
|
|
|
69
76
|
export function applyPatchOperations(
|
|
70
77
|
filePath: string,
|
|
71
|
-
operations: PatchOperation[]
|
|
78
|
+
operations: PatchOperation[],
|
|
79
|
+
options: ApplyPatchOptions = {},
|
|
72
80
|
): ApplyPatchResult {
|
|
73
81
|
if (!existsSync(filePath)) {
|
|
74
82
|
throw new PatchWardenError(
|
|
@@ -80,8 +88,40 @@ export function applyPatchOperations(
|
|
|
80
88
|
);
|
|
81
89
|
}
|
|
82
90
|
|
|
83
|
-
|
|
84
|
-
|
|
91
|
+
let descriptor: number | null = null;
|
|
92
|
+
let beforeBytes: Buffer;
|
|
93
|
+
let existingMode: number;
|
|
94
|
+
try {
|
|
95
|
+
descriptor = openSync(filePath, "r");
|
|
96
|
+
const metadata = fstatSync(descriptor);
|
|
97
|
+
if (!metadata.isFile()) {
|
|
98
|
+
throw new PatchWardenError(
|
|
99
|
+
"direct_target_not_file",
|
|
100
|
+
`Patch target is not a regular file: "${filePath}".`,
|
|
101
|
+
"Choose an existing text file inside the Direct session repository.",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
guardFileByteLimit(metadata.size, options.maxFileBytes);
|
|
105
|
+
existingMode = metadata.mode;
|
|
106
|
+
beforeBytes = readFileSync(descriptor);
|
|
107
|
+
} finally {
|
|
108
|
+
if (descriptor !== null) closeSync(descriptor);
|
|
109
|
+
}
|
|
110
|
+
guardFileByteLimit(beforeBytes.length, options.maxFileBytes);
|
|
111
|
+
const beforeSha256 = createHash("sha256").update(beforeBytes).digest("hex");
|
|
112
|
+
if (options.expectedSha256 && beforeSha256 !== options.expectedSha256) {
|
|
113
|
+
throw fileHashMismatch(options.expectedSha256, beforeSha256);
|
|
114
|
+
}
|
|
115
|
+
const beforeContent = beforeBytes.toString("utf-8");
|
|
116
|
+
if (!Buffer.from(beforeContent, "utf-8").equals(beforeBytes)) {
|
|
117
|
+
throw new PatchWardenError(
|
|
118
|
+
"unsupported_text_encoding",
|
|
119
|
+
`File "${filePath}" is not valid UTF-8 text.`,
|
|
120
|
+
"Use a normal task or convert the file to UTF-8 before Direct editing.",
|
|
121
|
+
true,
|
|
122
|
+
{ path: filePath },
|
|
123
|
+
);
|
|
124
|
+
}
|
|
85
125
|
let content = beforeContent;
|
|
86
126
|
let operationsApplied = 0;
|
|
87
127
|
|
|
@@ -91,13 +131,33 @@ export function applyPatchOperations(
|
|
|
91
131
|
}
|
|
92
132
|
|
|
93
133
|
const afterSha256 = computeContentSha256(content);
|
|
134
|
+
guardFileByteLimit(Buffer.byteLength(content, "utf-8"), options.maxFileBytes);
|
|
135
|
+
const sensitive = redactSensitiveContent(content);
|
|
136
|
+
if (sensitive.redacted) {
|
|
137
|
+
throw new PatchWardenError(
|
|
138
|
+
"sensitive_content_blocked",
|
|
139
|
+
`Patch result contains credential-like content (${sensitive.redaction_categories.join(", ")}).`,
|
|
140
|
+
"Remove the sensitive value and retry with placeholders or environment-variable references.",
|
|
141
|
+
true,
|
|
142
|
+
{ path: filePath, redaction_categories: sensitive.redaction_categories },
|
|
143
|
+
);
|
|
144
|
+
}
|
|
94
145
|
const bytesChanged = Math.abs(
|
|
95
146
|
Buffer.byteLength(content, "utf-8") - Buffer.byteLength(beforeContent, "utf-8")
|
|
96
147
|
);
|
|
97
148
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
149
|
+
if (options.revalidatePath && !samePath(options.revalidatePath(), filePath)) {
|
|
150
|
+
throw new PatchWardenError(
|
|
151
|
+
"direct_path_changed_during_write",
|
|
152
|
+
`Patch target path changed while preparing "${filePath}".`,
|
|
153
|
+
"Retry only after concurrent filesystem changes stop.",
|
|
154
|
+
true,
|
|
155
|
+
{ path: filePath, operation: "apply_patch" },
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const currentSha256 = computeFileSha256(filePath);
|
|
159
|
+
if (currentSha256 !== beforeSha256) throw fileHashMismatch(beforeSha256, currentSha256);
|
|
160
|
+
atomicWriteFileSync(filePath, content, { mode: existingMode });
|
|
101
161
|
|
|
102
162
|
return {
|
|
103
163
|
before_sha256: beforeSha256,
|
|
@@ -107,6 +167,33 @@ export function applyPatchOperations(
|
|
|
107
167
|
};
|
|
108
168
|
}
|
|
109
169
|
|
|
170
|
+
function guardFileByteLimit(fileBytes: number, maxFileBytes: number | undefined): void {
|
|
171
|
+
if (maxFileBytes === undefined || fileBytes <= maxFileBytes) return;
|
|
172
|
+
throw new PatchWardenError(
|
|
173
|
+
"file_too_large",
|
|
174
|
+
`File size ${fileBytes} bytes exceeds maximum ${maxFileBytes} bytes.`,
|
|
175
|
+
"Use a normal task for larger files.",
|
|
176
|
+
true,
|
|
177
|
+
{ file_bytes: fileBytes, max_bytes: maxFileBytes },
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function fileHashMismatch(expectedSha256: string, actualSha256: string): PatchWardenError {
|
|
182
|
+
return new PatchWardenError(
|
|
183
|
+
"file_hash_mismatch",
|
|
184
|
+
`File hash mismatch. Expected "${expectedSha256}" but got "${actualSha256}".`,
|
|
185
|
+
"Re-read the file to get the current sha256, then retry the patch.",
|
|
186
|
+
true,
|
|
187
|
+
{ expected_sha256: expectedSha256, actual_sha256: actualSha256 },
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function samePath(left: string, right: string): boolean {
|
|
192
|
+
return process.platform === "win32"
|
|
193
|
+
? left.toLowerCase() === right.toLowerCase()
|
|
194
|
+
: left === right;
|
|
195
|
+
}
|
|
196
|
+
|
|
110
197
|
function applySingleOperation(
|
|
111
198
|
content: string,
|
|
112
199
|
op: PatchOperation
|