patchwarden 0.4.0 → 0.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 +51 -0
- package/README.en.md +1022 -0
- package/README.md +897 -358
- package/dist/assessments/agentAssessor.d.ts +15 -0
- package/dist/assessments/agentAssessor.js +293 -0
- package/dist/assessments/assessmentStore.d.ts +133 -0
- package/dist/assessments/assessmentStore.js +238 -0
- package/dist/assessments/confirmCli.d.ts +9 -0
- package/dist/assessments/confirmCli.js +26 -0
- package/dist/config.d.ts +22 -1
- package/dist/config.js +156 -3
- package/dist/direct/directAudit.d.ts +23 -0
- package/dist/direct/directAudit.js +309 -0
- package/dist/direct/directGuards.d.ts +20 -0
- package/dist/direct/directGuards.js +137 -0
- package/dist/direct/directPatch.d.ts +17 -0
- package/dist/direct/directPatch.js +113 -0
- package/dist/direct/directSessionStore.d.ts +63 -0
- package/dist/direct/directSessionStore.js +192 -0
- package/dist/direct/directVerification.d.ts +12 -0
- package/dist/direct/directVerification.js +96 -0
- package/dist/doctor.js +73 -5
- package/dist/logging.d.ts +52 -0
- package/dist/logging.js +123 -0
- package/dist/runner/agentInvocation.d.ts +23 -0
- package/dist/runner/agentInvocation.js +106 -0
- package/dist/runner/changeCapture.d.ts +70 -0
- package/dist/runner/changeCapture.js +278 -6
- package/dist/runner/runTask.js +271 -65
- package/dist/runner/simpleProcess.d.ts +21 -0
- package/dist/runner/simpleProcess.js +206 -0
- package/dist/runner/watch.js +17 -2
- package/dist/security/commandGuard.d.ts +2 -1
- package/dist/security/commandGuard.js +25 -3
- package/dist/security/riskEngine.d.ts +27 -0
- package/dist/security/riskEngine.js +118 -0
- package/dist/security/runtimeGuard.d.ts +6 -0
- package/dist/security/runtimeGuard.js +28 -0
- package/dist/smoke-test.js +1353 -7
- package/dist/test/unit/android-doctor.test.d.ts +1 -0
- package/dist/test/unit/android-doctor.test.js +118 -0
- package/dist/test/unit/chinese-path.test.d.ts +1 -0
- package/dist/test/unit/chinese-path.test.js +91 -0
- package/dist/test/unit/command-guard.test.d.ts +1 -0
- package/dist/test/unit/command-guard.test.js +160 -0
- package/dist/test/unit/direct-guards.test.d.ts +1 -0
- package/dist/test/unit/direct-guards.test.js +213 -0
- package/dist/test/unit/logging.test.d.ts +1 -0
- package/dist/test/unit/logging.test.js +275 -0
- package/dist/test/unit/path-guard.test.d.ts +1 -0
- package/dist/test/unit/path-guard.test.js +109 -0
- package/dist/test/unit/safe-status.test.d.ts +1 -0
- package/dist/test/unit/safe-status.test.js +165 -0
- package/dist/test/unit/sensitive-guard.test.d.ts +1 -0
- package/dist/test/unit/sensitive-guard.test.js +104 -0
- package/dist/test/unit/sync-file.test.d.ts +1 -0
- package/dist/test/unit/sync-file.test.js +154 -0
- package/dist/test/unit/watcher-status.test.d.ts +1 -0
- package/dist/test/unit/watcher-status.test.js +169 -0
- package/dist/tools/androidDoctor.d.ts +38 -0
- package/dist/tools/androidDoctor.js +391 -0
- package/dist/tools/applyPatch.d.ts +16 -0
- package/dist/tools/applyPatch.js +41 -0
- package/dist/tools/auditSession.d.ts +5 -0
- package/dist/tools/auditSession.js +12 -0
- package/dist/tools/auditTask.d.ts +7 -0
- package/dist/tools/auditTask.js +105 -7
- package/dist/tools/createDirectSession.d.ts +14 -0
- package/dist/tools/createDirectSession.js +54 -0
- package/dist/tools/createTask.d.ts +48 -1
- package/dist/tools/createTask.js +298 -47
- package/dist/tools/finalizeDirectSession.d.ts +19 -0
- package/dist/tools/finalizeDirectSession.js +84 -0
- package/dist/tools/getTaskSummary.d.ts +50 -0
- package/dist/tools/getTaskSummary.js +79 -2
- package/dist/tools/healthCheck.d.ts +27 -12
- package/dist/tools/healthCheck.js +44 -6
- package/dist/tools/readWorkspaceFile.d.ts +7 -1
- package/dist/tools/readWorkspaceFile.js +48 -2
- package/dist/tools/registry.js +270 -19
- package/dist/tools/runVerification.d.ts +16 -0
- package/dist/tools/runVerification.js +32 -0
- package/dist/tools/safeStatus.d.ts +19 -0
- package/dist/tools/safeStatus.js +72 -0
- package/dist/tools/savePlan.d.ts +1 -0
- package/dist/tools/savePlan.js +38 -7
- package/dist/tools/searchWorkspace.d.ts +19 -0
- package/dist/tools/searchWorkspace.js +205 -0
- package/dist/tools/syncFile.d.ts +18 -0
- package/dist/tools/syncFile.js +65 -0
- package/dist/tools/taskOutputs.d.ts +2 -2
- package/dist/tools/taskTemplates.js +2 -1
- package/dist/tools/toolCatalog.d.ts +4 -3
- package/dist/tools/toolCatalog.js +34 -5
- package/dist/tools/waitForTask.d.ts +2 -2
- package/dist/tools/waitForTask.js +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/watcherStatus.d.ts +1 -0
- package/dist/watcherStatus.js +96 -4
- package/docs/chatgpt-usage.md +101 -0
- package/docs/performance-notes.md +55 -0
- package/docs/release-checklist.md +14 -0
- package/docs/release-v0.6.0.md +71 -0
- package/docs/release-v0.6.1.md +75 -0
- package/examples/config.example.json +5 -0
- package/examples/openai-tunnel/README.md +11 -4
- package/examples/openai-tunnel/chatgpt-test-prompt.md +18 -14
- package/examples/openai-tunnel/tunnel-client.example.yaml +8 -0
- package/package.json +13 -8
- package/scripts/brand-check.js +58 -12
- package/scripts/control-smoke.js +206 -0
- package/scripts/http-mcp-smoke.js +10 -2
- package/scripts/launchers/Check-PatchWarden-Health.cmd +6 -0
- package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +6 -0
- package/scripts/launchers/Restart-PatchWarden.cmd +6 -0
- package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +7 -0
- package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +7 -0
- package/scripts/lifecycle-smoke.js +402 -8
- package/scripts/manage-patchwarden.ps1 +639 -0
- package/scripts/mcp-manifest-check.js +123 -58
- package/scripts/mcp-smoke.js +160 -2
- package/scripts/pack-clean.js +160 -5
- package/scripts/package-manifest-check.js +78 -0
- package/scripts/patchwarden-mcp-direct.cmd +7 -0
- package/scripts/patchwarden-mcp-stdio.cmd +1 -1
- package/scripts/restart-patchwarden.ps1 +5 -4
- package/scripts/start-patchwarden-tunnel.ps1 +261 -30
- package/scripts/tunnel-supervisor-smoke.js +36 -4
- package/scripts/unit-tests.js +36 -0
- package/scripts/watcher-supervisor-smoke.js +10 -6
- package/src/assessments/agentAssessor.ts +324 -0
- package/src/assessments/assessmentStore.ts +426 -0
- package/src/assessments/confirmCli.ts +29 -0
- package/src/config.ts +178 -4
- package/src/direct/directAudit.ts +400 -0
- package/src/direct/directGuards.ts +279 -0
- package/src/direct/directPatch.ts +258 -0
- package/src/direct/directSessionStore.ts +345 -0
- package/src/direct/directVerification.ts +138 -0
- package/src/doctor.ts +103 -7
- package/src/logging.ts +152 -0
- package/src/runner/agentInvocation.ts +125 -0
- package/src/runner/changeCapture.ts +352 -6
- package/src/runner/runTask.ts +279 -63
- package/src/runner/simpleProcess.ts +223 -0
- package/src/runner/watch.ts +18 -2
- package/src/security/commandGuard.ts +46 -4
- package/src/security/riskEngine.ts +160 -0
- package/src/security/runtimeGuard.ts +41 -0
- package/src/smoke-test.ts +1291 -4
- package/src/test/unit/android-doctor.test.ts +158 -0
- package/src/test/unit/chinese-path.test.ts +106 -0
- package/src/test/unit/command-guard.test.ts +221 -0
- package/src/test/unit/direct-guards.test.ts +297 -0
- package/src/test/unit/logging.test.ts +325 -0
- package/src/test/unit/path-guard.test.ts +150 -0
- package/src/test/unit/safe-status.test.ts +187 -0
- package/src/test/unit/sensitive-guard.test.ts +124 -0
- package/src/test/unit/sync-file.test.ts +231 -0
- package/src/test/unit/watcher-status.test.ts +190 -0
- package/src/tools/androidDoctor.ts +424 -0
- package/src/tools/applyPatch.ts +86 -0
- package/src/tools/auditSession.ts +28 -0
- package/src/tools/auditTask.ts +111 -7
- package/src/tools/createDirectSession.ts +113 -0
- package/src/tools/createTask.ts +405 -55
- package/src/tools/finalizeDirectSession.ts +144 -0
- package/src/tools/getTaskSummary.ts +133 -2
- package/src/tools/healthCheck.ts +45 -6
- package/src/tools/readWorkspaceFile.ts +85 -2
- package/src/tools/registry.ts +305 -19
- package/src/tools/runVerification.ts +58 -0
- package/src/tools/safeStatus.ts +96 -0
- package/src/tools/savePlan.ts +57 -7
- package/src/tools/searchWorkspace.ts +275 -0
- package/src/tools/syncFile.ts +122 -0
- package/src/tools/taskTemplates.ts +2 -1
- package/src/tools/toolCatalog.ts +37 -6
- package/src/tools/waitForTask.ts +3 -3
- package/src/version.ts +2 -2
- package/src/watcherStatus.ts +101 -4
- package/tsconfig.json +18 -17
- package/Check-PatchWarden-Health.cmd +0 -6
- package/Reset-PatchWarden-Tunnel-Key.cmd +0 -6
- package/Restart-PatchWarden.cmd +0 -19
- package/Start-PatchWarden-Tunnel.cmd +0 -7
- package/docs/release-v0.3.0.md +0 -43
- package/docs/release-v0.4.0.md +0 -74
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# ChatGPT 调用规范 / ChatGPT usage guide
|
|
2
|
+
|
|
3
|
+
## 中文
|
|
4
|
+
|
|
5
|
+
这套约定用于降低长参数、长轮询、构建产物和完整日志触发连接器误拦截的概率,同时保留 PatchWarden 的本地安全边界和完整证据。
|
|
6
|
+
|
|
7
|
+
1. 一个任务只处理一个目标;功能实现、耗时构建和 Git/发布收尾分开执行。
|
|
8
|
+
2. 计划标题使用短英文 slug,详细说明放在目标或计划正文中。
|
|
9
|
+
3. ChatGPT 优先使用守护模板:只读诊断用 `inspect_only`,小范围修改用 `feature_small`,修复已知失败用 `fix_tests`。模板不够表达目标时才使用 `inline_plan` 或保存的长计划。
|
|
10
|
+
4. 创建任务时优先采用 assess → execute 两步流程。execute 必须直接使用 assess 返回的 `next_tool_call`,不要重复发送 goal、plan、repo、agent 或验证命令。
|
|
11
|
+
5. 短任务可以调用 `wait_for_task(timeout_seconds: 25)`;长任务使用 `list_tasks(repo_path=..., active_only=true)` 和 `get_task_status`。
|
|
12
|
+
6. 终态先读取 `get_task_summary(view: "compact")` 和 `audit_task`;证据不足时才读取 standard 摘要、完整 diff 或日志。
|
|
13
|
+
7. 构建验证与源码任务分开。`artifact_hygiene` 会区分源码、已跟踪构建物、忽略产物、运行态文件和可疑变更。
|
|
14
|
+
8. PatchWarden 保留未提交改动供人工审核;提交、推送和发布不属于普通任务范围。
|
|
15
|
+
|
|
16
|
+
### assess → execute
|
|
17
|
+
|
|
18
|
+
第一步发送完整目标:
|
|
19
|
+
|
|
20
|
+
```json
|
|
21
|
+
{
|
|
22
|
+
"tool": "create_task",
|
|
23
|
+
"execution_mode": "assess_only",
|
|
24
|
+
"template": "feature_small",
|
|
25
|
+
"goal": "Add a bounded activity timeline UI",
|
|
26
|
+
"agent": "opencode",
|
|
27
|
+
"repo_path": "my-project",
|
|
28
|
+
"verify_commands": ["npm test"]
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
当 `decision` 为 `allow` 时,响应会包含结构化的最小调用:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"next_tool_call": {
|
|
37
|
+
"name": "create_task",
|
|
38
|
+
"arguments": {
|
|
39
|
+
"execution_mode": "execute",
|
|
40
|
+
"assessment_id": "assessment_20260622_143000_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
请原样调用 `next_tool_call`。完整的 128-bit `assessment_id` 用于执行;`assessment_short_id` 仅用于展示。工作区、计划、策略或工具清单变化后会返回 `assessment_stale_*`,此时必须重新 assess。
|
|
47
|
+
|
|
48
|
+
当 `decision` 为 `needs_confirm` 时,用户需要在使用同一份本地配置的终端运行:
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
patchwarden-confirm assessment_20260622_143000_a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
确认入口不属于 MCP 工具,远程调用者不能自行确认。确认后再原样调用返回的 `next_tool_call`;`blocked` 票据不能确认或执行。
|
|
55
|
+
|
|
56
|
+
### audit_task 结果解释
|
|
57
|
+
|
|
58
|
+
- `confirmed_failures`:证据已经明确的失败,例如非零测试退出码、越界变更或失败状态。
|
|
59
|
+
- `possible_false_positives`:启发式规则可能产生的误报,例如文档属于其他包、构建物是有意跟踪的发布资产。
|
|
60
|
+
- `manual_verification_required` / `manual_verification_items`:需要人工或远程权威来源核实的事项。
|
|
61
|
+
|
|
62
|
+
因此 `verdict: "warn"` 不等于任务一定有错。应先核实上述字段,再决定是否接受结果。
|
|
63
|
+
|
|
64
|
+
仓库专属验证命令只能配置在本机可信的 `patchwarden.config.json` 中:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"repoAllowedTestCommands": {
|
|
69
|
+
"desktop-app": ["npm run release:check"]
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
路径和命令都采用精确匹配,不支持通配符,也不会读取目标仓库的 `package.json` 自动授权。
|
|
75
|
+
|
|
76
|
+
## English
|
|
77
|
+
|
|
78
|
+
These conventions reduce connector false positives while preserving local
|
|
79
|
+
safety boundaries and complete evidence.
|
|
80
|
+
|
|
81
|
+
1. Keep one goal per task; separate feature work, expensive builds, and
|
|
82
|
+
Git/release handoff.
|
|
83
|
+
2. Use a short English slug for the title.
|
|
84
|
+
3. Prefer guarded templates: `inspect_only` for diagnosis, `feature_small` for
|
|
85
|
+
a scoped change, and `fix_tests` for a known failure. Use a long plan only
|
|
86
|
+
when a template cannot express the goal.
|
|
87
|
+
4. Prefer assess → execute. Invoke the returned `next_tool_call` unchanged and
|
|
88
|
+
do not resend goal, plan, repository, agent, or verification arguments.
|
|
89
|
+
5. Poll long tasks with `list_tasks` and `get_task_status`; inspect
|
|
90
|
+
`get_task_summary(view: "compact")` before full logs or diffs.
|
|
91
|
+
6. Run build/package validation separately from source implementation.
|
|
92
|
+
7. Changes remain uncommitted for review; commit, push, and publish are outside
|
|
93
|
+
the ordinary task scope.
|
|
94
|
+
|
|
95
|
+
`needs_confirm` assessments must be confirmed locally with
|
|
96
|
+
`patchwarden-confirm <full_assessment_id>`. The confirmation command is not an
|
|
97
|
+
MCP tool. A `blocked` assessment cannot be confirmed.
|
|
98
|
+
|
|
99
|
+
In `audit_task`, evidence-backed failures appear in `confirmed_failures`,
|
|
100
|
+
heuristic warnings in `possible_false_positives`, and unresolved checks in
|
|
101
|
+
`manual_verification_items`. A warning is not automatically a confirmed error.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# PatchWarden Performance Notes
|
|
2
|
+
|
|
3
|
+
## Current State (v0.6.1)
|
|
4
|
+
|
|
5
|
+
### Identified Optimization Opportunities
|
|
6
|
+
|
|
7
|
+
The following performance optimizations have been identified for future implementation. They are documented here rather than implemented in v0.6.1 to avoid introducing risk to the core task execution path.
|
|
8
|
+
|
|
9
|
+
### 1. Async Git Operations in changeCapture.ts
|
|
10
|
+
|
|
11
|
+
**Current**: `runGit()` uses `spawnSync()` which blocks the event loop during git operations.
|
|
12
|
+
|
|
13
|
+
**Proposed**: Replace with `execFile()` (async) and use `Promise.all()` for independent git queries in `captureRepoSnapshot()`.
|
|
14
|
+
|
|
15
|
+
**Risk**: Medium — changes the core change capture path. Requires careful testing of all snapshot/diff/scope scenarios.
|
|
16
|
+
|
|
17
|
+
**Expected Benefit**: ~200-500ms improvement per task for repos with many files, as multiple git queries can run concurrently.
|
|
18
|
+
|
|
19
|
+
### 2. Streaming File Hash
|
|
20
|
+
|
|
21
|
+
**Current**: `computeFileSha256()` reads the entire file into memory with `readFileSync()`.
|
|
22
|
+
|
|
23
|
+
**Proposed**: Use `createReadStream()` + `crypto.createHash()` for streaming hash computation.
|
|
24
|
+
|
|
25
|
+
**Risk**: Low — isolated to hash computation, no semantic change.
|
|
26
|
+
|
|
27
|
+
**Expected Benefit**: Reduces peak memory usage for large files (e.g., release artifacts > 5MB).
|
|
28
|
+
|
|
29
|
+
### 3. Workspace Snapshot Caching
|
|
30
|
+
|
|
31
|
+
**Current**: `walkWorkspace()` traverses the entire workspace directory tree on every snapshot.
|
|
32
|
+
|
|
33
|
+
**Proposed**: Cache the directory listing and invalidate on file modification events.
|
|
34
|
+
|
|
35
|
+
**Risk**: High — requires file system watchers and cache invalidation logic. Could introduce stale data bugs.
|
|
36
|
+
|
|
37
|
+
**Expected Benefit**: ~100-1000ms improvement for large workspaces with many repos.
|
|
38
|
+
|
|
39
|
+
### 4. Parallel Task Status Reads
|
|
40
|
+
|
|
41
|
+
**Current**: `listTasks()` reads each task's `status.json` sequentially.
|
|
42
|
+
|
|
43
|
+
**Proposed**: Use `Promise.all()` to read multiple task status files concurrently.
|
|
44
|
+
|
|
45
|
+
**Risk**: Low — read-only operation, no semantic change.
|
|
46
|
+
|
|
47
|
+
**Expected Benefit**: ~50-200ms improvement when listing 10+ tasks.
|
|
48
|
+
|
|
49
|
+
## Decision for v0.6.1
|
|
50
|
+
|
|
51
|
+
All four optimizations are deferred to a future release. The current synchronous implementation is correct and well-tested. Changing the core paths would require extensive regression testing that is beyond the scope of this stability release.
|
|
52
|
+
|
|
53
|
+
## Monitoring
|
|
54
|
+
|
|
55
|
+
The structured logging module (`src/logging.ts`) added in v0.6.1 provides the infrastructure to measure tool call durations (`duration_ms` in audit logs). Once deployed, real-world timing data can be collected to prioritize which optimization to implement first.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Manual release checklist
|
|
2
|
+
|
|
3
|
+
PatchWarden releases remain confirmation-gated. Creating a tag or GitHub Release does not publish npm automatically.
|
|
4
|
+
|
|
5
|
+
1. Work on a release branch and update `package.json`, changelog/release notes, README, examples, and tool manifests together.
|
|
6
|
+
2. Run the complete local quality gates from `AGENTS.md`.
|
|
7
|
+
3. Open a pull request, wait for `CI gate`, review the diff and package contents, then merge.
|
|
8
|
+
4. Create the version tag from the verified merge commit.
|
|
9
|
+
5. Create the GitHub Release and attach only reviewed release artifacts and checksums.
|
|
10
|
+
6. Publish `patchwarden` to npm using process-scoped authentication; never store the raw token in the repository.
|
|
11
|
+
7. Verify `gh release view`, the remote tag, `npm.cmd view patchwarden version`, and `dist-tags.latest`.
|
|
12
|
+
8. Update or close the associated issue only after remote verification succeeds.
|
|
13
|
+
|
|
14
|
+
Do not publish new versions under the frozen pre-rename package name.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# PatchWarden v0.6.0
|
|
2
|
+
|
|
3
|
+
PatchWarden v0.6.0 在保持 `chatgpt_core` 固定 16 工具兼容的同时,加入默认关闭的
|
|
4
|
+
`chatgpt_direct` 安全直接开发模式、本地 assess → confirm → execute 闭环,以及更可靠的
|
|
5
|
+
Windows Core/Direct 双通道运行管理。
|
|
6
|
+
|
|
7
|
+
## 主要更新
|
|
8
|
+
|
|
9
|
+
### ChatGPT Core 安全体验
|
|
10
|
+
|
|
11
|
+
- `assess_only` 返回最小化的结构化 `next_tool_call`;execute 只需
|
|
12
|
+
`execution_mode` 和完整 `assessment_id`,不再重复传输目标、计划和仓库参数。
|
|
13
|
+
- 中风险票据通过本地 `patchwarden-confirm <full_assessment_id>` 确认。该入口不是 MCP
|
|
14
|
+
工具,远程调用者不能自行放行。
|
|
15
|
+
- 确认和执行会重新校验票据时效、工作区、计划、策略和工具 Manifest。
|
|
16
|
+
- `audit_task` 将已确认失败、可能误报和人工核查项分开呈现。
|
|
17
|
+
- `get_task_summary(view: "compact")` 和终态 `wait_for_task` 返回有界验收证据,避免长任务
|
|
18
|
+
结果淹没客户端上下文。
|
|
19
|
+
|
|
20
|
+
### ChatGPT Direct 模式
|
|
21
|
+
|
|
22
|
+
- 默认关闭;启用需设置 `enableDirectProfile: true` 并使用 `chatgpt_direct` Profile。
|
|
23
|
+
- 固定 9 工具:健康检查、工作区列表、会话创建、搜索、文件读取、JSON 补丁、白名单
|
|
24
|
+
验证、会话完成和独立审计。
|
|
25
|
+
- 所有写操作绑定 Direct session 和内容哈希;不支持任意 shell、删除/重命名、Git 推送、
|
|
26
|
+
npm 发布或远程部署。
|
|
27
|
+
|
|
28
|
+
### Windows Core/Direct 控制与可观测性
|
|
29
|
+
|
|
30
|
+
- 根目录统一使用 `PatchWarden.cmd`,支持 Core、Direct 或两者的
|
|
31
|
+
`start`、`stop`、`restart`、`status`、`health` 和受限 `kill`。
|
|
32
|
+
- Core 和 Direct 分别使用 `127.0.0.1:8080` 与 `127.0.0.1:8081`,并自动校正各自
|
|
33
|
+
tunnel profile 的 health 地址。
|
|
34
|
+
- supervisor 将 stdout/stderr 写入各自 runtime 目录;非零退出时显示 stderr 最后 30 行,
|
|
35
|
+
并把退出码、脱敏 tail 和日志路径写入 `tunnel-status.json`。
|
|
36
|
+
- 修复 Core watcher 的 `XDG_CONFIG_HOME` 泄漏到 tunnel-client,导致 Core 错误读取
|
|
37
|
+
`%LOCALAPPDATA%\patchwarden\opencode-config\tunnel-client\patchwarden.yaml` 并反复退出的问题。
|
|
38
|
+
- `stop` / `restart` 可清理 Profile 精确匹配的遗留 tunnel-client;无关端口占用只报告、不误杀。
|
|
39
|
+
- `status` 支持 health endpoint fallback,陈旧 JSON 不再把实际 ready 的实例误报为 stopped。
|
|
40
|
+
- 旧的单用途入口迁入 `scripts/launchers/`;个人 `.local/launchers/`、运行时配置、凭据和日志
|
|
41
|
+
不进入 Git、npm 包或 GitHub 发布资产。
|
|
42
|
+
|
|
43
|
+
## 兼容性
|
|
44
|
+
|
|
45
|
+
- `chatgpt_core` 仍为固定 16 工具;`chatgpt_direct` 为固定 9 工具。
|
|
46
|
+
- schema epoch 为 `2026-06-22-v6`。
|
|
47
|
+
- `assess_only`、`execute`、`assessment_id`、`plan_ref` 和既有任务记录保持兼容。
|
|
48
|
+
- npm 包名保持 `patchwarden`;旧的 `safe-bifrost` 包继续冻结在 v0.3.0。
|
|
49
|
+
|
|
50
|
+
## 验证
|
|
51
|
+
|
|
52
|
+
发布候选通过以下门禁:
|
|
53
|
+
|
|
54
|
+
```powershell
|
|
55
|
+
npm.cmd run build
|
|
56
|
+
npm.cmd test
|
|
57
|
+
npm.cmd run check:tool-manifest
|
|
58
|
+
npm.cmd run check:direct-tool-manifest
|
|
59
|
+
npm.cmd run test:mcp
|
|
60
|
+
npm.cmd run test:http-mcp
|
|
61
|
+
npm.cmd run doctor:ci
|
|
62
|
+
npm.cmd run pack:clean
|
|
63
|
+
npm.cmd run verify:package
|
|
64
|
+
git diff --check
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
本机双通道验收确认 Core 16 工具和 Direct 9 工具均为 `running`、`ready=true`,8080/8081
|
|
68
|
+
由对应 `tunnel-client.exe` 监听,两个 `/readyz` 均返回 `ready`。
|
|
69
|
+
|
|
70
|
+
GitHub Release 与 npm Registry 是独立发布面。创建 Tag/Release 不会自动发布 npm;npm 发布后
|
|
71
|
+
仍需单独核对 `patchwarden@latest`、版本时间、dist-tag 和包完整性。
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# PatchWarden v0.6.1 Release Notes
|
|
2
|
+
|
|
3
|
+
**Release Date**: 2026-06-25
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
PatchWarden v0.6.1 is a stability and observability release. It addresses watcher heartbeat false positives, adds Chinese path reliability, introduces two new tools (`safe_status` and `sync_file`), and adds comprehensive security guard unit tests.
|
|
8
|
+
|
|
9
|
+
## New Features
|
|
10
|
+
|
|
11
|
+
### safe_status Tool
|
|
12
|
+
|
|
13
|
+
A minimal task lifecycle status tool that returns task state without exposing diff, log content, or file contents. Useful when upper-layer security policies block content-bearing tools.
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"task_id": "task-001",
|
|
18
|
+
"status": "running",
|
|
19
|
+
"phase": "executing_agent",
|
|
20
|
+
"last_heartbeat_at": "2026-06-24T10:00:12Z",
|
|
21
|
+
"current_command": "codex exec",
|
|
22
|
+
"watcher_state": "healthy"
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### sync_file Tool
|
|
27
|
+
|
|
28
|
+
Copy a file from source to target within a Direct session repo. Both paths must be inside the session's `repo_path`. Supports optional sha256 verification for both source and target.
|
|
29
|
+
|
|
30
|
+
### Android Build Doctor
|
|
31
|
+
|
|
32
|
+
When a managed project contains an `android_app` directory, the health check now includes Android build environment diagnostics: Java version, JAVA_HOME, ANDROID_HOME, SDK platform, build-tools, Gradle wrapper, and APK output path.
|
|
33
|
+
|
|
34
|
+
## Bug Fixes
|
|
35
|
+
|
|
36
|
+
- **Watcher stale false positive**: Long-running tasks no longer cause the watcher to be incorrectly reported as stale. The watcher status now falls back to checking running task heartbeats.
|
|
37
|
+
- **Chinese path encoding**: All file I/O operations verified to use UTF-8 encoding. Health check now reports `path_encoding` status.
|
|
38
|
+
- **Tracked external dirty detection**: `captureRepoSnapshot` now correctly identifies tracked-but-modified external files via `dirty_paths` field parsed from `git status --porcelain`. The regex now includes `R` (rename) status, fixing dead code that previously never executed for renamed files.
|
|
39
|
+
- **Release zip path separators**: The packaging script (`pack-clean.js`) now creates zip archives with POSIX `/` path separators instead of Windows backslashes. Linux `unzip` no longer reports `warning: appears to use backslashes as path separators`.
|
|
40
|
+
|
|
41
|
+
## Security
|
|
42
|
+
|
|
43
|
+
- 136 unit tests covering all security guards (path guard, sensitive guard, command guard, direct guards)
|
|
44
|
+
- Tests use Node's built-in `node:test` — zero new dependencies
|
|
45
|
+
|
|
46
|
+
## Observability
|
|
47
|
+
|
|
48
|
+
- Structured JSON logging to stderr (never stdout, to avoid polluting MCP JSON-RPC)
|
|
49
|
+
- Tool call audit logs with duration tracking
|
|
50
|
+
- Global `unhandledRejection` and `uncaughtException` handlers
|
|
51
|
+
|
|
52
|
+
## Tool Profile Changes
|
|
53
|
+
|
|
54
|
+
| Profile | v0.6.0 | v0.6.1 |
|
|
55
|
+
|---------|--------|--------|
|
|
56
|
+
| full | 28 | 30 |
|
|
57
|
+
| chatgpt_core | 16 | 17 |
|
|
58
|
+
| chatgpt_direct | 9 | 10 |
|
|
59
|
+
|
|
60
|
+
## Migration
|
|
61
|
+
|
|
62
|
+
No breaking changes. All new fields are optional. New tools are additive and do not affect existing tools.
|
|
63
|
+
|
|
64
|
+
## Verification
|
|
65
|
+
|
|
66
|
+
- TypeScript compilation: PASS
|
|
67
|
+
- Unit tests: 136 pass, 0 fail, 1 skipped (Windows symlink)
|
|
68
|
+
- Smoke tests: 139 pass, 0 fail
|
|
69
|
+
- Lifecycle tests: 22 pass, 0 fail (includes tracked external file rename regression test)
|
|
70
|
+
- Doctor CI: PASS (81 OK, 0 WARN, 0 FAIL on the release check host)
|
|
71
|
+
- Tool manifest check: PASS
|
|
72
|
+
- Package manifest check: PASS (260 files)
|
|
73
|
+
- Brand check: PASS (128 tracked files)
|
|
74
|
+
- MCP HTTP tests: 13 pass, 0 fail
|
|
75
|
+
- Release zip: POSIX path separators verified (261 entries, no backslashes)
|
|
@@ -17,21 +17,28 @@ or local private paths.
|
|
|
17
17
|
2. Run `npm.cmd run build`.
|
|
18
18
|
3. Use `scripts/patchwarden-mcp-stdio.cmd` as the tunnel MCP command.
|
|
19
19
|
4. Start `npm.cmd run watch` in a separate terminal.
|
|
20
|
-
5. Start `tunnel-client run` or use `
|
|
20
|
+
5. Start `tunnel-client run` or use `PatchWarden.cmd start core`.
|
|
21
21
|
6. Create a ChatGPT Connector using the tunnel channel.
|
|
22
22
|
7. After a tunnel/schema refresh, reconnect the Connector and validate from a
|
|
23
23
|
new ChatGPT conversation; an already-open conversation may retain its older
|
|
24
24
|
tool catalog.
|
|
25
25
|
|
|
26
|
+
For ChatGPT Direct editing, set `enableDirectProfile: true` in the trusted
|
|
27
|
+
local config and run `PatchWarden.cmd start direct`. It uses
|
|
28
|
+
`scripts/patchwarden-mcp-direct.cmd`, the separate `patchwarden-direct` Tunnel
|
|
29
|
+
Client profile, no Watcher, and an isolated `runtime-direct` status directory.
|
|
30
|
+
Use a separate Direct Connector/Tunnel ID so the fixed 16-tool Core catalog and
|
|
31
|
+
the 9-tool Direct catalog never overwrite each other's cached schema.
|
|
32
|
+
|
|
26
33
|
The Windows launcher prompts for the runtime API key once and stores only a
|
|
27
34
|
Windows DPAPI-encrypted value under `%APPDATA%\patchwarden`. Use
|
|
28
|
-
`
|
|
35
|
+
`PatchWarden.cmd reset-key` to remove the saved credential.
|
|
29
36
|
|
|
30
37
|
Before the launcher starts the tunnel it performs a real MCP stdio handshake
|
|
31
38
|
and requires the exact `chatgpt_core` manifest. Run
|
|
32
|
-
`
|
|
39
|
+
`PatchWarden.cmd health` to see the version, profile, tool names, schema
|
|
33
40
|
hash, process sources, and any mixed-version warnings. The check is read-only.
|
|
34
|
-
The v0.4.
|
|
41
|
+
The v0.4.1 core manifest contains 16 tools. A different count or schema hash
|
|
35
42
|
requires a Connector refresh and validation from a new ChatGPT conversation.
|
|
36
43
|
|
|
37
44
|
## Architecture
|
|
@@ -9,7 +9,7 @@ Use the patchwarden connector tools to verify the workflow.
|
|
|
9
9
|
Step 1:
|
|
10
10
|
Call health_check and list_agents. Continue only if the watcher and selected
|
|
11
11
|
agent are available. Then call list_workspace for the configured workspace.
|
|
12
|
-
Confirm schema_epoch is 2026-06-
|
|
12
|
+
Confirm schema_epoch is 2026-06-22-v6, tool_profile is chatgpt_core, and
|
|
13
13
|
tool_count is 16. If the watcher is stale or missing, stop the task flow and
|
|
14
14
|
report the health_check recovery instruction.
|
|
15
15
|
|
|
@@ -17,30 +17,33 @@ Step 2:
|
|
|
17
17
|
Call read_workspace_file for README.md.
|
|
18
18
|
|
|
19
19
|
Step 3:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
title: Add Usage Section
|
|
23
|
-
|
|
24
|
-
content:
|
|
20
|
+
Prefer the feature_small template for this bounded change. Use goal:
|
|
25
21
|
Add a "## Usage" section to the end of README.md. The section should include
|
|
26
22
|
one sentence: "This repository was updated through PatchWarden."
|
|
27
23
|
Do not modify other files. After the change, run the configured test command.
|
|
28
24
|
|
|
29
25
|
Step 4:
|
|
30
|
-
Call create_task with
|
|
31
|
-
|
|
32
|
-
timeout_seconds
|
|
26
|
+
Call create_task with execution_mode "assess_only", template "feature_small",
|
|
27
|
+
the goal above, agent "opencode", an explicit repo_path, verify_commands
|
|
28
|
+
["npm test"], and timeout_seconds 600. If the decision is allow, invoke the
|
|
29
|
+
returned next_tool_call exactly as provided. Do not resend the goal, plan,
|
|
30
|
+
repository, agent, or verification arguments. If needs_confirm, stop and ask
|
|
31
|
+
the user to run the returned local patchwarden-confirm command. Never execute
|
|
32
|
+
a blocked assessment.
|
|
33
33
|
|
|
34
34
|
Step 5:
|
|
35
|
-
|
|
36
|
-
continuation_required: true, call
|
|
37
|
-
|
|
35
|
+
For this short task, call wait_for_task with timeout_seconds 25. If it returns
|
|
36
|
+
continuation_required: true, call it again in this same assistant turn. For a
|
|
37
|
+
long task, prefer list_tasks(repo_path=..., active_only=true) and
|
|
38
|
+
get_task_status instead of an extended wait loop.
|
|
38
39
|
If execution_blocked is true or next_tool_call is health_check, do not keep
|
|
39
40
|
polling; report the watcher recovery requirement.
|
|
40
41
|
|
|
41
42
|
Step 6:
|
|
42
|
-
When terminal is true, review the included summary, call
|
|
43
|
-
|
|
43
|
+
When terminal is true, review the included compact summary, call
|
|
44
|
+
get_task_summary with view "compact", and call audit_task. Request the
|
|
45
|
+
standard summary, diff, test log, or result JSON only when compact evidence is
|
|
46
|
+
insufficient.
|
|
44
47
|
|
|
45
48
|
Step 7:
|
|
46
49
|
Summarize:
|
|
@@ -49,5 +52,6 @@ Summarize:
|
|
|
49
52
|
- whether tests passed
|
|
50
53
|
- whether any out-of-scope changes were detected
|
|
51
54
|
- acceptance_status and audit verdict
|
|
55
|
+
- confirmed_failures, possible_false_positives, and manual verification items
|
|
52
56
|
- whether the diff is acceptable
|
|
53
57
|
```
|
|
@@ -14,6 +14,14 @@
|
|
|
14
14
|
# --tunnel-id tunnel_xxx \
|
|
15
15
|
# --mcp-command "C:/path/to/patchwarden/scripts/patchwarden-mcp-stdio.cmd"
|
|
16
16
|
|
|
17
|
+
# Direct mode uses a separate profile and command:
|
|
18
|
+
#
|
|
19
|
+
# tunnel-client init \
|
|
20
|
+
# --sample sample_mcp_stdio_local \
|
|
21
|
+
# --profile patchwarden-direct \
|
|
22
|
+
# --tunnel-id tunnel_xxx \
|
|
23
|
+
# --mcp-command "C:/path/to/patchwarden/scripts/patchwarden-mcp-direct.cmd"
|
|
24
|
+
|
|
17
25
|
mcp:
|
|
18
26
|
commands:
|
|
19
27
|
- channel: main
|
package/package.json
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "patchwarden",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "A secure local MCP bridge: clients save plans, local agents execute tasks, and results are returned through workspace-scoped files.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"bin": {
|
|
9
9
|
"patchwarden": "dist/index.js",
|
|
10
|
+
"patchwarden-confirm": "dist/assessments/confirmCli.js",
|
|
10
11
|
"patchwarden-runner": "dist/runner/cli.js"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"dist/",
|
|
14
15
|
"docs/",
|
|
16
|
+
"!docs/optimization-proposal.md",
|
|
15
17
|
"src/",
|
|
16
18
|
"examples/",
|
|
17
19
|
"scripts/",
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"Reset-PatchWarden-Tunnel-Key.cmd",
|
|
20
|
+
"!scripts/kill-patchwarden.ps1",
|
|
21
|
+
"!scripts/launchers/Kill-PatchWarden.cmd",
|
|
22
|
+
"PatchWarden.cmd",
|
|
22
23
|
"README.md",
|
|
24
|
+
"README.en.md",
|
|
23
25
|
"LICENSE",
|
|
24
26
|
"tsconfig.json"
|
|
25
27
|
],
|
|
@@ -31,25 +33,28 @@
|
|
|
31
33
|
"runner": "node dist/runner/cli.js",
|
|
32
34
|
"watch": "node dist/runner/watch.js",
|
|
33
35
|
"doctor": "node dist/doctor.js",
|
|
34
|
-
"
|
|
36
|
+
"doctor:ci": "node dist/doctor.js --allow-default-config",
|
|
37
|
+
"test:unit": "node scripts/unit-tests.js",
|
|
38
|
+
"test": "node dist/smoke-test.js && node scripts/unit-tests.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/control-smoke.js && node scripts/mcp-manifest-check.js && node scripts/brand-check.js",
|
|
35
39
|
"test:lifecycle": "npm run build && node scripts/lifecycle-smoke.js",
|
|
36
40
|
"test:doctor": "npm run build && node scripts/doctor-smoke.js",
|
|
37
41
|
"test:tunnel-supervisor": "npm run build && node scripts/tunnel-supervisor-smoke.js",
|
|
38
42
|
"test:watcher-supervisor": "npm run build && node scripts/watcher-supervisor-smoke.js",
|
|
39
43
|
"check:tool-manifest": "npm run build && node scripts/mcp-manifest-check.js",
|
|
44
|
+
"check:direct-tool-manifest": "npm run build && node scripts/mcp-manifest-check.js --profile chatgpt_direct",
|
|
40
45
|
"check:brand": "node scripts/brand-check.js",
|
|
41
46
|
"test:mcp": "npm run build && node scripts/mcp-smoke.js",
|
|
42
47
|
"test:http-mcp": "npm run build && node scripts/http-mcp-smoke.js",
|
|
43
48
|
"pretest": "npm run build",
|
|
44
49
|
"pack:clean": "npm run build && node scripts/pack-clean.js",
|
|
45
|
-
"verify:package": "npm run build &&
|
|
50
|
+
"verify:package": "npm run build && node scripts/package-manifest-check.js"
|
|
46
51
|
},
|
|
47
52
|
"dependencies": {
|
|
48
53
|
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
49
54
|
},
|
|
50
55
|
"devDependencies": {
|
|
51
56
|
"@types/node": "^20.0.0",
|
|
52
|
-
"typescript": "^
|
|
57
|
+
"typescript": "^6.0.3"
|
|
53
58
|
},
|
|
54
59
|
"engines": {
|
|
55
60
|
"node": ">=18.0.0"
|
package/scripts/brand-check.js
CHANGED
|
@@ -1,25 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
4
|
+
import { join, relative } from "node:path";
|
|
4
5
|
|
|
5
6
|
const allowedLegacyFiles = new Set([
|
|
6
7
|
".gitignore",
|
|
7
8
|
".npmignore",
|
|
8
9
|
"README.md",
|
|
10
|
+
"README.en.md",
|
|
9
11
|
"docs/migration-from-safe-bifrost.md",
|
|
10
12
|
"docs/release-v0.3.0.md",
|
|
11
13
|
"docs/release-v0.4.0.md",
|
|
14
|
+
"docs/release-v0.6.0.md",
|
|
12
15
|
"scripts/brand-check.js",
|
|
13
16
|
"scripts/pack-clean.js",
|
|
14
17
|
]);
|
|
15
18
|
const legacyPattern = /safe-bifrost|Safe-Bifrost|SAFE_BIFROST|SafeBifrost|safe_bifrost/;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
|
|
20
|
+
const EXCLUDE_DIRS = new Set(["node_modules", ".npm-cache", "dist", "release", ".patchwarden", ".git", "logs", "tmp", "coverage", "build", "out", ".next"]);
|
|
21
|
+
|
|
22
|
+
let trackedFiles;
|
|
23
|
+
let inGit = false;
|
|
24
|
+
try {
|
|
25
|
+
trackedFiles = execFileSync(
|
|
26
|
+
"git",
|
|
27
|
+
["ls-files", "--cached", "--others", "--exclude-standard"],
|
|
28
|
+
{ encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
|
|
29
|
+
)
|
|
30
|
+
.split(/\r?\n/)
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
inGit = true;
|
|
33
|
+
} catch {
|
|
34
|
+
// Not a git repository — fall back to filesystem walk
|
|
35
|
+
console.warn("[brand-check] Not a Git repository; using filesystem walk.");
|
|
36
|
+
trackedFiles = walkFiles(".");
|
|
37
|
+
if (trackedFiles.length === 0) {
|
|
38
|
+
console.warn("[brand-check] WARNING: no files found in non-Git walk. Exiting cleanly.");
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
23
43
|
const failures = [];
|
|
24
44
|
|
|
25
45
|
for (const file of trackedFiles) {
|
|
@@ -29,9 +49,13 @@ for (const file of trackedFiles) {
|
|
|
29
49
|
failures.push(`${normalized}: legacy brand in path`);
|
|
30
50
|
continue;
|
|
31
51
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
52
|
+
try {
|
|
53
|
+
const content = readFileSync(file);
|
|
54
|
+
if (!content.includes(0) && legacyPattern.test(content.toString("utf-8"))) {
|
|
55
|
+
failures.push(`${normalized}: legacy brand in content`);
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// skip unreadable files
|
|
35
59
|
}
|
|
36
60
|
}
|
|
37
61
|
|
|
@@ -41,4 +65,26 @@ if (failures.length > 0) {
|
|
|
41
65
|
process.exit(1);
|
|
42
66
|
}
|
|
43
67
|
|
|
44
|
-
|
|
68
|
+
const label = inGit ? "tracked files checked" : "files scanned (non-Git fallback)";
|
|
69
|
+
console.log(`[brand-check] OK: ${trackedFiles.length} ${label}.`);
|
|
70
|
+
|
|
71
|
+
function walkFiles(root) {
|
|
72
|
+
const results = [];
|
|
73
|
+
const visit = (dir) => {
|
|
74
|
+
if (results.length > 10000) return;
|
|
75
|
+
let entries;
|
|
76
|
+
try { entries = readdirSync(dir, { withFileTypes: true }); } catch { return; }
|
|
77
|
+
for (const e of entries) {
|
|
78
|
+
if (results.length > 10000) break;
|
|
79
|
+
if (e.isDirectory()) {
|
|
80
|
+
if (EXCLUDE_DIRS.has(e.name)) continue;
|
|
81
|
+
visit(join(dir, e.name));
|
|
82
|
+
} else if (e.isFile()) {
|
|
83
|
+
const rel = relative(root, join(dir, e.name)).replace(/\\/g, "/");
|
|
84
|
+
results.push(rel);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
visit(root);
|
|
89
|
+
return results;
|
|
90
|
+
}
|