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,19 +1,24 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import {
|
|
3
|
+
createReadStream,
|
|
3
4
|
existsSync,
|
|
4
5
|
lstatSync,
|
|
5
6
|
readFileSync,
|
|
6
7
|
readdirSync,
|
|
7
8
|
statSync,
|
|
8
|
-
writeFileSync,
|
|
9
9
|
} from "node:fs";
|
|
10
10
|
import { join, relative, resolve, isAbsolute } from "node:path";
|
|
11
|
-
import {
|
|
11
|
+
import { execFile } from "node:child_process";
|
|
12
|
+
import { redactSensitiveContent } from "../security/contentRedaction.js";
|
|
12
13
|
import { isSensitivePath } from "../security/sensitiveGuard.js";
|
|
14
|
+
import { nullDevice } from "../utils/platform.js";
|
|
15
|
+
import { buildGitEnvironment, resolveTrustedExecutable } from "./processSecurity.js";
|
|
16
|
+
import { atomicWriteJsonFileSync } from "../utils/atomicFile.js";
|
|
13
17
|
|
|
14
18
|
const MAX_HASH_BYTES = 5 * 1024 * 1024;
|
|
15
19
|
const MAX_SNAPSHOT_FILES = 5000;
|
|
16
20
|
const MAX_DIFF_BYTES = 20 * 1024 * 1024;
|
|
21
|
+
const DIFF_TRUNCATION_MARKER = "\n[PATCHWARDEN DIFF TRUNCATED]\n";
|
|
17
22
|
const SKIP_DIRECTORIES = new Set([".git", ".patchwarden", "node_modules"]);
|
|
18
23
|
|
|
19
24
|
export interface FileFingerprint {
|
|
@@ -74,6 +79,8 @@ export interface ChangeArtifacts {
|
|
|
74
79
|
diff: string;
|
|
75
80
|
diff_available: boolean;
|
|
76
81
|
diff_truncated: boolean;
|
|
82
|
+
diff_redacted?: boolean;
|
|
83
|
+
diff_redaction_categories?: string[];
|
|
77
84
|
diff_size_bytes: number;
|
|
78
85
|
additions: number;
|
|
79
86
|
deletions: number;
|
|
@@ -90,20 +97,30 @@ export interface ChangeArtifacts {
|
|
|
90
97
|
artifact_hygiene: ArtifactHygiene;
|
|
91
98
|
}
|
|
92
99
|
|
|
93
|
-
export function captureRepoSnapshot(repoPath: string): RepoSnapshot {
|
|
100
|
+
export async function captureRepoSnapshot(repoPath: string, signal?: AbortSignal): Promise<RepoSnapshot> {
|
|
101
|
+
throwIfAborted(signal);
|
|
94
102
|
const warnings: string[] = [];
|
|
95
|
-
const
|
|
103
|
+
const isGitResult = await runGit(repoPath, ["rev-parse", "--is-inside-work-tree"], signal);
|
|
104
|
+
const isGit = isGitResult.stdout.trim() === "true";
|
|
96
105
|
let head: string | null = null;
|
|
97
106
|
let status = "";
|
|
98
107
|
let paths: string[] = [];
|
|
99
|
-
const trackedPaths = new Set<string
|
|
100
|
-
const ignoredPaths = new Set<string
|
|
108
|
+
const trackedPaths = new Map<string, Set<string>>();
|
|
109
|
+
const ignoredPaths = new Map<string, Set<string>>();
|
|
101
110
|
|
|
102
111
|
const dirtyPaths = new Set<string>();
|
|
103
112
|
if (isGit) {
|
|
104
|
-
|
|
113
|
+
// The following five git calls are independent of each other and can run in parallel.
|
|
114
|
+
const [headResult, statusResult, trackedResult, ignoredResult, listedResult] = await Promise.all([
|
|
115
|
+
runGit(repoPath, ["rev-parse", "HEAD"], signal),
|
|
116
|
+
runGit(repoPath, ["status", "--porcelain=v1", "-uall"], signal),
|
|
117
|
+
runGit(repoPath, ["ls-files", "-z"], signal),
|
|
118
|
+
runGit(repoPath, ["ls-files", "-o", "-i", "--exclude-standard", "-z"], signal),
|
|
119
|
+
runGit(repoPath, ["ls-files", "-co", "--exclude-standard", "-z"], signal),
|
|
120
|
+
]);
|
|
121
|
+
|
|
105
122
|
if (headResult.status === 0) head = headResult.stdout.trim() || null;
|
|
106
|
-
status =
|
|
123
|
+
status = statusResult.stdout.trimEnd();
|
|
107
124
|
// Parse git status --porcelain to collect all dirty paths
|
|
108
125
|
for (const line of status.split("\n")) {
|
|
109
126
|
if (line.length < 4) continue;
|
|
@@ -125,29 +142,26 @@ export function captureRepoSnapshot(repoPath: string): RepoSnapshot {
|
|
|
125
142
|
}
|
|
126
143
|
}
|
|
127
144
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
for (const path of tracked.stdout.split("\0").filter(Boolean)) trackedPaths.add(normalizePath(path));
|
|
145
|
+
if (trackedResult.status === 0) {
|
|
146
|
+
for (const path of trackedResult.stdout.split("\0").filter(Boolean)) addSnapshotPath(trackedPaths, path);
|
|
131
147
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
for (const path of ignored.stdout.split("\0").filter(Boolean)) ignoredPaths.add(normalizePath(path));
|
|
148
|
+
if (ignoredResult.status === 0) {
|
|
149
|
+
for (const path of ignoredResult.stdout.split("\0").filter(Boolean)) addSnapshotPath(ignoredPaths, path);
|
|
135
150
|
} else {
|
|
136
151
|
warnings.push("git ignored-file discovery failed; ignored classification may be incomplete");
|
|
137
152
|
}
|
|
138
|
-
|
|
139
|
-
if (listed.status === 0) {
|
|
153
|
+
if (listedResult.status === 0) {
|
|
140
154
|
paths = [...new Set([
|
|
141
|
-
...
|
|
142
|
-
...walkWorkspace(repoPath),
|
|
155
|
+
...listedResult.stdout.split("\0").filter(Boolean),
|
|
156
|
+
...walkWorkspace(repoPath, signal),
|
|
143
157
|
])];
|
|
144
158
|
} else {
|
|
145
159
|
warnings.push("git ls-files failed; using bounded filesystem scan");
|
|
146
|
-
paths = walkWorkspace(repoPath);
|
|
160
|
+
paths = walkWorkspace(repoPath, signal);
|
|
147
161
|
}
|
|
148
162
|
} else {
|
|
149
163
|
warnings.push("repository is not a Git worktree; diff will contain file-change evidence only");
|
|
150
|
-
paths = walkWorkspace(repoPath);
|
|
164
|
+
paths = walkWorkspace(repoPath, signal);
|
|
151
165
|
}
|
|
152
166
|
|
|
153
167
|
if (paths.length > MAX_SNAPSHOT_FILES) {
|
|
@@ -157,6 +171,7 @@ export function captureRepoSnapshot(repoPath: string): RepoSnapshot {
|
|
|
157
171
|
|
|
158
172
|
const files: Record<string, FileFingerprint> = {};
|
|
159
173
|
for (const inputPath of paths.sort()) {
|
|
174
|
+
throwIfAborted(signal);
|
|
160
175
|
const normalized = normalizePath(inputPath);
|
|
161
176
|
if (!normalized || normalized.startsWith(".patchwarden/") || isSensitivePath(normalized)) continue;
|
|
162
177
|
const absolutePath = resolve(repoPath, inputPath);
|
|
@@ -169,8 +184,8 @@ export function captureRepoSnapshot(repoPath: string): RepoSnapshot {
|
|
|
169
184
|
files[normalized] = {
|
|
170
185
|
size: stat.size,
|
|
171
186
|
sha256,
|
|
172
|
-
tracked: trackedPaths
|
|
173
|
-
ignored: !trackedPaths
|
|
187
|
+
tracked: hasSnapshotPath(trackedPaths, normalized),
|
|
188
|
+
ignored: !hasSnapshotPath(trackedPaths, normalized) && hasSnapshotPath(ignoredPaths, normalized),
|
|
174
189
|
};
|
|
175
190
|
} catch {
|
|
176
191
|
warnings.push(`could not fingerprint: ${normalized}`);
|
|
@@ -190,55 +205,96 @@ export function captureRepoSnapshot(repoPath: string): RepoSnapshot {
|
|
|
190
205
|
}
|
|
191
206
|
|
|
192
207
|
export function writeSnapshot(taskDir: string, filename: string, snapshot: RepoSnapshot): void {
|
|
193
|
-
|
|
208
|
+
atomicWriteJsonFileSync(join(taskDir, filename), snapshot);
|
|
194
209
|
}
|
|
195
210
|
|
|
196
|
-
export function buildChangeArtifacts(
|
|
211
|
+
export async function buildChangeArtifacts(
|
|
197
212
|
repoPath: string,
|
|
198
213
|
before: RepoSnapshot,
|
|
199
|
-
after: RepoSnapshot
|
|
200
|
-
|
|
214
|
+
after: RepoSnapshot,
|
|
215
|
+
signal?: AbortSignal,
|
|
216
|
+
): Promise<ChangeArtifacts> {
|
|
217
|
+
throwIfAborted(signal);
|
|
201
218
|
const changedFiles = compareSnapshots(before, after);
|
|
202
219
|
const artifactHygiene = classifyArtifactHygiene(changedFiles);
|
|
203
220
|
const sections: string[] = [];
|
|
204
221
|
const scopedPaths = [...new Set(changedFiles.flatMap((file) => file.old_path ? [file.old_path, file.path] : [file.path]))];
|
|
222
|
+
const evidence = [
|
|
223
|
+
"# PatchWarden change evidence",
|
|
224
|
+
`# changed_files: ${changedFiles.length}`,
|
|
225
|
+
`# workspace_dirty_before: ${before.workspace_dirty}`,
|
|
226
|
+
`# workspace_dirty_after: ${after.workspace_dirty}`,
|
|
227
|
+
...changedFiles.map((file) => `# ${file.change}: ${file.path}`),
|
|
228
|
+
].join("\n");
|
|
229
|
+
let retainedSectionBytes = 0;
|
|
230
|
+
let diffTruncated = Buffer.byteLength(evidence, "utf-8") > MAX_DIFF_BYTES;
|
|
231
|
+
const sectionBudget = Math.max(
|
|
232
|
+
0,
|
|
233
|
+
MAX_DIFF_BYTES
|
|
234
|
+
- Math.min(Buffer.byteLength(evidence, "utf-8"), MAX_DIFF_BYTES)
|
|
235
|
+
- Buffer.byteLength(DIFF_TRUNCATION_MARKER, "utf-8")
|
|
236
|
+
- 2,
|
|
237
|
+
);
|
|
238
|
+
const appendSection = (label: string, content: string, commandTruncated = false): void => {
|
|
239
|
+
if (!content.trim()) {
|
|
240
|
+
if (commandTruncated) diffTruncated = true;
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
const block = `${sections.length > 0 ? "\n\n" : ""}${label}\n\n${content.trimEnd()}`;
|
|
244
|
+
const remaining = Math.max(0, sectionBudget - retainedSectionBytes);
|
|
245
|
+
const bounded = utf8Prefix(block, remaining);
|
|
246
|
+
if (bounded) {
|
|
247
|
+
sections.push(bounded);
|
|
248
|
+
retainedSectionBytes += Buffer.byteLength(bounded, "utf-8");
|
|
249
|
+
}
|
|
250
|
+
if (commandTruncated || Buffer.byteLength(bounded, "utf-8") < Buffer.byteLength(block, "utf-8")) {
|
|
251
|
+
diffTruncated = true;
|
|
252
|
+
}
|
|
253
|
+
};
|
|
205
254
|
|
|
206
255
|
if (before.is_git && after.is_git && scopedPaths.length > 0) {
|
|
207
256
|
if (before.head && after.head && before.head !== after.head) {
|
|
208
|
-
const committed = runGit(repoPath, ["diff", "--no-color", "--binary", before.head, after.head, "--", ...scopedPaths]);
|
|
209
|
-
|
|
257
|
+
const committed = await runGit(repoPath, ["diff", "--no-ext-diff", "--no-textconv", "--no-color", "--binary", before.head, after.head, "--", ...scopedPaths], signal);
|
|
258
|
+
appendSection("# Changes committed during task", committed.stdout, committed.truncated);
|
|
210
259
|
}
|
|
211
260
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
261
|
+
if (retainedSectionBytes < sectionBudget) {
|
|
262
|
+
const base = after.head || "HEAD";
|
|
263
|
+
const working = await runGit(repoPath, ["diff", "--no-ext-diff", "--no-textconv", "--no-color", "--binary", base, "--", ...scopedPaths], signal);
|
|
264
|
+
appendSection("# Staged and unstaged changes", working.stdout, working.truncated);
|
|
265
|
+
} else {
|
|
266
|
+
diffTruncated = true;
|
|
267
|
+
}
|
|
215
268
|
|
|
216
269
|
for (const file of changedFiles.filter((item) => item.change === "added").slice(0, 100)) {
|
|
217
|
-
|
|
270
|
+
throwIfAborted(signal);
|
|
271
|
+
if (retainedSectionBytes >= sectionBudget) {
|
|
272
|
+
diffTruncated = true;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
const tracked = await runGit(repoPath, ["ls-files", "--error-unmatch", "--", file.path], signal);
|
|
218
276
|
if (tracked.status === 0) continue;
|
|
219
|
-
const untracked = runGit(repoPath, ["diff", "--no-index", "--no-color", "--binary", "--",
|
|
220
|
-
|
|
277
|
+
const untracked = await runGit(repoPath, ["diff", "--no-ext-diff", "--no-textconv", "--no-index", "--no-color", "--binary", "--", nullDevice, file.path], signal);
|
|
278
|
+
appendSection("# Untracked file", untracked.stdout, untracked.truncated);
|
|
221
279
|
}
|
|
222
280
|
}
|
|
223
281
|
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
...changedFiles.map((file) => `# ${file.change}: ${file.path}`),
|
|
230
|
-
].join("\n");
|
|
231
|
-
const body = sections.join("\n\n");
|
|
232
|
-
const fullDiff = `${evidence}\n\n${body || (changedFiles.length ? "(textual patch unavailable; see changed-files.json for hash evidence)" : "(no task file changes detected)")}\n`;
|
|
282
|
+
const body = sections.join("");
|
|
283
|
+
const rawDiff = `${evidence}\n\n${body || (changedFiles.length ? "(textual patch unavailable; see changed-files.json for hash evidence)" : "(no task file changes detected)")}\n`;
|
|
284
|
+
const sanitized = sanitizeDiffEvidence(rawDiff, MAX_DIFF_BYTES);
|
|
285
|
+
const fullDiff = sanitized.content;
|
|
286
|
+
diffTruncated ||= sanitized.truncated;
|
|
233
287
|
const additions = fullDiff.split(/\r?\n/).filter((line) => line.startsWith("+") && !line.startsWith("+++")).length;
|
|
234
288
|
const deletions = fullDiff.split(/\r?\n/).filter((line) => line.startsWith("-") && !line.startsWith("---")).length;
|
|
235
|
-
const fileStats = buildFileStats(repoPath, before, after, changedFiles);
|
|
289
|
+
const fileStats = await buildFileStats(repoPath, before, after, changedFiles, signal);
|
|
236
290
|
|
|
237
291
|
return {
|
|
238
292
|
changed_files: changedFiles,
|
|
239
293
|
diff: fullDiff,
|
|
240
294
|
diff_available: changedFiles.length > 0,
|
|
241
|
-
diff_truncated:
|
|
295
|
+
diff_truncated: diffTruncated,
|
|
296
|
+
diff_redacted: sanitized.redacted,
|
|
297
|
+
diff_redaction_categories: sanitized.redaction_categories,
|
|
242
298
|
diff_size_bytes: Buffer.byteLength(fullDiff, "utf-8"),
|
|
243
299
|
additions,
|
|
244
300
|
deletions,
|
|
@@ -255,13 +311,47 @@ export function buildChangeArtifacts(
|
|
|
255
311
|
};
|
|
256
312
|
}
|
|
257
313
|
|
|
258
|
-
function
|
|
314
|
+
export function sanitizeDiffEvidence(
|
|
315
|
+
input: string,
|
|
316
|
+
maxBytes = MAX_DIFF_BYTES,
|
|
317
|
+
): {
|
|
318
|
+
content: string;
|
|
319
|
+
truncated: boolean;
|
|
320
|
+
redacted: boolean;
|
|
321
|
+
redaction_categories: string[];
|
|
322
|
+
} {
|
|
323
|
+
if (!Number.isInteger(maxBytes) || maxBytes <= 0) {
|
|
324
|
+
throw new Error("maxBytes must be a positive integer");
|
|
325
|
+
}
|
|
326
|
+
const redaction = redactSensitiveContent(input);
|
|
327
|
+
const encodedBytes = Buffer.byteLength(redaction.content, "utf-8");
|
|
328
|
+
if (encodedBytes <= maxBytes) {
|
|
329
|
+
return {
|
|
330
|
+
content: redaction.content,
|
|
331
|
+
truncated: false,
|
|
332
|
+
redacted: redaction.redacted,
|
|
333
|
+
redaction_categories: redaction.redaction_categories,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
const marker = utf8Prefix(DIFF_TRUNCATION_MARKER, maxBytes);
|
|
337
|
+
const markerBytes = Buffer.byteLength(marker, "utf-8");
|
|
338
|
+
return {
|
|
339
|
+
content: utf8Prefix(redaction.content, Math.max(0, maxBytes - markerBytes)) + marker,
|
|
340
|
+
truncated: true,
|
|
341
|
+
redacted: redaction.redacted,
|
|
342
|
+
redaction_categories: redaction.redaction_categories,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function buildFileStats(
|
|
259
347
|
repoPath: string,
|
|
260
348
|
before: RepoSnapshot,
|
|
261
349
|
after: RepoSnapshot,
|
|
262
|
-
changedFiles: ChangedFile[]
|
|
263
|
-
|
|
264
|
-
|
|
350
|
+
changedFiles: ChangedFile[],
|
|
351
|
+
signal?: AbortSignal,
|
|
352
|
+
): Promise<ChangeArtifacts["file_stats"]> {
|
|
353
|
+
const results = await Promise.all(changedFiles.map(async (file) => {
|
|
354
|
+
throwIfAborted(signal);
|
|
265
355
|
let additions = 0;
|
|
266
356
|
let deletions = 0;
|
|
267
357
|
const paths = file.old_path ? [file.old_path, file.path] : [file.path];
|
|
@@ -272,8 +362,10 @@ function buildFileStats(
|
|
|
272
362
|
ranges.push([before.head, after.head]);
|
|
273
363
|
}
|
|
274
364
|
ranges.push([after.head || "HEAD"]);
|
|
275
|
-
|
|
276
|
-
|
|
365
|
+
const rangeResults = await Promise.all(
|
|
366
|
+
ranges.map((range) => runGit(repoPath, ["diff", "--no-ext-diff", "--no-textconv", "--numstat", ...range, "--", ...paths], signal))
|
|
367
|
+
);
|
|
368
|
+
for (const result of rangeResults) {
|
|
277
369
|
for (const line of result.stdout.split(/\r?\n/).filter(Boolean)) {
|
|
278
370
|
const [added, removed] = line.split(/\s+/);
|
|
279
371
|
if (/^\d+$/.test(added)) additions += Number(added);
|
|
@@ -286,11 +378,12 @@ function buildFileStats(
|
|
|
286
378
|
try {
|
|
287
379
|
const content = readFileSync(resolve(repoPath, file.path), "utf-8");
|
|
288
380
|
additions = countLines(content);
|
|
289
|
-
} catch {}
|
|
381
|
+
} catch {} // probe failure handled by return value (additions stays 0)
|
|
290
382
|
}
|
|
291
383
|
|
|
292
384
|
return { path: file.path, status: file.change, additions, deletions };
|
|
293
|
-
});
|
|
385
|
+
}));
|
|
386
|
+
return results;
|
|
294
387
|
}
|
|
295
388
|
|
|
296
389
|
function countLines(content: string): number {
|
|
@@ -298,17 +391,31 @@ function countLines(content: string): number {
|
|
|
298
391
|
return content.split(/\r?\n/).length - (content.endsWith("\n") ? 1 : 0);
|
|
299
392
|
}
|
|
300
393
|
|
|
301
|
-
export function compareSnapshots(
|
|
302
|
-
|
|
394
|
+
export function compareSnapshots(
|
|
395
|
+
before: RepoSnapshot,
|
|
396
|
+
after: RepoSnapshot,
|
|
397
|
+
platform: NodeJS.Platform = process.platform,
|
|
398
|
+
): ChangedFile[] {
|
|
303
399
|
const changed: ChangedFile[] = [];
|
|
304
|
-
for (const path of
|
|
305
|
-
const left = before.files[path];
|
|
306
|
-
const right = after.files[path];
|
|
400
|
+
for (const { path, leftPath, rightPath, left, right } of pairSnapshotFiles(before.files, after.files, platform)) {
|
|
307
401
|
if (!left && right) {
|
|
308
402
|
changed.push(classifyChangedFile(path, "added", null, right));
|
|
309
403
|
} else if (left && !right) {
|
|
310
404
|
changed.push(classifyChangedFile(path, "deleted", left, null));
|
|
311
|
-
} else if (left.sha256
|
|
405
|
+
} else if (left && right && left.sha256 === right.sha256 && leftPath !== rightPath) {
|
|
406
|
+
// Windows lookup is case-insensitive, but a case-only Git rename is still
|
|
407
|
+
// auditable work and must not disappear from the evidence set.
|
|
408
|
+
changed.push({
|
|
409
|
+
path: rightPath!,
|
|
410
|
+
old_path: leftPath!,
|
|
411
|
+
change: "renamed",
|
|
412
|
+
before_sha256: left.sha256,
|
|
413
|
+
after_sha256: right.sha256,
|
|
414
|
+
tracked: left.tracked || right.tracked,
|
|
415
|
+
ignored: right.ignored,
|
|
416
|
+
kind: classifyPathKind(rightPath!),
|
|
417
|
+
});
|
|
418
|
+
} else if (left && right && left.sha256 !== right.sha256) {
|
|
312
419
|
changed.push(classifyChangedFile(path, "modified", left, right));
|
|
313
420
|
}
|
|
314
421
|
}
|
|
@@ -376,10 +483,12 @@ export interface ExternalDirtyFile {
|
|
|
376
483
|
export function extractExternalDirtyFiles(
|
|
377
484
|
workspaceSnapshot: RepoSnapshot,
|
|
378
485
|
repoPath: string,
|
|
379
|
-
workspaceRoot: string
|
|
486
|
+
workspaceRoot: string,
|
|
487
|
+
platform: NodeJS.Platform = process.platform,
|
|
380
488
|
): ExternalDirtyFile[] {
|
|
381
489
|
const dirtyFiles: ExternalDirtyFile[] = [];
|
|
382
|
-
const dirtyPathSet = new Set(
|
|
490
|
+
const dirtyPathSet = new Map<string, Set<string>>();
|
|
491
|
+
for (const path of workspaceSnapshot.dirty_paths) addSnapshotPath(dirtyPathSet, path, platform);
|
|
383
492
|
for (const [path, fingerprint] of Object.entries(workspaceSnapshot.files)) {
|
|
384
493
|
const absolutePath = resolve(workspaceRoot, path);
|
|
385
494
|
const rel = relative(repoPath, absolutePath);
|
|
@@ -389,7 +498,7 @@ export function extractExternalDirtyFiles(
|
|
|
389
498
|
// 1. Git reports it as dirty (modified/added/deleted/untracked) via dirty_paths, OR
|
|
390
499
|
// 2. It's not tracked by git (untracked file), OR
|
|
391
500
|
// 3. It's explicitly ignored
|
|
392
|
-
const isDirty = dirtyPathSet
|
|
501
|
+
const isDirty = hasSnapshotPath(dirtyPathSet, path, platform);
|
|
393
502
|
const isUntracked = !fingerprint.tracked;
|
|
394
503
|
const isIgnored = fingerprint.ignored;
|
|
395
504
|
if (isDirty || isUntracked || isIgnored) {
|
|
@@ -412,11 +521,19 @@ export function extractExternalDirtyFiles(
|
|
|
412
521
|
*/
|
|
413
522
|
export function findNewExternalDirtyFiles(
|
|
414
523
|
baseline: ExternalDirtyFile[],
|
|
415
|
-
current: ExternalDirtyFile[]
|
|
524
|
+
current: ExternalDirtyFile[],
|
|
525
|
+
platform: NodeJS.Platform = process.platform,
|
|
416
526
|
): ExternalDirtyFile[] {
|
|
417
|
-
const baselineMap = new Map
|
|
527
|
+
const baselineMap = new Map<string, ExternalDirtyFile[]>();
|
|
528
|
+
for (const file of baseline) {
|
|
529
|
+
const key = comparableSnapshotPath(file.path, platform);
|
|
530
|
+
baselineMap.set(key, [...(baselineMap.get(key) ?? []), file]);
|
|
531
|
+
}
|
|
418
532
|
return current.filter((f) => {
|
|
419
|
-
const
|
|
533
|
+
const candidates = baselineMap.get(comparableSnapshotPath(f.path, platform)) ?? [];
|
|
534
|
+
const baselineFile = candidates.length <= 1
|
|
535
|
+
? candidates[0]
|
|
536
|
+
: candidates.find((candidate) => normalizePath(candidate.path) === normalizePath(f.path));
|
|
420
537
|
if (!baselineFile) return true; // New path — definitely new
|
|
421
538
|
// Same path but content changed during task execution
|
|
422
539
|
if (baselineFile.before_sha256 !== f.before_sha256) return true;
|
|
@@ -441,11 +558,11 @@ export interface ArtifactManifest {
|
|
|
441
558
|
artifacts: ArtifactManifestEntry[];
|
|
442
559
|
}
|
|
443
560
|
|
|
444
|
-
export function buildArtifactManifest(
|
|
561
|
+
export async function buildArtifactManifest(
|
|
445
562
|
changedFiles: ChangedFile[],
|
|
446
563
|
repoPath: string,
|
|
447
564
|
taskId?: string
|
|
448
|
-
): ArtifactManifest {
|
|
565
|
+
): Promise<ArtifactManifest> {
|
|
449
566
|
const entries: ArtifactManifestEntry[] = [];
|
|
450
567
|
for (const file of changedFiles) {
|
|
451
568
|
if (file.kind !== "build_artifact") continue;
|
|
@@ -457,7 +574,7 @@ export function buildArtifactManifest(
|
|
|
457
574
|
if (stat.isFile()) {
|
|
458
575
|
size = stat.size;
|
|
459
576
|
if (size <= MAX_HASH_BYTES) {
|
|
460
|
-
sha256 =
|
|
577
|
+
sha256 = await hashFileAsync(absolutePath);
|
|
461
578
|
}
|
|
462
579
|
}
|
|
463
580
|
} catch {
|
|
@@ -613,9 +730,98 @@ function normalizePath(value: string): string {
|
|
|
613
730
|
return value.replace(/\\/g, "/");
|
|
614
731
|
}
|
|
615
732
|
|
|
616
|
-
function
|
|
733
|
+
function comparableSnapshotPath(value: string, platform: NodeJS.Platform = process.platform): string {
|
|
734
|
+
const normalized = normalizePath(value);
|
|
735
|
+
return platform === "win32" ? normalized.toLowerCase() : normalized;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function addSnapshotPath(
|
|
739
|
+
index: Map<string, Set<string>>,
|
|
740
|
+
value: string,
|
|
741
|
+
platform: NodeJS.Platform = process.platform,
|
|
742
|
+
): void {
|
|
743
|
+
const normalized = normalizePath(value);
|
|
744
|
+
const key = comparableSnapshotPath(normalized, platform);
|
|
745
|
+
const exactPaths = index.get(key) ?? new Set<string>();
|
|
746
|
+
exactPaths.add(normalized);
|
|
747
|
+
index.set(key, exactPaths);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function hasSnapshotPath(
|
|
751
|
+
index: Map<string, Set<string>>,
|
|
752
|
+
value: string,
|
|
753
|
+
platform: NodeJS.Platform = process.platform,
|
|
754
|
+
): boolean {
|
|
755
|
+
const normalized = normalizePath(value);
|
|
756
|
+
const exactPaths = index.get(comparableSnapshotPath(normalized, platform));
|
|
757
|
+
if (!exactPaths) return false;
|
|
758
|
+
return platform !== "win32" || exactPaths.size <= 1 || exactPaths.has(normalized);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
interface SnapshotFileEntry {
|
|
762
|
+
path: string;
|
|
763
|
+
fingerprint: FileFingerprint;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
function pairSnapshotFiles(
|
|
767
|
+
before: Record<string, FileFingerprint>,
|
|
768
|
+
after: Record<string, FileFingerprint>,
|
|
769
|
+
platform: NodeJS.Platform,
|
|
770
|
+
): Array<{ path: string; leftPath?: string; rightPath?: string; left?: FileFingerprint; right?: FileFingerprint }> {
|
|
771
|
+
const beforeGroups = groupSnapshotFiles(before, platform);
|
|
772
|
+
const afterGroups = groupSnapshotFiles(after, platform);
|
|
773
|
+
const pairs: Array<{ path: string; leftPath?: string; rightPath?: string; left?: FileFingerprint; right?: FileFingerprint }> = [];
|
|
774
|
+
const comparablePaths = [...new Set([...beforeGroups.keys(), ...afterGroups.keys()])].sort();
|
|
775
|
+
|
|
776
|
+
for (const comparablePath of comparablePaths) {
|
|
777
|
+
const beforeEntries = beforeGroups.get(comparablePath) ?? [];
|
|
778
|
+
const afterEntries = afterGroups.get(comparablePath) ?? [];
|
|
779
|
+
if (platform !== "win32" || (beforeEntries.length <= 1 && afterEntries.length <= 1)) {
|
|
780
|
+
pairs.push({
|
|
781
|
+
path: afterEntries[0]?.path ?? beforeEntries[0].path,
|
|
782
|
+
leftPath: beforeEntries[0]?.path,
|
|
783
|
+
rightPath: afterEntries[0]?.path,
|
|
784
|
+
left: beforeEntries[0]?.fingerprint,
|
|
785
|
+
right: afterEntries[0]?.fingerprint,
|
|
786
|
+
});
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// NTFS can opt individual directories into case-sensitive mode. If either
|
|
791
|
+
// snapshot contains a case collision, preserve every exact path instead of
|
|
792
|
+
// silently dropping one entry from the evidence.
|
|
793
|
+
const beforeExact = new Map(beforeEntries.map((entry) => [normalizePath(entry.path), entry]));
|
|
794
|
+
const afterExact = new Map(afterEntries.map((entry) => [normalizePath(entry.path), entry]));
|
|
795
|
+
const exactPaths = [...new Set([...beforeExact.keys(), ...afterExact.keys()])].sort();
|
|
796
|
+
for (const exactPath of exactPaths) {
|
|
797
|
+
pairs.push({
|
|
798
|
+
path: afterExact.get(exactPath)?.path ?? beforeExact.get(exactPath)!.path,
|
|
799
|
+
leftPath: beforeExact.get(exactPath)?.path,
|
|
800
|
+
rightPath: afterExact.get(exactPath)?.path,
|
|
801
|
+
left: beforeExact.get(exactPath)?.fingerprint,
|
|
802
|
+
right: afterExact.get(exactPath)?.fingerprint,
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return pairs;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function groupSnapshotFiles(
|
|
810
|
+
files: Record<string, FileFingerprint>,
|
|
811
|
+
platform: NodeJS.Platform,
|
|
812
|
+
): Map<string, SnapshotFileEntry[]> {
|
|
813
|
+
const grouped = new Map<string, SnapshotFileEntry[]>();
|
|
814
|
+
for (const [path, fingerprint] of Object.entries(files)) {
|
|
815
|
+
const key = comparableSnapshotPath(path, platform);
|
|
816
|
+
grouped.set(key, [...(grouped.get(key) ?? []), { path, fingerprint }]);
|
|
817
|
+
}
|
|
818
|
+
return grouped;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
function walkWorkspace(root: string, signal?: AbortSignal): string[] {
|
|
617
822
|
const result: string[] = [];
|
|
618
823
|
const visit = (directory: string) => {
|
|
824
|
+
throwIfAborted(signal);
|
|
619
825
|
if (result.length >= MAX_SNAPSHOT_FILES) return;
|
|
620
826
|
let entries;
|
|
621
827
|
try {
|
|
@@ -624,6 +830,7 @@ function walkWorkspace(root: string): string[] {
|
|
|
624
830
|
return;
|
|
625
831
|
}
|
|
626
832
|
for (const entry of entries) {
|
|
833
|
+
throwIfAborted(signal);
|
|
627
834
|
if (result.length >= MAX_SNAPSHOT_FILES) break;
|
|
628
835
|
if (entry.isDirectory() && SKIP_DIRECTORIES.has(entry.name)) continue;
|
|
629
836
|
const absolute = join(directory, entry.name);
|
|
@@ -635,16 +842,71 @@ function walkWorkspace(root: string): string[] {
|
|
|
635
842
|
return result;
|
|
636
843
|
}
|
|
637
844
|
|
|
638
|
-
function runGit(repoPath: string, args: string[]) {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
845
|
+
function runGit(repoPath: string, args: string[], signal?: AbortSignal): Promise<{
|
|
846
|
+
status: number | null;
|
|
847
|
+
stdout: string;
|
|
848
|
+
stderr: string;
|
|
849
|
+
truncated: boolean;
|
|
850
|
+
}> {
|
|
851
|
+
throwIfAborted(signal);
|
|
852
|
+
return new Promise((resolve, reject) => {
|
|
853
|
+
const env = buildGitEnvironment(repoPath);
|
|
854
|
+
const git = resolveTrustedExecutable("git", repoPath, { pathValue: env.PATH });
|
|
855
|
+
execFile(git, args, {
|
|
856
|
+
cwd: repoPath,
|
|
857
|
+
encoding: "utf-8",
|
|
858
|
+
timeout: 30_000,
|
|
859
|
+
maxBuffer: MAX_DIFF_BYTES,
|
|
860
|
+
windowsHide: true,
|
|
861
|
+
env,
|
|
862
|
+
signal,
|
|
863
|
+
}, (err, stdout, stderr) => {
|
|
864
|
+
if (signal?.aborted) {
|
|
865
|
+
reject(abortReason(signal));
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
let status: number | null;
|
|
869
|
+
if (err) {
|
|
870
|
+
// err.code is the exit code (number) when the process exited with a non-zero status;
|
|
871
|
+
// it is a string (e.g. "ENOENT") when spawning failed or the buffer limit was hit.
|
|
872
|
+
status = typeof err.code === "number" ? err.code : null;
|
|
873
|
+
} else {
|
|
874
|
+
status = 0;
|
|
875
|
+
}
|
|
876
|
+
const truncated = Boolean(
|
|
877
|
+
err
|
|
878
|
+
&& typeof err.code === "string"
|
|
879
|
+
&& err.code.includes("MAXBUFFER"),
|
|
880
|
+
);
|
|
881
|
+
resolve({ status, stdout: stdout || "", stderr: stderr || "", truncated });
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
function throwIfAborted(signal?: AbortSignal): void {
|
|
887
|
+
if (signal?.aborted) throw abortReason(signal);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
function abortReason(signal: AbortSignal): Error {
|
|
891
|
+
return signal.reason instanceof Error ? signal.reason : new Error("Operation aborted");
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function utf8Prefix(value: string, maxBytes: number): string {
|
|
895
|
+
if (maxBytes <= 0) return "";
|
|
896
|
+
const encoded = Buffer.from(value, "utf-8");
|
|
897
|
+
if (encoded.length <= maxBytes) return value;
|
|
898
|
+
let prefix = encoded.subarray(0, maxBytes).toString("utf-8").replace(/\uFFFD$/u, "");
|
|
899
|
+
while (Buffer.byteLength(prefix, "utf-8") > maxBytes) prefix = prefix.slice(0, -1);
|
|
900
|
+
return prefix;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function hashFileAsync(filePath: string): Promise<string> {
|
|
904
|
+
return new Promise((resolve, reject) => {
|
|
905
|
+
const hash = createHash("sha256");
|
|
906
|
+
const stream = createReadStream(filePath);
|
|
907
|
+
stream.on("error", reject);
|
|
908
|
+
hash.on("error", reject);
|
|
909
|
+
hash.on("finish", () => resolve(hash.digest("hex")));
|
|
910
|
+
stream.pipe(hash);
|
|
644
911
|
});
|
|
645
|
-
return {
|
|
646
|
-
status: result.status,
|
|
647
|
-
stdout: result.stdout || "",
|
|
648
|
-
stderr: result.stderr || "",
|
|
649
|
-
};
|
|
650
912
|
}
|
package/src/runner/cli.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { runTask } from "./runTask.js";
|
|
11
11
|
import { loadConfig } from "../config.js";
|
|
12
|
+
import { logger } from "../logging.js";
|
|
12
13
|
|
|
13
14
|
// Load config early
|
|
14
15
|
loadConfig();
|
|
@@ -16,19 +17,19 @@ loadConfig();
|
|
|
16
17
|
const taskId = process.argv[2] || process.env.PATCHWARDEN_TASK_ID;
|
|
17
18
|
|
|
18
19
|
if (!taskId) {
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
logger.info("Usage: node dist/runner/cli.js <task_id>");
|
|
21
|
+
logger.info(" or: npm run runner -- <task_id>");
|
|
21
22
|
process.exit(1);
|
|
22
23
|
}
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
logger.info(`[runner] Starting task: ${taskId}`);
|
|
25
26
|
|
|
26
27
|
const result = await runTask(taskId);
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
logger.info(`[runner] Task ${result.task_id}: ${result.status}`);
|
|
29
30
|
if (result.error) {
|
|
30
|
-
|
|
31
|
+
logger.fatal(`[runner] Error: ${result.error}`);
|
|
31
32
|
process.exit(1);
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
logger.info("[runner] Done.");
|