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
package/dist/doctor.js
CHANGED
|
@@ -9,461 +9,700 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, statSync, readFileSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
11
11
|
import { isAbsolute, resolve, normalize, join } from "node:path";
|
|
12
|
-
import { execSync } from "node:child_process";
|
|
13
12
|
import { createServer } from "node:net";
|
|
14
13
|
import { getConfig } from "./config.js";
|
|
15
14
|
import { guardPath, guardWorkspacePath } from "./security/pathGuard.js";
|
|
16
15
|
import { isSensitivePath } from "./security/sensitiveGuard.js";
|
|
17
16
|
import { guardPlanContent } from "./security/planGuard.js";
|
|
18
|
-
import { TASK_READ_ONLY_FILES } from "./tools/getTaskFile.js";
|
|
17
|
+
import { TASK_READ_ONLY_FILES } from "./tools/tasks/getTaskFile.js";
|
|
19
18
|
import { getToolDefs } from "./tools/registry.js";
|
|
20
|
-
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile } from "./tools/toolCatalog.js";
|
|
21
|
-
import { buildToolRegistry } from "./tools/toolRegistry.js";
|
|
22
|
-
import { runAllSchemaDriftChecks } from "./tools/schemaDriftCheck.js";
|
|
19
|
+
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile } from "./tools/catalog/toolCatalog.js";
|
|
20
|
+
import { buildToolRegistry } from "./tools/catalog/toolRegistry.js";
|
|
21
|
+
import { runAllSchemaDriftChecks } from "./tools/diagnostics/schemaDriftCheck.js";
|
|
23
22
|
import { runReleaseGateCheck } from "./release/releaseGate.js";
|
|
24
23
|
import { PATCHWARDEN_VERSION } from "./version.js";
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
function
|
|
31
|
-
if (condition) {
|
|
32
|
-
results.push(`[OK] ${name}`);
|
|
33
|
-
ok++;
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
results.push(`[FAIL] ${name}${detail ? " — " + detail : ""}`);
|
|
37
|
-
fail++;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function warnCheck(name, condition, detail) {
|
|
41
|
-
if (condition) {
|
|
42
|
-
results.push(`[OK] ${name}`);
|
|
43
|
-
ok++;
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
results.push(`[WARN] ${name}${detail ? " — " + detail : ""}`);
|
|
47
|
-
warn++;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
function cmd(cmdStr) {
|
|
24
|
+
import { logger } from "./logging.js";
|
|
25
|
+
import { unsafeWorkspaceRootLabel } from "./security/workspaceRootGuard.js";
|
|
26
|
+
import { runSimpleProcessSync } from "./runner/simpleProcess.js";
|
|
27
|
+
import { resolveAgentLaunch } from "./runner/agentInvocation.js";
|
|
28
|
+
// ── Helpers ────────────────────────────────────────────────────────
|
|
29
|
+
function cmd(command, args = []) {
|
|
51
30
|
try {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
31
|
+
const result = runSimpleProcessSync({
|
|
32
|
+
command,
|
|
33
|
+
args: [...args],
|
|
34
|
+
cwd: process.cwd(),
|
|
35
|
+
timeoutMs: 5000,
|
|
36
|
+
maxStdoutBytes: 64 * 1024,
|
|
37
|
+
maxStderrBytes: 64 * 1024,
|
|
38
|
+
});
|
|
39
|
+
if (result.exitCode !== 0)
|
|
40
|
+
return "";
|
|
41
|
+
return `${result.stdout}${result.stderr}`.trim();
|
|
57
42
|
}
|
|
58
43
|
catch {
|
|
59
44
|
return "";
|
|
60
45
|
}
|
|
61
46
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
47
|
+
function okResult(message, details) {
|
|
48
|
+
return { level: "OK", message, details };
|
|
49
|
+
}
|
|
50
|
+
function warnResult(message, details) {
|
|
51
|
+
return { level: "WARN", message, details };
|
|
52
|
+
}
|
|
53
|
+
function failResult(message, details) {
|
|
54
|
+
return { level: "FAIL", message, details };
|
|
55
|
+
}
|
|
56
|
+
/** Mirrors original check(): OK if condition true, FAIL otherwise. */
|
|
57
|
+
function checkResult(name, condition, detail) {
|
|
58
|
+
return condition ? okResult(name, detail) : failResult(name, detail);
|
|
59
|
+
}
|
|
60
|
+
/** Mirrors original warnCheck(): OK if condition true, WARN otherwise. */
|
|
61
|
+
function warnCheckResult(name, condition, detail) {
|
|
62
|
+
return condition ? okResult(name, detail) : warnResult(name, detail);
|
|
63
|
+
}
|
|
64
|
+
function formatResult(result) {
|
|
65
|
+
const tag = result.level === "OK" ? "[OK] " : result.level === "WARN" ? "[WARN] " : "[FAIL] ";
|
|
66
|
+
if (result.level !== "OK" && result.details) {
|
|
67
|
+
return `${tag}${result.message} — ${result.details}`;
|
|
82
68
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
69
|
+
return `${tag}${result.message}`;
|
|
70
|
+
}
|
|
71
|
+
// ── Checks ─────────────────────────────────────────────────────────
|
|
72
|
+
const checkNodeVersion = {
|
|
73
|
+
id: "node-version",
|
|
74
|
+
description: "Node.js version",
|
|
75
|
+
run() {
|
|
76
|
+
const nodeVer = process.version;
|
|
77
|
+
const nodeMajor = parseInt(nodeVer.slice(1).split(".")[0]);
|
|
78
|
+
const detail = nodeMajor < 18 ? `v${nodeVer} — need >=18.0.0` : `v${nodeVer}`;
|
|
79
|
+
return [checkResult("Node.js version", nodeMajor >= 18, detail)];
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
const checkNpmAvailable = {
|
|
83
|
+
id: "npm-available",
|
|
84
|
+
description: "npm available",
|
|
85
|
+
run() {
|
|
86
|
+
const npmVer = cmd(process.platform === "win32" ? "npm.cmd" : "npm", ["--version"]);
|
|
87
|
+
return [checkResult("npm available", npmVer !== "", npmVer || "npm not found in PATH")];
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
// Local-only medium-risk confirmation entrypoint. It must exist as a package
|
|
91
|
+
// binary but must not be exposed as an MCP tool.
|
|
92
|
+
const checkConfirmPackage = {
|
|
93
|
+
id: "confirm-package",
|
|
94
|
+
description: "patchwarden-confirm package",
|
|
95
|
+
run() {
|
|
96
|
+
try {
|
|
97
|
+
const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
|
|
98
|
+
return [
|
|
99
|
+
checkResult("patchwarden-confirm package binary", packageJson.bin?.["patchwarden-confirm"] === "dist/assessments/confirmCli.js", packageJson.bin?.["patchwarden-confirm"] || "missing"),
|
|
100
|
+
checkResult("patchwarden-confirm compiled entrypoint", existsSync(resolve(process.cwd(), "dist/assessments/confirmCli.js")), "run npm.cmd run build"),
|
|
101
|
+
];
|
|
96
102
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
103
|
+
catch (error) {
|
|
104
|
+
return [checkResult("patchwarden-confirm package binary", false, error instanceof Error ? error.message : String(error))];
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
const checkGitAvailable = {
|
|
109
|
+
id: "git-available",
|
|
110
|
+
description: "Git available",
|
|
111
|
+
run() {
|
|
112
|
+
const gitVer = cmd("git", ["--version"]);
|
|
113
|
+
return [warnCheckResult("Git available", gitVer !== "", gitVer || "git not found — runner git.diff will not work")];
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const checkConfigFile = {
|
|
117
|
+
id: "config-file",
|
|
118
|
+
description: "Config file exists",
|
|
119
|
+
run(context) {
|
|
120
|
+
const configDetail = context.configPathUsed
|
|
121
|
+
? context.configPathUsed
|
|
122
|
+
: 'Create one: cp examples/config.example.json patchwarden.config.json';
|
|
123
|
+
if (context.allowDefaultConfig) {
|
|
124
|
+
return [warnCheckResult("Config file exists", context.configPathUsed !== "", configDetail)];
|
|
125
|
+
}
|
|
126
|
+
return [checkResult("Config file exists", context.configPathUsed !== "", configDetail)];
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
const checkPatchwardenConfigEnv = {
|
|
130
|
+
id: "patchwarden-config-env",
|
|
131
|
+
description: "PATCHWARDEN_CONFIG env",
|
|
132
|
+
run() {
|
|
133
|
+
if (process.env.PATCHWARDEN_CONFIG) {
|
|
134
|
+
return [okResult(`PATCHWARDEN_CONFIG = ${process.env.PATCHWARDEN_CONFIG}`)];
|
|
135
|
+
}
|
|
136
|
+
return [okResult("PATCHWARDEN_CONFIG not set (using default: patchwarden.config.json)")];
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
const checkConfigParseable = {
|
|
140
|
+
id: "config-parseable",
|
|
141
|
+
description: "Config parseable",
|
|
142
|
+
run(context) {
|
|
143
|
+
if (context.config) {
|
|
144
|
+
return [checkResult("Config parseable", true, `workspaceRoot: ${context.config.workspaceRoot}`)];
|
|
145
|
+
}
|
|
146
|
+
return [checkResult("Config parseable", false, context.configError || "unknown error")];
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
const checkWorkspaceRoot = {
|
|
150
|
+
id: "workspace-root",
|
|
151
|
+
description: "workspaceRoot checks",
|
|
152
|
+
run(context) {
|
|
153
|
+
if (!context.config)
|
|
154
|
+
return [];
|
|
155
|
+
const results = [];
|
|
156
|
+
const ws = normalize(resolve(context.config.workspaceRoot));
|
|
128
157
|
const exists = existsSync(ws);
|
|
129
|
-
|
|
158
|
+
results.push(checkResult("workspaceRoot exists", exists, ws));
|
|
130
159
|
let isDir = false;
|
|
131
160
|
try {
|
|
132
161
|
isDir = statSync(ws).isDirectory();
|
|
133
162
|
}
|
|
134
163
|
catch { }
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
{
|
|
139
|
-
{ pattern: /\\Users\\[^\\]+$/, label: "user home directory" },
|
|
140
|
-
{ pattern: /\\Desktop$/, label: "Desktop" },
|
|
141
|
-
{ pattern: /\\Downloads$/, label: "Downloads" },
|
|
142
|
-
{ pattern: /\\Documents$/, label: "Documents" },
|
|
143
|
-
];
|
|
144
|
-
for (const { pattern, label } of dangerousRoots) {
|
|
145
|
-
const matches = pattern.test(ws);
|
|
146
|
-
if (matches) {
|
|
147
|
-
results.push(`[WARN] workspaceRoot is ${label}: ${ws} — consider narrowing to a project directory`);
|
|
148
|
-
warn++;
|
|
149
|
-
}
|
|
164
|
+
results.push(checkResult("workspaceRoot is directory", isDir, ws));
|
|
165
|
+
const unsafeLabel = unsafeWorkspaceRootLabel(ws);
|
|
166
|
+
if (unsafeLabel) {
|
|
167
|
+
results.push(warnResult(`workspaceRoot is ${unsafeLabel}: ${ws} — consider narrowing to a project directory`));
|
|
150
168
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
169
|
+
return results;
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
const checkPathGuard = {
|
|
173
|
+
id: "path-guard",
|
|
174
|
+
description: "Path guard test",
|
|
175
|
+
run(context) {
|
|
176
|
+
if (!context.config)
|
|
177
|
+
return [];
|
|
178
|
+
const results = [];
|
|
154
179
|
try {
|
|
155
|
-
guardPath("test-file.txt", config.workspaceRoot);
|
|
156
|
-
results.push(
|
|
157
|
-
ok++;
|
|
180
|
+
guardPath("test-file.txt", context.config.workspaceRoot);
|
|
181
|
+
results.push(okResult("pathGuard allows workspace-internal path"));
|
|
158
182
|
}
|
|
159
183
|
catch (err) {
|
|
160
|
-
results.push(`
|
|
161
|
-
fail++;
|
|
184
|
+
results.push(failResult(`pathGuard rejects internal path: ${err instanceof Error ? err.message : String(err)}`));
|
|
162
185
|
}
|
|
163
186
|
try {
|
|
164
|
-
guardPath("../outside", config.workspaceRoot);
|
|
165
|
-
results.push(
|
|
166
|
-
fail++;
|
|
187
|
+
guardPath("../outside", context.config.workspaceRoot);
|
|
188
|
+
results.push(failResult("pathGuard should have blocked ../escape"));
|
|
167
189
|
}
|
|
168
190
|
catch {
|
|
169
|
-
results.push(
|
|
170
|
-
ok++;
|
|
191
|
+
results.push(okResult("pathGuard blocks ../ path escape"));
|
|
171
192
|
}
|
|
172
193
|
try {
|
|
173
|
-
const relativeRepo = guardWorkspacePath(".", config.workspaceRoot);
|
|
174
|
-
const absoluteRepo = guardWorkspacePath(config.workspaceRoot, config.workspaceRoot);
|
|
175
|
-
|
|
194
|
+
const relativeRepo = guardWorkspacePath(".", context.config.workspaceRoot);
|
|
195
|
+
const absoluteRepo = guardWorkspacePath(context.config.workspaceRoot, context.config.workspaceRoot);
|
|
196
|
+
results.push(checkResult("repo_path resolver supports relative and absolute paths", relativeRepo === absoluteRepo, relativeRepo));
|
|
176
197
|
}
|
|
177
198
|
catch (error) {
|
|
178
|
-
|
|
199
|
+
results.push(checkResult("repo_path resolver supports relative and absolute paths", false, error instanceof Error ? error.message : String(error)));
|
|
179
200
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
201
|
+
return results;
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
const checkSensitiveGuard = {
|
|
205
|
+
id: "sensitive-guard",
|
|
206
|
+
description: "Sensitive file guard test",
|
|
207
|
+
run() {
|
|
208
|
+
const results = [];
|
|
209
|
+
const sensitivePaths = [".env", ".ssh/id_rsa", "token.json", "credentials"];
|
|
210
|
+
for (const sp of sensitivePaths) {
|
|
211
|
+
const blocked = isSensitivePath(sp);
|
|
212
|
+
if (blocked) {
|
|
213
|
+
results.push(okResult(`sensitiveGuard blocks "${sp}"`));
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
results.push(failResult(`sensitiveGuard does NOT block "${sp}"`));
|
|
217
|
+
}
|
|
188
218
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
219
|
+
return results;
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
const checkSavePlanRules = {
|
|
223
|
+
id: "save-plan-rules",
|
|
224
|
+
description: "save_plan guard test",
|
|
225
|
+
run() {
|
|
226
|
+
const results = [];
|
|
227
|
+
try {
|
|
228
|
+
guardPlanContent("Normal build plan", "Run npm test, npm run lint, release check, and npm run dist.");
|
|
229
|
+
results.push(okResult("save_plan allows normal development plans"));
|
|
192
230
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
guardPlanContent("Normal build plan", "Run npm test, npm run lint, release check, and npm run dist.");
|
|
196
|
-
results.push("[OK] save_plan allows normal development plans");
|
|
197
|
-
ok++;
|
|
198
|
-
}
|
|
199
|
-
catch {
|
|
200
|
-
results.push("[FAIL] save_plan incorrectly blocks a normal development plan");
|
|
201
|
-
fail++;
|
|
202
|
-
}
|
|
203
|
-
try {
|
|
204
|
-
guardPlanContent("Unsafe plan", "Read the .env access token and export it.");
|
|
205
|
-
results.push("[FAIL] save_plan security rule did not block credential access");
|
|
206
|
-
fail++;
|
|
207
|
-
}
|
|
208
|
-
catch {
|
|
209
|
-
results.push("[OK] save_plan security rules loaded");
|
|
210
|
-
ok++;
|
|
211
|
-
}
|
|
212
|
-
const requiredReadOnlyFiles = ["status.json", "result.md", "result.json", "diff.patch", "file-stats.json", "test.log", "verify.json"];
|
|
213
|
-
check("Read-only task artifact allowlist", requiredReadOnlyFiles.every((name) => TASK_READ_ONLY_FILES.includes(name)), requiredReadOnlyFiles.join(", "));
|
|
214
|
-
const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
|
|
215
|
-
check("Server version matches package.json", packageJson.version === PATCHWARDEN_VERSION, `${PATCHWARDEN_VERSION} vs ${packageJson.version}`);
|
|
216
|
-
check("Manifest preflight script exists", existsSync(resolve(process.cwd(), "scripts/checks/mcp-manifest-check.js")), "scripts/checks/mcp-manifest-check.js");
|
|
217
|
-
const previousProfile = process.env.PATCHWARDEN_TOOL_PROFILE;
|
|
218
|
-
try {
|
|
219
|
-
process.env.PATCHWARDEN_TOOL_PROFILE = "full";
|
|
220
|
-
const fullTools = getToolDefs();
|
|
221
|
-
const coreTools = selectToolsForProfile(fullTools, "chatgpt_core", config?.enableDirectProfile);
|
|
222
|
-
const createSchema = coreTools.find((tool) => tool.name === "create_task")?.inputSchema;
|
|
223
|
-
const waitSchema = coreTools.find((tool) => tool.name === "wait_for_task")?.inputSchema;
|
|
224
|
-
check("Full tool profile exposes 64 tools", fullTools.length === 64, `${fullTools.length} tools`);
|
|
225
|
-
check(`chatgpt_core profile exposes the exact ${CHATGPT_CORE_TOOL_NAMES.length}-tool manifest`, JSON.stringify(coreTools.map((tool) => tool.name)) === JSON.stringify(CHATGPT_CORE_TOOL_NAMES), coreTools.map((tool) => tool.name).join(", "));
|
|
226
|
-
check("Core task schemas expose inline_plan, verify_commands, and wait aliases", Boolean(createSchema?.properties?.inline_plan &&
|
|
227
|
-
createSchema?.properties?.verify_commands &&
|
|
228
|
-
waitSchema?.properties?.timeout_seconds &&
|
|
229
|
-
waitSchema?.properties?.wait_seconds));
|
|
230
|
-
// chatgpt_direct profile checks
|
|
231
|
-
const directDisabledTools = selectToolsForProfile(fullTools, "chatgpt_direct", false);
|
|
232
|
-
check("chatgpt_direct disabled exposes only health_check (degraded mode)", directDisabledTools.length === 1 && directDisabledTools[0].name === "health_check", `${directDisabledTools.map((t) => t.name).join(", ")}`);
|
|
233
|
-
if (config?.enableDirectProfile) {
|
|
234
|
-
const directEnabledTools = selectToolsForProfile(fullTools, "chatgpt_direct", true);
|
|
235
|
-
check(`chatgpt_direct enabled exposes the exact ${CHATGPT_DIRECT_TOOL_NAMES.length}-tool manifest`, JSON.stringify(directEnabledTools.map((tool) => tool.name)) === JSON.stringify(CHATGPT_DIRECT_TOOL_NAMES), directEnabledTools.map((tool) => tool.name).join(", "));
|
|
236
|
-
}
|
|
237
|
-
else {
|
|
238
|
-
results.push(`[OK] chatgpt_direct enabled check skipped (enableDirectProfile=false)`);
|
|
239
|
-
ok++;
|
|
231
|
+
catch {
|
|
232
|
+
results.push(failResult("save_plan incorrectly blocks a normal development plan"));
|
|
240
233
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const driftToolDefs = new Map();
|
|
245
|
-
for (const tool of fullTools) {
|
|
246
|
-
driftToolDefs.set(tool.name, { inputSchema: tool.inputSchema });
|
|
234
|
+
try {
|
|
235
|
+
guardPlanContent("Unsafe plan", "Read the .env access token and export it.");
|
|
236
|
+
results.push(failResult("save_plan security rule did not block credential access"));
|
|
247
237
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
results.push(`[OK] Schema drift check — no drift detected`);
|
|
251
|
-
ok++;
|
|
238
|
+
catch {
|
|
239
|
+
results.push(okResult("save_plan security rules loaded"));
|
|
252
240
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
241
|
+
return results;
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
const checkTaskArtifactAllowlist = {
|
|
245
|
+
id: "task-artifact-allowlist",
|
|
246
|
+
description: "Read-only task artifact allowlist",
|
|
247
|
+
run() {
|
|
248
|
+
const requiredReadOnlyFiles = ["status.json", "result.md", "result.json", "diff.patch", "file-stats.json", "test.log", "verify.json"];
|
|
249
|
+
return [
|
|
250
|
+
checkResult("Read-only task artifact allowlist", requiredReadOnlyFiles.every((name) => TASK_READ_ONLY_FILES.includes(name)), requiredReadOnlyFiles.join(", ")),
|
|
251
|
+
];
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
const checkServerVersion = {
|
|
255
|
+
id: "server-version",
|
|
256
|
+
description: "Server version and manifest preflight",
|
|
257
|
+
run() {
|
|
258
|
+
const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
|
|
259
|
+
return [
|
|
260
|
+
checkResult("Server version matches package.json", packageJson.version === PATCHWARDEN_VERSION, `${PATCHWARDEN_VERSION} vs ${packageJson.version}`),
|
|
261
|
+
process.env.PATCHWARDEN_DESKTOP_RUNTIME === "1"
|
|
262
|
+
? okResult("Manifest preflight covered by compiled desktop runtime checks")
|
|
263
|
+
: checkResult("Manifest preflight script exists", existsSync(resolve(process.cwd(), "scripts/checks/mcp-manifest-check.js")), "scripts/checks/mcp-manifest-check.js"),
|
|
264
|
+
];
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
const checkToolProfiles = {
|
|
268
|
+
id: "tool-profiles",
|
|
269
|
+
description: "Tool profile and schema drift checks",
|
|
270
|
+
run(context) {
|
|
271
|
+
const results = [];
|
|
272
|
+
const previousProfile = process.env.PATCHWARDEN_TOOL_PROFILE;
|
|
273
|
+
try {
|
|
274
|
+
process.env.PATCHWARDEN_TOOL_PROFILE = "full";
|
|
275
|
+
const fullTools = getToolDefs();
|
|
276
|
+
const coreTools = selectToolsForProfile(fullTools, "chatgpt_core", context.config?.enableDirectProfile);
|
|
277
|
+
const createSchema = coreTools.find((tool) => tool.name === "create_task")?.inputSchema;
|
|
278
|
+
const waitSchema = coreTools.find((tool) => tool.name === "wait_for_task")?.inputSchema;
|
|
279
|
+
results.push(checkResult("Full tool profile exposes 66 tools", fullTools.length === 66, `${fullTools.length} tools`));
|
|
280
|
+
results.push(checkResult(`chatgpt_core profile exposes the exact ${CHATGPT_CORE_TOOL_NAMES.length}-tool manifest`, JSON.stringify(coreTools.map((tool) => tool.name)) === JSON.stringify(CHATGPT_CORE_TOOL_NAMES), coreTools.map((tool) => tool.name).join(", ")));
|
|
281
|
+
results.push(checkResult("Core task schemas expose inline_plan, verify_commands, and wait aliases", Boolean(createSchema?.properties?.inline_plan &&
|
|
282
|
+
createSchema?.properties?.verify_commands &&
|
|
283
|
+
waitSchema?.properties?.timeout_seconds &&
|
|
284
|
+
waitSchema?.properties?.wait_seconds)));
|
|
285
|
+
// chatgpt_direct profile checks
|
|
286
|
+
const directDisabledTools = selectToolsForProfile(fullTools, "chatgpt_direct", false);
|
|
287
|
+
results.push(checkResult("chatgpt_direct disabled exposes only health_check (degraded mode)", directDisabledTools.length === 1 && directDisabledTools[0].name === "health_check", `${directDisabledTools.map((t) => t.name).join(", ")}`));
|
|
288
|
+
if (context.config?.enableDirectProfile) {
|
|
289
|
+
const directEnabledTools = selectToolsForProfile(fullTools, "chatgpt_direct", true);
|
|
290
|
+
results.push(checkResult(`chatgpt_direct enabled exposes the exact ${CHATGPT_DIRECT_TOOL_NAMES.length}-tool manifest`, JSON.stringify(directEnabledTools.map((tool) => tool.name)) === JSON.stringify(CHATGPT_DIRECT_TOOL_NAMES), directEnabledTools.map((tool) => tool.name).join(", ")));
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
results.push(okResult("chatgpt_direct enabled check skipped (enableDirectProfile=false)"));
|
|
294
|
+
}
|
|
295
|
+
// Schema drift 检查(v0.9.0)—— warn 级别,不阻断 doctor:ci
|
|
296
|
+
const driftRegistry = buildToolRegistry(fullTools);
|
|
297
|
+
const driftToolDefs = new Map();
|
|
298
|
+
for (const tool of fullTools) {
|
|
299
|
+
driftToolDefs.set(tool.name, { inputSchema: tool.inputSchema });
|
|
300
|
+
}
|
|
301
|
+
const driftResult = runAllSchemaDriftChecks(driftRegistry, driftToolDefs);
|
|
302
|
+
if (driftResult.ok) {
|
|
303
|
+
results.push(okResult("Schema drift check — no drift detected"));
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
for (const w of driftResult.warnings) {
|
|
307
|
+
results.push(warnResult(`Schema drift: ${w}`));
|
|
308
|
+
}
|
|
258
309
|
}
|
|
259
310
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
311
|
+
finally {
|
|
312
|
+
if (previousProfile === undefined)
|
|
313
|
+
delete process.env.PATCHWARDEN_TOOL_PROFILE;
|
|
314
|
+
else
|
|
315
|
+
process.env.PATCHWARDEN_TOOL_PROFILE = previousProfile;
|
|
316
|
+
}
|
|
317
|
+
return results;
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
// Release gate module loadable (v1.0.0) — module integrity only.
|
|
321
|
+
// Does NOT execute local_ready (would recurse into doctor:ci).
|
|
322
|
+
const checkReleaseGate = {
|
|
323
|
+
id: "release-gate",
|
|
324
|
+
description: "Release gate module loadable",
|
|
325
|
+
run() {
|
|
326
|
+
try {
|
|
327
|
+
const releaseGateReady = typeof runReleaseGateCheck === "function";
|
|
328
|
+
return [
|
|
329
|
+
checkResult("Release gate module loadable", releaseGateReady, releaseGateReady ? "runReleaseGateCheck exported" : "runReleaseGateCheck missing or invalid"),
|
|
330
|
+
];
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
return [checkResult("Release gate module loadable", false, error instanceof Error ? error.message : String(error))];
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
const checkHttpPort = {
|
|
338
|
+
id: "http-port",
|
|
339
|
+
description: "HTTP port check",
|
|
340
|
+
async run(context) {
|
|
341
|
+
const httpPort = context.config?.http?.port || context.config?.httpPort || 7331;
|
|
342
|
+
try {
|
|
343
|
+
const server = createServer();
|
|
344
|
+
await new Promise((resolvePort, rejectPort) => {
|
|
345
|
+
server.once("error", rejectPort);
|
|
346
|
+
server.listen(httpPort, "127.0.0.1", () => {
|
|
347
|
+
server.close();
|
|
348
|
+
resolvePort();
|
|
349
|
+
});
|
|
285
350
|
});
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
const
|
|
351
|
+
return [okResult(`HTTP port ${httpPort} is free`)];
|
|
352
|
+
}
|
|
353
|
+
catch {
|
|
354
|
+
return [warnResult(`HTTP port ${httpPort} is in use — change http.port in config`)];
|
|
355
|
+
}
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
const checkDistFiles = {
|
|
359
|
+
id: "dist-files",
|
|
360
|
+
description: "dist file checks",
|
|
361
|
+
run() {
|
|
362
|
+
const distChecks = [
|
|
363
|
+
{ file: "dist/index.js", label: "stdio MCP entry", cmd: "npm run build" },
|
|
364
|
+
{ file: "dist/httpServer.js", label: "HTTP MCP entry", cmd: "npm run build" },
|
|
365
|
+
{ file: "dist/runner/watch.js", label: "watcher entry (npm run watch)", cmd: "npm run build" },
|
|
366
|
+
];
|
|
367
|
+
const results = [];
|
|
368
|
+
for (const { file, label, cmd: buildCmd } of distChecks) {
|
|
369
|
+
const exists = existsSync(resolve(process.cwd(), file));
|
|
370
|
+
results.push(checkResult(`${label} exists`, exists, exists ? file : `Missing — run: ${buildCmd}`));
|
|
371
|
+
}
|
|
372
|
+
return results;
|
|
373
|
+
},
|
|
374
|
+
};
|
|
375
|
+
const checkToolModules = {
|
|
376
|
+
id: "tool-modules",
|
|
377
|
+
description: "Tool module checks",
|
|
378
|
+
run() {
|
|
379
|
+
const newTools = [
|
|
380
|
+
{ name: "listTasks", subdir: "tasks" },
|
|
381
|
+
{ name: "listAgents", subdir: "workspace" },
|
|
382
|
+
{ name: "cancelTask", subdir: "tasks" },
|
|
383
|
+
{ name: "killTask", subdir: "tasks" },
|
|
384
|
+
{ name: "retryTask", subdir: "tasks" },
|
|
385
|
+
{ name: "getTaskProgress", subdir: "tasks" },
|
|
386
|
+
{ name: "getTaskSummary", subdir: "tasks" },
|
|
387
|
+
{ name: "waitForTask", subdir: "tasks" },
|
|
388
|
+
{ name: "getTaskStdoutTail", subdir: "tasks" },
|
|
389
|
+
{ name: "healthCheck", subdir: "diagnostics" },
|
|
390
|
+
{ name: "auditTask", subdir: "diagnostics" },
|
|
391
|
+
];
|
|
392
|
+
const results = [];
|
|
393
|
+
for (const t of newTools) {
|
|
394
|
+
const compiled = resolve(process.cwd(), "dist/tools", t.subdir, `${t.name}.js`);
|
|
395
|
+
const exists = existsSync(compiled);
|
|
396
|
+
results.push(checkResult(`Tool module: ${t.name}`, exists, exists ? "compiled" : "missing"));
|
|
397
|
+
}
|
|
398
|
+
return results;
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
const checkDirectToolModules = {
|
|
402
|
+
id: "direct-tool-modules",
|
|
403
|
+
description: "Direct tool module checks",
|
|
404
|
+
run() {
|
|
405
|
+
const directToolModules = [
|
|
406
|
+
{ name: "createDirectSession", subdir: "direct" },
|
|
407
|
+
{ name: "searchWorkspace", subdir: "workspace" },
|
|
408
|
+
{ name: "applyPatch", subdir: "workspace" },
|
|
409
|
+
{ name: "runVerification", subdir: "tasks" },
|
|
410
|
+
{ name: "finalizeDirectSession", subdir: "direct" },
|
|
411
|
+
{ name: "auditSession", subdir: "diagnostics" },
|
|
412
|
+
];
|
|
413
|
+
const results = [];
|
|
414
|
+
for (const t of directToolModules) {
|
|
415
|
+
const compiled = resolve(process.cwd(), "dist/tools", t.subdir, `${t.name}.js`);
|
|
416
|
+
const exists = existsSync(compiled);
|
|
417
|
+
results.push(checkResult(`Direct tool module: ${t.name}`, exists, exists ? "compiled" : "missing"));
|
|
418
|
+
}
|
|
419
|
+
return results;
|
|
420
|
+
},
|
|
421
|
+
};
|
|
422
|
+
const checkDirectSupportModules = {
|
|
423
|
+
id: "direct-support-modules",
|
|
424
|
+
description: "Direct support module checks",
|
|
425
|
+
run() {
|
|
426
|
+
const directSupportModules = [
|
|
427
|
+
"directSessionStore",
|
|
428
|
+
"directGuards",
|
|
429
|
+
"directPatch",
|
|
430
|
+
"directVerification",
|
|
431
|
+
"directAudit",
|
|
432
|
+
];
|
|
433
|
+
const results = [];
|
|
434
|
+
for (const t of directSupportModules) {
|
|
435
|
+
const compiled = resolve(process.cwd(), "dist/direct", `${t}.js`);
|
|
436
|
+
const exists = existsSync(compiled);
|
|
437
|
+
results.push(checkResult(`Direct support module: ${t}`, exists, exists ? "compiled" : "missing"));
|
|
438
|
+
}
|
|
439
|
+
return results;
|
|
440
|
+
},
|
|
441
|
+
};
|
|
442
|
+
const checkTaskDirectoryWritable = {
|
|
443
|
+
id: "task-directory-writable",
|
|
444
|
+
description: "Task directory and workspaceRoot writable",
|
|
445
|
+
run(context) {
|
|
446
|
+
if (!context.config)
|
|
447
|
+
return [];
|
|
448
|
+
const results = [];
|
|
449
|
+
const tasksDir = resolve(context.config.workspaceRoot, context.config.tasksDir);
|
|
349
450
|
try {
|
|
350
451
|
mkdirSync(tasksDir, { recursive: true });
|
|
351
452
|
const testFile = join(tasksDir, ".doctor-write-test");
|
|
352
453
|
writeFileSync(testFile, "ok", "utf-8");
|
|
353
454
|
rmSync(testFile);
|
|
354
|
-
|
|
455
|
+
results.push(checkResult("Task directory writable", true, tasksDir));
|
|
355
456
|
const sampleTaskDir = join(tasksDir, ".doctor-sample-task");
|
|
356
457
|
mkdirSync(sampleTaskDir, { recursive: true });
|
|
357
458
|
const sampleStatus = join(sampleTaskDir, "status.json");
|
|
358
459
|
writeFileSync(sampleStatus, JSON.stringify({ status: "doctor" }), "utf-8");
|
|
359
460
|
const sampleReadable = JSON.parse(readFileSync(sampleStatus, "utf-8")).status === "doctor";
|
|
360
461
|
rmSync(sampleTaskDir, { recursive: true, force: true });
|
|
361
|
-
|
|
462
|
+
results.push(checkResult("Example task directory read/write", sampleReadable, sampleTaskDir));
|
|
362
463
|
}
|
|
363
464
|
catch {
|
|
364
|
-
|
|
465
|
+
results.push(warnCheckResult("Task directory writable", false, tasksDir));
|
|
365
466
|
}
|
|
366
467
|
// workspaceRoot writable
|
|
367
468
|
try {
|
|
368
|
-
const testFile = resolve(config.workspaceRoot, ".doctor-write-test");
|
|
469
|
+
const testFile = resolve(context.config.workspaceRoot, ".doctor-write-test");
|
|
369
470
|
writeFileSync(testFile, "ok", "utf-8");
|
|
370
471
|
rmSync(testFile);
|
|
371
|
-
|
|
472
|
+
results.push(checkResult("workspaceRoot writable", true, context.config.workspaceRoot));
|
|
372
473
|
}
|
|
373
474
|
catch {
|
|
374
|
-
|
|
475
|
+
results.push(warnCheckResult("workspaceRoot writable", false, context.config.workspaceRoot));
|
|
375
476
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
477
|
+
return results;
|
|
478
|
+
},
|
|
479
|
+
};
|
|
480
|
+
const checkWatcherStaleThreshold = {
|
|
481
|
+
id: "watcher-stale-threshold",
|
|
482
|
+
description: "Watcher stale threshold is valid",
|
|
483
|
+
run(context) {
|
|
484
|
+
if (!context.config)
|
|
485
|
+
return [];
|
|
486
|
+
return [
|
|
487
|
+
checkResult("Watcher stale threshold is valid", context.config.watcherStaleSeconds >= 5 && context.config.watcherStaleSeconds <= 3600, `${context.config.watcherStaleSeconds}s`),
|
|
488
|
+
];
|
|
489
|
+
},
|
|
490
|
+
};
|
|
491
|
+
const checkAllowedTestCommandsIncludesNpmTest = {
|
|
492
|
+
id: "allowed-test-commands-npm-test",
|
|
493
|
+
description: "allowedTestCommands includes npm test",
|
|
494
|
+
run(context) {
|
|
495
|
+
if (!context.config)
|
|
496
|
+
return [];
|
|
497
|
+
const hasNpmTest = context.config.allowedTestCommands.some((c) => c === "npm test" || c === "npm run test");
|
|
498
|
+
return [warnCheckResult("allowedTestCommands includes npm test", hasNpmTest, hasNpmTest ? "present" : "npm test is missing — add it to allowedTestCommands")];
|
|
499
|
+
},
|
|
500
|
+
};
|
|
501
|
+
const checkTaskTimeoutDefaults = {
|
|
502
|
+
id: "task-timeout-defaults",
|
|
503
|
+
description: "Task timeout defaults are valid",
|
|
504
|
+
run(context) {
|
|
505
|
+
if (!context.config)
|
|
506
|
+
return [];
|
|
507
|
+
return [
|
|
508
|
+
checkResult("Task timeout defaults are valid", context.config.defaultTaskTimeoutSeconds > 0 && context.config.defaultTaskTimeoutSeconds <= context.config.maxTaskTimeoutSeconds, `default ${context.config.defaultTaskTimeoutSeconds}s, max ${context.config.maxTaskTimeoutSeconds}s`),
|
|
509
|
+
];
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
const checkAgentCommands = {
|
|
513
|
+
id: "agent-commands",
|
|
514
|
+
description: "Agent command check",
|
|
515
|
+
run(context) {
|
|
516
|
+
if (!context.config)
|
|
517
|
+
return [];
|
|
518
|
+
const results = [];
|
|
519
|
+
for (const [name, agentCfg] of Object.entries(context.config.agents)) {
|
|
392
520
|
const cmdName = agentCfg.command;
|
|
393
521
|
const looksLikePath = isAbsolute(cmdName) || cmdName.includes("/") || cmdName.includes("\\");
|
|
394
522
|
if (looksLikePath) {
|
|
395
523
|
const agentExists = existsSync(cmdName);
|
|
396
|
-
|
|
524
|
+
results.push(warnCheckResult(`Agent "${name}" command available`, agentExists, agentExists ? `Found: ${cmdName}` : `"${cmdName}" does not exist — agent tasks will fail`));
|
|
397
525
|
continue;
|
|
398
526
|
}
|
|
399
|
-
//
|
|
527
|
+
// Use an argumentized lookup utility; never interpolate config into a shell.
|
|
400
528
|
const isWin = process.platform === "win32";
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
529
|
+
let found = "";
|
|
530
|
+
if (isWin) {
|
|
531
|
+
try {
|
|
532
|
+
found = resolveAgentLaunch(name, cmdName, process.platform, process.env.PATH || "", existsSync, context.config.workspaceRoot, typeof agentCfg.adapter === "string" ? agentCfg.adapter : name).command;
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
found = "";
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
found = cmd("which", [cmdName]);
|
|
540
|
+
}
|
|
541
|
+
results.push(warnCheckResult(`Agent "${name}" command available`, found !== "", found ? `Found: ${found.split("\n")[0]}` : `"${cmdName}" not in PATH — agent tasks will fail`));
|
|
405
542
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
543
|
+
return results;
|
|
544
|
+
},
|
|
545
|
+
};
|
|
546
|
+
const checkAllowedTestCommandsSafety = {
|
|
547
|
+
id: "allowed-test-commands-safety",
|
|
548
|
+
description: "allowedTestCommands safety check",
|
|
549
|
+
run(context) {
|
|
550
|
+
if (!context.config)
|
|
551
|
+
return [];
|
|
552
|
+
const results = [];
|
|
553
|
+
const testCmds = context.config.allowedTestCommands || [];
|
|
554
|
+
results.push(checkResult("allowedTestCommands is non-empty", testCmds.length > 0, testCmds.length > 0 ? `${testCmds.length} commands` : "No test commands configured"));
|
|
411
555
|
const dangerous = ["rm -rf", "del /s", "format", "shutdown", "curl |", "wget |"];
|
|
412
556
|
for (const cmdStr of testCmds) {
|
|
413
557
|
for (const danger of dangerous) {
|
|
414
558
|
if (cmdStr.toLowerCase().includes(danger)) {
|
|
415
|
-
results.push(`
|
|
416
|
-
warn++;
|
|
559
|
+
results.push(warnResult(`allowedTestCommands contains dangerous pattern: "${cmdStr}"`));
|
|
417
560
|
}
|
|
418
561
|
}
|
|
419
562
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
563
|
+
return results;
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
// Direct profile config checks
|
|
567
|
+
const checkDirectProfileConfig = {
|
|
568
|
+
id: "direct-profile-config",
|
|
569
|
+
description: "Direct profile config checks",
|
|
570
|
+
run(context) {
|
|
571
|
+
if (!context.config)
|
|
572
|
+
return [];
|
|
573
|
+
const results = [];
|
|
574
|
+
const directCmds = context.config.directAllowedCommands || [];
|
|
575
|
+
results.push(warnCheckResult("directAllowedCommands is non-empty", directCmds.length > 0, directCmds.length > 0 ? `${directCmds.length} commands` : "No Direct commands configured"));
|
|
425
576
|
// npm run doctor should NOT be in default Direct whitelist
|
|
426
577
|
const hasDoctor = directCmds.some((c) => c === "npm run doctor");
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
578
|
+
results.push(checkResult("directAllowedCommands does not include npm run doctor", !hasDoctor, hasDoctor ? "npm run doctor found in Direct whitelist — remove it for tighter security" : "not present"));
|
|
579
|
+
results.push(checkResult("directSessionTtlSeconds is valid", context.config.directSessionTtlSeconds >= 60 && context.config.directSessionTtlSeconds <= 86400, `${context.config.directSessionTtlSeconds}s`));
|
|
580
|
+
results.push(checkResult("directMaxPatchBytes is positive", context.config.directMaxPatchBytes > 0, `${context.config.directMaxPatchBytes}`));
|
|
581
|
+
results.push(checkResult("directMaxFileBytes is positive", context.config.directMaxFileBytes > 0, `${context.config.directMaxFileBytes}`));
|
|
582
|
+
return results;
|
|
583
|
+
},
|
|
584
|
+
};
|
|
585
|
+
// Tunnel example files check
|
|
586
|
+
const checkTunnelExamples = {
|
|
587
|
+
id: "tunnel-examples",
|
|
588
|
+
description: "Tunnel example files check",
|
|
589
|
+
run() {
|
|
590
|
+
const tunnelFiles = [
|
|
591
|
+
"examples/openai-tunnel/README.md",
|
|
592
|
+
"examples/openai-tunnel/tunnel-client.example.yaml",
|
|
593
|
+
"examples/openai-tunnel/chatgpt-test-prompt.md",
|
|
594
|
+
"scripts/mcp/patchwarden-mcp-direct.cmd",
|
|
595
|
+
"PatchWarden.cmd",
|
|
596
|
+
"scripts/control/manage-patchwarden.ps1",
|
|
597
|
+
"scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
|
|
598
|
+
];
|
|
599
|
+
const results = [];
|
|
600
|
+
for (const tf of tunnelFiles) {
|
|
601
|
+
const full = resolve(process.cwd(), tf);
|
|
602
|
+
const exists = existsSync(full);
|
|
603
|
+
results.push(checkResult(`Tunnel example: ${tf}`, exists, exists ? "present" : "missing"));
|
|
604
|
+
// Check for leaked secrets in example files
|
|
605
|
+
if (exists) {
|
|
606
|
+
const content = readFileSync(full, "utf-8");
|
|
607
|
+
// Only flag actual key-value assignments, not comments/mentions
|
|
608
|
+
const leaked = /(?:api_key|sk-[a-zA-Z0-9]{10,}|token\s*[:=]\s*\S{4,}|secret\s*[:=]\s*\S{4,}|password\s*[:=]\s*\S{4,})/gi.test(
|
|
609
|
+
// Strip comment lines first
|
|
610
|
+
content.split("\n").filter(l => !l.trim().startsWith("#") && !l.trim().startsWith("//")).join("\n"));
|
|
611
|
+
if (leaked) {
|
|
612
|
+
results.push(warnResult(`${tf} may contain secrets`));
|
|
613
|
+
}
|
|
614
|
+
else {
|
|
615
|
+
results.push(okResult(`${tf} — no real secrets`));
|
|
616
|
+
}
|
|
460
617
|
}
|
|
461
618
|
}
|
|
619
|
+
return results;
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
// ── Check registry ─────────────────────────────────────────────────
|
|
623
|
+
const checks = [
|
|
624
|
+
checkNodeVersion,
|
|
625
|
+
checkNpmAvailable,
|
|
626
|
+
checkConfirmPackage,
|
|
627
|
+
checkGitAvailable,
|
|
628
|
+
checkConfigFile,
|
|
629
|
+
checkPatchwardenConfigEnv,
|
|
630
|
+
checkConfigParseable,
|
|
631
|
+
checkWorkspaceRoot,
|
|
632
|
+
checkPathGuard,
|
|
633
|
+
checkSensitiveGuard,
|
|
634
|
+
checkSavePlanRules,
|
|
635
|
+
checkTaskArtifactAllowlist,
|
|
636
|
+
checkServerVersion,
|
|
637
|
+
checkToolProfiles,
|
|
638
|
+
checkReleaseGate,
|
|
639
|
+
checkHttpPort,
|
|
640
|
+
checkDistFiles,
|
|
641
|
+
checkToolModules,
|
|
642
|
+
checkDirectToolModules,
|
|
643
|
+
checkDirectSupportModules,
|
|
644
|
+
checkTaskDirectoryWritable,
|
|
645
|
+
checkWatcherStaleThreshold,
|
|
646
|
+
checkAllowedTestCommandsIncludesNpmTest,
|
|
647
|
+
checkTaskTimeoutDefaults,
|
|
648
|
+
checkAgentCommands,
|
|
649
|
+
checkAllowedTestCommandsSafety,
|
|
650
|
+
checkDirectProfileConfig,
|
|
651
|
+
checkTunnelExamples,
|
|
652
|
+
];
|
|
653
|
+
// ── Context preparation ────────────────────────────────────────────
|
|
654
|
+
function prepareContext() {
|
|
655
|
+
const allowDefaultConfig = process.argv.includes("--allow-default-config");
|
|
656
|
+
const configPaths = [
|
|
657
|
+
resolve(process.cwd(), "patchwarden.config.json"),
|
|
658
|
+
process.env.PATCHWARDEN_CONFIG || "",
|
|
659
|
+
].filter(Boolean);
|
|
660
|
+
let configPathUsed = "";
|
|
661
|
+
for (const p of configPaths) {
|
|
662
|
+
if (existsSync(p)) {
|
|
663
|
+
configPathUsed = p;
|
|
664
|
+
break;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
// Load config (may fail)
|
|
668
|
+
let config = null;
|
|
669
|
+
let configError = null;
|
|
670
|
+
try {
|
|
671
|
+
config = getConfig();
|
|
672
|
+
}
|
|
673
|
+
catch (err) {
|
|
674
|
+
configError = err instanceof Error ? err.message : String(err);
|
|
675
|
+
}
|
|
676
|
+
return { config, configError, allowDefaultConfig, configPathUsed };
|
|
677
|
+
}
|
|
678
|
+
// ══════════════════════════════════════════════════════════════════
|
|
679
|
+
// Main
|
|
680
|
+
// ══════════════════════════════════════════════════════════════════
|
|
681
|
+
async function main() {
|
|
682
|
+
console.log("PatchWarden Doctor\n");
|
|
683
|
+
const context = prepareContext();
|
|
684
|
+
const allResults = [];
|
|
685
|
+
for (const check of checks) {
|
|
686
|
+
try {
|
|
687
|
+
const results = await check.run(context);
|
|
688
|
+
allResults.push(...results);
|
|
689
|
+
}
|
|
690
|
+
catch (err) {
|
|
691
|
+
allResults.push(failResult(check.description, err instanceof Error ? err.message : String(err)));
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
console.log(allResults.map(formatResult).join("\n"));
|
|
695
|
+
let ok = 0;
|
|
696
|
+
let warn = 0;
|
|
697
|
+
let fail = 0;
|
|
698
|
+
for (const r of allResults) {
|
|
699
|
+
if (r.level === "OK")
|
|
700
|
+
ok++;
|
|
701
|
+
else if (r.level === "WARN")
|
|
702
|
+
warn++;
|
|
703
|
+
else
|
|
704
|
+
fail++;
|
|
462
705
|
}
|
|
463
|
-
// ══════════════════════════════════════════════════════════════════
|
|
464
|
-
// Summary
|
|
465
|
-
// ══════════════════════════════════════════════════════════════════
|
|
466
|
-
console.log(results.join("\n"));
|
|
467
706
|
console.log(`\n${"=".repeat(50)}`);
|
|
468
707
|
console.log(`OK: ${ok} WARN: ${warn} FAIL: ${fail}`);
|
|
469
708
|
console.log(`${"=".repeat(50)}`);
|
|
@@ -480,8 +719,10 @@ async function main() {
|
|
|
480
719
|
console.log("\n✅ All checks passed.");
|
|
481
720
|
process.exit(0);
|
|
482
721
|
}
|
|
483
|
-
}
|
|
722
|
+
}
|
|
484
723
|
main().catch((err) => {
|
|
485
|
-
|
|
724
|
+
logger.fatal("Doctor crashed", {
|
|
725
|
+
error: err instanceof Error ? err.message : String(err),
|
|
726
|
+
});
|
|
486
727
|
process.exit(1);
|
|
487
728
|
});
|