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,30 +1,36 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { createReadStream, lstatSync, readFileSync, readdirSync,
|
|
2
|
+
import { createReadStream, lstatSync, readFileSync, readdirSync, } from "node:fs";
|
|
3
3
|
import { join, relative, resolve, isAbsolute } from "node:path";
|
|
4
4
|
import { execFile } from "node:child_process";
|
|
5
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
5
6
|
import { isSensitivePath } from "../security/sensitiveGuard.js";
|
|
7
|
+
import { nullDevice } from "../utils/platform.js";
|
|
8
|
+
import { buildGitEnvironment, resolveTrustedExecutable } from "./processSecurity.js";
|
|
9
|
+
import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
6
10
|
const MAX_HASH_BYTES = 5 * 1024 * 1024;
|
|
7
11
|
const MAX_SNAPSHOT_FILES = 5000;
|
|
8
12
|
const MAX_DIFF_BYTES = 20 * 1024 * 1024;
|
|
13
|
+
const DIFF_TRUNCATION_MARKER = "\n[PATCHWARDEN DIFF TRUNCATED]\n";
|
|
9
14
|
const SKIP_DIRECTORIES = new Set([".git", ".patchwarden", "node_modules"]);
|
|
10
|
-
export async function captureRepoSnapshot(repoPath) {
|
|
15
|
+
export async function captureRepoSnapshot(repoPath, signal) {
|
|
16
|
+
throwIfAborted(signal);
|
|
11
17
|
const warnings = [];
|
|
12
|
-
const isGitResult = await runGit(repoPath, ["rev-parse", "--is-inside-work-tree"]);
|
|
18
|
+
const isGitResult = await runGit(repoPath, ["rev-parse", "--is-inside-work-tree"], signal);
|
|
13
19
|
const isGit = isGitResult.stdout.trim() === "true";
|
|
14
20
|
let head = null;
|
|
15
21
|
let status = "";
|
|
16
22
|
let paths = [];
|
|
17
|
-
const trackedPaths = new
|
|
18
|
-
const ignoredPaths = new
|
|
23
|
+
const trackedPaths = new Map();
|
|
24
|
+
const ignoredPaths = new Map();
|
|
19
25
|
const dirtyPaths = new Set();
|
|
20
26
|
if (isGit) {
|
|
21
27
|
// The following five git calls are independent of each other and can run in parallel.
|
|
22
28
|
const [headResult, statusResult, trackedResult, ignoredResult, listedResult] = await Promise.all([
|
|
23
|
-
runGit(repoPath, ["rev-parse", "HEAD"]),
|
|
24
|
-
runGit(repoPath, ["status", "--porcelain=v1", "-uall"]),
|
|
25
|
-
runGit(repoPath, ["ls-files", "-z"]),
|
|
26
|
-
runGit(repoPath, ["ls-files", "-o", "-i", "--exclude-standard", "-z"]),
|
|
27
|
-
runGit(repoPath, ["ls-files", "-co", "--exclude-standard", "-z"]),
|
|
29
|
+
runGit(repoPath, ["rev-parse", "HEAD"], signal),
|
|
30
|
+
runGit(repoPath, ["status", "--porcelain=v1", "-uall"], signal),
|
|
31
|
+
runGit(repoPath, ["ls-files", "-z"], signal),
|
|
32
|
+
runGit(repoPath, ["ls-files", "-o", "-i", "--exclude-standard", "-z"], signal),
|
|
33
|
+
runGit(repoPath, ["ls-files", "-co", "--exclude-standard", "-z"], signal),
|
|
28
34
|
]);
|
|
29
35
|
if (headResult.status === 0)
|
|
30
36
|
head = headResult.stdout.trim() || null;
|
|
@@ -55,11 +61,11 @@ export async function captureRepoSnapshot(repoPath) {
|
|
|
55
61
|
}
|
|
56
62
|
if (trackedResult.status === 0) {
|
|
57
63
|
for (const path of trackedResult.stdout.split("\0").filter(Boolean))
|
|
58
|
-
trackedPaths
|
|
64
|
+
addSnapshotPath(trackedPaths, path);
|
|
59
65
|
}
|
|
60
66
|
if (ignoredResult.status === 0) {
|
|
61
67
|
for (const path of ignoredResult.stdout.split("\0").filter(Boolean))
|
|
62
|
-
ignoredPaths
|
|
68
|
+
addSnapshotPath(ignoredPaths, path);
|
|
63
69
|
}
|
|
64
70
|
else {
|
|
65
71
|
warnings.push("git ignored-file discovery failed; ignored classification may be incomplete");
|
|
@@ -67,17 +73,17 @@ export async function captureRepoSnapshot(repoPath) {
|
|
|
67
73
|
if (listedResult.status === 0) {
|
|
68
74
|
paths = [...new Set([
|
|
69
75
|
...listedResult.stdout.split("\0").filter(Boolean),
|
|
70
|
-
...walkWorkspace(repoPath),
|
|
76
|
+
...walkWorkspace(repoPath, signal),
|
|
71
77
|
])];
|
|
72
78
|
}
|
|
73
79
|
else {
|
|
74
80
|
warnings.push("git ls-files failed; using bounded filesystem scan");
|
|
75
|
-
paths = walkWorkspace(repoPath);
|
|
81
|
+
paths = walkWorkspace(repoPath, signal);
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
84
|
else {
|
|
79
85
|
warnings.push("repository is not a Git worktree; diff will contain file-change evidence only");
|
|
80
|
-
paths = walkWorkspace(repoPath);
|
|
86
|
+
paths = walkWorkspace(repoPath, signal);
|
|
81
87
|
}
|
|
82
88
|
if (paths.length > MAX_SNAPSHOT_FILES) {
|
|
83
89
|
warnings.push(`snapshot limited to ${MAX_SNAPSHOT_FILES} files`);
|
|
@@ -85,6 +91,7 @@ export async function captureRepoSnapshot(repoPath) {
|
|
|
85
91
|
}
|
|
86
92
|
const files = {};
|
|
87
93
|
for (const inputPath of paths.sort()) {
|
|
94
|
+
throwIfAborted(signal);
|
|
88
95
|
const normalized = normalizePath(inputPath);
|
|
89
96
|
if (!normalized || normalized.startsWith(".patchwarden/") || isSensitivePath(normalized))
|
|
90
97
|
continue;
|
|
@@ -99,8 +106,8 @@ export async function captureRepoSnapshot(repoPath) {
|
|
|
99
106
|
files[normalized] = {
|
|
100
107
|
size: stat.size,
|
|
101
108
|
sha256,
|
|
102
|
-
tracked: trackedPaths
|
|
103
|
-
ignored: !trackedPaths
|
|
109
|
+
tracked: hasSnapshotPath(trackedPaths, normalized),
|
|
110
|
+
ignored: !hasSnapshotPath(trackedPaths, normalized) && hasSnapshotPath(ignoredPaths, normalized),
|
|
104
111
|
};
|
|
105
112
|
}
|
|
106
113
|
catch {
|
|
@@ -119,32 +126,14 @@ export async function captureRepoSnapshot(repoPath) {
|
|
|
119
126
|
};
|
|
120
127
|
}
|
|
121
128
|
export function writeSnapshot(taskDir, filename, snapshot) {
|
|
122
|
-
|
|
129
|
+
atomicWriteJsonFileSync(join(taskDir, filename), snapshot);
|
|
123
130
|
}
|
|
124
|
-
export async function buildChangeArtifacts(repoPath, before, after) {
|
|
131
|
+
export async function buildChangeArtifacts(repoPath, before, after, signal) {
|
|
132
|
+
throwIfAborted(signal);
|
|
125
133
|
const changedFiles = compareSnapshots(before, after);
|
|
126
134
|
const artifactHygiene = classifyArtifactHygiene(changedFiles);
|
|
127
135
|
const sections = [];
|
|
128
136
|
const scopedPaths = [...new Set(changedFiles.flatMap((file) => file.old_path ? [file.old_path, file.path] : [file.path]))];
|
|
129
|
-
if (before.is_git && after.is_git && scopedPaths.length > 0) {
|
|
130
|
-
if (before.head && after.head && before.head !== after.head) {
|
|
131
|
-
const committed = await runGit(repoPath, ["diff", "--no-color", "--binary", before.head, after.head, "--", ...scopedPaths]);
|
|
132
|
-
if (committed.stdout.trim())
|
|
133
|
-
sections.push("# Changes committed during task\n", committed.stdout.trimEnd());
|
|
134
|
-
}
|
|
135
|
-
const base = after.head || "HEAD";
|
|
136
|
-
const working = await runGit(repoPath, ["diff", "--no-color", "--binary", base, "--", ...scopedPaths]);
|
|
137
|
-
if (working.stdout.trim())
|
|
138
|
-
sections.push("# Staged and unstaged changes\n", working.stdout.trimEnd());
|
|
139
|
-
for (const file of changedFiles.filter((item) => item.change === "added").slice(0, 100)) {
|
|
140
|
-
const tracked = await runGit(repoPath, ["ls-files", "--error-unmatch", "--", file.path]);
|
|
141
|
-
if (tracked.status === 0)
|
|
142
|
-
continue;
|
|
143
|
-
const untracked = await runGit(repoPath, ["diff", "--no-index", "--no-color", "--binary", "--", "/dev/null", file.path]);
|
|
144
|
-
if (untracked.stdout.trim())
|
|
145
|
-
sections.push("# Untracked file\n", untracked.stdout.trimEnd());
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
137
|
const evidence = [
|
|
149
138
|
"# PatchWarden change evidence",
|
|
150
139
|
`# changed_files: ${changedFiles.length}`,
|
|
@@ -152,16 +141,70 @@ export async function buildChangeArtifacts(repoPath, before, after) {
|
|
|
152
141
|
`# workspace_dirty_after: ${after.workspace_dirty}`,
|
|
153
142
|
...changedFiles.map((file) => `# ${file.change}: ${file.path}`),
|
|
154
143
|
].join("\n");
|
|
155
|
-
|
|
156
|
-
|
|
144
|
+
let retainedSectionBytes = 0;
|
|
145
|
+
let diffTruncated = Buffer.byteLength(evidence, "utf-8") > MAX_DIFF_BYTES;
|
|
146
|
+
const sectionBudget = Math.max(0, MAX_DIFF_BYTES
|
|
147
|
+
- Math.min(Buffer.byteLength(evidence, "utf-8"), MAX_DIFF_BYTES)
|
|
148
|
+
- Buffer.byteLength(DIFF_TRUNCATION_MARKER, "utf-8")
|
|
149
|
+
- 2);
|
|
150
|
+
const appendSection = (label, content, commandTruncated = false) => {
|
|
151
|
+
if (!content.trim()) {
|
|
152
|
+
if (commandTruncated)
|
|
153
|
+
diffTruncated = true;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const block = `${sections.length > 0 ? "\n\n" : ""}${label}\n\n${content.trimEnd()}`;
|
|
157
|
+
const remaining = Math.max(0, sectionBudget - retainedSectionBytes);
|
|
158
|
+
const bounded = utf8Prefix(block, remaining);
|
|
159
|
+
if (bounded) {
|
|
160
|
+
sections.push(bounded);
|
|
161
|
+
retainedSectionBytes += Buffer.byteLength(bounded, "utf-8");
|
|
162
|
+
}
|
|
163
|
+
if (commandTruncated || Buffer.byteLength(bounded, "utf-8") < Buffer.byteLength(block, "utf-8")) {
|
|
164
|
+
diffTruncated = true;
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
if (before.is_git && after.is_git && scopedPaths.length > 0) {
|
|
168
|
+
if (before.head && after.head && before.head !== after.head) {
|
|
169
|
+
const committed = await runGit(repoPath, ["diff", "--no-ext-diff", "--no-textconv", "--no-color", "--binary", before.head, after.head, "--", ...scopedPaths], signal);
|
|
170
|
+
appendSection("# Changes committed during task", committed.stdout, committed.truncated);
|
|
171
|
+
}
|
|
172
|
+
if (retainedSectionBytes < sectionBudget) {
|
|
173
|
+
const base = after.head || "HEAD";
|
|
174
|
+
const working = await runGit(repoPath, ["diff", "--no-ext-diff", "--no-textconv", "--no-color", "--binary", base, "--", ...scopedPaths], signal);
|
|
175
|
+
appendSection("# Staged and unstaged changes", working.stdout, working.truncated);
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
diffTruncated = true;
|
|
179
|
+
}
|
|
180
|
+
for (const file of changedFiles.filter((item) => item.change === "added").slice(0, 100)) {
|
|
181
|
+
throwIfAborted(signal);
|
|
182
|
+
if (retainedSectionBytes >= sectionBudget) {
|
|
183
|
+
diffTruncated = true;
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
const tracked = await runGit(repoPath, ["ls-files", "--error-unmatch", "--", file.path], signal);
|
|
187
|
+
if (tracked.status === 0)
|
|
188
|
+
continue;
|
|
189
|
+
const untracked = await runGit(repoPath, ["diff", "--no-ext-diff", "--no-textconv", "--no-index", "--no-color", "--binary", "--", nullDevice, file.path], signal);
|
|
190
|
+
appendSection("# Untracked file", untracked.stdout, untracked.truncated);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const body = sections.join("");
|
|
194
|
+
const rawDiff = `${evidence}\n\n${body || (changedFiles.length ? "(textual patch unavailable; see changed-files.json for hash evidence)" : "(no task file changes detected)")}\n`;
|
|
195
|
+
const sanitized = sanitizeDiffEvidence(rawDiff, MAX_DIFF_BYTES);
|
|
196
|
+
const fullDiff = sanitized.content;
|
|
197
|
+
diffTruncated ||= sanitized.truncated;
|
|
157
198
|
const additions = fullDiff.split(/\r?\n/).filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
|
|
158
199
|
const deletions = fullDiff.split(/\r?\n/).filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
|
|
159
|
-
const fileStats = await buildFileStats(repoPath, before, after, changedFiles);
|
|
200
|
+
const fileStats = await buildFileStats(repoPath, before, after, changedFiles, signal);
|
|
160
201
|
return {
|
|
161
202
|
changed_files: changedFiles,
|
|
162
203
|
diff: fullDiff,
|
|
163
204
|
diff_available: changedFiles.length > 0,
|
|
164
|
-
diff_truncated:
|
|
205
|
+
diff_truncated: diffTruncated,
|
|
206
|
+
diff_redacted: sanitized.redacted,
|
|
207
|
+
diff_redaction_categories: sanitized.redaction_categories,
|
|
165
208
|
diff_size_bytes: Buffer.byteLength(fullDiff, "utf-8"),
|
|
166
209
|
additions,
|
|
167
210
|
deletions,
|
|
@@ -177,8 +220,32 @@ export async function buildChangeArtifacts(repoPath, before, after) {
|
|
|
177
220
|
artifact_hygiene: artifactHygiene,
|
|
178
221
|
};
|
|
179
222
|
}
|
|
180
|
-
|
|
223
|
+
export function sanitizeDiffEvidence(input, maxBytes = MAX_DIFF_BYTES) {
|
|
224
|
+
if (!Number.isInteger(maxBytes) || maxBytes <= 0) {
|
|
225
|
+
throw new Error("maxBytes must be a positive integer");
|
|
226
|
+
}
|
|
227
|
+
const redaction = redactSensitiveContent(input);
|
|
228
|
+
const encodedBytes = Buffer.byteLength(redaction.content, "utf-8");
|
|
229
|
+
if (encodedBytes <= maxBytes) {
|
|
230
|
+
return {
|
|
231
|
+
content: redaction.content,
|
|
232
|
+
truncated: false,
|
|
233
|
+
redacted: redaction.redacted,
|
|
234
|
+
redaction_categories: redaction.redaction_categories,
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const marker = utf8Prefix(DIFF_TRUNCATION_MARKER, maxBytes);
|
|
238
|
+
const markerBytes = Buffer.byteLength(marker, "utf-8");
|
|
239
|
+
return {
|
|
240
|
+
content: utf8Prefix(redaction.content, Math.max(0, maxBytes - markerBytes)) + marker,
|
|
241
|
+
truncated: true,
|
|
242
|
+
redacted: redaction.redacted,
|
|
243
|
+
redaction_categories: redaction.redaction_categories,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
async function buildFileStats(repoPath, before, after, changedFiles, signal) {
|
|
181
247
|
const results = await Promise.all(changedFiles.map(async (file) => {
|
|
248
|
+
throwIfAborted(signal);
|
|
182
249
|
let additions = 0;
|
|
183
250
|
let deletions = 0;
|
|
184
251
|
const paths = file.old_path ? [file.old_path, file.path] : [file.path];
|
|
@@ -188,7 +255,7 @@ async function buildFileStats(repoPath, before, after, changedFiles) {
|
|
|
188
255
|
ranges.push([before.head, after.head]);
|
|
189
256
|
}
|
|
190
257
|
ranges.push([after.head || "HEAD"]);
|
|
191
|
-
const rangeResults = await Promise.all(ranges.map((range) => runGit(repoPath, ["diff", "--numstat", ...range, "--", ...paths])));
|
|
258
|
+
const rangeResults = await Promise.all(ranges.map((range) => runGit(repoPath, ["diff", "--no-ext-diff", "--no-textconv", "--numstat", ...range, "--", ...paths], signal)));
|
|
192
259
|
for (const result of rangeResults) {
|
|
193
260
|
for (const line of result.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
194
261
|
const [added, removed] = line.split(/\s+/);
|
|
@@ -215,19 +282,30 @@ function countLines(content) {
|
|
|
215
282
|
return 0;
|
|
216
283
|
return content.split(/\r?\n/).length - (content.endsWith("\n") ? 1 : 0);
|
|
217
284
|
}
|
|
218
|
-
export function compareSnapshots(before, after) {
|
|
219
|
-
const paths = [...new Set([...Object.keys(before.files), ...Object.keys(after.files)])].sort();
|
|
285
|
+
export function compareSnapshots(before, after, platform = process.platform) {
|
|
220
286
|
const changed = [];
|
|
221
|
-
for (const path of
|
|
222
|
-
const left = before.files[path];
|
|
223
|
-
const right = after.files[path];
|
|
287
|
+
for (const { path, leftPath, rightPath, left, right } of pairSnapshotFiles(before.files, after.files, platform)) {
|
|
224
288
|
if (!left && right) {
|
|
225
289
|
changed.push(classifyChangedFile(path, "added", null, right));
|
|
226
290
|
}
|
|
227
291
|
else if (left && !right) {
|
|
228
292
|
changed.push(classifyChangedFile(path, "deleted", left, null));
|
|
229
293
|
}
|
|
230
|
-
else if (left.sha256
|
|
294
|
+
else if (left && right && left.sha256 === right.sha256 && leftPath !== rightPath) {
|
|
295
|
+
// Windows lookup is case-insensitive, but a case-only Git rename is still
|
|
296
|
+
// auditable work and must not disappear from the evidence set.
|
|
297
|
+
changed.push({
|
|
298
|
+
path: rightPath,
|
|
299
|
+
old_path: leftPath,
|
|
300
|
+
change: "renamed",
|
|
301
|
+
before_sha256: left.sha256,
|
|
302
|
+
after_sha256: right.sha256,
|
|
303
|
+
tracked: left.tracked || right.tracked,
|
|
304
|
+
ignored: right.ignored,
|
|
305
|
+
kind: classifyPathKind(rightPath),
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
else if (left && right && left.sha256 !== right.sha256) {
|
|
231
309
|
changed.push(classifyChangedFile(path, "modified", left, right));
|
|
232
310
|
}
|
|
233
311
|
}
|
|
@@ -280,9 +358,11 @@ export function emptyArtifactHygiene() {
|
|
|
280
358
|
* Extract files that are dirty in the workspace but outside the target repo.
|
|
281
359
|
* Used to establish a baseline before task execution.
|
|
282
360
|
*/
|
|
283
|
-
export function extractExternalDirtyFiles(workspaceSnapshot, repoPath, workspaceRoot) {
|
|
361
|
+
export function extractExternalDirtyFiles(workspaceSnapshot, repoPath, workspaceRoot, platform = process.platform) {
|
|
284
362
|
const dirtyFiles = [];
|
|
285
|
-
const dirtyPathSet = new
|
|
363
|
+
const dirtyPathSet = new Map();
|
|
364
|
+
for (const path of workspaceSnapshot.dirty_paths)
|
|
365
|
+
addSnapshotPath(dirtyPathSet, path, platform);
|
|
286
366
|
for (const [path, fingerprint] of Object.entries(workspaceSnapshot.files)) {
|
|
287
367
|
const absolutePath = resolve(workspaceRoot, path);
|
|
288
368
|
const rel = relative(repoPath, absolutePath);
|
|
@@ -292,7 +372,7 @@ export function extractExternalDirtyFiles(workspaceSnapshot, repoPath, workspace
|
|
|
292
372
|
// 1. Git reports it as dirty (modified/added/deleted/untracked) via dirty_paths, OR
|
|
293
373
|
// 2. It's not tracked by git (untracked file), OR
|
|
294
374
|
// 3. It's explicitly ignored
|
|
295
|
-
const isDirty = dirtyPathSet
|
|
375
|
+
const isDirty = hasSnapshotPath(dirtyPathSet, path, platform);
|
|
296
376
|
const isUntracked = !fingerprint.tracked;
|
|
297
377
|
const isIgnored = fingerprint.ignored;
|
|
298
378
|
if (isDirty || isUntracked || isIgnored) {
|
|
@@ -312,10 +392,17 @@ export function extractExternalDirtyFiles(workspaceSnapshot, repoPath, workspace
|
|
|
312
392
|
* Returns files that are NEW (not present in baseline) or CHANGED
|
|
313
393
|
* (same path but different sha256, meaning the task modified them).
|
|
314
394
|
*/
|
|
315
|
-
export function findNewExternalDirtyFiles(baseline, current) {
|
|
316
|
-
const baselineMap = new Map(
|
|
395
|
+
export function findNewExternalDirtyFiles(baseline, current, platform = process.platform) {
|
|
396
|
+
const baselineMap = new Map();
|
|
397
|
+
for (const file of baseline) {
|
|
398
|
+
const key = comparableSnapshotPath(file.path, platform);
|
|
399
|
+
baselineMap.set(key, [...(baselineMap.get(key) ?? []), file]);
|
|
400
|
+
}
|
|
317
401
|
return current.filter((f) => {
|
|
318
|
-
const
|
|
402
|
+
const candidates = baselineMap.get(comparableSnapshotPath(f.path, platform)) ?? [];
|
|
403
|
+
const baselineFile = candidates.length <= 1
|
|
404
|
+
? candidates[0]
|
|
405
|
+
: candidates.find((candidate) => normalizePath(candidate.path) === normalizePath(f.path));
|
|
319
406
|
if (!baselineFile)
|
|
320
407
|
return true; // New path — definitely new
|
|
321
408
|
// Same path but content changed during task execution
|
|
@@ -481,9 +568,72 @@ function classificationReason(change) {
|
|
|
481
568
|
function normalizePath(value) {
|
|
482
569
|
return value.replace(/\\/g, "/");
|
|
483
570
|
}
|
|
484
|
-
function
|
|
571
|
+
function comparableSnapshotPath(value, platform = process.platform) {
|
|
572
|
+
const normalized = normalizePath(value);
|
|
573
|
+
return platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
574
|
+
}
|
|
575
|
+
function addSnapshotPath(index, value, platform = process.platform) {
|
|
576
|
+
const normalized = normalizePath(value);
|
|
577
|
+
const key = comparableSnapshotPath(normalized, platform);
|
|
578
|
+
const exactPaths = index.get(key) ?? new Set();
|
|
579
|
+
exactPaths.add(normalized);
|
|
580
|
+
index.set(key, exactPaths);
|
|
581
|
+
}
|
|
582
|
+
function hasSnapshotPath(index, value, platform = process.platform) {
|
|
583
|
+
const normalized = normalizePath(value);
|
|
584
|
+
const exactPaths = index.get(comparableSnapshotPath(normalized, platform));
|
|
585
|
+
if (!exactPaths)
|
|
586
|
+
return false;
|
|
587
|
+
return platform !== "win32" || exactPaths.size <= 1 || exactPaths.has(normalized);
|
|
588
|
+
}
|
|
589
|
+
function pairSnapshotFiles(before, after, platform) {
|
|
590
|
+
const beforeGroups = groupSnapshotFiles(before, platform);
|
|
591
|
+
const afterGroups = groupSnapshotFiles(after, platform);
|
|
592
|
+
const pairs = [];
|
|
593
|
+
const comparablePaths = [...new Set([...beforeGroups.keys(), ...afterGroups.keys()])].sort();
|
|
594
|
+
for (const comparablePath of comparablePaths) {
|
|
595
|
+
const beforeEntries = beforeGroups.get(comparablePath) ?? [];
|
|
596
|
+
const afterEntries = afterGroups.get(comparablePath) ?? [];
|
|
597
|
+
if (platform !== "win32" || (beforeEntries.length <= 1 && afterEntries.length <= 1)) {
|
|
598
|
+
pairs.push({
|
|
599
|
+
path: afterEntries[0]?.path ?? beforeEntries[0].path,
|
|
600
|
+
leftPath: beforeEntries[0]?.path,
|
|
601
|
+
rightPath: afterEntries[0]?.path,
|
|
602
|
+
left: beforeEntries[0]?.fingerprint,
|
|
603
|
+
right: afterEntries[0]?.fingerprint,
|
|
604
|
+
});
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
// NTFS can opt individual directories into case-sensitive mode. If either
|
|
608
|
+
// snapshot contains a case collision, preserve every exact path instead of
|
|
609
|
+
// silently dropping one entry from the evidence.
|
|
610
|
+
const beforeExact = new Map(beforeEntries.map((entry) => [normalizePath(entry.path), entry]));
|
|
611
|
+
const afterExact = new Map(afterEntries.map((entry) => [normalizePath(entry.path), entry]));
|
|
612
|
+
const exactPaths = [...new Set([...beforeExact.keys(), ...afterExact.keys()])].sort();
|
|
613
|
+
for (const exactPath of exactPaths) {
|
|
614
|
+
pairs.push({
|
|
615
|
+
path: afterExact.get(exactPath)?.path ?? beforeExact.get(exactPath).path,
|
|
616
|
+
leftPath: beforeExact.get(exactPath)?.path,
|
|
617
|
+
rightPath: afterExact.get(exactPath)?.path,
|
|
618
|
+
left: beforeExact.get(exactPath)?.fingerprint,
|
|
619
|
+
right: afterExact.get(exactPath)?.fingerprint,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
return pairs;
|
|
624
|
+
}
|
|
625
|
+
function groupSnapshotFiles(files, platform) {
|
|
626
|
+
const grouped = new Map();
|
|
627
|
+
for (const [path, fingerprint] of Object.entries(files)) {
|
|
628
|
+
const key = comparableSnapshotPath(path, platform);
|
|
629
|
+
grouped.set(key, [...(grouped.get(key) ?? []), { path, fingerprint }]);
|
|
630
|
+
}
|
|
631
|
+
return grouped;
|
|
632
|
+
}
|
|
633
|
+
function walkWorkspace(root, signal) {
|
|
485
634
|
const result = [];
|
|
486
635
|
const visit = (directory) => {
|
|
636
|
+
throwIfAborted(signal);
|
|
487
637
|
if (result.length >= MAX_SNAPSHOT_FILES)
|
|
488
638
|
return;
|
|
489
639
|
let entries;
|
|
@@ -494,6 +644,7 @@ function walkWorkspace(root) {
|
|
|
494
644
|
return;
|
|
495
645
|
}
|
|
496
646
|
for (const entry of entries) {
|
|
647
|
+
throwIfAborted(signal);
|
|
497
648
|
if (result.length >= MAX_SNAPSHOT_FILES)
|
|
498
649
|
break;
|
|
499
650
|
if (entry.isDirectory() && SKIP_DIRECTORIES.has(entry.name))
|
|
@@ -508,14 +659,24 @@ function walkWorkspace(root) {
|
|
|
508
659
|
visit(root);
|
|
509
660
|
return result;
|
|
510
661
|
}
|
|
511
|
-
function runGit(repoPath, args) {
|
|
512
|
-
|
|
513
|
-
|
|
662
|
+
function runGit(repoPath, args, signal) {
|
|
663
|
+
throwIfAborted(signal);
|
|
664
|
+
return new Promise((resolve, reject) => {
|
|
665
|
+
const env = buildGitEnvironment(repoPath);
|
|
666
|
+
const git = resolveTrustedExecutable("git", repoPath, { pathValue: env.PATH });
|
|
667
|
+
execFile(git, args, {
|
|
514
668
|
cwd: repoPath,
|
|
515
669
|
encoding: "utf-8",
|
|
516
670
|
timeout: 30_000,
|
|
517
671
|
maxBuffer: MAX_DIFF_BYTES,
|
|
672
|
+
windowsHide: true,
|
|
673
|
+
env,
|
|
674
|
+
signal,
|
|
518
675
|
}, (err, stdout, stderr) => {
|
|
676
|
+
if (signal?.aborted) {
|
|
677
|
+
reject(abortReason(signal));
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
519
680
|
let status;
|
|
520
681
|
if (err) {
|
|
521
682
|
// err.code is the exit code (number) when the process exited with a non-zero status;
|
|
@@ -525,10 +686,31 @@ function runGit(repoPath, args) {
|
|
|
525
686
|
else {
|
|
526
687
|
status = 0;
|
|
527
688
|
}
|
|
528
|
-
|
|
689
|
+
const truncated = Boolean(err
|
|
690
|
+
&& typeof err.code === "string"
|
|
691
|
+
&& err.code.includes("MAXBUFFER"));
|
|
692
|
+
resolve({ status, stdout: stdout || "", stderr: stderr || "", truncated });
|
|
529
693
|
});
|
|
530
694
|
});
|
|
531
695
|
}
|
|
696
|
+
function throwIfAborted(signal) {
|
|
697
|
+
if (signal?.aborted)
|
|
698
|
+
throw abortReason(signal);
|
|
699
|
+
}
|
|
700
|
+
function abortReason(signal) {
|
|
701
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
702
|
+
}
|
|
703
|
+
function utf8Prefix(value, maxBytes) {
|
|
704
|
+
if (maxBytes <= 0)
|
|
705
|
+
return "";
|
|
706
|
+
const encoded = Buffer.from(value, "utf-8");
|
|
707
|
+
if (encoded.length <= maxBytes)
|
|
708
|
+
return value;
|
|
709
|
+
let prefix = encoded.subarray(0, maxBytes).toString("utf-8").replace(/\uFFFD$/u, "");
|
|
710
|
+
while (Buffer.byteLength(prefix, "utf-8") > maxBytes)
|
|
711
|
+
prefix = prefix.slice(0, -1);
|
|
712
|
+
return prefix;
|
|
713
|
+
}
|
|
532
714
|
function hashFileAsync(filePath) {
|
|
533
715
|
return new Promise((resolve, reject) => {
|
|
534
716
|
const hash = createHash("sha256");
|
|
@@ -1,29 +1,50 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
|
-
import {
|
|
2
|
+
import { chmodSync, existsSync, lstatSync, readdirSync, rmSync } from "node:fs";
|
|
3
3
|
import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { platform } from "node:os";
|
|
5
|
+
import { buildGitEnvironment, resolveTrustedExecutable } from "./processSecurity.js";
|
|
6
|
+
import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
5
7
|
const isWindows = platform() === "win32";
|
|
6
8
|
/**
|
|
7
9
|
* Remove a file or directory, working around Node.js fs.rmSync issues on Windows
|
|
8
10
|
* where recursive directory deletion silently fails (Node v22+).
|
|
9
|
-
*
|
|
11
|
+
* On Windows, clears read-only attributes before one bounded native retry.
|
|
10
12
|
*/
|
|
11
13
|
function safeRemoveSync(targetPath) {
|
|
12
14
|
rmSync(targetPath, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
13
15
|
if (isWindows && existsSync(targetPath)) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
16
|
+
makeWritableSync(targetPath);
|
|
17
|
+
rmSync(targetPath, { recursive: true, force: true, maxRetries: 5, retryDelay: 150 });
|
|
18
|
+
}
|
|
19
|
+
if (existsSync(targetPath)) {
|
|
20
|
+
throw new Error(`Cleanup path still exists after bounded retries: ${targetPath}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function makeWritableSync(targetPath) {
|
|
24
|
+
let stat;
|
|
25
|
+
try {
|
|
26
|
+
stat = lstatSync(targetPath);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (stat.isSymbolicLink())
|
|
32
|
+
return;
|
|
33
|
+
try {
|
|
34
|
+
chmodSync(targetPath, stat.isDirectory() ? 0o700 : 0o600);
|
|
35
|
+
}
|
|
36
|
+
catch { /* best effort */ }
|
|
37
|
+
if (!stat.isDirectory())
|
|
38
|
+
return;
|
|
39
|
+
let entries;
|
|
40
|
+
try {
|
|
41
|
+
entries = readdirSync(targetPath);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return;
|
|
26
45
|
}
|
|
46
|
+
for (const entry of entries)
|
|
47
|
+
makeWritableSync(join(targetPath, entry));
|
|
27
48
|
}
|
|
28
49
|
const EXCLUDED_DIRS = new Set([".git", ".patchwarden", ".venv", "node_modules", "samples", "docs"]);
|
|
29
50
|
const CLEANUP_DIRS = new Set(["__pycache__", "dist", "release_packages", "temp_files"]);
|
|
@@ -63,7 +84,7 @@ export function runPostTaskCleanup(repoPath, taskDir) {
|
|
|
63
84
|
});
|
|
64
85
|
}
|
|
65
86
|
}
|
|
66
|
-
|
|
87
|
+
atomicWriteJsonFileSync(join(taskDir, "post-task-cleanup.json"), report);
|
|
67
88
|
return report;
|
|
68
89
|
}
|
|
69
90
|
function collectCandidates(root) {
|
|
@@ -109,10 +130,12 @@ function hasTrackedGitContent(root, rel) {
|
|
|
109
130
|
if (!isGitWorktree(root))
|
|
110
131
|
return false;
|
|
111
132
|
try {
|
|
112
|
-
const
|
|
133
|
+
const env = buildGitEnvironment(root);
|
|
134
|
+
const output = execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["ls-files", "--", rel], {
|
|
113
135
|
cwd: root,
|
|
114
136
|
encoding: "utf-8",
|
|
115
137
|
windowsHide: true,
|
|
138
|
+
env,
|
|
116
139
|
});
|
|
117
140
|
return output.trim().length > 0;
|
|
118
141
|
}
|
|
@@ -123,20 +146,24 @@ function hasTrackedGitContent(root, rel) {
|
|
|
123
146
|
function isIgnoredOrUntracked(root, rel) {
|
|
124
147
|
if (!isGitWorktree(root))
|
|
125
148
|
return true;
|
|
149
|
+
const env = buildGitEnvironment(root);
|
|
150
|
+
const git = resolveTrustedExecutable("git", root, { pathValue: env.PATH });
|
|
126
151
|
try {
|
|
127
|
-
execFileSync(
|
|
152
|
+
execFileSync(git, ["check-ignore", "-q", "--", rel], {
|
|
128
153
|
cwd: root,
|
|
129
154
|
stdio: "ignore",
|
|
130
155
|
windowsHide: true,
|
|
156
|
+
env,
|
|
131
157
|
});
|
|
132
158
|
return true;
|
|
133
159
|
}
|
|
134
160
|
catch {
|
|
135
161
|
try {
|
|
136
|
-
const output = execFileSync(
|
|
162
|
+
const output = execFileSync(git, ["ls-files", "--others", "--exclude-standard", "--", rel], {
|
|
137
163
|
cwd: root,
|
|
138
164
|
encoding: "utf-8",
|
|
139
165
|
windowsHide: true,
|
|
166
|
+
env,
|
|
140
167
|
});
|
|
141
168
|
return output.trim().length > 0 || !existsSync(resolve(root, rel));
|
|
142
169
|
}
|
|
@@ -147,10 +174,12 @@ function isIgnoredOrUntracked(root, rel) {
|
|
|
147
174
|
}
|
|
148
175
|
function isGitWorktree(root) {
|
|
149
176
|
try {
|
|
150
|
-
|
|
177
|
+
const env = buildGitEnvironment(root);
|
|
178
|
+
return execFileSync(resolveTrustedExecutable("git", root, { pathValue: env.PATH }), ["rev-parse", "--is-inside-work-tree"], {
|
|
151
179
|
cwd: root,
|
|
152
180
|
encoding: "utf-8",
|
|
153
181
|
windowsHide: true,
|
|
182
|
+
env,
|
|
154
183
|
}).trim() === "true";
|
|
155
184
|
}
|
|
156
185
|
catch {
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export declare const DEFAULT_MAX_PROCESS_LOG_BYTES: number;
|
|
2
|
+
export interface ChildEnvironmentOptions {
|
|
3
|
+
cwd: string;
|
|
4
|
+
allowedNames?: readonly string[];
|
|
5
|
+
blockedNames?: readonly string[];
|
|
6
|
+
sourceEnvironment?: NodeJS.ProcessEnv;
|
|
7
|
+
platform?: NodeJS.Platform;
|
|
8
|
+
}
|
|
9
|
+
export interface ExecutableResolutionOptions {
|
|
10
|
+
platform?: NodeJS.Platform;
|
|
11
|
+
pathValue?: string;
|
|
12
|
+
pathExtValue?: string;
|
|
13
|
+
fileExists?: (path: string) => boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface ResolvedProcessCommand {
|
|
16
|
+
command: string;
|
|
17
|
+
argsPrefix: string[];
|
|
18
|
+
}
|
|
19
|
+
export interface PreparedProcessCommand {
|
|
20
|
+
command: string;
|
|
21
|
+
args: string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Build the environment for a task-owned child. Provider credentials are not
|
|
25
|
+
* ambient capability: an agent receives them only when their variable names
|
|
26
|
+
* are explicitly allow-listed in trusted local configuration.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildChildEnvironment(options: ChildEnvironmentOptions): NodeJS.ProcessEnv;
|
|
29
|
+
/** Build a non-interactive Git environment that disables repo-defined hooks and fsmonitor. */
|
|
30
|
+
export declare function buildGitEnvironment(cwd: string): NodeJS.ProcessEnv;
|
|
31
|
+
/** Resolve a Windows bare command before spawn so CreateProcess never searches cwd. */
|
|
32
|
+
export declare function resolveTrustedExecutable(command: string, cwd: string, options?: ExecutableResolutionOptions): string;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve Windows package-manager shims to their JavaScript CLI entry point.
|
|
35
|
+
* This keeps npm/npx/pnpm execution shell-free while still supporting their
|
|
36
|
+
* normal .cmd installations.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolvePackageManagerInvocation(managerCommand: string, cwd: string, options?: ExecutableResolutionOptions): ResolvedProcessCommand;
|
|
39
|
+
/** Replace the one supported Windows cmd wrapper with a native Node CLI. */
|
|
40
|
+
export declare function prepareShellFreeCommand(command: string, args: readonly string[], cwd: string, options?: ExecutableResolutionOptions): PreparedProcessCommand;
|
|
41
|
+
/** Parse the intentionally simple allow-listed command grammar without a shell. */
|
|
42
|
+
export declare function resolveTrustedCommandLine(commandLine: string, cwd: string, options?: ExecutableResolutionOptions): ResolvedProcessCommand;
|
|
43
|
+
export declare function sanitizeTrustedPath(pathValue: string, cwd: string, platform?: NodeJS.Platform): string;
|
|
44
|
+
export declare function allowedEnvironmentValues(names: readonly string[] | undefined, sourceEnvironment?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): string[];
|
|
45
|
+
export declare function redactProcessOutput(input: string, exactValues?: readonly string[]): string;
|
|
46
|
+
/** Captures complete process logs up to one auditable, redacted byte budget. */
|
|
47
|
+
export declare class SecureProcessLogCapture {
|
|
48
|
+
private readonly chunks;
|
|
49
|
+
private readonly maxBytes;
|
|
50
|
+
private readonly existingBytes;
|
|
51
|
+
private capturedBytes;
|
|
52
|
+
private truncatedPath;
|
|
53
|
+
constructor(paths: Array<string | undefined>, maxBytes?: number);
|
|
54
|
+
append(path: string | undefined, chunk: Buffer | string): void;
|
|
55
|
+
flush(exactValues?: readonly string[]): void;
|
|
56
|
+
}
|