patchwarden 0.4.0

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.
Files changed (153) hide show
  1. package/Check-PatchWarden-Health.cmd +6 -0
  2. package/LICENSE +21 -0
  3. package/README.md +526 -0
  4. package/Reset-PatchWarden-Tunnel-Key.cmd +6 -0
  5. package/Restart-PatchWarden.cmd +19 -0
  6. package/Start-PatchWarden-Tunnel.cmd +7 -0
  7. package/dist/config.d.ts +24 -0
  8. package/dist/config.js +131 -0
  9. package/dist/doctor.d.ts +10 -0
  10. package/dist/doctor.js +388 -0
  11. package/dist/errors.d.ts +16 -0
  12. package/dist/errors.js +27 -0
  13. package/dist/httpServer.d.ts +17 -0
  14. package/dist/httpServer.js +255 -0
  15. package/dist/index.d.ts +9 -0
  16. package/dist/index.js +24 -0
  17. package/dist/runner/changeCapture.d.ts +43 -0
  18. package/dist/runner/changeCapture.js +245 -0
  19. package/dist/runner/cli.d.ts +9 -0
  20. package/dist/runner/cli.js +26 -0
  21. package/dist/runner/runTask.d.ts +8 -0
  22. package/dist/runner/runTask.js +821 -0
  23. package/dist/runner/watch.d.ts +19 -0
  24. package/dist/runner/watch.js +136 -0
  25. package/dist/security/commandGuard.d.ts +22 -0
  26. package/dist/security/commandGuard.js +56 -0
  27. package/dist/security/contentRedaction.d.ts +12 -0
  28. package/dist/security/contentRedaction.js +65 -0
  29. package/dist/security/pathGuard.d.ts +3 -0
  30. package/dist/security/pathGuard.js +98 -0
  31. package/dist/security/planGuard.d.ts +1 -0
  32. package/dist/security/planGuard.js +65 -0
  33. package/dist/security/sensitiveGuard.d.ts +2 -0
  34. package/dist/security/sensitiveGuard.js +60 -0
  35. package/dist/smoke-test.d.ts +15 -0
  36. package/dist/smoke-test.js +989 -0
  37. package/dist/taskProgress.d.ts +6 -0
  38. package/dist/taskProgress.js +36 -0
  39. package/dist/taskRuntime.d.ts +10 -0
  40. package/dist/taskRuntime.js +25 -0
  41. package/dist/tools/auditTask.d.ts +19 -0
  42. package/dist/tools/auditTask.js +307 -0
  43. package/dist/tools/cancelTask.d.ts +30 -0
  44. package/dist/tools/cancelTask.js +64 -0
  45. package/dist/tools/createTask.d.ts +42 -0
  46. package/dist/tools/createTask.js +188 -0
  47. package/dist/tools/getPlan.d.ts +12 -0
  48. package/dist/tools/getPlan.js +29 -0
  49. package/dist/tools/getTaskFile.d.ts +24 -0
  50. package/dist/tools/getTaskFile.js +84 -0
  51. package/dist/tools/getTaskProgress.d.ts +1 -0
  52. package/dist/tools/getTaskProgress.js +4 -0
  53. package/dist/tools/getTaskStatus.d.ts +46 -0
  54. package/dist/tools/getTaskStatus.js +36 -0
  55. package/dist/tools/getTaskStdoutTail.d.ts +9 -0
  56. package/dist/tools/getTaskStdoutTail.js +56 -0
  57. package/dist/tools/getTaskSummary.d.ts +66 -0
  58. package/dist/tools/getTaskSummary.js +237 -0
  59. package/dist/tools/healthCheck.d.ts +79 -0
  60. package/dist/tools/healthCheck.js +230 -0
  61. package/dist/tools/killTask.d.ts +15 -0
  62. package/dist/tools/killTask.js +4 -0
  63. package/dist/tools/listAgents.d.ts +12 -0
  64. package/dist/tools/listAgents.js +42 -0
  65. package/dist/tools/listTasks.d.ts +36 -0
  66. package/dist/tools/listTasks.js +94 -0
  67. package/dist/tools/listWorkspace.d.ts +10 -0
  68. package/dist/tools/listWorkspace.js +54 -0
  69. package/dist/tools/readWorkspaceFile.d.ts +8 -0
  70. package/dist/tools/readWorkspaceFile.js +26 -0
  71. package/dist/tools/registry.d.ts +24 -0
  72. package/dist/tools/registry.js +566 -0
  73. package/dist/tools/retryTask.d.ts +8 -0
  74. package/dist/tools/retryTask.js +45 -0
  75. package/dist/tools/savePlan.d.ts +10 -0
  76. package/dist/tools/savePlan.js +28 -0
  77. package/dist/tools/taskOutputs.d.ts +49 -0
  78. package/dist/tools/taskOutputs.js +136 -0
  79. package/dist/tools/taskTemplates.d.ts +15 -0
  80. package/dist/tools/taskTemplates.js +116 -0
  81. package/dist/tools/toolCatalog.d.ts +19 -0
  82. package/dist/tools/toolCatalog.js +71 -0
  83. package/dist/tools/waitForTask.d.ts +25 -0
  84. package/dist/tools/waitForTask.js +59 -0
  85. package/dist/version.d.ts +2 -0
  86. package/dist/version.js +2 -0
  87. package/dist/watcherStatus.d.ts +20 -0
  88. package/dist/watcherStatus.js +77 -0
  89. package/docs/assets/patchwarden-chatgpt-demo.svg +30 -0
  90. package/docs/demo.md +58 -0
  91. package/docs/migration-from-safe-bifrost.md +31 -0
  92. package/docs/release-v0.3.0.md +43 -0
  93. package/docs/release-v0.4.0.md +74 -0
  94. package/examples/config.example.json +40 -0
  95. package/examples/openai-tunnel/README.md +48 -0
  96. package/examples/openai-tunnel/chatgpt-test-prompt.md +53 -0
  97. package/examples/openai-tunnel/tunnel-client.example.yaml +39 -0
  98. package/package.json +73 -0
  99. package/scripts/brand-check.js +44 -0
  100. package/scripts/doctor-smoke.js +52 -0
  101. package/scripts/get-patchwarden-health.ps1 +114 -0
  102. package/scripts/http-mcp-smoke.js +351 -0
  103. package/scripts/lifecycle-smoke.js +453 -0
  104. package/scripts/mcp-manifest-check.js +120 -0
  105. package/scripts/mcp-smoke.js +336 -0
  106. package/scripts/pack-clean.js +142 -0
  107. package/scripts/patchwarden-mcp-stdio.cmd +7 -0
  108. package/scripts/restart-patchwarden.ps1 +242 -0
  109. package/scripts/start-patchwarden-tunnel.ps1 +503 -0
  110. package/scripts/tunnel-supervisor-smoke.js +77 -0
  111. package/scripts/watcher-supervisor-smoke.js +195 -0
  112. package/src/config.ts +165 -0
  113. package/src/doctor.ts +438 -0
  114. package/src/errors.ts +26 -0
  115. package/src/httpServer.ts +277 -0
  116. package/src/index.ts +34 -0
  117. package/src/runner/changeCapture.ts +304 -0
  118. package/src/runner/cli.ts +34 -0
  119. package/src/runner/runTask.ts +935 -0
  120. package/src/runner/watch.ts +152 -0
  121. package/src/security/commandGuard.ts +97 -0
  122. package/src/security/contentRedaction.ts +85 -0
  123. package/src/security/pathGuard.ts +175 -0
  124. package/src/security/planGuard.ts +93 -0
  125. package/src/security/sensitiveGuard.ts +72 -0
  126. package/src/smoke-test.ts +1078 -0
  127. package/src/taskProgress.ts +41 -0
  128. package/src/taskRuntime.ts +37 -0
  129. package/src/tools/auditTask.ts +340 -0
  130. package/src/tools/cancelTask.ts +70 -0
  131. package/src/tools/createTask.ts +337 -0
  132. package/src/tools/getPlan.ts +48 -0
  133. package/src/tools/getTaskFile.ts +111 -0
  134. package/src/tools/getTaskProgress.ts +5 -0
  135. package/src/tools/getTaskStatus.ts +85 -0
  136. package/src/tools/getTaskStdoutTail.ts +61 -0
  137. package/src/tools/getTaskSummary.ts +307 -0
  138. package/src/tools/healthCheck.ts +234 -0
  139. package/src/tools/killTask.ts +5 -0
  140. package/src/tools/listAgents.ts +54 -0
  141. package/src/tools/listTasks.ts +139 -0
  142. package/src/tools/listWorkspace.ts +71 -0
  143. package/src/tools/readWorkspaceFile.ts +41 -0
  144. package/src/tools/registry.ts +666 -0
  145. package/src/tools/retryTask.ts +53 -0
  146. package/src/tools/savePlan.ts +46 -0
  147. package/src/tools/taskOutputs.ts +155 -0
  148. package/src/tools/taskTemplates.ts +162 -0
  149. package/src/tools/toolCatalog.ts +92 -0
  150. package/src/tools/waitForTask.ts +90 -0
  151. package/src/version.ts +2 -0
  152. package/src/watcherStatus.ts +102 -0
  153. package/tsconfig.json +17 -0
