patchwarden 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PatchWarden.cmd +32 -2
- package/README.en.md +152 -25
- package/README.md +120 -23
- package/dist/assessments/agentAssessor.js +21 -12
- package/dist/assessments/assessmentStore.js +42 -34
- package/dist/config.d.ts +15 -0
- package/dist/config.js +66 -13
- package/dist/control/fileManager.d.ts +2 -0
- package/dist/control/fileManager.js +39 -0
- package/dist/control/middleware/auth.d.ts +6 -0
- package/dist/control/middleware/auth.js +14 -5
- package/dist/control/middleware/static.js +16 -3
- package/dist/control/routeTable.d.ts +24 -0
- package/dist/control/routeTable.js +263 -0
- package/dist/control/routes/audit.js +17 -7
- package/dist/control/routes/evidence.js +1 -1
- package/dist/control/routes/lineage.js +1 -1
- package/dist/control/routes/process.d.ts +7 -0
- package/dist/control/routes/process.js +329 -42
- package/dist/control/routes/sessions.d.ts +9 -1
- package/dist/control/routes/sessions.js +84 -21
- package/dist/control/routes/status.d.ts +46 -1
- package/dist/control/routes/status.js +72 -15
- package/dist/control/routes/taskActions.js +24 -60
- package/dist/control/routes/tasks.d.ts +4 -0
- package/dist/control/routes/tasks.js +61 -62
- package/dist/control/routes/workspace.js +50 -23
- package/dist/control/runtime.d.ts +3 -2
- package/dist/control/runtime.js +63 -30
- package/dist/control/server.js +36 -365
- package/dist/control/shared.d.ts +25 -1
- package/dist/control/shared.js +64 -11
- package/dist/direct/directAudit.js +234 -225
- package/dist/direct/directGuards.d.ts +2 -1
- package/dist/direct/directGuards.js +42 -29
- package/dist/direct/directPatch.d.ts +6 -1
- package/dist/direct/directPatch.js +55 -8
- package/dist/direct/directSessionStore.d.ts +16 -6
- package/dist/direct/directSessionStore.js +127 -35
- package/dist/direct/directVerification.js +8 -16
- package/dist/doctor.js +62 -52
- package/dist/goal/acceptanceEngine.d.ts +1 -1
- package/dist/goal/goalProgress.js +75 -69
- package/dist/goal/goalReport.js +5 -12
- package/dist/goal/goalStatus.d.ts +2 -0
- package/dist/goal/goalStatus.js +10 -3
- package/dist/goal/goalStore.d.ts +9 -2
- package/dist/goal/goalStore.js +152 -45
- package/dist/goal/handoffExport.js +6 -6
- package/dist/goal/specKitImport.js +97 -96
- package/dist/goal/subgoalSync.js +11 -9
- package/dist/goal/worktreeManager.d.ts +2 -0
- package/dist/goal/worktreeManager.js +209 -136
- package/dist/httpServer.js +111 -32
- package/dist/logging.js +5 -3
- package/dist/policy/projectPolicy.js +5 -2
- package/dist/release/releaseGate.d.ts +1 -1
- package/dist/release/releaseGate.js +78 -25
- package/dist/runner/agentInvocation.d.ts +14 -0
- package/dist/runner/agentInvocation.js +96 -4
- package/dist/runner/changeCapture.d.ts +13 -5
- package/dist/runner/changeCapture.js +243 -61
- package/dist/runner/postTaskCleanup.js +48 -19
- package/dist/runner/processSecurity.d.ts +56 -0
- package/dist/runner/processSecurity.js +373 -0
- package/dist/runner/runTask.d.ts +1 -1
- package/dist/runner/runTask.js +115 -87
- package/dist/runner/simpleProcess.d.ts +3 -0
- package/dist/runner/simpleProcess.js +50 -36
- package/dist/{taskProgress.d.ts → runner/taskProgress.d.ts} +1 -1
- package/dist/{taskProgress.js → runner/taskProgress.js} +2 -2
- package/dist/{taskRuntime.d.ts → runner/taskRuntime.d.ts} +1 -1
- package/dist/{taskRuntime.js → runner/taskRuntime.js} +3 -2
- package/dist/runner/taskStatusStore.d.ts +11 -0
- package/dist/runner/taskStatusStore.js +24 -0
- package/dist/runner/watch.d.ts +14 -1
- package/dist/runner/watch.js +256 -48
- package/dist/security/contentRedaction.js +2 -2
- package/dist/security/discoveryTokenStore.d.ts +3 -2
- package/dist/security/discoveryTokenStore.js +17 -2
- package/dist/security/loopbackHost.d.ts +2 -0
- package/dist/security/loopbackHost.js +16 -0
- package/dist/security/pathGuard.js +32 -17
- package/dist/security/planGuard.js +2 -1
- package/dist/security/runtimeGuard.js +9 -6
- package/dist/security/secretComparison.d.ts +3 -0
- package/dist/security/secretComparison.js +11 -0
- package/dist/security/sensitiveGuard.d.ts +5 -0
- package/dist/security/sensitiveGuard.js +27 -8
- package/dist/security/toolInvocationGuard.d.ts +2 -2
- package/dist/security/toolInvocationGuard.js +1 -1
- package/dist/security/workspaceRootGuard.d.ts +8 -0
- package/dist/security/workspaceRootGuard.js +47 -0
- package/dist/smoke-test.js +24 -23
- package/dist/tools/catalog/index.d.ts +4 -0
- package/dist/tools/catalog/index.js +4 -0
- package/dist/tools/{toolCatalog.js → catalog/toolCatalog.js} +3 -12
- package/dist/tools/{toolRegistry.d.ts → catalog/toolRegistry.d.ts} +3 -3
- package/dist/tools/{toolRegistry.js → catalog/toolRegistry.js} +6 -15
- package/dist/tools/definitions/toolDefs.d.ts +18 -0
- package/dist/tools/definitions/toolDefs.js +1177 -0
- package/dist/tools/{auditSession.d.ts → diagnostics/auditSession.d.ts} +1 -1
- package/dist/tools/{auditSession.js → diagnostics/auditSession.js} +2 -2
- package/dist/tools/{auditTask.d.ts → diagnostics/auditTask.d.ts} +1 -1
- package/dist/tools/{auditTask.js → diagnostics/auditTask.js} +99 -29
- package/dist/tools/{healthCheck.d.ts → diagnostics/healthCheck.d.ts} +6 -6
- package/dist/tools/{healthCheck.js → diagnostics/healthCheck.js} +7 -7
- package/dist/tools/diagnostics/index.d.ts +6 -0
- package/dist/tools/diagnostics/index.js +6 -0
- package/dist/tools/{safeStatus.d.ts → diagnostics/safeStatus.d.ts} +3 -3
- package/dist/tools/{safeStatus.js → diagnostics/safeStatus.js} +6 -6
- package/dist/tools/{safeViews.d.ts → diagnostics/safeViews.d.ts} +12 -8
- package/dist/tools/{safeViews.js → diagnostics/safeViews.js} +23 -13
- package/dist/tools/{schemaDriftCheck.d.ts → diagnostics/schemaDriftCheck.d.ts} +1 -1
- package/dist/tools/{schemaDriftCheck.js → diagnostics/schemaDriftCheck.js} +2 -2
- package/dist/tools/{createDirectSession.js → direct/createDirectSession.js} +6 -6
- package/dist/tools/{finalizeDirectSession.d.ts → direct/finalizeDirectSession.d.ts} +1 -1
- package/dist/tools/direct/finalizeDirectSession.js +86 -0
- package/dist/tools/direct/index.d.ts +3 -0
- package/dist/tools/direct/index.js +3 -0
- package/dist/tools/{runDirectVerificationBundle.js → direct/runDirectVerificationBundle.js} +2 -2
- package/dist/tools/{discoverTools.d.ts → discovery/discoverTools.d.ts} +2 -2
- package/dist/tools/{discoverTools.js → discovery/discoverTools.js} +5 -5
- package/dist/tools/{explainTool.d.ts → discovery/explainTool.d.ts} +2 -2
- package/dist/tools/{explainTool.js → discovery/explainTool.js} +2 -2
- package/dist/tools/discovery/index.d.ts +3 -0
- package/dist/tools/discovery/index.js +3 -0
- package/dist/tools/{invokeDiscoveredTool.d.ts → discovery/invokeDiscoveredTool.d.ts} +2 -2
- package/dist/tools/{invokeDiscoveredTool.js → discovery/invokeDiscoveredTool.js} +5 -5
- package/dist/tools/dispatch/coreDispatch.js +32 -31
- package/dist/tools/dispatch/diagnosticDispatch.d.ts +3 -5
- package/dist/tools/dispatch/diagnosticDispatch.js +70 -68
- package/dist/tools/dispatch/directDispatch.js +11 -10
- package/dist/tools/dispatch/goalDispatch.js +5 -4
- package/dist/tools/dispatch/releaseDispatch.js +3 -2
- package/dist/tools/dispatch/validation.d.ts +7 -0
- package/dist/tools/dispatch/validation.js +71 -0
- package/dist/tools/{getPlan.js → goals/getPlan.js} +4 -4
- package/dist/tools/{goalSubgoalTask.d.ts → goals/goalSubgoalTask.d.ts} +1 -1
- package/dist/tools/goals/goalSubgoalTask.js +118 -0
- package/dist/tools/goals/index.d.ts +3 -0
- package/dist/tools/goals/index.js +3 -0
- package/dist/tools/{savePlan.js → goals/savePlan.js} +28 -8
- package/dist/tools/registry.d.ts +7 -11
- package/dist/tools/registry.js +17 -1178
- package/dist/tools/{checkReleaseGate.d.ts → release/checkReleaseGate.d.ts} +1 -1
- package/dist/tools/{checkReleaseGate.js → release/checkReleaseGate.js} +3 -3
- package/dist/tools/release/index.d.ts +2 -0
- package/dist/tools/release/index.js +2 -0
- package/dist/tools/{releaseMode.d.ts → release/releaseMode.d.ts} +2 -2
- package/dist/tools/{releaseMode.js → release/releaseMode.js} +46 -26
- package/dist/tools/tasks/cancelTask.d.ts +11 -0
- package/dist/tools/tasks/cancelTask.js +85 -0
- package/dist/tools/{createTask.d.ts → tasks/createTask.d.ts} +10 -2
- package/dist/tools/{createTask.js → tasks/createTask.js} +42 -27
- package/dist/tools/{diagnoseTask.d.ts → tasks/diagnoseTask.d.ts} +1 -1
- package/dist/tools/{diagnoseTask.js → tasks/diagnoseTask.js} +8 -8
- package/dist/tools/{evidencePack.js → tasks/evidencePack.js} +34 -22
- package/dist/tools/{getTaskFile.js → tasks/getTaskFile.js} +4 -4
- package/dist/tools/{getTaskStatus.d.ts → tasks/getTaskStatus.d.ts} +6 -1
- package/dist/tools/{getTaskStatus.js → tasks/getTaskStatus.js} +5 -5
- package/dist/tools/{getTaskStdoutTail.js → tasks/getTaskStdoutTail.js} +13 -11
- package/dist/tools/{getTaskSummary.js → tasks/getTaskSummary.js} +33 -26
- package/dist/tools/tasks/index.d.ts +18 -0
- package/dist/tools/tasks/index.js +18 -0
- package/dist/tools/tasks/killTask.d.ts +1 -0
- package/dist/tools/{listTasks.d.ts → tasks/listTasks.d.ts} +1 -1
- package/dist/tools/{listTasks.js → tasks/listTasks.js} +3 -3
- package/dist/tools/{reconcileTasks.d.ts → tasks/reconcileTasks.d.ts} +1 -1
- package/dist/tools/{reconcileTasks.js → tasks/reconcileTasks.js} +64 -66
- package/dist/tools/tasks/retryTask.js +60 -0
- package/dist/tools/{runTaskLoop.d.ts → tasks/runTaskLoop.d.ts} +6 -6
- package/dist/tools/{runTaskLoop.js → tasks/runTaskLoop.js} +58 -30
- package/dist/tools/tasks/runVerification.js +38 -0
- package/dist/tools/{taskLineage.js → tasks/taskLineage.js} +8 -7
- package/dist/tools/{taskOutputs.js → tasks/taskOutputs.js} +17 -16
- package/dist/tools/{androidDoctor.js → workspace/androidDoctor.js} +16 -10
- package/dist/tools/{applyPatch.d.ts → workspace/applyPatch.d.ts} +1 -1
- package/dist/tools/workspace/applyPatch.js +53 -0
- package/dist/tools/{discardWorktree.js → workspace/discardWorktree.js} +3 -3
- package/dist/tools/workspace/index.d.ts +10 -0
- package/dist/tools/workspace/index.js +10 -0
- package/dist/tools/{listAgents.d.ts → workspace/listAgents.d.ts} +5 -0
- package/dist/tools/{listAgents.js → workspace/listAgents.js} +8 -4
- package/dist/tools/{listWorkspace.js → workspace/listWorkspace.js} +3 -3
- package/dist/tools/{mergeWorktree.js → workspace/mergeWorktree.js} +3 -3
- package/dist/tools/{readWorkspaceFile.js → workspace/readWorkspaceFile.js} +8 -8
- package/dist/tools/{recommendAgentForTask.js → workspace/recommendAgentForTask.js} +3 -3
- package/dist/tools/{searchWorkspace.js → workspace/searchWorkspace.js} +5 -5
- package/dist/tools/{syncFile.d.ts → workspace/syncFile.d.ts} +1 -1
- package/dist/tools/workspace/syncFile.js +134 -0
- package/dist/utils/atomicFile.d.ts +7 -0
- package/dist/utils/atomicFile.js +48 -0
- package/dist/utils/boundedFile.d.ts +8 -0
- package/dist/utils/boundedFile.js +93 -0
- package/dist/utils/lockedJsonFile.d.ts +15 -0
- package/dist/utils/lockedJsonFile.js +224 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +26 -0
- package/dist/utils/stableJson.d.ts +2 -0
- package/dist/utils/stableJson.js +12 -0
- package/dist/version.d.ts +3 -2
- package/dist/version.js +3 -2
- package/dist/watcherStatus.d.ts +2 -2
- package/dist/watcherStatus.js +18 -0
- package/docs/CODE_WIKI.md +808 -143
- package/docs/architecture.md +15 -55
- package/docs/assets/patchwarden-desktop-onboarding.png +0 -0
- package/docs/control-center/README.md +4 -4
- package/docs/dashboard-overview.md +4 -4
- package/docs/desktop-app.md +175 -0
- package/docs/evidence-pack-schema.md +3 -3
- package/docs/open-source-application.md +17 -11
- package/docs/release-checklist.md +1 -1
- package/docs/release-evidence.md +39 -29
- package/docs/task-safe-review-workflow.md +5 -5
- package/docs/threat-model.md +19 -1
- package/examples/config.example.json +4 -2
- package/package.json +27 -10
- package/scripts/README.md +15 -6
- package/scripts/build.js +11 -0
- package/scripts/checks/brand-check.js +3 -0
- package/scripts/checks/build-output-check.js +37 -0
- package/scripts/checks/control-center-smoke.js +91 -6
- package/scripts/checks/control-smoke.js +19 -8
- package/scripts/checks/http-mcp-smoke.js +82 -4
- package/scripts/checks/lifecycle-smoke.js +10 -10
- package/scripts/checks/mcp-manifest-check.js +1 -1
- package/scripts/checks/mcp-smoke.js +27 -3
- package/scripts/checks/package-manifest-check.js +20 -7
- package/scripts/checks/unit-tests.js +3 -5
- package/scripts/checks/watcher-supervisor-smoke.js +9 -0
- package/scripts/control/get-patchwarden-health.ps1 +1 -1
- package/scripts/control/manage-patchwarden.ps1 +69 -4
- package/scripts/control/provision-patchwarden-tunnel.ps1 +127 -0
- package/scripts/control/run-background-supervisor.ps1 +41 -0
- package/scripts/control/start-patchwarden-tunnel.ps1 +126 -32
- package/{PatchWarden-Control-Tray.cmd → scripts/launchers/PatchWarden-Control-Tray.cmd} +3 -3
- package/scripts/launchers/PatchWarden-Control.cmd +6 -0
- package/{PatchWarden-Desktop.cmd → scripts/launchers/PatchWarden-Desktop.cmd} +2 -2
- package/scripts/launchers/Restart-PatchWarden-Control.cmd +6 -0
- package/{Stop-PatchWarden.cmd → scripts/launchers/Stop-PatchWarden.cmd} +2 -2
- package/scripts/lib/clean-generated-output.js +11 -0
- package/scripts/lib/file-discovery.js +12 -0
- package/scripts/release/desktop-preflight.js +211 -0
- package/scripts/release/pack-clean.js +59 -14
- package/src/assessments/agentAssessor.ts +26 -12
- package/src/assessments/assessmentStore.ts +52 -40
- package/src/config.ts +70 -13
- package/src/control/fileManager.ts +40 -0
- package/src/control/middleware/auth.ts +22 -4
- package/src/control/middleware/static.ts +16 -3
- package/src/control/routeTable.ts +301 -0
- package/src/control/routes/audit.ts +14 -6
- package/src/control/routes/evidence.ts +1 -1
- package/src/control/routes/lineage.ts +1 -1
- package/src/control/routes/process.ts +341 -40
- package/src/control/routes/sessions.ts +88 -25
- package/src/control/routes/status.ts +97 -17
- package/src/control/routes/taskActions.ts +24 -58
- package/src/control/routes/tasks.ts +66 -58
- package/src/control/routes/workspace.ts +56 -25
- package/src/control/runtime.ts +69 -33
- package/src/control/server.ts +36 -342
- package/src/control/shared.ts +77 -11
- package/src/direct/directAudit.ts +17 -9
- package/src/direct/directGuards.ts +72 -44
- package/src/direct/directPatch.ts +95 -8
- package/src/direct/directSessionStore.ts +211 -55
- package/src/direct/directVerification.ts +9 -23
- package/src/doctor.ts +73 -59
- package/src/goal/acceptanceEngine.ts +1 -1
- package/src/goal/goalProgress.ts +17 -13
- package/src/goal/goalReport.ts +5 -13
- package/src/goal/goalStatus.ts +19 -3
- package/src/goal/goalStore.ts +241 -54
- package/src/goal/handoffExport.ts +6 -6
- package/src/goal/specKitImport.ts +12 -12
- package/src/goal/subgoalSync.ts +11 -8
- package/src/goal/worktreeManager.ts +139 -22
- package/src/httpServer.ts +113 -30
- package/src/logging.ts +10 -3
- package/src/policy/projectPolicy.ts +5 -2
- package/src/release/releaseGate.ts +74 -27
- package/src/runner/agentInvocation.ts +126 -4
- package/src/runner/changeCapture.ts +291 -57
- package/src/runner/postTaskCleanup.ts +41 -17
- package/src/runner/processSecurity.ts +433 -0
- package/src/runner/runTask.ts +138 -91
- package/src/runner/simpleProcess.ts +66 -31
- package/src/{taskProgress.ts → runner/taskProgress.ts} +3 -3
- package/src/{taskRuntime.ts → runner/taskRuntime.ts} +4 -3
- package/src/runner/taskStatusStore.ts +54 -0
- package/src/runner/watch.ts +281 -51
- package/src/security/contentRedaction.ts +2 -2
- package/src/security/discoveryTokenStore.ts +18 -4
- package/src/security/loopbackHost.ts +19 -0
- package/src/security/pathGuard.ts +35 -16
- package/src/security/planGuard.ts +2 -1
- package/src/security/runtimeGuard.ts +9 -6
- package/src/security/secretComparison.ts +13 -0
- package/src/security/sensitiveGuard.ts +27 -8
- package/src/security/toolInvocationGuard.ts +2 -2
- package/src/security/workspaceRootGuard.ts +53 -0
- package/src/smoke-test.ts +25 -24
- package/src/tools/catalog/index.ts +4 -0
- package/src/tools/{toolCatalog.ts → catalog/toolCatalog.ts} +3 -12
- package/src/tools/{toolRegistry.ts → catalog/toolRegistry.ts} +7 -16
- package/src/tools/definitions/toolDefs.ts +1264 -0
- package/src/tools/{auditSession.ts → diagnostics/auditSession.ts} +2 -2
- package/src/tools/{auditTask.ts → diagnostics/auditTask.ts} +99 -32
- package/src/tools/{healthCheck.ts → diagnostics/healthCheck.ts} +8 -8
- package/src/tools/diagnostics/index.ts +6 -0
- package/src/tools/{safeStatus.ts → diagnostics/safeStatus.ts} +7 -7
- package/src/tools/{safeViews.ts → diagnostics/safeViews.ts} +27 -17
- package/src/tools/{schemaDriftCheck.ts → diagnostics/schemaDriftCheck.ts} +3 -3
- package/src/tools/{createDirectSession.ts → direct/createDirectSession.ts} +6 -6
- package/src/tools/{finalizeDirectSession.ts → direct/finalizeDirectSession.ts} +7 -5
- package/src/tools/direct/index.ts +3 -0
- package/src/tools/{runDirectVerificationBundle.ts → direct/runDirectVerificationBundle.ts} +2 -2
- package/src/tools/{discoverTools.ts → discovery/discoverTools.ts} +6 -6
- package/src/tools/{explainTool.ts → discovery/explainTool.ts} +3 -3
- package/src/tools/discovery/index.ts +3 -0
- package/src/tools/{invokeDiscoveredTool.ts → discovery/invokeDiscoveredTool.ts} +7 -7
- package/src/tools/dispatch/coreDispatch.ts +36 -31
- package/src/tools/dispatch/diagnosticDispatch.ts +26 -21
- package/src/tools/dispatch/directDispatch.ts +11 -10
- package/src/tools/dispatch/goalDispatch.ts +5 -4
- package/src/tools/dispatch/releaseDispatch.ts +3 -2
- package/src/tools/dispatch/validation.ts +78 -0
- package/src/tools/{getPlan.ts → goals/getPlan.ts} +4 -4
- package/src/tools/{goalSubgoalTask.ts → goals/goalSubgoalTask.ts} +41 -26
- package/src/tools/goals/index.ts +3 -0
- package/src/tools/{savePlan.ts → goals/savePlan.ts} +38 -8
- package/src/tools/registry.ts +17 -1261
- package/src/tools/{checkReleaseGate.ts → release/checkReleaseGate.ts} +3 -3
- package/src/tools/release/index.ts +2 -0
- package/src/tools/{releaseMode.ts → release/releaseMode.ts} +42 -24
- package/src/tools/tasks/cancelTask.ts +109 -0
- package/src/tools/{createTask.ts → tasks/createTask.ts} +49 -26
- package/src/tools/{diagnoseTask.ts → tasks/diagnoseTask.ts} +8 -8
- package/src/tools/{evidencePack.ts → tasks/evidencePack.ts} +36 -22
- package/src/tools/{getTaskFile.ts → tasks/getTaskFile.ts} +4 -4
- package/src/tools/{getTaskStatus.ts → tasks/getTaskStatus.ts} +7 -5
- package/src/tools/{getTaskStdoutTail.ts → tasks/getTaskStdoutTail.ts} +13 -11
- package/src/tools/{getTaskSummary.ts → tasks/getTaskSummary.ts} +44 -33
- package/src/tools/tasks/index.ts +18 -0
- package/src/tools/{listTasks.ts → tasks/listTasks.ts} +4 -4
- package/src/tools/{reconcileTasks.ts → tasks/reconcileTasks.ts} +68 -66
- package/src/tools/tasks/retryTask.ts +69 -0
- package/src/tools/{runTaskLoop.ts → tasks/runTaskLoop.ts} +69 -34
- package/src/tools/{runVerification.ts → tasks/runVerification.ts} +6 -3
- package/src/tools/{taskLineage.ts → tasks/taskLineage.ts} +8 -7
- package/src/tools/{taskOutputs.ts → tasks/taskOutputs.ts} +27 -20
- package/src/tools/{androidDoctor.ts → workspace/androidDoctor.ts} +15 -10
- package/src/tools/{applyPatch.ts → workspace/applyPatch.ts} +38 -16
- package/src/tools/{discardWorktree.ts → workspace/discardWorktree.ts} +3 -3
- package/src/tools/workspace/index.ts +10 -0
- package/src/tools/{listAgents.ts → workspace/listAgents.ts} +11 -4
- package/src/tools/{listWorkspace.ts → workspace/listWorkspace.ts} +3 -3
- package/src/tools/{mergeWorktree.ts → workspace/mergeWorktree.ts} +3 -3
- package/src/tools/{readWorkspaceFile.ts → workspace/readWorkspaceFile.ts} +8 -8
- package/src/tools/{recommendAgentForTask.ts → workspace/recommendAgentForTask.ts} +3 -3
- package/src/tools/{searchWorkspace.ts → workspace/searchWorkspace.ts} +5 -5
- package/src/tools/workspace/syncFile.ts +292 -0
- package/src/utils/atomicFile.ts +58 -0
- package/src/utils/boundedFile.ts +116 -0
- package/src/utils/lockedJsonFile.ts +251 -0
- package/src/utils/platform.ts +31 -0
- package/src/utils/stableJson.ts +12 -0
- package/src/version.ts +3 -2
- package/src/watcherStatus.ts +19 -1
- package/ui/desktop-bootstrap.js +11 -0
- package/ui/desktop-bridge.js +36 -0
- package/ui/desktop.css +160 -0
- package/ui/getting-started.js +93 -0
- package/ui/i18n.js +484 -0
- package/ui/log-parser.js +30 -0
- package/ui/pages/audit.html +12 -0
- package/ui/pages/dashboard.html +90 -147
- package/ui/pages/direct-sessions.html +123 -26
- package/ui/pages/getting-started.html +43 -0
- package/ui/pages/logs.html +105 -22
- package/ui/pages/settings.html +101 -0
- package/ui/pages/task-detail.html +12 -0
- package/ui/pages/tasks.html +54 -8
- package/ui/pages/workspace.html +38 -1
- package/ui/settings.js +245 -0
- package/PatchWarden-Control.cmd +0 -6
- package/Restart-PatchWarden-Control.cmd +0 -6
- package/dist/test/unit/acceptance-engine.test.d.ts +0 -1
- package/dist/test/unit/acceptance-engine.test.js +0 -228
- package/dist/test/unit/agent-router.test.d.ts +0 -1
- package/dist/test/unit/agent-router.test.js +0 -287
- package/dist/test/unit/android-doctor.test.d.ts +0 -1
- package/dist/test/unit/android-doctor.test.js +0 -118
- package/dist/test/unit/apply-patch.test.d.ts +0 -1
- package/dist/test/unit/apply-patch.test.js +0 -225
- package/dist/test/unit/audit-checks.test.d.ts +0 -1
- package/dist/test/unit/audit-checks.test.js +0 -350
- package/dist/test/unit/chinese-path.test.d.ts +0 -1
- package/dist/test/unit/chinese-path.test.js +0 -91
- package/dist/test/unit/command-guard.test.d.ts +0 -1
- package/dist/test/unit/command-guard.test.js +0 -160
- package/dist/test/unit/create-task.test.d.ts +0 -1
- package/dist/test/unit/create-task.test.js +0 -197
- package/dist/test/unit/diagnose-task.test.d.ts +0 -1
- package/dist/test/unit/diagnose-task.test.js +0 -457
- package/dist/test/unit/direct-guards.test.d.ts +0 -1
- package/dist/test/unit/direct-guards.test.js +0 -328
- package/dist/test/unit/discovery-token-store.test.d.ts +0 -1
- package/dist/test/unit/discovery-token-store.test.js +0 -139
- package/dist/test/unit/evidence-pack.test.d.ts +0 -1
- package/dist/test/unit/evidence-pack.test.js +0 -224
- package/dist/test/unit/get-task-status.test.d.ts +0 -1
- package/dist/test/unit/get-task-status.test.js +0 -174
- package/dist/test/unit/get-task-summary.test.d.ts +0 -1
- package/dist/test/unit/get-task-summary.test.js +0 -146
- package/dist/test/unit/goal-graph.test.d.ts +0 -1
- package/dist/test/unit/goal-graph.test.js +0 -298
- package/dist/test/unit/goal-progress.test.d.ts +0 -1
- package/dist/test/unit/goal-progress.test.js +0 -381
- package/dist/test/unit/goal-report.test.d.ts +0 -1
- package/dist/test/unit/goal-report.test.js +0 -159
- package/dist/test/unit/goal-status.test.d.ts +0 -1
- package/dist/test/unit/goal-status.test.js +0 -215
- package/dist/test/unit/goal-store.test.d.ts +0 -1
- package/dist/test/unit/goal-store.test.js +0 -253
- package/dist/test/unit/goal-subgoal-task.test.d.ts +0 -1
- package/dist/test/unit/goal-subgoal-task.test.js +0 -55
- package/dist/test/unit/goal-tools-registry.test.d.ts +0 -1
- package/dist/test/unit/goal-tools-registry.test.js +0 -192
- package/dist/test/unit/handoff-export.test.d.ts +0 -1
- package/dist/test/unit/handoff-export.test.js +0 -263
- package/dist/test/unit/invoke-discovered-tool.test.d.ts +0 -1
- package/dist/test/unit/invoke-discovered-tool.test.js +0 -167
- package/dist/test/unit/logging.test.d.ts +0 -1
- package/dist/test/unit/logging.test.js +0 -397
- package/dist/test/unit/path-guard.test.d.ts +0 -1
- package/dist/test/unit/path-guard.test.js +0 -133
- package/dist/test/unit/post-task-cleanup.test.d.ts +0 -1
- package/dist/test/unit/post-task-cleanup.test.js +0 -48
- package/dist/test/unit/project-policy-release-mode.test.d.ts +0 -1
- package/dist/test/unit/project-policy-release-mode.test.js +0 -125
- package/dist/test/unit/reconcile-tasks.test.d.ts +0 -1
- package/dist/test/unit/reconcile-tasks.test.js +0 -456
- package/dist/test/unit/release-gate.test.d.ts +0 -1
- package/dist/test/unit/release-gate.test.js +0 -242
- package/dist/test/unit/run-task-loop.test.d.ts +0 -1
- package/dist/test/unit/run-task-loop.test.js +0 -380
- package/dist/test/unit/safe-status.test.d.ts +0 -1
- package/dist/test/unit/safe-status.test.js +0 -165
- package/dist/test/unit/safe-views.test.d.ts +0 -1
- package/dist/test/unit/safe-views.test.js +0 -171
- package/dist/test/unit/schema-drift-check.test.d.ts +0 -1
- package/dist/test/unit/schema-drift-check.test.js +0 -176
- package/dist/test/unit/sensitive-guard.test.d.ts +0 -1
- package/dist/test/unit/sensitive-guard.test.js +0 -104
- package/dist/test/unit/spec-kit-import.test.d.ts +0 -1
- package/dist/test/unit/spec-kit-import.test.js +0 -341
- package/dist/test/unit/subgoal-sync.test.d.ts +0 -1
- package/dist/test/unit/subgoal-sync.test.js +0 -183
- package/dist/test/unit/sync-file.test.d.ts +0 -1
- package/dist/test/unit/sync-file.test.js +0 -154
- package/dist/test/unit/tool-invocation-guard.test.d.ts +0 -1
- package/dist/test/unit/tool-invocation-guard.test.js +0 -432
- package/dist/test/unit/tool-usage-stats.test.d.ts +0 -1
- package/dist/test/unit/tool-usage-stats.test.js +0 -300
- package/dist/test/unit/toolSearch.test.d.ts +0 -1
- package/dist/test/unit/toolSearch.test.js +0 -571
- package/dist/test/unit/wait-for-task.test.d.ts +0 -1
- package/dist/test/unit/wait-for-task.test.js +0 -144
- package/dist/test/unit/watcher-status.test.d.ts +0 -1
- package/dist/test/unit/watcher-status.test.js +0 -169
- package/dist/test/unit/worktree-manager.test.d.ts +0 -1
- package/dist/test/unit/worktree-manager.test.js +0 -176
- package/dist/tools/applyPatch.js +0 -41
- package/dist/tools/cancelTask.d.ts +0 -30
- package/dist/tools/cancelTask.js +0 -64
- package/dist/tools/finalizeDirectSession.js +0 -84
- package/dist/tools/goalSubgoalTask.js +0 -110
- package/dist/tools/killTask.d.ts +0 -15
- package/dist/tools/retryTask.js +0 -45
- package/dist/tools/runVerification.js +0 -36
- package/dist/tools/syncFile.js +0 -65
- package/docs/control-center/control-center-daily-driver.md +0 -211
- package/docs/control-center/control-center-mvp.md +0 -205
- package/docs/control-center/control-center-phase2.md +0 -159
- package/docs/execution-plan-2026-07-09.md +0 -315
- package/docs/release-v0.6.0.md +0 -71
- package/docs/release-v0.6.1.md +0 -75
- package/docs/release-v0.6.4.md +0 -45
- package/docs/roadmap-execution-and-acceptance.md +0 -365
- package/src/test/unit/acceptance-engine.test.ts +0 -261
- package/src/test/unit/agent-router.test.ts +0 -342
- package/src/test/unit/android-doctor.test.ts +0 -158
- package/src/test/unit/apply-patch.test.ts +0 -293
- package/src/test/unit/audit-checks.test.ts +0 -567
- package/src/test/unit/chinese-path.test.ts +0 -106
- package/src/test/unit/command-guard.test.ts +0 -221
- package/src/test/unit/create-task.test.ts +0 -255
- package/src/test/unit/diagnose-task.test.ts +0 -544
- package/src/test/unit/direct-guards.test.ts +0 -467
- package/src/test/unit/discovery-token-store.test.ts +0 -181
- package/src/test/unit/evidence-pack.test.ts +0 -251
- package/src/test/unit/get-task-status.test.ts +0 -203
- package/src/test/unit/get-task-summary.test.ts +0 -173
- package/src/test/unit/goal-graph.test.ts +0 -347
- package/src/test/unit/goal-progress.test.ts +0 -538
- package/src/test/unit/goal-report.test.ts +0 -189
- package/src/test/unit/goal-status.test.ts +0 -270
- package/src/test/unit/goal-store.test.ts +0 -318
- package/src/test/unit/goal-subgoal-task.test.ts +0 -72
- package/src/test/unit/goal-tools-registry.test.ts +0 -245
- package/src/test/unit/handoff-export.test.ts +0 -295
- package/src/test/unit/invoke-discovered-tool.test.ts +0 -216
- package/src/test/unit/logging.test.ts +0 -497
- package/src/test/unit/path-guard.test.ts +0 -180
- package/src/test/unit/post-task-cleanup.test.ts +0 -53
- package/src/test/unit/project-policy-release-mode.test.ts +0 -156
- package/src/test/unit/reconcile-tasks.test.ts +0 -551
- package/src/test/unit/release-gate.test.ts +0 -314
- package/src/test/unit/run-task-loop.test.ts +0 -425
- package/src/test/unit/safe-status.test.ts +0 -187
- package/src/test/unit/safe-views.test.ts +0 -184
- package/src/test/unit/schema-drift-check.test.ts +0 -260
- package/src/test/unit/sensitive-guard.test.ts +0 -124
- package/src/test/unit/spec-kit-import.test.ts +0 -429
- package/src/test/unit/subgoal-sync.test.ts +0 -236
- package/src/test/unit/sync-file.test.ts +0 -231
- package/src/test/unit/tool-invocation-guard.test.ts +0 -542
- package/src/test/unit/tool-usage-stats.test.ts +0 -384
- package/src/test/unit/toolSearch.test.ts +0 -756
- package/src/test/unit/wait-for-task.test.ts +0 -176
- package/src/test/unit/watcher-status.test.ts +0 -190
- package/src/test/unit/worktree-manager.test.ts +0 -247
- package/src/tools/cancelTask.ts +0 -70
- package/src/tools/retryTask.ts +0 -53
- package/src/tools/syncFile.ts +0 -122
- /package/dist/tools/{toolCatalog.d.ts → catalog/toolCatalog.d.ts} +0 -0
- /package/dist/tools/{toolSearch.d.ts → catalog/toolSearch.d.ts} +0 -0
- /package/dist/tools/{toolSearch.js → catalog/toolSearch.js} +0 -0
- /package/dist/tools/{toolUsageStats.d.ts → catalog/toolUsageStats.d.ts} +0 -0
- /package/dist/tools/{toolUsageStats.js → catalog/toolUsageStats.js} +0 -0
- /package/dist/tools/{createDirectSession.d.ts → direct/createDirectSession.d.ts} +0 -0
- /package/dist/tools/{runDirectVerificationBundle.d.ts → direct/runDirectVerificationBundle.d.ts} +0 -0
- /package/dist/tools/{getPlan.d.ts → goals/getPlan.d.ts} +0 -0
- /package/dist/tools/{savePlan.d.ts → goals/savePlan.d.ts} +0 -0
- /package/dist/tools/{evidencePack.d.ts → tasks/evidencePack.d.ts} +0 -0
- /package/dist/tools/{getTaskFile.d.ts → tasks/getTaskFile.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.d.ts → tasks/getTaskProgress.d.ts} +0 -0
- /package/dist/tools/{getTaskProgress.js → tasks/getTaskProgress.js} +0 -0
- /package/dist/tools/{getTaskStdoutTail.d.ts → tasks/getTaskStdoutTail.d.ts} +0 -0
- /package/dist/tools/{getTaskSummary.d.ts → tasks/getTaskSummary.d.ts} +0 -0
- /package/dist/tools/{killTask.js → tasks/killTask.js} +0 -0
- /package/dist/tools/{retryTask.d.ts → tasks/retryTask.d.ts} +0 -0
- /package/dist/tools/{runVerification.d.ts → tasks/runVerification.d.ts} +0 -0
- /package/dist/tools/{taskLineage.d.ts → tasks/taskLineage.d.ts} +0 -0
- /package/dist/tools/{taskOutputs.d.ts → tasks/taskOutputs.d.ts} +0 -0
- /package/dist/tools/{waitForTask.d.ts → tasks/waitForTask.d.ts} +0 -0
- /package/dist/tools/{waitForTask.js → tasks/waitForTask.js} +0 -0
- /package/dist/tools/{androidDoctor.d.ts → workspace/androidDoctor.d.ts} +0 -0
- /package/dist/tools/{discardWorktree.d.ts → workspace/discardWorktree.d.ts} +0 -0
- /package/dist/tools/{listWorkspace.d.ts → workspace/listWorkspace.d.ts} +0 -0
- /package/dist/tools/{mergeWorktree.d.ts → workspace/mergeWorktree.d.ts} +0 -0
- /package/dist/tools/{readWorkspaceFile.d.ts → workspace/readWorkspaceFile.d.ts} +0 -0
- /package/dist/tools/{recommendAgentForTask.d.ts → workspace/recommendAgentForTask.d.ts} +0 -0
- /package/dist/tools/{searchWorkspace.d.ts → workspace/searchWorkspace.d.ts} +0 -0
- /package/src/tools/{toolSearch.ts → catalog/toolSearch.ts} +0 -0
- /package/src/tools/{toolUsageStats.ts → catalog/toolUsageStats.ts} +0 -0
- /package/src/tools/{getTaskProgress.ts → tasks/getTaskProgress.ts} +0 -0
- /package/src/tools/{killTask.ts → tasks/killTask.ts} +0 -0
- /package/src/tools/{waitForTask.ts → tasks/waitForTask.ts} +0 -0
|
@@ -1,350 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
2
|
-
import { strict as assert } from "node:assert";
|
|
3
|
-
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import { checkForbiddenScope, checkDoneEvidenceMissing, checkReadmeChangelogSync, checkPackageManifestConsistency, checkSensitivePathAccess, checkUnrecordedCommandExecution, } from "../../tools/auditTask.js";
|
|
7
|
-
// ── Helpers ───────────────────────────────────────────────────────
|
|
8
|
-
function makeChangedFile(path, change = "modified", oldPath) {
|
|
9
|
-
return {
|
|
10
|
-
path,
|
|
11
|
-
change,
|
|
12
|
-
old_path: oldPath,
|
|
13
|
-
before_sha256: null,
|
|
14
|
-
after_sha256: null,
|
|
15
|
-
tracked: true,
|
|
16
|
-
ignored: false,
|
|
17
|
-
kind: "source",
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
describe("auditTask new checks", () => {
|
|
21
|
-
let tempDir;
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
tempDir = mkdtempSync(join(tmpdir(), "pw-audit-checks-"));
|
|
24
|
-
});
|
|
25
|
-
afterEach(() => {
|
|
26
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
27
|
-
});
|
|
28
|
-
// ── 1. checkForbiddenScope ──────────────────────────────────────
|
|
29
|
-
describe("checkForbiddenScope", () => {
|
|
30
|
-
it("returns null when forbidden is null (skip)", () => {
|
|
31
|
-
const result = checkForbiddenScope([makeChangedFile("src/index.ts")], null);
|
|
32
|
-
assert.equal(result, null);
|
|
33
|
-
});
|
|
34
|
-
it("returns null when forbidden is empty array (skip)", () => {
|
|
35
|
-
const result = checkForbiddenScope([makeChangedFile("src/index.ts")], []);
|
|
36
|
-
assert.equal(result, null);
|
|
37
|
-
});
|
|
38
|
-
it("returns pass when changedFiles is empty", () => {
|
|
39
|
-
const result = checkForbiddenScope([], ["src/security/**"]);
|
|
40
|
-
assert.notEqual(result, null);
|
|
41
|
-
assert.equal(result.result, "pass");
|
|
42
|
-
assert.equal(result.name, "forbidden_scope_violation");
|
|
43
|
-
});
|
|
44
|
-
it("returns pass when no changed file matches forbidden patterns", () => {
|
|
45
|
-
const result = checkForbiddenScope([makeChangedFile("src/tools/auditTask.ts")], ["src/security/**"]);
|
|
46
|
-
assert.equal(result.result, "pass");
|
|
47
|
-
});
|
|
48
|
-
it("returns fail when a changed file matches a ** glob (src/security/** matches src/security/pathGuard.ts)", () => {
|
|
49
|
-
const result = checkForbiddenScope([makeChangedFile("src/security/pathGuard.ts")], ["src/security/**"]);
|
|
50
|
-
assert.equal(result.result, "fail");
|
|
51
|
-
assert.ok(result.detail.includes("src/security/pathGuard.ts"));
|
|
52
|
-
assert.ok(result.detail.includes("forbidden paths"));
|
|
53
|
-
});
|
|
54
|
-
it("returns fail for src/tools/* matching src/tools/auditTask.ts (single segment)", () => {
|
|
55
|
-
const result = checkForbiddenScope([makeChangedFile("src/tools/auditTask.ts")], ["src/tools/*"]);
|
|
56
|
-
assert.equal(result.result, "fail");
|
|
57
|
-
});
|
|
58
|
-
it("returns fail for src/tools/** matching src/tools/auditTask.ts", () => {
|
|
59
|
-
const result = checkForbiddenScope([makeChangedFile("src/tools/auditTask.ts")], ["src/tools/**"]);
|
|
60
|
-
assert.equal(result.result, "fail");
|
|
61
|
-
});
|
|
62
|
-
it("single * does NOT match nested path (src/tools/* does not match src/tools/sub/file.ts)", () => {
|
|
63
|
-
const result = checkForbiddenScope([makeChangedFile("src/tools/sub/file.ts")], ["src/tools/*"]);
|
|
64
|
-
assert.equal(result.result, "pass");
|
|
65
|
-
});
|
|
66
|
-
it("checks old_path for renamed files", () => {
|
|
67
|
-
const result = checkForbiddenScope([makeChangedFile("src/new-path.ts", "renamed", "src/secret/old.ts")], ["src/secret/**"]);
|
|
68
|
-
assert.equal(result.result, "fail");
|
|
69
|
-
assert.ok(result.detail.includes("src/secret/old.ts"));
|
|
70
|
-
});
|
|
71
|
-
it("handles backslash path separators", () => {
|
|
72
|
-
const result = checkForbiddenScope([makeChangedFile("src\\security\\pathGuard.ts")], ["src/security/**"]);
|
|
73
|
-
assert.equal(result.result, "fail");
|
|
74
|
-
});
|
|
75
|
-
it("deduplicates hit paths in detail", () => {
|
|
76
|
-
const result = checkForbiddenScope([makeChangedFile("src/secret/a.ts"), makeChangedFile("src/secret/a.ts")], ["src/secret/**"]);
|
|
77
|
-
const occurrences = result.detail.split("src/secret/a.ts").length - 1;
|
|
78
|
-
assert.equal(occurrences, 1, "path should appear only once");
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
// ── 2. checkDoneEvidenceMissing ─────────────────────────────────
|
|
82
|
-
describe("checkDoneEvidenceMissing", () => {
|
|
83
|
-
it("returns null when doneEvidence is null (skip)", () => {
|
|
84
|
-
const result = checkDoneEvidenceMissing(tempDir, null);
|
|
85
|
-
assert.equal(result, null);
|
|
86
|
-
});
|
|
87
|
-
it("returns null when doneEvidence is empty (skip)", () => {
|
|
88
|
-
const result = checkDoneEvidenceMissing(tempDir, []);
|
|
89
|
-
assert.equal(result, null);
|
|
90
|
-
});
|
|
91
|
-
it("returns pass when all evidence files exist", () => {
|
|
92
|
-
writeFileSync(join(tempDir, "result.md"), "# Result", "utf-8");
|
|
93
|
-
writeFileSync(join(tempDir, "test.log"), "ok", "utf-8");
|
|
94
|
-
const result = checkDoneEvidenceMissing(tempDir, ["result.md", "test.log"]);
|
|
95
|
-
assert.equal(result.result, "pass");
|
|
96
|
-
assert.equal(result.name, "done_evidence_missing");
|
|
97
|
-
assert.ok(result.detail.includes("present"));
|
|
98
|
-
});
|
|
99
|
-
it("returns warn when some evidence files are missing", () => {
|
|
100
|
-
writeFileSync(join(tempDir, "result.md"), "# Result", "utf-8");
|
|
101
|
-
const result = checkDoneEvidenceMissing(tempDir, ["result.md", "test.log", "git.diff"]);
|
|
102
|
-
assert.equal(result.result, "warn");
|
|
103
|
-
assert.ok(result.detail.includes("test.log"));
|
|
104
|
-
assert.ok(result.detail.includes("git.diff"));
|
|
105
|
-
assert.ok(!result.detail.includes("result.md"));
|
|
106
|
-
});
|
|
107
|
-
it("returns warn when all evidence files are missing", () => {
|
|
108
|
-
const result = checkDoneEvidenceMissing(tempDir, ["result.md", "test.log"]);
|
|
109
|
-
assert.equal(result.result, "warn");
|
|
110
|
-
assert.ok(result.detail.includes("result.md"));
|
|
111
|
-
assert.ok(result.detail.includes("test.log"));
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
// ── 3. checkReadmeChangelogSync ─────────────────────────────────
|
|
115
|
-
describe("checkReadmeChangelogSync", () => {
|
|
116
|
-
it("returns null when no code files changed (skip)", () => {
|
|
117
|
-
const result = checkReadmeChangelogSync([
|
|
118
|
-
makeChangedFile("README.md"),
|
|
119
|
-
makeChangedFile("docs/guide.md"),
|
|
120
|
-
]);
|
|
121
|
-
assert.equal(result, null);
|
|
122
|
-
});
|
|
123
|
-
it("returns null when changedFiles is empty (skip)", () => {
|
|
124
|
-
const result = checkReadmeChangelogSync([]);
|
|
125
|
-
assert.equal(result, null);
|
|
126
|
-
});
|
|
127
|
-
it("returns warn when code changed but no README/CHANGELOG updated", () => {
|
|
128
|
-
const result = checkReadmeChangelogSync([
|
|
129
|
-
makeChangedFile("src/index.ts"),
|
|
130
|
-
makeChangedFile("src/tools/auditTask.ts"),
|
|
131
|
-
]);
|
|
132
|
-
assert.equal(result.result, "warn");
|
|
133
|
-
assert.equal(result.name, "readme_changelog_sync");
|
|
134
|
-
assert.ok(result.detail.includes("not updated"));
|
|
135
|
-
});
|
|
136
|
-
it("returns pass when README.md is also changed", () => {
|
|
137
|
-
const result = checkReadmeChangelogSync([
|
|
138
|
-
makeChangedFile("src/index.ts"),
|
|
139
|
-
makeChangedFile("README.md"),
|
|
140
|
-
]);
|
|
141
|
-
assert.equal(result.result, "pass");
|
|
142
|
-
});
|
|
143
|
-
it("returns pass when CHANGELOG.md is also changed", () => {
|
|
144
|
-
const result = checkReadmeChangelogSync([
|
|
145
|
-
makeChangedFile("src/index.ts"),
|
|
146
|
-
makeChangedFile("CHANGELOG.md"),
|
|
147
|
-
]);
|
|
148
|
-
assert.equal(result.result, "pass");
|
|
149
|
-
});
|
|
150
|
-
it("is case-insensitive for README.md basename", () => {
|
|
151
|
-
const result = checkReadmeChangelogSync([
|
|
152
|
-
makeChangedFile("src/index.ts"),
|
|
153
|
-
makeChangedFile("docs/Readme.md"),
|
|
154
|
-
]);
|
|
155
|
-
assert.equal(result.result, "pass");
|
|
156
|
-
});
|
|
157
|
-
it("detects code files by various extensions", () => {
|
|
158
|
-
const extensions = [".ts", ".js", ".py", ".go", ".rs", ".java"];
|
|
159
|
-
for (const ext of extensions) {
|
|
160
|
-
const result = checkReadmeChangelogSync([makeChangedFile(`src/file${ext}`)]);
|
|
161
|
-
assert.equal(result.result, "warn", `extension ${ext} should be detected as code`);
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
it("does not treat .md as code", () => {
|
|
165
|
-
const result = checkReadmeChangelogSync([makeChangedFile("docs/guide.md")]);
|
|
166
|
-
assert.equal(result, null);
|
|
167
|
-
});
|
|
168
|
-
});
|
|
169
|
-
// ── 4. checkPackageManifestConsistency ──────────────────────────
|
|
170
|
-
describe("checkPackageManifestConsistency", () => {
|
|
171
|
-
it("returns null when package.json is not in changed files (skip)", () => {
|
|
172
|
-
const result = checkPackageManifestConsistency([makeChangedFile("src/index.ts")], tempDir);
|
|
173
|
-
assert.equal(result, null);
|
|
174
|
-
});
|
|
175
|
-
it("returns pass when package.json has valid name and version", () => {
|
|
176
|
-
writeFileSync(join(tempDir, "package.json"), JSON.stringify({ name: "patchwarden", version: "0.7.2" }), "utf-8");
|
|
177
|
-
const result = checkPackageManifestConsistency([makeChangedFile("package.json")], tempDir);
|
|
178
|
-
assert.equal(result.result, "pass");
|
|
179
|
-
assert.equal(result.name, "package_manifest_consistency");
|
|
180
|
-
});
|
|
181
|
-
it("returns warn when package.json is missing version field", () => {
|
|
182
|
-
writeFileSync(join(tempDir, "package.json"), JSON.stringify({ name: "patchwarden" }), "utf-8");
|
|
183
|
-
const result = checkPackageManifestConsistency([makeChangedFile("package.json")], tempDir);
|
|
184
|
-
assert.equal(result.result, "warn");
|
|
185
|
-
});
|
|
186
|
-
it("returns warn when package.json is missing name field", () => {
|
|
187
|
-
writeFileSync(join(tempDir, "package.json"), JSON.stringify({ version: "0.7.2" }), "utf-8");
|
|
188
|
-
const result = checkPackageManifestConsistency([makeChangedFile("package.json")], tempDir);
|
|
189
|
-
assert.equal(result.result, "warn");
|
|
190
|
-
});
|
|
191
|
-
it("returns warn when package.json is invalid JSON", () => {
|
|
192
|
-
writeFileSync(join(tempDir, "package.json"), "{ not valid json", "utf-8");
|
|
193
|
-
const result = checkPackageManifestConsistency([makeChangedFile("package.json")], tempDir);
|
|
194
|
-
assert.equal(result.result, "warn");
|
|
195
|
-
});
|
|
196
|
-
it("returns warn when package.json does not exist on disk", () => {
|
|
197
|
-
const result = checkPackageManifestConsistency([makeChangedFile("package.json")], tempDir);
|
|
198
|
-
assert.equal(result.result, "warn");
|
|
199
|
-
});
|
|
200
|
-
it("detects package.json case-insensitively by basename", () => {
|
|
201
|
-
writeFileSync(join(tempDir, "package.json"), JSON.stringify({ name: "x", version: "1.0.0" }), "utf-8");
|
|
202
|
-
const result = checkPackageManifestConsistency([makeChangedFile("subdir/Package.json")], tempDir);
|
|
203
|
-
assert.equal(result.result, "pass");
|
|
204
|
-
});
|
|
205
|
-
it("returns warn when name/version are empty strings", () => {
|
|
206
|
-
writeFileSync(join(tempDir, "package.json"), JSON.stringify({ name: "", version: "" }), "utf-8");
|
|
207
|
-
const result = checkPackageManifestConsistency([makeChangedFile("package.json")], tempDir);
|
|
208
|
-
assert.equal(result.result, "warn");
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
// ── 5. checkSensitivePathAccess ─────────────────────────────────
|
|
212
|
-
describe("checkSensitivePathAccess", () => {
|
|
213
|
-
it("returns null when changedFiles is empty (skip)", () => {
|
|
214
|
-
const result = checkSensitivePathAccess([]);
|
|
215
|
-
assert.equal(result, null);
|
|
216
|
-
});
|
|
217
|
-
it("returns pass when no sensitive paths are accessed", () => {
|
|
218
|
-
const result = checkSensitivePathAccess([
|
|
219
|
-
makeChangedFile("src/index.ts"),
|
|
220
|
-
makeChangedFile("README.md"),
|
|
221
|
-
]);
|
|
222
|
-
assert.equal(result.result, "pass");
|
|
223
|
-
assert.equal(result.name, "sensitive_path_access");
|
|
224
|
-
assert.ok(result.detail.includes("No sensitive"));
|
|
225
|
-
});
|
|
226
|
-
it("returns fail when .env is accessed", () => {
|
|
227
|
-
const result = checkSensitivePathAccess([makeChangedFile(".env")]);
|
|
228
|
-
assert.equal(result.result, "fail");
|
|
229
|
-
assert.ok(result.detail.includes(".env"));
|
|
230
|
-
});
|
|
231
|
-
it("returns fail when id_rsa is accessed", () => {
|
|
232
|
-
const result = checkSensitivePathAccess([makeChangedFile("id_rsa")]);
|
|
233
|
-
assert.equal(result.result, "fail");
|
|
234
|
-
});
|
|
235
|
-
it("returns fail when credentials.json is accessed", () => {
|
|
236
|
-
const result = checkSensitivePathAccess([makeChangedFile("credentials.json")]);
|
|
237
|
-
assert.equal(result.result, "fail");
|
|
238
|
-
});
|
|
239
|
-
it("returns fail when a sensitive path is in a subdirectory", () => {
|
|
240
|
-
const result = checkSensitivePathAccess([
|
|
241
|
-
makeChangedFile("src/index.ts"),
|
|
242
|
-
makeChangedFile("config/secrets.pem"),
|
|
243
|
-
]);
|
|
244
|
-
assert.equal(result.result, "fail");
|
|
245
|
-
assert.ok(result.detail.includes("config/secrets.pem"));
|
|
246
|
-
});
|
|
247
|
-
it("returns fail when old_path of a renamed file is sensitive", () => {
|
|
248
|
-
const result = checkSensitivePathAccess([
|
|
249
|
-
makeChangedFile("src/new-location.ts", "renamed", ".env"),
|
|
250
|
-
]);
|
|
251
|
-
assert.equal(result.result, "fail");
|
|
252
|
-
assert.ok(result.detail.includes(".env"));
|
|
253
|
-
});
|
|
254
|
-
it("returns pass for src/index.ts (non-sensitive)", () => {
|
|
255
|
-
const result = checkSensitivePathAccess([makeChangedFile("src/index.ts")]);
|
|
256
|
-
assert.equal(result.result, "pass");
|
|
257
|
-
});
|
|
258
|
-
it("deduplicates hit paths in detail", () => {
|
|
259
|
-
const result = checkSensitivePathAccess([
|
|
260
|
-
makeChangedFile(".env"),
|
|
261
|
-
makeChangedFile(".env"),
|
|
262
|
-
]);
|
|
263
|
-
const occurrences = result.detail.split(".env").length - 1;
|
|
264
|
-
assert.equal(occurrences, 1, "path should appear only once");
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
// ── 6. checkUnrecordedCommandExecution ──────────────────────────
|
|
268
|
-
describe("checkUnrecordedCommandExecution", () => {
|
|
269
|
-
it("returns null when both contents are null (skip)", () => {
|
|
270
|
-
const result = checkUnrecordedCommandExecution(null, null, [], null);
|
|
271
|
-
assert.equal(result, null);
|
|
272
|
-
});
|
|
273
|
-
it("returns null when both contents are empty strings (skip)", () => {
|
|
274
|
-
const result = checkUnrecordedCommandExecution("", "", [], null);
|
|
275
|
-
assert.equal(result, null);
|
|
276
|
-
});
|
|
277
|
-
it("returns pass when test log command is in whitelist (npm test)", () => {
|
|
278
|
-
const result = checkUnrecordedCommandExecution("Running npm test\nExit code: 0", null, [], "npm test");
|
|
279
|
-
assert.equal(result.result, "pass");
|
|
280
|
-
assert.equal(result.name, "unrecorded_command_execution");
|
|
281
|
-
assert.ok(result.detail.includes("whitelist"));
|
|
282
|
-
});
|
|
283
|
-
it("returns warn when npm run build is not in whitelist", () => {
|
|
284
|
-
const result = checkUnrecordedCommandExecution("Running npm run build\nDone", null, [], null);
|
|
285
|
-
assert.equal(result.result, "warn");
|
|
286
|
-
assert.ok(result.detail.includes("build"));
|
|
287
|
-
});
|
|
288
|
-
it("returns pass when npm run build IS in whitelist (via verify_commands)", () => {
|
|
289
|
-
const result = checkUnrecordedCommandExecution("Running npm run build\nDone", null, ["npm run build"], null);
|
|
290
|
-
assert.equal(result.result, "pass");
|
|
291
|
-
});
|
|
292
|
-
it("returns pass when npm run build IS in whitelist (via test_command)", () => {
|
|
293
|
-
const result = checkUnrecordedCommandExecution("Running npm run build\nDone", null, [], "npm run build");
|
|
294
|
-
assert.equal(result.result, "pass");
|
|
295
|
-
});
|
|
296
|
-
it("scans result.md content as well as test.log", () => {
|
|
297
|
-
const result = checkUnrecordedCommandExecution(null, "I ran npm run deploy to publish", ["npm test"], "npm test");
|
|
298
|
-
assert.equal(result.result, "warn");
|
|
299
|
-
assert.ok(result.detail.includes("deploy"));
|
|
300
|
-
});
|
|
301
|
-
it("handles npm.cmd variant", () => {
|
|
302
|
-
const result = checkUnrecordedCommandExecution("npm.cmd run build\nDone", null, [], null);
|
|
303
|
-
assert.equal(result.result, "warn");
|
|
304
|
-
assert.ok(result.detail.includes("build"));
|
|
305
|
-
});
|
|
306
|
-
it("handles npx commands", () => {
|
|
307
|
-
const result = checkUnrecordedCommandExecution("npx eslint src/\nDone", null, [], null);
|
|
308
|
-
assert.equal(result.result, "warn");
|
|
309
|
-
assert.ok(result.detail.includes("eslint"));
|
|
310
|
-
});
|
|
311
|
-
it("handles node commands", () => {
|
|
312
|
-
const result = checkUnrecordedCommandExecution("node ./scripts/check.js\nDone", null, [], null);
|
|
313
|
-
assert.equal(result.result, "warn");
|
|
314
|
-
assert.ok(result.detail.includes("scripts/check.js"));
|
|
315
|
-
});
|
|
316
|
-
it("returns pass when whitelist contains all discovered commands", () => {
|
|
317
|
-
const log = "npm run build && npm test && npx tsc";
|
|
318
|
-
const result = checkUnrecordedCommandExecution(log, null, ["npm run build", "npx tsc"], "npm test");
|
|
319
|
-
assert.equal(result.result, "pass");
|
|
320
|
-
});
|
|
321
|
-
it("recognizes commands reached transitively through npm scripts and local verifier files", () => {
|
|
322
|
-
mkdirSync(join(tempDir, "scripts"), { recursive: true });
|
|
323
|
-
writeFileSync(join(tempDir, "package.json"), JSON.stringify({
|
|
324
|
-
scripts: {
|
|
325
|
-
test: "node scripts/verify-all.js",
|
|
326
|
-
build: "node scripts/build.js",
|
|
327
|
-
check: "node scripts/check.js",
|
|
328
|
-
},
|
|
329
|
-
}), "utf-8");
|
|
330
|
-
writeFileSync(join(tempDir, "scripts", "verify-all.js"), "npm run build\nnpm run check\n", "utf-8");
|
|
331
|
-
const result = checkUnrecordedCommandExecution("npm test\nnode scripts/verify-all.js\nnpm run build\nnpm run check\n", null, [], "npm test", tempDir);
|
|
332
|
-
assert.equal(result.result, "pass");
|
|
333
|
-
assert.ok(result.detail.includes("transitive_verified_command"));
|
|
334
|
-
});
|
|
335
|
-
it("fails high-risk publication commands even when they look intentional", () => {
|
|
336
|
-
const result = checkUnrecordedCommandExecution("npm publish --access public\n", null, ["npm publish --access public"], null, tempDir);
|
|
337
|
-
assert.equal(result.result, "fail");
|
|
338
|
-
assert.ok(result.detail.includes("High-risk"));
|
|
339
|
-
});
|
|
340
|
-
it("returns pass when there are no commands in the log", () => {
|
|
341
|
-
const result = checkUnrecordedCommandExecution("No commands here, just text.\nExit code: 0", null, [], null);
|
|
342
|
-
assert.equal(result.result, "pass");
|
|
343
|
-
});
|
|
344
|
-
it("deduplicates unrecorded commands in detail", () => {
|
|
345
|
-
const result = checkUnrecordedCommandExecution("npm run build && npm run build && npm run build", null, [], null);
|
|
346
|
-
const occurrences = result.detail.split("build").length - 1;
|
|
347
|
-
assert.equal(occurrences, 1, "command should appear only once");
|
|
348
|
-
});
|
|
349
|
-
});
|
|
350
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
2
|
-
import { strict as assert } from "node:assert";
|
|
3
|
-
import { mkdtempSync, rmSync, mkdirSync, writeFileSync, readFileSync, existsSync } from "node:fs";
|
|
4
|
-
import { join, resolve } from "node:path";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
describe("Chinese path handling", () => {
|
|
7
|
-
let tempDir;
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
tempDir = mkdtempSync(join(tmpdir(), "pw-chinese-"));
|
|
10
|
-
});
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
13
|
-
});
|
|
14
|
-
it("writes and reads status.json with Chinese repo_path", () => {
|
|
15
|
-
const chinesePath = "念念小伴_release";
|
|
16
|
-
const status = {
|
|
17
|
-
task_id: "test-001",
|
|
18
|
-
status: "pending",
|
|
19
|
-
repo_path: chinesePath,
|
|
20
|
-
resolved_repo_path: resolve(tempDir, chinesePath),
|
|
21
|
-
};
|
|
22
|
-
const statusFile = join(tempDir, "status.json");
|
|
23
|
-
writeFileSync(statusFile, JSON.stringify(status, null, 2), "utf-8");
|
|
24
|
-
const raw = readFileSync(statusFile, "utf-8");
|
|
25
|
-
const parsed = JSON.parse(raw);
|
|
26
|
-
assert.equal(parsed.repo_path, chinesePath);
|
|
27
|
-
assert.equal(parsed.resolved_repo_path, resolve(tempDir, chinesePath));
|
|
28
|
-
});
|
|
29
|
-
it("preserves Chinese characters through JSON stringify/parse", () => {
|
|
30
|
-
const testPaths = [
|
|
31
|
-
"念念小伴_release",
|
|
32
|
-
"测试项目",
|
|
33
|
-
"项目目录/子目录",
|
|
34
|
-
"日本語プロジェクト",
|
|
35
|
-
"한국어_프로젝트",
|
|
36
|
-
];
|
|
37
|
-
for (const path of testPaths) {
|
|
38
|
-
const json = JSON.stringify({ repo_path: path });
|
|
39
|
-
const parsed = JSON.parse(json);
|
|
40
|
-
assert.equal(parsed.repo_path, path, `Path "${path}" was corrupted`);
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
it("creates and reads directories with Chinese names", () => {
|
|
44
|
-
const chineseDir = join(tempDir, "念念小伴_release");
|
|
45
|
-
mkdirSync(chineseDir, { recursive: true });
|
|
46
|
-
assert.ok(existsSync(chineseDir));
|
|
47
|
-
const filePath = join(chineseDir, "status.json");
|
|
48
|
-
const data = { repo_path: "念念小伴_release", status: "running" };
|
|
49
|
-
writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
50
|
-
const raw = readFileSync(filePath, "utf-8");
|
|
51
|
-
const parsed = JSON.parse(raw);
|
|
52
|
-
assert.equal(parsed.repo_path, "念念小伴_release");
|
|
53
|
-
});
|
|
54
|
-
it("handles Chinese characters in result.md", () => {
|
|
55
|
-
const resultMd = [
|
|
56
|
-
"# PatchWarden Task Result",
|
|
57
|
-
"",
|
|
58
|
-
"## Status",
|
|
59
|
-
"done",
|
|
60
|
-
"",
|
|
61
|
-
"## Files changed",
|
|
62
|
-
"- modified: 念念小伴_release/src/main.ts",
|
|
63
|
-
"- added: 测试项目/config.json",
|
|
64
|
-
"",
|
|
65
|
-
].join("\n");
|
|
66
|
-
const resultFile = join(tempDir, "result.md");
|
|
67
|
-
writeFileSync(resultFile, resultMd, "utf-8");
|
|
68
|
-
const content = readFileSync(resultFile, "utf-8");
|
|
69
|
-
assert.ok(content.includes("念念小伴_release"));
|
|
70
|
-
assert.ok(content.includes("测试项目"));
|
|
71
|
-
});
|
|
72
|
-
it("handles mixed Chinese and ASCII paths", () => {
|
|
73
|
-
const mixedPath = "src/组件/Button.tsx";
|
|
74
|
-
const data = { path: mixedPath };
|
|
75
|
-
const json = JSON.stringify(data);
|
|
76
|
-
const parsed = JSON.parse(json);
|
|
77
|
-
assert.equal(parsed.path, mixedPath);
|
|
78
|
-
});
|
|
79
|
-
it("handles Chinese characters in error messages", () => {
|
|
80
|
-
const errorMsg = 'repo_path "念念小伴_release" is outside workspace';
|
|
81
|
-
const errorFile = join(tempDir, "error.log");
|
|
82
|
-
writeFileSync(errorFile, errorMsg, "utf-8");
|
|
83
|
-
const content = readFileSync(errorFile, "utf-8");
|
|
84
|
-
assert.ok(content.includes("念念小伴_release"));
|
|
85
|
-
});
|
|
86
|
-
it("handles Windows backslash paths with Chinese characters", () => {
|
|
87
|
-
const winPath = "念念小伴_release\\src\\main.ts";
|
|
88
|
-
const normalized = winPath.replace(/\\/g, "/");
|
|
89
|
-
assert.equal(normalized, "念念小伴_release/src/main.ts");
|
|
90
|
-
});
|
|
91
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
2
|
-
import { strict as assert } from "node:assert";
|
|
3
|
-
import { mkdtempSync, rmSync } from "node:fs";
|
|
4
|
-
import { join } from "node:path";
|
|
5
|
-
import { tmpdir } from "node:os";
|
|
6
|
-
import { guardAgentCommand, guardTestCommand, guardDirectCommand, sanitizePromptArg } from "../../security/commandGuard.js";
|
|
7
|
-
import { PatchWardenError } from "../../errors.js";
|
|
8
|
-
function makeConfig(workspaceRoot) {
|
|
9
|
-
return {
|
|
10
|
-
workspaceRoot,
|
|
11
|
-
plansDir: ".patchwarden/plans",
|
|
12
|
-
tasksDir: ".patchwarden/tasks",
|
|
13
|
-
assessmentsDir: ".patchwarden/assessments",
|
|
14
|
-
assessmentTtlSeconds: 3600,
|
|
15
|
-
agents: {
|
|
16
|
-
codex: { command: "codex", args: ["exec", "{repo}", "{prompt}"] },
|
|
17
|
-
opencode: { command: "opencode", args: ["run", "{prompt}"] },
|
|
18
|
-
},
|
|
19
|
-
allowedTestCommands: ["npm test", "npm run build", "npm run lint"],
|
|
20
|
-
repoAllowedTestCommands: {},
|
|
21
|
-
maxReadFileBytes: 200_000,
|
|
22
|
-
defaultTaskTimeoutSeconds: 900,
|
|
23
|
-
maxTaskTimeoutSeconds: 3600,
|
|
24
|
-
watcherStaleSeconds: 30,
|
|
25
|
-
directSessionsDir: ".patchwarden/direct-sessions",
|
|
26
|
-
directSessionTtlSeconds: 3600,
|
|
27
|
-
directMaxPatchBytes: 200_000,
|
|
28
|
-
directMaxFileBytes: 500_000,
|
|
29
|
-
directAllowedCommands: ["npm test", "npm run build"],
|
|
30
|
-
repoDirectAllowedCommands: {},
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
describe("guardTestCommand", () => {
|
|
34
|
-
let tempDir;
|
|
35
|
-
let config;
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
tempDir = mkdtempSync(join(tmpdir(), "pw-cmdguard-"));
|
|
38
|
-
config = makeConfig(tempDir);
|
|
39
|
-
});
|
|
40
|
-
afterEach(() => {
|
|
41
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
42
|
-
});
|
|
43
|
-
it("allows exact match from allowlist", () => {
|
|
44
|
-
assert.equal(guardTestCommand("npm test", config), "npm test");
|
|
45
|
-
assert.equal(guardTestCommand("npm run build", config), "npm run build");
|
|
46
|
-
assert.equal(guardTestCommand("npm run lint", config), "npm run lint");
|
|
47
|
-
});
|
|
48
|
-
it("trims leading spaces and matches", () => {
|
|
49
|
-
assert.equal(guardTestCommand(" npm test", config), "npm test");
|
|
50
|
-
});
|
|
51
|
-
it("trims trailing spaces and matches", () => {
|
|
52
|
-
assert.equal(guardTestCommand("npm test ", config), "npm test");
|
|
53
|
-
});
|
|
54
|
-
it("trims and matches with double spaces in middle (trim only affects ends)", () => {
|
|
55
|
-
// "npm test" has double space — trim() only trims ends, so "npm test" != "npm test"
|
|
56
|
-
assert.throws(() => guardTestCommand("npm test", config), PatchWardenError);
|
|
57
|
-
});
|
|
58
|
-
it("rejects non-allowlisted commands", () => {
|
|
59
|
-
assert.throws(() => guardTestCommand("rm -rf /", config), PatchWardenError);
|
|
60
|
-
assert.throws(() => guardTestCommand("curl http://evil.com", config), PatchWardenError);
|
|
61
|
-
});
|
|
62
|
-
it("returns empty string for empty or undefined command", () => {
|
|
63
|
-
assert.equal(guardTestCommand("", config), "");
|
|
64
|
-
assert.equal(guardTestCommand(undefined, config), "");
|
|
65
|
-
});
|
|
66
|
-
it("returns empty string for whitespace-only command", () => {
|
|
67
|
-
assert.equal(guardTestCommand(" ", config), "");
|
|
68
|
-
});
|
|
69
|
-
it("cannot be bypassed by parameter concatenation", () => {
|
|
70
|
-
// "npm test && rm -rf /" is not in the allowlist
|
|
71
|
-
assert.throws(() => guardTestCommand("npm test && rm -rf /", config), PatchWardenError);
|
|
72
|
-
// "npm test; evil" is not in the allowlist
|
|
73
|
-
assert.throws(() => guardTestCommand("npm test; evil", config), PatchWardenError);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
describe("guardAgentCommand", () => {
|
|
77
|
-
let tempDir;
|
|
78
|
-
let config;
|
|
79
|
-
beforeEach(() => {
|
|
80
|
-
tempDir = mkdtempSync(join(tmpdir(), "pw-agentguard-"));
|
|
81
|
-
config = makeConfig(tempDir);
|
|
82
|
-
});
|
|
83
|
-
afterEach(() => {
|
|
84
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
85
|
-
});
|
|
86
|
-
it("allows configured agents", () => {
|
|
87
|
-
const result = guardAgentCommand("codex", config);
|
|
88
|
-
assert.equal(result.command, "codex");
|
|
89
|
-
assert.deepEqual(result.args, ["exec", "{repo}", "{prompt}"]);
|
|
90
|
-
const result2 = guardAgentCommand("opencode", config);
|
|
91
|
-
assert.equal(result2.command, "opencode");
|
|
92
|
-
});
|
|
93
|
-
it("rejects unconfigured agents", () => {
|
|
94
|
-
assert.throws(() => guardAgentCommand("evil-agent", config), (err) => err instanceof PatchWardenError && err.reason === "agent_not_configured");
|
|
95
|
-
});
|
|
96
|
-
it("rejects empty agent name", () => {
|
|
97
|
-
assert.throws(() => guardAgentCommand("", config), PatchWardenError);
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
describe("guardDirectCommand", () => {
|
|
101
|
-
let tempDir;
|
|
102
|
-
let config;
|
|
103
|
-
beforeEach(() => {
|
|
104
|
-
tempDir = mkdtempSync(join(tmpdir(), "pw-directcmd-"));
|
|
105
|
-
config = makeConfig(tempDir);
|
|
106
|
-
});
|
|
107
|
-
afterEach(() => {
|
|
108
|
-
rmSync(tempDir, { recursive: true, force: true });
|
|
109
|
-
});
|
|
110
|
-
it("allows commands from direct allowlist", () => {
|
|
111
|
-
assert.equal(guardDirectCommand("npm test", config), "npm test");
|
|
112
|
-
assert.equal(guardDirectCommand("npm run build", config), "npm run build");
|
|
113
|
-
});
|
|
114
|
-
it("rejects non-allowlisted direct commands", () => {
|
|
115
|
-
assert.throws(() => guardDirectCommand("rm -rf /", config), PatchWardenError);
|
|
116
|
-
});
|
|
117
|
-
it("rejects empty command", () => {
|
|
118
|
-
assert.throws(() => guardDirectCommand("", config), PatchWardenError);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
describe("sanitizePromptArg", () => {
|
|
122
|
-
it("removes null bytes", () => {
|
|
123
|
-
const input = "hello\x00world";
|
|
124
|
-
const result = sanitizePromptArg(input);
|
|
125
|
-
assert.equal(result, "helloworld");
|
|
126
|
-
});
|
|
127
|
-
it("removes control characters (except tab and newline)", () => {
|
|
128
|
-
const input = "hello\x01\x02\x03world";
|
|
129
|
-
const result = sanitizePromptArg(input);
|
|
130
|
-
assert.equal(result, "helloworld");
|
|
131
|
-
});
|
|
132
|
-
it("preserves tab characters", () => {
|
|
133
|
-
const input = "hello\tworld";
|
|
134
|
-
const result = sanitizePromptArg(input);
|
|
135
|
-
assert.equal(result, "hello\tworld");
|
|
136
|
-
});
|
|
137
|
-
it("preserves newline characters", () => {
|
|
138
|
-
const input = "hello\nworld";
|
|
139
|
-
const result = sanitizePromptArg(input);
|
|
140
|
-
assert.equal(result, "hello\nworld");
|
|
141
|
-
});
|
|
142
|
-
it("preserves carriage return", () => {
|
|
143
|
-
const input = "hello\rworld";
|
|
144
|
-
const result = sanitizePromptArg(input);
|
|
145
|
-
assert.equal(result, "hello\rworld");
|
|
146
|
-
});
|
|
147
|
-
it("preserves normal text", () => {
|
|
148
|
-
const input = "Fix the bug in main.ts";
|
|
149
|
-
const result = sanitizePromptArg(input);
|
|
150
|
-
assert.equal(result, input);
|
|
151
|
-
});
|
|
152
|
-
it("removes vertical tab and form feed", () => {
|
|
153
|
-
const input = "hello\x0B\x0Cworld";
|
|
154
|
-
const result = sanitizePromptArg(input);
|
|
155
|
-
assert.equal(result, "helloworld");
|
|
156
|
-
});
|
|
157
|
-
it("handles empty string", () => {
|
|
158
|
-
assert.equal(sanitizePromptArg(""), "");
|
|
159
|
-
});
|
|
160
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|