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.
Files changed (70) hide show
  1. package/README.en.md +41 -7
  2. package/README.md +36 -9
  3. package/dist/controlCenter.js +197 -1
  4. package/dist/direct/directSessionStore.d.ts +2 -0
  5. package/dist/direct/directVerification.js +7 -0
  6. package/dist/doctor.js +1 -1
  7. package/dist/policy/projectPolicy.d.ts +55 -0
  8. package/dist/policy/projectPolicy.js +286 -0
  9. package/dist/smoke-test.js +8 -8
  10. package/dist/test/unit/evidence-pack.test.d.ts +1 -0
  11. package/dist/test/unit/evidence-pack.test.js +130 -0
  12. package/dist/test/unit/project-policy-release-mode.test.d.ts +1 -0
  13. package/dist/test/unit/project-policy-release-mode.test.js +125 -0
  14. package/dist/test/unit/run-task-loop.test.d.ts +1 -0
  15. package/dist/test/unit/run-task-loop.test.js +380 -0
  16. package/dist/test/unit/schema-drift-check.test.js +10 -9
  17. package/dist/tools/evidencePack.d.ts +39 -0
  18. package/dist/tools/evidencePack.js +168 -0
  19. package/dist/tools/recommendAgentForTask.d.ts +19 -0
  20. package/dist/tools/recommendAgentForTask.js +56 -0
  21. package/dist/tools/registry.js +376 -2
  22. package/dist/tools/releaseMode.d.ts +50 -0
  23. package/dist/tools/releaseMode.js +370 -0
  24. package/dist/tools/runDirectVerificationBundle.d.ts +26 -0
  25. package/dist/tools/runDirectVerificationBundle.js +64 -0
  26. package/dist/tools/runTaskLoop.d.ts +57 -0
  27. package/dist/tools/runTaskLoop.js +417 -0
  28. package/dist/tools/runVerification.d.ts +4 -0
  29. package/dist/tools/runVerification.js +4 -0
  30. package/dist/tools/safeViews.d.ts +6 -0
  31. package/dist/tools/safeViews.js +2 -0
  32. package/dist/tools/taskLineage.d.ts +91 -0
  33. package/dist/tools/taskLineage.js +175 -0
  34. package/dist/tools/toolCatalog.d.ts +2 -2
  35. package/dist/tools/toolCatalog.js +6 -0
  36. package/dist/tools/toolRegistry.js +110 -0
  37. package/dist/version.d.ts +2 -2
  38. package/dist/version.js +2 -2
  39. package/docs/chatgpt-usage.md +31 -0
  40. package/docs/control-center/README.md +9 -0
  41. package/package.json +2 -2
  42. package/scripts/checks/control-center-smoke.js +87 -0
  43. package/scripts/checks/control-smoke.js +2 -2
  44. package/scripts/checks/mcp-manifest-check.js +12 -0
  45. package/scripts/checks/mcp-smoke.js +31 -7
  46. package/scripts/checks/watcher-supervisor-smoke.js +1 -1
  47. package/src/controlCenter.ts +198 -1
  48. package/src/direct/directSessionStore.ts +2 -0
  49. package/src/direct/directVerification.ts +7 -0
  50. package/src/doctor.ts +1 -1
  51. package/src/policy/projectPolicy.ts +344 -0
  52. package/src/smoke-test.ts +5 -5
  53. package/src/test/unit/evidence-pack.test.ts +142 -0
  54. package/src/test/unit/project-policy-release-mode.test.ts +156 -0
  55. package/src/test/unit/run-task-loop.test.ts +425 -0
  56. package/src/test/unit/schema-drift-check.test.ts +11 -9
  57. package/src/tools/evidencePack.ts +205 -0
  58. package/src/tools/listWorkspace.ts +71 -71
  59. package/src/tools/recommendAgentForTask.ts +79 -0
  60. package/src/tools/registry.ts +405 -2
  61. package/src/tools/releaseMode.ts +450 -0
  62. package/src/tools/runDirectVerificationBundle.ts +98 -0
  63. package/src/tools/runTaskLoop.ts +526 -0
  64. package/src/tools/runVerification.ts +8 -0
  65. package/src/tools/safeViews.ts +2 -0
  66. package/src/tools/taskLineage.ts +300 -0
  67. package/src/tools/toolCatalog.ts +6 -0
  68. package/src/tools/toolRegistry.ts +110 -0
  69. package/src/version.ts +2 -2
  70. package/ui/pages/dashboard.html +143 -2
package/README.en.md CHANGED
@@ -8,7 +8,7 @@
8
8
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-339933.svg)](https://nodejs.org/)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
10
 
11
- Current source version: **v1.1.0**. See the
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.1.0, the fixed 21-tool `chatgpt_core` catalog, and its schema
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 21-tool profile used by the ChatGPT tunnel:
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 thirteen guarded tools so ChatGPT can create an editing
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`, thirteen
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
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-339933.svg)](https://nodejs.org/)
9
9
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
10
 
11
- 当前源码版本:**v1.1.0**。查看
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` 的版本、21 个核心工具和 Schema Manifest。
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` 是固定的 21 工具 Profile,适合 ChatGPT Tunnel:
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
- `full` 提供完整 54 工具本地开发目录,包含核心工具、管理工具和 Direct
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.1 包含 13 个工具:
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
- 21 工具清单。
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=13`、
950
+ `tool_profile=chatgpt_direct`、`tool_count=14`、
924
951
  `direct_profile_enabled=true`。已有对话可能缓存旧工具清单,需要重新连接
925
952
  Connector 后再新建对话。
926
953
 
@@ -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;
@@ -13,6 +13,8 @@ export interface DirectSessionVerificationRun {
13
13
  exit_code: number | null;
14
14
  passed: boolean;
15
15
  timed_out: boolean;
16
+ redacted?: boolean;
17
+ redaction_categories?: string[];
16
18
  stdout_tail: string;
17
19
  stderr_tail: string;
18
20
  started_at: string;
@@ -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 54 tools", fullTools.length === 54, `${fullTools.length} tools`);
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
+ };