package/docs/demo.md ADDED
@@ -0,0 +1,58 @@
1
+ # PatchWarden Demo
2
+
3
+ This is a privacy-safe demo flow for GitHub. It uses placeholder workspace
4
+ names and avoids real tunnel IDs, account names, API keys, or local user paths.
5
+
6
+ ## Demo: ChatGPT Lists A Workspace
7
+
8
+ Prompt in ChatGPT with the PatchWarden connector selected:
9
+
10
+ ```text
11
+ Use the patchwarden tool list_workspace to list the configured workspace.
12
+ ```
13
+
14
+ Expected response shape:
15
+
16
+ ```text
17
+ Workspace files:
18
+
19
+ | Name | Type | Size |
20
+ |--------------|------|------|
21
+ | README.md | file | ... |
22
+ | package.json | file | ... |
23
+ ```
24
+
25
+ ![PatchWarden ChatGPT connector demo](assets/patchwarden-chatgpt-demo.svg)
26
+
27
+ ## Demo: Plan And Execute
28
+
29
+ Prompt in ChatGPT:
30
+
31
+ ```text
32
+ Use patchwarden to:
33
+ 1. read README.md
34
+ 2. save a plan that appends a Usage section
35
+ 3. create a task with an explicit repo_path and verify_commands
36
+ 4. call wait_for_task repeatedly while continuation_required is true
37
+ 5. review get_task_summary, audit_task, result, diff, and verification logs
38
+ ```
39
+
40
+ Expected artifacts under the configured workspace:
41
+
42
+ ```text
43
+ .patchwarden/tasks/<task_id>/status.json
44
+ .patchwarden/tasks/<task_id>/result.md
45
+ .patchwarden/tasks/<task_id>/git.diff
46
+ .patchwarden/tasks/<task_id>/test.log
47
+ ```
48
+
49
+ ## Privacy Notes
50
+
51
+ Do not publish:
52
+
53
+ - real API keys or runtime keys
54
+ - real tunnel IDs
55
+ - ChatGPT account names or workspace IDs
56
+ - screenshots containing sidebars with private chat/project names
57
+ - `patchwarden.config.json`
58
+ - `.patchwarden/` task history from private workspaces
@@ -0,0 +1,31 @@
1
+ # Migrating from Safe-Bifrost to PatchWarden
2
+
3
+ PatchWarden v0.4.0 is an intentional pre-1.0 breaking rename. It does not load
4
+ legacy names automatically.
5
+
6
+ ## Package and commands
7
+
8
+ - Replace `safe-bifrost` with `patchwarden` in npm dependencies and MCP client
9
+ commands.
10
+ - Replace the old runner command with `patchwarden-runner`.
11
+ - Replace Windows launchers with the corresponding `PatchWarden` launchers.
12
+
13
+ ## Configuration
14
+
15
+ 1. Copy `safe-bifrost.config.json` to `patchwarden.config.json`.
16
+ 2. Change `plansDir` and `tasksDir` from `.safe-bifrost/...` to
17
+ `.patchwarden/...`.
18
+ 3. Rename every `SAFE_BIFROST_*` environment variable to `PATCHWARDEN_*`.
19
+ 4. Replace `x-safe-bifrost-token` with `x-patchwarden-token` for local HTTP
20
+ authentication.
21
+
22
+ ## Local state
23
+
24
+ PatchWarden writes new task state under `.patchwarden`, runtime status under
25
+ `%LOCALAPPDATA%\patchwarden`, and the DPAPI tunnel credential under
26
+ `%APPDATA%\patchwarden`. Old directories are not deleted or migrated
27
+ automatically. Keep them as a backup until the new installation is verified.
28
+
29
+ The old DPAPI credential is intentionally not copied. Run the new tunnel
30
+ launcher and enter the control-plane key again so PatchWarden can save it under
31
+ the new application directory.
@@ -0,0 +1,43 @@
1
+ # Safe-Bifrost v0.3.0
2
+
3
+ Safe-Bifrost v0.3.0 makes ChatGPT-managed local agent work easier to monitor,
4
+ verify, and accept without weakening the workspace and command boundaries.
5
+
6
+ ## Highlights
7
+
8
+ - Adds `wait_for_task` so ChatGPT can stay in the tool loop until a task is
9
+ terminal, plus `get_task_summary` for one-call acceptance evidence.
10
+ - Requires an explicit existing repository directory and supports both
11
+ workspace-relative and absolute `repo_path` values.
12
+ - Adds independent multi-command `verify_commands` while keeping
13
+ `test_command` compatibility.
14
+ - Generates structured `result.json`, `verify.json`, readable `verify.log`,
15
+ and a complete `diff.patch` for every terminal task.
16
+ - Adds task-scoped file statistics, clear large/no-diff responses, and avoids
17
+ including unchanged pre-existing workspace files in task diff evidence.
18
+ - Detects changes outside `resolved_repo_path`, marks the task
19
+ `failed_scope_violation`, invalidates verification acceptance, and creates a
20
+ review-only `rollback_scope_violation_plan.md`.
21
+ - Allows ordinary long build/test/release plans while blocking explicit
22
+ credential theft, destructive disk deletion, and malicious persistence.
23
+ - Redacts secret-like values in ordinary task artifacts instead of rejecting
24
+ the entire read.
25
+ - Expands `doctor`, stdio/HTTP MCP smoke tests, lifecycle coverage, packaging
26
+ checks, and ChatGPT workflow documentation.
27
+
28
+ ## Compatibility note
29
+
30
+ `create_task` now requires `repo_path`. Existing clients that omitted it must
31
+ pass an existing directory under `workspaceRoot`. `test_command` remains
32
+ supported and is converted into a one-entry verification list.
33
+
34
+ ## Verification
35
+
36
+ - `npm test`: 63 security tests, 14 lifecycle tests, and doctor smoke passed
37
+ - `npm run test:mcp`: passed
38
+ - `npm run test:http-mcp`: 11 passed
39
+ - `npm run doctor`: 47 OK, 0 WARN, 0 FAIL
40
+ - `npm run pack:clean`: passed
41
+ - `npm run verify:package`: passed
42
+
43
+ Tracked in [issue #1](https://github.com/jiezeng2004-design/safe-bifrost/issues/1).
@@ -0,0 +1,74 @@
1
+ # PatchWarden v0.4.0
2
+
3
+ PatchWarden v0.4.0 hardens the ChatGPT-to-local-agent task loop without
4
+ weakening workspace, command, or sensitive-file boundaries.
5
+
6
+ This release also completes the project rename from Safe-Bifrost to
7
+ PatchWarden. The npm package is now `patchwarden`; the old package is retained
8
+ only to point existing users to the new name.
9
+
10
+ ## Highlights
11
+
12
+ - Adds supervised Windows tunnel recovery with local readiness probes,
13
+ structured failure categories, capped retry backoff, and redacted runtime
14
+ state under `%LOCALAPPDATA%\patchwarden\runtime`.
15
+ - Adds `Check-PatchWarden-Health.cmd` for diagnostics when the MCP tunnel itself
16
+ is unreachable.
17
+ - Expands `health_check` with workspace, tasks directory, watcher, agent, tunnel,
18
+ and last-error evidence; local HTTP `/healthz` and `/readyz` are also available.
19
+ - Lets `create_task` use exactly one of a saved `plan_id`, persisted
20
+ `inline_plan`, or a guarded task template.
21
+ - Adds `inspect_only`, `feature_small`, `fix_tests`, `release_check`, and safe
22
+ review-only `rollback_scope_violation` templates.
23
+ - Enforces no-change templates with `failed_policy_violation`.
24
+ - Adds deterministic failure guidance through `failure_reason`,
25
+ `failed_command`, `suggested_next_action`, and `safe_followup_prompt`.
26
+ - Adds standalone `file-stats.json` while preserving complete `diff.patch`,
27
+ `changed-files.json`, and the v0.3.0 acceptance contract.
28
+ - Adds deterministic `full` and `chatgpt_core` tool profiles. Tunnel stdio uses
29
+ an exact 16-tool core manifest while ordinary local launches remain on the
30
+ 22-tool full profile.
31
+ - Adds schema-inclusive tool manifest hashing, `schema_epoch`, server version,
32
+ and a real MCP stdio preflight before tunnel startup.
33
+ - Adds structured `create_task.next_tool_call`, preferred
34
+ `wait_for_task(timeout_seconds)`, legacy `wait_seconds` compatibility, and a
35
+ complete terminal summary in the wait response.
36
+ - Adds recursive redaction for structured result/verification summaries and
37
+ concise verification counts/headlines such as `166 passed`.
38
+ - Adds explicit `get_diff.patch_mode` values for textual, no-change, and
39
+ hash-only evidence, including a reason when a textual patch is unavailable.
40
+ - Enhances the local health report with process source/version evidence and
41
+ mixed-version warnings without automatically ending any process.
42
+ - Adds first-class watcher stale/missing evidence, structured pending artifact
43
+ responses, and controlled recovery for launcher-owned watcher processes.
44
+ - Reports stale client catalogs as `tool_catalog_mismatch` with the active
45
+ profile, schema epoch, manifest hash, and Connector refresh guidance.
46
+ - Fixes a plan-guard bypass where generic security wording could suppress a
47
+ later credential-access instruction. Every dangerous occurrence is now
48
+ evaluated independently and only directly negated actions are allowed.
49
+
50
+ ## Compatibility
51
+
52
+ The MCP tool names and `save_plan` -> `create_task` workflow remain stable, but
53
+ the product rename is intentionally breaking: old CLI names, `SAFE_BIFROST_*`
54
+ variables, `safe-bifrost.config.json`, `.safe-bifrost/`, and legacy AppData
55
+ paths are not loaded. Follow the migration guide before replacing an existing
56
+ installation. `repo_path`, configured agent validation, and exact
57
+ verification-command allowlists remain mandatory.
58
+
59
+ ## Verification
60
+
61
+ Run the following from Windows PowerShell:
62
+
63
+ ```powershell
64
+ npm.cmd test
65
+ npm.cmd run test:mcp
66
+ npm.cmd run test:http-mcp
67
+ npm.cmd run doctor
68
+ npm.cmd run check:tool-manifest
69
+ npm.cmd run check:brand
70
+ npm.cmd run test:tunnel-supervisor
71
+ npm.cmd run test:watcher-supervisor
72
+ npm.cmd run pack:clean
73
+ npm.cmd run verify:package
74
+ ```
@@ -0,0 +1,40 @@
1
+ {
2
+ "workspaceRoot": "D:/path/to/test-project",
3
+ "plansDir": ".patchwarden/plans",
4
+ "tasksDir": ".patchwarden/tasks",
5
+ "toolProfile": "full",
6
+ "agents": {
7
+ "codex": {
8
+ "command": "codex",
9
+ "args": ["exec", "--cd", "{repo}", "{prompt}"]
10
+ },
11
+ "opencode": {
12
+ "command": "opencode",
13
+ "args": ["run", "{prompt}"]
14
+ }
15
+ },
16
+ "allowedTestCommands": [
17
+ "npm test",
18
+ "npm run test",
19
+ "npm run lint",
20
+ "npm run format:check",
21
+ "npm run build",
22
+ "npm run dist",
23
+ "npm run doctor",
24
+ "pnpm test",
25
+ "pnpm run test",
26
+ "pnpm run lint",
27
+ "pnpm run format:check",
28
+ "pnpm run build",
29
+ "pnpm run dist",
30
+ "pnpm run doctor",
31
+ "yarn test",
32
+ "pytest",
33
+ "cargo test",
34
+ "go test ./..."
35
+ ],
36
+ "maxReadFileBytes": 200000,
37
+ "defaultTaskTimeoutSeconds": 900,
38
+ "maxTaskTimeoutSeconds": 3600,
39
+ "watcherStaleSeconds": 30
40
+ }
@@ -0,0 +1,48 @@
1
+ # OpenAI Tunnel Examples
2
+
3
+ This directory contains privacy-safe examples for connecting PatchWarden to
4
+ ChatGPT through an OpenAI Secure MCP Tunnel.
5
+
6
+ Do not commit real API keys, tunnel IDs, ChatGPT workspace IDs, account names,
7
+ or local private paths.
8
+
9
+ ## Files
10
+
11
+ - `tunnel-client.example.yaml` - profile snippets for stdio and HTTP mode
12
+ - `chatgpt-test-prompt.md` - prompt to verify the connector from ChatGPT
13
+
14
+ ## Recommended Windows Flow
15
+
16
+ 1. Configure `patchwarden.config.json`.
17
+ 2. Run `npm.cmd run build`.
18
+ 3. Use `scripts/patchwarden-mcp-stdio.cmd` as the tunnel MCP command.
19
+ 4. Start `npm.cmd run watch` in a separate terminal.
20
+ 5. Start `tunnel-client run` or use `Start-PatchWarden-Tunnel.cmd`.
21
+ 6. Create a ChatGPT Connector using the tunnel channel.
22
+ 7. After a tunnel/schema refresh, reconnect the Connector and validate from a
23
+ new ChatGPT conversation; an already-open conversation may retain its older
24
+ tool catalog.
25
+
26
+ The Windows launcher prompts for the runtime API key once and stores only a
27
+ Windows DPAPI-encrypted value under `%APPDATA%\patchwarden`. Use
28
+ `Reset-PatchWarden-Tunnel-Key.cmd` to remove the saved credential.
29
+
30
+ Before the launcher starts the tunnel it performs a real MCP stdio handshake
31
+ and requires the exact `chatgpt_core` manifest. Run
32
+ `Check-PatchWarden-Health.cmd` to see the version, profile, tool names, schema
33
+ hash, process sources, and any mixed-version warnings. The check is read-only.
34
+ The v0.4.0 core manifest contains 16 tools. A different count or schema hash
35
+ requires a Connector refresh and validation from a new ChatGPT conversation.
36
+
37
+ ## Architecture
38
+
39
+ ```text
40
+ ChatGPT Web
41
+ -> ChatGPT Connector
42
+ -> OpenAI Secure MCP Tunnel
43
+ -> PatchWarden MCP Server
44
+ -> watcher
45
+ -> local agent
46
+ -> .patchwarden/tasks/<task_id>/
47
+ -> ChatGPT reads result, diff, and test log
48
+ ```
@@ -0,0 +1,53 @@
1
+ # PatchWarden ChatGPT Test Prompt
2
+
3
+ Paste this into a ChatGPT conversation where the PatchWarden Connector is
4
+ selected.
5
+
6
+ ```text
7
+ Use the patchwarden connector tools to verify the workflow.
8
+
9
+ Step 1:
10
+ Call health_check and list_agents. Continue only if the watcher and selected
11
+ agent are available. Then call list_workspace for the configured workspace.
12
+ Confirm schema_epoch is 2026-06-20-v2, tool_profile is chatgpt_core, and
13
+ tool_count is 16. If the watcher is stale or missing, stop the task flow and
14
+ report the health_check recovery instruction.
15
+
16
+ Step 2:
17
+ Call read_workspace_file for README.md.
18
+
19
+ Step 3:
20
+ Call save_plan with:
21
+
22
+ title: Add Usage Section
23
+
24
+ content:
25
+ Add a "## Usage" section to the end of README.md. The section should include
26
+ one sentence: "This repository was updated through PatchWarden."
27
+ Do not modify other files. After the change, run the configured test command.
28
+
29
+ Step 4:
30
+ Call create_task with the returned plan_id, agent "opencode", an explicit
31
+ repo_path for the target repository, and verify_commands ["npm test"]. Set
32
+ timeout_seconds to 600.
33
+
34
+ Step 5:
35
+ Immediately call wait_for_task with timeout_seconds 25. If it returns
36
+ continuation_required: true, call wait_for_task again in this same assistant
37
+ turn. Do not stop or reply to the user until terminal is true.
38
+ If execution_blocked is true or next_tool_call is health_check, do not keep
39
+ polling; report the watcher recovery requirement.
40
+
41
+ Step 6:
42
+ When terminal is true, review the included summary, call get_task_summary and
43
+ audit_task, then call get_diff, get_test_log, and get_result_json as needed.
44
+
45
+ Step 7:
46
+ Summarize:
47
+ - final task status
48
+ - files changed
49
+ - whether tests passed
50
+ - whether any out-of-scope changes were detected
51
+ - acceptance_status and audit verdict
52
+ - whether the diff is acceptable
53
+ ```
@@ -0,0 +1,39 @@
1
+ # PatchWarden tunnel-client example configuration
2
+ #
3
+ # This is an example only. Do not commit real keys, tunnel IDs, account names,
4
+ # workspace IDs, or local private paths.
5
+
6
+ # Mode A: stdio
7
+ #
8
+ # On Windows, prefer the wrapper. It sets PATCHWARDEN_CONFIG and starts the
9
+ # MCP server from the PatchWarden project root.
10
+ #
11
+ # tunnel-client init \
12
+ # --sample sample_mcp_stdio_local \
13
+ # --profile patchwarden \
14
+ # --tunnel-id tunnel_xxx \
15
+ # --mcp-command "C:/path/to/patchwarden/scripts/patchwarden-mcp-stdio.cmd"
16
+
17
+ mcp:
18
+ commands:
19
+ - channel: main
20
+ command: "C:/path/to/patchwarden/scripts/patchwarden-mcp-stdio.cmd"
21
+
22
+ # Mode B: HTTP
23
+ #
24
+ # Start PatchWarden HTTP first:
25
+ #
26
+ # npm run start:http
27
+ #
28
+ # Then point tunnel-client at it:
29
+ #
30
+ # tunnel-client init \
31
+ # --sample sample_mcp_with_dcr \
32
+ # --profile patchwarden-http \
33
+ # --tunnel-id tunnel_xxx \
34
+ # --mcp-server-url "http://127.0.0.1:7331/mcp"
35
+ #
36
+ # mcp:
37
+ # server_urls:
38
+ # - channel: main
39
+ # url: "http://127.0.0.1:7331/mcp"
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "patchwarden",
3
+ "version": "0.4.0",
4
+ "description": "A secure local MCP bridge: clients save plans, local agents execute tasks, and results are returned through workspace-scoped files.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "patchwarden": "dist/index.js",
10
+ "patchwarden-runner": "dist/runner/cli.js"
11
+ },
12
+ "files": [
13
+ "dist/",
14
+ "docs/",
15
+ "src/",
16
+ "examples/",
17
+ "scripts/",
18
+ "Start-PatchWarden-Tunnel.cmd",
19
+ "Restart-PatchWarden.cmd",
20
+ "Check-PatchWarden-Health.cmd",
21
+ "Reset-PatchWarden-Tunnel-Key.cmd",
22
+ "README.md",
23
+ "LICENSE",
24
+ "tsconfig.json"
25
+ ],
26
+ "scripts": {
27
+ "build": "node ./node_modules/typescript/bin/tsc",
28
+ "start": "node dist/index.js",
29
+ "start:http": "node dist/httpServer.js",
30
+ "dev": "npm run build && node dist/index.js",
31
+ "runner": "node dist/runner/cli.js",
32
+ "watch": "node dist/runner/watch.js",
33
+ "doctor": "node dist/doctor.js",
34
+ "test": "node dist/smoke-test.js && node scripts/lifecycle-smoke.js && node scripts/doctor-smoke.js && node scripts/tunnel-supervisor-smoke.js && node scripts/watcher-supervisor-smoke.js && node scripts/mcp-manifest-check.js && node scripts/brand-check.js",
35
+ "test:lifecycle": "npm run build && node scripts/lifecycle-smoke.js",
36
+ "test:doctor": "npm run build && node scripts/doctor-smoke.js",
37
+ "test:tunnel-supervisor": "npm run build && node scripts/tunnel-supervisor-smoke.js",
38
+ "test:watcher-supervisor": "npm run build && node scripts/watcher-supervisor-smoke.js",
39
+ "check:tool-manifest": "npm run build && node scripts/mcp-manifest-check.js",
40
+ "check:brand": "node scripts/brand-check.js",
41
+ "test:mcp": "npm run build && node scripts/mcp-smoke.js",
42
+ "test:http-mcp": "npm run build && node scripts/http-mcp-smoke.js",
43
+ "pretest": "npm run build",
44
+ "pack:clean": "npm run build && node scripts/pack-clean.js",
45
+ "verify:package": "npm run build && npm pack --dry-run"
46
+ },
47
+ "dependencies": {
48
+ "@modelcontextprotocol/sdk": "^1.0.0"
49
+ },
50
+ "devDependencies": {
51
+ "@types/node": "^20.0.0",
52
+ "typescript": "^5.0.0"
53
+ },
54
+ "engines": {
55
+ "node": ">=18.0.0"
56
+ },
57
+ "keywords": [
58
+ "mcp",
59
+ "model-context-protocol",
60
+ "codex",
61
+ "opencode",
62
+ "local-agent"
63
+ ],
64
+ "repository": {
65
+ "type": "git",
66
+ "url": "git+https://github.com/jiezeng2004-design/PatchWarden.git"
67
+ },
68
+ "bugs": {
69
+ "url": "https://github.com/jiezeng2004-design/PatchWarden/issues"
70
+ },
71
+ "homepage": "https://github.com/jiezeng2004-design/PatchWarden#readme",
72
+ "license": "MIT"
73
+ }
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ import { execFileSync } from "node:child_process";
3
+ import { readFileSync } from "node:fs";
4
+
5
+ const allowedLegacyFiles = new Set([
6
+ ".gitignore",
7
+ ".npmignore",
8
+ "README.md",
9
+ "docs/migration-from-safe-bifrost.md",
10
+ "docs/release-v0.3.0.md",
11
+ "docs/release-v0.4.0.md",
12
+ "scripts/brand-check.js",
13
+ "scripts/pack-clean.js",
14
+ ]);
15
+ const legacyPattern = /safe-bifrost|Safe-Bifrost|SAFE_BIFROST|SafeBifrost|safe_bifrost/;
16
+ const trackedFiles = execFileSync(
17
+ "git",
18
+ ["ls-files", "--cached", "--others", "--exclude-standard"],
19
+ { encoding: "utf-8" }
20
+ )
21
+ .split(/\r?\n/)
22
+ .filter(Boolean);
23
+ const failures = [];
24
+
25
+ for (const file of trackedFiles) {
26
+ const normalized = file.replace(/\\/g, "/");
27
+ if (allowedLegacyFiles.has(normalized)) continue;
28
+ if (legacyPattern.test(normalized)) {
29
+ failures.push(`${normalized}: legacy brand in path`);
30
+ continue;
31
+ }
32
+ const content = readFileSync(file);
33
+ if (!content.includes(0) && legacyPattern.test(content.toString("utf-8"))) {
34
+ failures.push(`${normalized}: legacy brand in content`);
35
+ }
36
+ }
37
+
38
+ if (failures.length > 0) {
39
+ console.error("[brand-check] Legacy brand found outside the approved migration/history files:");
40
+ for (const failure of failures) console.error(` ${failure}`);
41
+ process.exit(1);
42
+ }
43
+
44
+ console.log(`[brand-check] OK: ${trackedFiles.length} tracked files checked.`);
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join, resolve } from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
9
+ const tempRoot = mkdtempSync(join(tmpdir(), "patchwarden-doctor-"));
10
+ const workspaceRoot = join(tempRoot, "workspace");
11
+ const configPath = join(tempRoot, "patchwarden.config.json");
12
+
13
+ try {
14
+ mkdirSync(workspaceRoot, { recursive: true });
15
+ writeFileSync(configPath, JSON.stringify({
16
+ workspaceRoot,
17
+ plansDir: ".patchwarden/plans",
18
+ tasksDir: ".patchwarden/tasks",
19
+ agents: {
20
+ fixture: { command: process.execPath, args: ["-e", "console.log('fixture')"] },
21
+ },
22
+ allowedTestCommands: ["npm test", "npm run lint"],
23
+ maxReadFileBytes: 200000,
24
+ defaultTaskTimeoutSeconds: 30,
25
+ maxTaskTimeoutSeconds: 60,
26
+ }, null, 2), "utf-8");
27
+
28
+ const result = spawnSync(process.execPath, ["dist/doctor.js"], {
29
+ cwd: root,
30
+ env: { ...process.env, PATCHWARDEN_CONFIG: configPath },
31
+ encoding: "utf-8",
32
+ timeout: 30000,
33
+ });
34
+ if (result.status !== 0) {
35
+ throw new Error(`doctor exited ${result.status}: ${result.stdout}\n${result.stderr}`);
36
+ }
37
+ const requiredLines = [
38
+ "[OK] workspaceRoot exists",
39
+ "[OK] repo_path resolver supports relative and absolute paths",
40
+ "[OK] save_plan security rules loaded",
41
+ "[OK] Read-only task artifact allowlist",
42
+ "[OK] Task directory writable",
43
+ "[OK] Example task directory read/write",
44
+ "[OK] allowedTestCommands is non-empty",
45
+ ];
46
+ for (const line of requiredLines) {
47
+ if (!result.stdout.includes(line)) throw new Error(`doctor output missing stable line: ${line}`);
48
+ }
49
+ console.log("ok - doctor stable output and self-checks");
50
+ } finally {
51
+ rmSync(tempRoot, { recursive: true, force: true });
52
+ }
@@ -0,0 +1,114 @@
1
+ $ErrorActionPreference = "Stop"
2
+
3
+ $ProjectRoot = Split-Path -Parent $PSScriptRoot
4
+ $ConfigPath = Join-Path $ProjectRoot "patchwarden.config.json"
5
+ $RuntimeDirectory = Join-Path $env:LOCALAPPDATA "patchwarden\runtime"
6
+ $HealthUrlFile = Join-Path $RuntimeDirectory "tunnel-health-url.txt"
7
+
8
+ $env:PATCHWARDEN_CONFIG = $ConfigPath
9
+ $healthJson = node --input-type=module -e "Promise.all([import('./dist/tools/healthCheck.js'),import('./dist/tools/registry.js')]).then(([h,r])=>console.log(JSON.stringify(h.healthCheck(r.getToolCatalogSnapshot()))))" 2>$null
10
+ if ($LASTEXITCODE -ne 0 -or -not $healthJson) {
11
+ throw "Could not load PatchWarden health information. Run npm.cmd run build first."
12
+ }
13
+ $health = $healthJson | ConvertFrom-Json
14
+ $packageVersion = [string](Get-Content -LiteralPath (Join-Path $ProjectRoot "package.json") -Raw | ConvertFrom-Json).version
15
+
16
+ $configuredTunnelManifest = $null
17
+ try {
18
+ $manifestJson = node (Join-Path $ProjectRoot "scripts\mcp-manifest-check.js") 2>$null
19
+ if ($LASTEXITCODE -eq 0 -and $manifestJson) {
20
+ $configuredTunnelManifest = $manifestJson | ConvertFrom-Json
21
+ }
22
+ } catch {}
23
+
24
+ $safeProcesses = @()
25
+ try {
26
+ $allProcesses = Get-CimInstance Win32_Process
27
+ foreach ($process in $allProcesses) {
28
+ $commandLine = [string]$process.CommandLine
29
+ if (-not $commandLine -or $commandLine -notmatch '(?i)patchwarden') { continue }
30
+ if ($process.ProcessId -eq $PID -or $commandLine -match 'get-patchwarden-health\.ps1') { continue }
31
+ $detectedVersion = "unknown"
32
+ $origin = "unknown"
33
+ if ($commandLine -match '(?i)patchwarden@([0-9]+(?:\.[0-9]+){1,2})') {
34
+ $detectedVersion = $matches[1]
35
+ $origin = "npm_package"
36
+ } elseif ($commandLine -like "*$ProjectRoot*") {
37
+ $detectedVersion = [string]$health.server_version
38
+ $origin = "current_workspace"
39
+ }
40
+ $safeCommand = $commandLine `
41
+ -replace '(?i)(CONTROL_PLANE_API_KEY\s*=\s*)\S+', '$1[REDACTED]' `
42
+ -replace '(?i)tunnel_[a-z0-9_-]{12,}', '[REDACTED_TUNNEL_ID]'
43
+ $safeProcesses += [ordered]@{
44
+ pid = $process.ProcessId
45
+ parent_pid = $process.ParentProcessId
46
+ name = $process.Name
47
+ detected_version = $detectedVersion
48
+ origin = $origin
49
+ command_line = $safeCommand.Substring(0, [Math]::Min(500, $safeCommand.Length))
50
+ version_conflict = $detectedVersion -ne "unknown" -and $detectedVersion -ne [string]$health.server_version
51
+ }
52
+ }
53
+ } catch {}
54
+
55
+ $liveTunnel = [ordered]@{ observed = $false; healthz = $false; readyz = $false; error = $null }
56
+ if (Test-Path -LiteralPath $HealthUrlFile) {
57
+ try {
58
+ $baseUrl = (Get-Content -LiteralPath $HealthUrlFile -Raw).Trim().TrimEnd('/')
59
+ if ($baseUrl -notmatch '^http://127\.0\.0\.1:\d+$') { throw "Health URL is not loopback-only." }
60
+ $healthz = Invoke-WebRequest -Uri "$baseUrl/healthz" -UseBasicParsing -TimeoutSec 3
61
+ $readyz = Invoke-WebRequest -Uri "$baseUrl/readyz" -UseBasicParsing -TimeoutSec 3
62
+ $liveTunnel.observed = $true
63
+ $liveTunnel.healthz = $healthz.StatusCode -eq 200
64
+ $liveTunnel.readyz = $readyz.StatusCode -eq 200
65
+ } catch {
66
+ $liveTunnel.observed = $true
67
+ $liveTunnel.error = "Tunnel local health endpoint is unavailable."
68
+ }
69
+ }
70
+
71
+ $tunnelProcesses = @($safeProcesses | Where-Object { $_.name -eq "tunnel-client.exe" })
72
+ $mcpChildProcesses = @($safeProcesses | Where-Object {
73
+ $_.command_line -match '(?i)patchwarden-mcp-stdio\.cmd|patchwarden\\dist\\index\.js|patchwarden/scripts/\.\./dist/index\.js'
74
+ })
75
+ $deploymentConsistent = [bool](
76
+ $configuredTunnelManifest -and
77
+ $health.tunnel.tool_manifest_sha256 -and
78
+ [string]$configuredTunnelManifest.tool_manifest_sha256 -eq [string]$health.tunnel.tool_manifest_sha256
79
+ )
80
+
81
+ [ordered]@{
82
+ checked_at = (Get-Date).ToUniversalTime().ToString("o")
83
+ overall_status = $health.status
84
+ source_version = $packageVersion
85
+ dist_version = $health.server_version
86
+ schema_epoch = $health.schema_epoch
87
+ local_server_catalog = [ordered]@{
88
+ tool_profile = $health.tool_profile
89
+ tool_count = $health.tool_count
90
+ tool_names = $health.tool_names
91
+ tool_manifest_sha256 = $health.tool_manifest_sha256
92
+ catalog_consistent = $health.catalog_consistent
93
+ mismatch_report = $health.mismatch_report
94
+ tunnel_catalog_comparison = $health.tunnel_catalog_comparison
95
+ }
96
+ configured_tunnel_manifest = $configuredTunnelManifest
97
+ deployment_consistency = [ordered]@{
98
+ consistent = $deploymentConsistent
99
+ reason = if ($deploymentConsistent) { $null } else { "Configured tunnel manifest differs from the running tunnel state. Restart the owned tunnel, then refresh the Connector and use a new conversation." }
100
+ }
101
+ connector_visibility = $health.connector_visibility
102
+ mcp_server = $health.mcp_server
103
+ workspace_root = $health.workspace_root
104
+ tasks_dir = $health.tasks_dir
105
+ watcher = $health.watcher
106
+ agents = $health.agents
107
+ tunnel_state = $health.tunnel
108
+ tunnel_live_probe = $liveTunnel
109
+ tunnel_processes = $tunnelProcesses
110
+ mcp_child_processes = $mcpChildProcesses
111
+ patchwarden_processes = $safeProcesses
112
+ version_conflicts = @($safeProcesses | Where-Object { $_.version_conflict })
113
+ last_error = $health.last_error
114
+ } | ConvertTo-Json -Depth 8