patchwarden 1.1.0 → 1.5.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.
- package/README.en.md +41 -7
- package/README.md +36 -9
- package/dist/controlCenter.js +197 -1
- package/dist/direct/directSessionStore.d.ts +2 -0
- package/dist/direct/directVerification.js +7 -0
- package/dist/doctor.js +1 -1
- package/dist/policy/projectPolicy.d.ts +55 -0
- package/dist/policy/projectPolicy.js +286 -0
- package/dist/smoke-test.js +8 -8
- package/dist/test/unit/evidence-pack.test.d.ts +1 -0
- package/dist/test/unit/evidence-pack.test.js +130 -0
- package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
- package/dist/test/unit/project-policy-release-mode.test.js +125 -0
- package/dist/test/unit/run-task-loop.test.d.ts +1 -0
- package/dist/test/unit/run-task-loop.test.js +380 -0
- package/dist/test/unit/schema-drift-check.test.js +10 -9
- package/dist/tools/evidencePack.d.ts +39 -0
- package/dist/tools/evidencePack.js +168 -0
- package/dist/tools/recommendAgentForTask.d.ts +19 -0
- package/dist/tools/recommendAgentForTask.js +56 -0
- package/dist/tools/registry.js +376 -2
- package/dist/tools/releaseMode.d.ts +50 -0
- package/dist/tools/releaseMode.js +370 -0
- package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
- package/dist/tools/runDirectVerificationBundle.js +64 -0
- package/dist/tools/runTaskLoop.d.ts +57 -0
- package/dist/tools/runTaskLoop.js +417 -0
- package/dist/tools/runVerification.d.ts +4 -0
- package/dist/tools/runVerification.js +4 -0
- package/dist/tools/safeViews.d.ts +6 -0
- package/dist/tools/safeViews.js +2 -0
- package/dist/tools/taskLineage.d.ts +91 -0
- package/dist/tools/taskLineage.js +175 -0
- package/dist/tools/toolCatalog.d.ts +2 -2
- package/dist/tools/toolCatalog.js +6 -0
- package/dist/tools/toolRegistry.js +110 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/docs/chatgpt-usage.md +31 -0
- package/docs/control-center/README.md +9 -0
- package/package.json +2 -2
- package/scripts/checks/control-center-smoke.js +87 -0
- package/scripts/checks/control-smoke.js +2 -2
- package/scripts/checks/mcp-manifest-check.js +12 -0
- package/scripts/checks/mcp-smoke.js +31 -7
- package/scripts/checks/watcher-supervisor-smoke.js +1 -1
- package/src/controlCenter.ts +198 -1
- package/src/direct/directSessionStore.ts +2 -0
- package/src/direct/directVerification.ts +7 -0
- package/src/doctor.ts +1 -1
- package/src/policy/projectPolicy.ts +344 -0
- package/src/smoke-test.ts +5 -5
- package/src/test/unit/evidence-pack.test.ts +142 -0
- package/src/test/unit/project-policy-release-mode.test.ts +156 -0
- package/src/test/unit/run-task-loop.test.ts +425 -0
- package/src/test/unit/schema-drift-check.test.ts +11 -9
- package/src/tools/evidencePack.ts +205 -0
- package/src/tools/listWorkspace.ts +71 -71
- package/src/tools/recommendAgentForTask.ts +79 -0
- package/src/tools/registry.ts +405 -2
- package/src/tools/releaseMode.ts +450 -0
- package/src/tools/runDirectVerificationBundle.ts +98 -0
- package/src/tools/runTaskLoop.ts +526 -0
- package/src/tools/runVerification.ts +8 -0
- package/src/tools/safeViews.ts +2 -0
- package/src/tools/taskLineage.ts +300 -0
- package/src/tools/toolCatalog.ts +6 -0
- package/src/tools/toolRegistry.ts +110 -0
- package/src/version.ts +2 -2
- package/ui/pages/dashboard.html +143 -2
package/README.en.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
|
|
11
|
-
Current source version: **v1.
|
|
11
|
+
Current source version: **v1.5.0**. See the
|
|
12
12
|
[CHANGELOG](CHANGELOG.md), [migration guide](docs/migration-from-safe-bifrost.md), and
|
|
13
13
|
[release checklist](docs/release-checklist.md). Verify GitHub Release / npm publication separately before release.
|
|
14
14
|
|
|
@@ -493,7 +493,7 @@ PatchWarden.cmd start core
|
|
|
493
493
|
The launcher:
|
|
494
494
|
|
|
495
495
|
- Builds `dist/index.js` if it is missing.
|
|
496
|
-
- Verifies v1.
|
|
496
|
+
- Verifies v1.5.0, the fixed 26-tool `chatgpt_core` catalog, and its schema
|
|
497
497
|
manifest.
|
|
498
498
|
- Reads or prompts for the Tunnel ID.
|
|
499
499
|
- Reads or prompts for the runtime API key.
|
|
@@ -930,10 +930,12 @@ names.
|
|
|
930
930
|
|
|
931
931
|
## MCP tools and profiles
|
|
932
932
|
|
|
933
|
-
`chatgpt_core` is the fixed
|
|
933
|
+
`chatgpt_core` is the fixed 26-tool profile used by the ChatGPT tunnel:
|
|
934
934
|
|
|
935
935
|
`health_check`, `list_agents`, `list_workspace`,
|
|
936
|
-
`read_workspace_file`, `save_plan`, `create_task`,
|
|
936
|
+
`read_workspace_file`, `save_plan`, `create_task`, `run_task_loop`,
|
|
937
|
+
`recommend_agent_for_task`, `get_task_lineage`, `export_task_evidence_pack`,
|
|
938
|
+
`get_project_policy`,
|
|
937
939
|
`wait_for_task`, `get_task_summary`, `get_diff`, `get_result`,
|
|
938
940
|
`get_result_json`, `get_test_log`, `get_task_status`, `list_tasks`,
|
|
939
941
|
`cancel_task`, `audit_task`, `safe_status`, `safe_result`, `safe_audit`, `safe_test_summary`, and `safe_diff_summary`.
|
|
@@ -942,6 +944,38 @@ names.
|
|
|
942
944
|
ChatGPT should request `view: "compact"` first; terminal `wait_for_task`
|
|
943
945
|
responses also embed compact acceptance evidence only.
|
|
944
946
|
|
|
947
|
+
`run_task_loop` is the v1.2 safe orchestration entrypoint. It only composes
|
|
948
|
+
existing `create_task`, `wait_for_task`, safe summary, and `audit_task`
|
|
949
|
+
behavior; it does not bypass the Watcher, command allowlist, workspace
|
|
950
|
+
confinement, or local confirmation boundaries. `get_task_lineage` reads the
|
|
951
|
+
bounded `.patchwarden/lineages/<lineage_id>/` summary without returning full
|
|
952
|
+
logs or diffs.
|
|
953
|
+
|
|
954
|
+
`get_project_policy` is the v1.3 read-only policy entrypoint. It returns the
|
|
955
|
+
bounded effective `.patchwarden/project-policy.json` policy and release
|
|
956
|
+
readiness summary without expanding command permissions. The v1.3 release mode
|
|
957
|
+
tools, `release_check`, `release_prepare`, `release_verify`, and
|
|
958
|
+
`release_cleanup`, are full-profile only and never perform publish, push, tag,
|
|
959
|
+
or GitHub Release writes. The Control Center dashboard also shows bounded
|
|
960
|
+
lineage, policy, and release status summaries without full logs, diffs, or
|
|
961
|
+
secret-bearing content.
|
|
962
|
+
|
|
963
|
+
v1.4 adds Direct-assisted loop verification. `run_task_loop` can opt into
|
|
964
|
+
`direct_verify=true`; after the guarded task and audit succeed, PatchWarden
|
|
965
|
+
creates a Direct session, runs only allowlisted verification commands, safe
|
|
966
|
+
finalizes, safe audits, and records bounded Direct evidence in lineage. It does
|
|
967
|
+
not call Direct patching tools, publish, push, tag, create releases, restart
|
|
968
|
+
watchers, or return full stdout/stderr/diffs.
|
|
969
|
+
|
|
970
|
+
v1.5 adds worktree-assisted loop isolation, bounded agent routing, and evidence
|
|
971
|
+
pack export. `run_task_loop(isolation_mode="worktree")` creates an isolated git
|
|
972
|
+
worktree for the task and records the worktree id, path, branch, and next action
|
|
973
|
+
in lineage. It does not auto-merge or auto-delete the worktree. `agent="auto"`
|
|
974
|
+
uses `recommend_agent_for_task` to select a configured agent before task
|
|
975
|
+
creation. `export_task_evidence_pack` writes bounded `evidence.json` and
|
|
976
|
+
`EVIDENCE.md` files under `.patchwarden/evidence-packs/<lineage_id>/` without
|
|
977
|
+
stdout/stderr tails, full diffs, verification logs, or sensitive file content.
|
|
978
|
+
|
|
945
979
|
`full` additionally provides:
|
|
946
980
|
|
|
947
981
|
- `get_plan`
|
|
@@ -956,9 +990,9 @@ to `full`.
|
|
|
956
990
|
|
|
957
991
|
### ChatGPT Direct mode
|
|
958
992
|
|
|
959
|
-
Direct mode exposes
|
|
993
|
+
Direct mode exposes fourteen guarded tools so ChatGPT can create an editing
|
|
960
994
|
session, read and search source files, apply hash-bound JSON patches, run
|
|
961
|
-
exactly allowlisted verification commands, finalize the evidence, and audit
|
|
995
|
+
exactly allowlisted verification commands or a bounded verification bundle, finalize the evidence, and audit
|
|
962
996
|
the result without a local execution agent.
|
|
963
997
|
|
|
964
998
|
Enable it in the trusted local configuration while keeping the ordinary Core
|
|
@@ -980,7 +1014,7 @@ On first use, provide the `tunnel-client.exe` path and a Tunnel ID dedicated
|
|
|
980
1014
|
to the Direct Connector. The launcher uses the `patchwarden-direct` profile,
|
|
981
1015
|
stores runtime state under `%LOCALAPPDATA%\patchwarden\runtime-direct`, skips
|
|
982
1016
|
the Watcher, and retains the existing DPAPI credential handling. In a fresh
|
|
983
|
-
ChatGPT conversation, `health_check` should report `chatgpt_direct`,
|
|
1017
|
+
ChatGPT conversation, `health_check` should report `chatgpt_direct`, fourteen
|
|
984
1018
|
tools, and `direct_profile_enabled=true`.
|
|
985
1019
|
|
|
986
1020
|
## Security boundaries and local data
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
|
|
11
|
-
当前源码版本:**v1.
|
|
11
|
+
当前源码版本:**v1.5.0**。查看
|
|
12
12
|
[CHANGELOG](CHANGELOG.md)、[迁移指南](docs/migration-from-safe-bifrost.md)和
|
|
13
13
|
[发布检查清单](docs/release-checklist.md)。GitHub Release / npm 发布状态需要在发布前单独核对。
|
|
14
14
|
|
|
@@ -399,7 +399,7 @@ PatchWarden.cmd start core
|
|
|
399
399
|
启动器会:
|
|
400
400
|
|
|
401
401
|
- 检查 `dist/index.js`,缺失时自动构建。
|
|
402
|
-
- 校验 `chatgpt_core` 的版本、
|
|
402
|
+
- 校验 `chatgpt_core` 的版本、26 个核心工具和 Schema Manifest。
|
|
403
403
|
- 读取或提示输入 Tunnel ID。
|
|
404
404
|
- 读取或提示输入运行时 API Key。
|
|
405
405
|
- 使用 Windows DPAPI 保存凭据到 `%APPDATA%\patchwarden`。
|
|
@@ -836,10 +836,11 @@ PatchWarden v0.4.0 不自动读取旧 CLI、旧环境变量、旧 Header、旧
|
|
|
836
836
|
|
|
837
837
|
## MCP 工具与 Profile
|
|
838
838
|
|
|
839
|
-
`chatgpt_core` 是固定的
|
|
839
|
+
`chatgpt_core` 是固定的 26 工具 Profile,适合 ChatGPT Tunnel:
|
|
840
840
|
|
|
841
841
|
`health_check`、`list_agents`、`list_workspace`、
|
|
842
|
-
`read_workspace_file`、`save_plan`、`create_task`、
|
|
842
|
+
`read_workspace_file`、`save_plan`、`create_task`、`run_task_loop`、`recommend_agent_for_task`、
|
|
843
|
+
`get_task_lineage`、`export_task_evidence_pack`、`get_project_policy`、
|
|
843
844
|
`wait_for_task`、`get_task_summary`、`get_diff`、`get_result`、
|
|
844
845
|
`get_result_json`、`get_test_log`、`get_task_status`、`list_tasks`、
|
|
845
846
|
`cancel_task`、`audit_task`、`safe_status`、`safe_result`、`safe_audit`、`safe_test_summary`、`safe_diff_summary`。
|
|
@@ -847,7 +848,33 @@ PatchWarden v0.4.0 不自动读取旧 CLI、旧环境变量、旧 Header、旧
|
|
|
847
848
|
`get_task_summary` 默认保留兼容的 `standard` 视图;ChatGPT 应优先使用
|
|
848
849
|
`view: "compact"`,终态 `wait_for_task` 也只内嵌 compact 验收证据。
|
|
849
850
|
|
|
850
|
-
`
|
|
851
|
+
`run_task_loop` 是 v1.2 的安全编排入口:它只组合现有 `create_task`、`wait_for_task`、safe summary 和
|
|
852
|
+
`audit_task`,不会绕过 Watcher、命令白名单、workspace confinement 或确认边界。`get_task_lineage`
|
|
853
|
+
读取 `.patchwarden/lineages/<lineage_id>/` 中的有界链路摘要,不返回完整日志或 diff。
|
|
854
|
+
|
|
855
|
+
v1.4 adds Direct-assisted loop verification. `run_task_loop(direct_verify=true)`
|
|
856
|
+
will create a Direct session only after the watcher-driven task and normal audit
|
|
857
|
+
succeed, run allowlisted Direct verification commands, safe-finalize, safe-audit,
|
|
858
|
+
and write bounded Direct evidence into lineage. It does not call Direct patching
|
|
859
|
+
tools, publish, push, tag, create releases, restart live services, or return full
|
|
860
|
+
stdout/stderr/diffs.
|
|
861
|
+
|
|
862
|
+
v1.5 adds worktree-assisted loop isolation, bounded agent routing, and evidence
|
|
863
|
+
pack export. `run_task_loop(isolation_mode="worktree")` creates an isolated git
|
|
864
|
+
worktree for the task and records the worktree id, path, branch, and next action
|
|
865
|
+
in lineage. It does not auto-merge or auto-delete the worktree. `agent="auto"`
|
|
866
|
+
uses `recommend_agent_for_task` to select a configured agent before task
|
|
867
|
+
creation. `export_task_evidence_pack` writes bounded `evidence.json` and
|
|
868
|
+
`EVIDENCE.md` files under `.patchwarden/evidence-packs/<lineage_id>/` without
|
|
869
|
+
stdout/stderr tails, full diffs, verification logs, or sensitive file content.
|
|
870
|
+
|
|
871
|
+
`get_project_policy` 是 v1.3 的只读策略入口,返回 `.patchwarden/project-policy.json`
|
|
872
|
+
的有界 effective policy 与 release readiness,不会扩大命令白名单。v1.3 的
|
|
873
|
+
`release_check`、`release_prepare`、`release_verify`、`release_cleanup` 仅在 `full`
|
|
874
|
+
Profile 中可见,不执行 publish、push、tag 或 GitHub Release 写操作。Control Center
|
|
875
|
+
Dashboard 会展示 lineage、policy 和 release status 的有界摘要,不返回完整日志、diff 或密钥内容。
|
|
876
|
+
|
|
877
|
+
`full` 提供完整 64 工具本地开发目录,包含核心工具、管理工具和 Direct
|
|
851
878
|
工具。除 `chatgpt_core` 外,常用额外管理工具包括:
|
|
852
879
|
|
|
853
880
|
- `get_plan`
|
|
@@ -857,15 +884,15 @@ PatchWarden v0.4.0 不自动读取旧 CLI、旧环境变量、旧 Header、旧
|
|
|
857
884
|
- `get_task_stdout_tail`
|
|
858
885
|
- `get_task_log_tail`
|
|
859
886
|
|
|
860
|
-
`chatgpt_direct` 是 v0.6.0 新增的 Direct 直接开发 Profile,v1.
|
|
887
|
+
`chatgpt_direct` 是 v0.6.0 新增的 Direct 直接开发 Profile,v1.4 包含 14 个工具:
|
|
861
888
|
|
|
862
889
|
`health_check`、`list_workspace`、`create_direct_session`、
|
|
863
890
|
`search_workspace`、`read_workspace_file`、`apply_patch`、
|
|
864
|
-
`run_verification`、`finalize_direct_session`、`audit_session`、`safe_direct_summary`、`safe_finalize_direct_session`、`safe_audit_direct_session`、`sync_file`。
|
|
891
|
+
`run_verification`、`run_direct_verification_bundle`、`finalize_direct_session`、`audit_session`、`safe_direct_summary`、`safe_finalize_direct_session`、`safe_audit_direct_session`、`sync_file`。
|
|
865
892
|
|
|
866
893
|
`chatgpt_direct` 默认关闭,需要通过 `enableDirectProfile: true` 或
|
|
867
894
|
`PATCHWARDEN_TOOL_PROFILE=chatgpt_direct` 显式启用。`chatgpt_core` 保持
|
|
868
|
-
|
|
895
|
+
26 工具清单。
|
|
869
896
|
|
|
870
897
|
Tunnel 包装脚本会强制使用 `chatgpt_core`;普通本地开发默认使用 `full`。
|
|
871
898
|
|
|
@@ -920,7 +947,7 @@ Tunnel ID。启动器使用 `patchwarden-direct` Profile、独立的
|
|
|
920
947
|
Tunnel API Key 仍通过现有 Windows DPAPI 缓存处理,不会写入仓库。
|
|
921
948
|
|
|
922
949
|
连接 ChatGPT 后新建对话并先调用 `health_check`。预期
|
|
923
|
-
`tool_profile=chatgpt_direct`、`tool_count=
|
|
950
|
+
`tool_profile=chatgpt_direct`、`tool_count=14`、
|
|
924
951
|
`direct_profile_enabled=true`。已有对话可能缓存旧工具清单,需要重新连接
|
|
925
952
|
Connector 后再新建对话。
|
|
926
953
|
|
package/dist/controlCenter.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Port override: PATCHWARDEN_CONTROL_PORT=<n> or --port <n>
|
|
13
13
|
*/
|
|
14
14
|
import { createServer, get as httpGet } from "node:http";
|
|
15
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
16
|
import { delimiter, dirname, extname, isAbsolute, join, relative, resolve } from "node:path";
|
|
17
17
|
import { fileURLToPath } from "node:url";
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
@@ -25,6 +25,10 @@ import { readWatcherStatus } from "./watcherStatus.js";
|
|
|
25
25
|
import { redactSensitiveContent } from "./security/contentRedaction.js";
|
|
26
26
|
import { guardWorkspacePath } from "./security/pathGuard.js";
|
|
27
27
|
import { auditTask } from "./tools/auditTask.js";
|
|
28
|
+
import { getProjectPolicySummary } from "./policy/projectPolicy.js";
|
|
29
|
+
import { safeDirectSummary } from "./tools/safeViews.js";
|
|
30
|
+
import { toSafeTaskLineage } from "./tools/taskLineage.js";
|
|
31
|
+
import { listEvidencePacks, readEvidencePack } from "./tools/evidencePack.js";
|
|
28
32
|
import { PATCHWARDEN_VERSION, TOOL_SCHEMA_EPOCH } from "./version.js";
|
|
29
33
|
// ── Paths ─────────────────────────────────────────────────────────
|
|
30
34
|
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
@@ -231,6 +235,26 @@ function readJsonFileSafe(filePath) {
|
|
|
231
235
|
return null;
|
|
232
236
|
}
|
|
233
237
|
}
|
|
238
|
+
function isPathInside(root, target) {
|
|
239
|
+
const rel = relative(root, target);
|
|
240
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
241
|
+
}
|
|
242
|
+
function readJsonFileSafeUnder(root, relPath) {
|
|
243
|
+
const base = resolve(root);
|
|
244
|
+
const target = resolve(base, relPath);
|
|
245
|
+
if (!isPathInside(base, target) || !existsSync(target))
|
|
246
|
+
return null;
|
|
247
|
+
try {
|
|
248
|
+
const realBase = realpathSync(base);
|
|
249
|
+
const realTarget = realpathSync(target);
|
|
250
|
+
if (!isPathInside(realBase, realTarget))
|
|
251
|
+
return null;
|
|
252
|
+
return readJsonFileSafe(realTarget);
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
234
258
|
function readTextFileSafe(filePath) {
|
|
235
259
|
if (!existsSync(filePath))
|
|
236
260
|
return null;
|
|
@@ -902,6 +926,105 @@ function handleStaleTasks(res) {
|
|
|
902
926
|
sendJson(res, 200, { stale_tasks: [], total: 0, reason: errorMessage(err) });
|
|
903
927
|
}
|
|
904
928
|
}
|
|
929
|
+
function handleLineages(res) {
|
|
930
|
+
try {
|
|
931
|
+
const root = join(config.workspaceRoot, ".patchwarden", "lineages");
|
|
932
|
+
if (!existsSync(root)) {
|
|
933
|
+
sendJson(res, 200, { lineages: [], total: 0, reason: null });
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const lineages = readdirSync(root, { withFileTypes: true })
|
|
937
|
+
.filter((entry) => entry.isDirectory())
|
|
938
|
+
.map((entry) => readJsonFileSafeUnder(root, join(entry.name, "lineage.json")))
|
|
939
|
+
.filter((entry) => entry !== null)
|
|
940
|
+
.map((entry) => toSafeTaskLineage(entry, 6))
|
|
941
|
+
.sort((a, b) => b.updated_at.localeCompare(a.updated_at))
|
|
942
|
+
.slice(0, 50);
|
|
943
|
+
sendJson(res, 200, { lineages, total: lineages.length, reason: null });
|
|
944
|
+
}
|
|
945
|
+
catch (err) {
|
|
946
|
+
sendJson(res, 200, { lineages: [], total: 0, reason: errorMessage(err) });
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function handleLineageDetail(res, lineageId) {
|
|
950
|
+
try {
|
|
951
|
+
if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
|
|
952
|
+
sendJson(res, 400, { error: "Invalid lineage id" });
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
const data = readJsonFileSafeUnder(join(config.workspaceRoot, ".patchwarden", "lineages"), join(lineageId, "lineage.json"));
|
|
956
|
+
if (!data) {
|
|
957
|
+
sendJson(res, 404, { error: "Lineage not found" });
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
sendJson(res, 200, toSafeTaskLineage(data, 20));
|
|
961
|
+
}
|
|
962
|
+
catch (err) {
|
|
963
|
+
sendJson(res, 200, { lineage_id: lineageId, error: errorMessage(err) });
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function handleProjectPolicy(res, repoPath) {
|
|
967
|
+
try {
|
|
968
|
+
sendJson(res, 200, getProjectPolicySummary(repoPath || "."));
|
|
969
|
+
}
|
|
970
|
+
catch (err) {
|
|
971
|
+
sendJson(res, 200, {
|
|
972
|
+
repo_path: repoPath || ".",
|
|
973
|
+
valid: false,
|
|
974
|
+
issues: [{ code: "policy_unavailable", severity: "error", field: "repo_path", message: errorMessage(err) }],
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function handleReleaseStatus(res, repoPath) {
|
|
979
|
+
try {
|
|
980
|
+
const policy = getProjectPolicySummary(repoPath || ".");
|
|
981
|
+
sendJson(res, 200, {
|
|
982
|
+
repo_path: repoPath || ".",
|
|
983
|
+
resolved_repo_path: policy.resolved_repo_path,
|
|
984
|
+
policy_valid: policy.valid,
|
|
985
|
+
policy_issue_count: policy.issues.length,
|
|
986
|
+
policy_issues: policy.issues.slice(0, 10),
|
|
987
|
+
release_readiness: policy.release_readiness,
|
|
988
|
+
next_action: policy.valid && policy.release_readiness.version_consistent !== false
|
|
989
|
+
? "Run release_check or release_prepare from the full MCP profile when ready."
|
|
990
|
+
: "Fix project-policy or version consistency issues before release preparation.",
|
|
991
|
+
remote_write_performed: false,
|
|
992
|
+
});
|
|
993
|
+
}
|
|
994
|
+
catch (err) {
|
|
995
|
+
sendJson(res, 200, {
|
|
996
|
+
repo_path: repoPath || ".",
|
|
997
|
+
policy_valid: false,
|
|
998
|
+
error: errorMessage(err),
|
|
999
|
+
remote_write_performed: false,
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function handleEvidencePacks(res) {
|
|
1004
|
+
try {
|
|
1005
|
+
sendJson(res, 200, listEvidencePacks({ max_items: 50 }));
|
|
1006
|
+
}
|
|
1007
|
+
catch (err) {
|
|
1008
|
+
sendJson(res, 200, { evidence_packs: [], total: 0, reason: errorMessage(err) });
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function handleEvidencePackDetail(res, lineageId) {
|
|
1012
|
+
try {
|
|
1013
|
+
if (!/^[A-Za-z0-9_-]+$/.test(lineageId)) {
|
|
1014
|
+
sendJson(res, 400, { error: "Invalid lineage id" });
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const pack = readEvidencePack(lineageId);
|
|
1018
|
+
if (!pack) {
|
|
1019
|
+
sendJson(res, 404, { error: "Evidence pack not found" });
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
sendJson(res, 200, pack);
|
|
1023
|
+
}
|
|
1024
|
+
catch (err) {
|
|
1025
|
+
sendJson(res, 200, { lineage_id: lineageId, error: errorMessage(err), bounded: true });
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
905
1028
|
/**
|
|
906
1029
|
* Reconcile a stale task. Does NOT delete the task. Reads the task files,
|
|
907
1030
|
* decides whether it is safe to mark the task as stale/archived, writes a
|
|
@@ -1498,6 +1621,27 @@ function handleDirectSessionDetail(res, sessionId) {
|
|
|
1498
1621
|
sendJson(res, 200, { session_id: sessionId, error: errorMessage(err) });
|
|
1499
1622
|
}
|
|
1500
1623
|
}
|
|
1624
|
+
function handleDirectSessionSafeSummary(res, sessionId) {
|
|
1625
|
+
try {
|
|
1626
|
+
if (sessionId === "." ||
|
|
1627
|
+
sessionId === ".." ||
|
|
1628
|
+
sessionId.includes("/") ||
|
|
1629
|
+
sessionId.includes("\\") ||
|
|
1630
|
+
sessionId.includes("\0")) {
|
|
1631
|
+
sendJson(res, 400, { error: "Invalid session id" });
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
sendJson(res, 200, safeDirectSummary(sessionId, { max_items: 12 }));
|
|
1635
|
+
}
|
|
1636
|
+
catch (err) {
|
|
1637
|
+
sendJson(res, 200, {
|
|
1638
|
+
session_id: sessionId,
|
|
1639
|
+
error: errorMessage(err),
|
|
1640
|
+
large_logs_omitted: true,
|
|
1641
|
+
diff_omitted: true,
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1501
1645
|
function parseReviewVerdict(content) {
|
|
1502
1646
|
// independent-review.md format: "**Verdict**: PASS" (case-insensitive)
|
|
1503
1647
|
const m = content.match(/\*\*Verdict\*\*\s*:\s*([A-Za-z]+)/);
|
|
@@ -1787,6 +1931,46 @@ async function handleRequest(req, res) {
|
|
|
1787
1931
|
handleStaleTasks(res);
|
|
1788
1932
|
return;
|
|
1789
1933
|
}
|
|
1934
|
+
if (method === "GET" && pathname === "/api/lineages") {
|
|
1935
|
+
handleLineages(res);
|
|
1936
|
+
return;
|
|
1937
|
+
}
|
|
1938
|
+
const lineageMatch = pathname.match(/^\/api\/lineages\/([^/]+)$/);
|
|
1939
|
+
if (method === "GET" && lineageMatch) {
|
|
1940
|
+
let lineageId;
|
|
1941
|
+
try {
|
|
1942
|
+
lineageId = decodeURIComponent(lineageMatch[1]);
|
|
1943
|
+
}
|
|
1944
|
+
catch {
|
|
1945
|
+
lineageId = lineageMatch[1];
|
|
1946
|
+
}
|
|
1947
|
+
handleLineageDetail(res, lineageId);
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
if (method === "GET" && pathname === "/api/project-policy") {
|
|
1951
|
+
handleProjectPolicy(res, parsedUrl.searchParams.get("repo_path") || ".");
|
|
1952
|
+
return;
|
|
1953
|
+
}
|
|
1954
|
+
if (method === "GET" && pathname === "/api/release/status") {
|
|
1955
|
+
handleReleaseStatus(res, parsedUrl.searchParams.get("repo_path") || ".");
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
if (method === "GET" && pathname === "/api/evidence-packs") {
|
|
1959
|
+
handleEvidencePacks(res);
|
|
1960
|
+
return;
|
|
1961
|
+
}
|
|
1962
|
+
const evidencePackMatch = pathname.match(/^\/api\/evidence-packs\/([^/]+)$/);
|
|
1963
|
+
if (method === "GET" && evidencePackMatch) {
|
|
1964
|
+
let lineageId;
|
|
1965
|
+
try {
|
|
1966
|
+
lineageId = decodeURIComponent(evidencePackMatch[1]);
|
|
1967
|
+
}
|
|
1968
|
+
catch {
|
|
1969
|
+
lineageId = evidencePackMatch[1];
|
|
1970
|
+
}
|
|
1971
|
+
handleEvidencePackDetail(res, lineageId);
|
|
1972
|
+
return;
|
|
1973
|
+
}
|
|
1790
1974
|
const taskMatch = pathname.match(/^\/api\/tasks\/([^/]+)$/);
|
|
1791
1975
|
if (method === "GET" && taskMatch) {
|
|
1792
1976
|
let taskId;
|
|
@@ -1845,6 +2029,18 @@ async function handleRequest(req, res) {
|
|
|
1845
2029
|
handleDirectSessions(res);
|
|
1846
2030
|
return;
|
|
1847
2031
|
}
|
|
2032
|
+
const directSessionSummaryMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)\/summary$/);
|
|
2033
|
+
if (method === "GET" && directSessionSummaryMatch) {
|
|
2034
|
+
let sessionId;
|
|
2035
|
+
try {
|
|
2036
|
+
sessionId = decodeURIComponent(directSessionSummaryMatch[1]);
|
|
2037
|
+
}
|
|
2038
|
+
catch {
|
|
2039
|
+
sessionId = directSessionSummaryMatch[1];
|
|
2040
|
+
}
|
|
2041
|
+
handleDirectSessionSafeSummary(res, sessionId);
|
|
2042
|
+
return;
|
|
2043
|
+
}
|
|
1848
2044
|
const directSessionMatch = pathname.match(/^\/api\/direct-sessions\/([^/]+)$/);
|
|
1849
2045
|
if (method === "GET" && directSessionMatch) {
|
|
1850
2046
|
let sessionId;
|
|
@@ -40,6 +40,13 @@ export async function runDirectVerification(input) {
|
|
|
40
40
|
exit_code: result.exitCode,
|
|
41
41
|
passed,
|
|
42
42
|
timed_out: result.timedOut,
|
|
43
|
+
redacted: stdoutRedacted.redacted || stderrRedacted.redacted,
|
|
44
|
+
redaction_categories: [
|
|
45
|
+
...new Set([
|
|
46
|
+
...stdoutRedacted.redaction_categories,
|
|
47
|
+
...stderrRedacted.redaction_categories,
|
|
48
|
+
]),
|
|
49
|
+
],
|
|
43
50
|
stdout_tail: stdoutTail,
|
|
44
51
|
stderr_tail: stderrTail,
|
|
45
52
|
started_at: startedAt,
|
package/dist/doctor.js
CHANGED
|
@@ -221,7 +221,7 @@ async function main() {
|
|
|
221
221
|
const coreTools = selectToolsForProfile(fullTools, "chatgpt_core", config?.enableDirectProfile);
|
|
222
222
|
const createSchema = coreTools.find((tool) => tool.name === "create_task")?.inputSchema;
|
|
223
223
|
const waitSchema = coreTools.find((tool) => tool.name === "wait_for_task")?.inputSchema;
|
|
224
|
-
check("Full tool profile exposes
|
|
224
|
+
check("Full tool profile exposes 64 tools", fullTools.length === 64, `${fullTools.length} tools`);
|
|
225
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
226
|
check("Core task schemas expose inline_plan, verify_commands, and wait aliases", Boolean(createSchema?.properties?.inline_plan &&
|
|
227
227
|
createSchema?.properties?.verify_commands &&
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export interface ProjectPolicy {
|
|
2
|
+
allowed_commands: string[];
|
|
3
|
+
auto_cleanup: {
|
|
4
|
+
enabled: boolean;
|
|
5
|
+
patterns: string[];
|
|
6
|
+
exclude: string[];
|
|
7
|
+
};
|
|
8
|
+
high_risk_commands: string[];
|
|
9
|
+
protected_paths: string[];
|
|
10
|
+
release_mode: {
|
|
11
|
+
version_source: string;
|
|
12
|
+
required_commands: string[];
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export interface ProjectPolicyIssue {
|
|
16
|
+
code: string;
|
|
17
|
+
severity: "error" | "warn";
|
|
18
|
+
field: string;
|
|
19
|
+
message: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ReleaseReadinessSummary {
|
|
22
|
+
version_source: string;
|
|
23
|
+
version: string | null;
|
|
24
|
+
package_json_version: string | null;
|
|
25
|
+
package_name: string | null;
|
|
26
|
+
version_consistent: boolean | null;
|
|
27
|
+
required_commands: Array<{
|
|
28
|
+
command: string;
|
|
29
|
+
allowed: boolean;
|
|
30
|
+
reason: string | null;
|
|
31
|
+
}>;
|
|
32
|
+
}
|
|
33
|
+
export interface ProjectPolicySummary {
|
|
34
|
+
repo_path: string;
|
|
35
|
+
resolved_repo_path: string;
|
|
36
|
+
policy_path: string;
|
|
37
|
+
exists: boolean;
|
|
38
|
+
valid: boolean;
|
|
39
|
+
effective_policy: ProjectPolicy;
|
|
40
|
+
issues: ProjectPolicyIssue[];
|
|
41
|
+
release_readiness: ReleaseReadinessSummary;
|
|
42
|
+
}
|
|
43
|
+
export declare function getProjectPolicySummary(repoPathInput: string): ProjectPolicySummary;
|
|
44
|
+
export declare function getDefaultProjectPolicy(): ProjectPolicy;
|
|
45
|
+
export declare function commandAllowedByProjectPolicy(command: string, summary: ProjectPolicySummary): {
|
|
46
|
+
allowed: boolean;
|
|
47
|
+
reason: string | null;
|
|
48
|
+
};
|
|
49
|
+
export declare function isProtectedByProjectPolicy(relPath: string, policy: ProjectPolicy): boolean;
|
|
50
|
+
export declare function resolveVersionFromPolicy(repoPath: string, policy: ProjectPolicy): string | null;
|
|
51
|
+
export declare function readPackageJson(repoPath: string): {
|
|
52
|
+
name: string | null;
|
|
53
|
+
version: string | null;
|
|
54
|
+
githubRepo: string | null;
|
|
55
|
+
};
|