patchwarden 0.6.1 → 1.1.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 (261) hide show
  1. package/PatchWarden-Control-Tray.cmd +11 -0
  2. package/PatchWarden-Control.cmd +6 -0
  3. package/PatchWarden-Desktop.cmd +5 -0
  4. package/PatchWarden.cmd +1 -1
  5. package/README.en.md +112 -24
  6. package/README.md +36 -25
  7. package/Restart-PatchWarden-Control.cmd +6 -0
  8. package/Stop-PatchWarden.cmd +11 -0
  9. package/dist/agents/agentRouter.d.ts +40 -0
  10. package/dist/agents/agentRouter.js +95 -0
  11. package/dist/config.d.ts +1 -1
  12. package/dist/config.js +6 -2
  13. package/dist/controlCenter.d.ts +14 -0
  14. package/dist/controlCenter.js +2015 -0
  15. package/dist/doctor.js +35 -4
  16. package/dist/goal/acceptanceEngine.d.ts +62 -0
  17. package/dist/goal/acceptanceEngine.js +103 -0
  18. package/dist/goal/acceptanceTemplate.d.ts +10 -0
  19. package/dist/goal/acceptanceTemplate.js +104 -0
  20. package/dist/goal/goalGraph.d.ts +58 -0
  21. package/dist/goal/goalGraph.js +189 -0
  22. package/dist/goal/goalProgress.d.ts +81 -0
  23. package/dist/goal/goalProgress.js +216 -0
  24. package/dist/goal/goalStatus.d.ts +60 -0
  25. package/dist/goal/goalStatus.js +137 -0
  26. package/dist/goal/goalStore.d.ts +79 -0
  27. package/dist/goal/goalStore.js +211 -0
  28. package/dist/goal/handoffExport.d.ts +34 -0
  29. package/dist/goal/handoffExport.js +183 -0
  30. package/dist/goal/subgoalSync.d.ts +40 -0
  31. package/dist/goal/subgoalSync.js +72 -0
  32. package/dist/goal/worktreeManager.d.ts +96 -0
  33. package/dist/goal/worktreeManager.js +292 -0
  34. package/dist/logging.d.ts +44 -0
  35. package/dist/logging.js +68 -0
  36. package/dist/release/releaseGate.d.ts +99 -0
  37. package/dist/release/releaseGate.js +475 -0
  38. package/dist/runner/postTaskCleanup.d.ts +13 -0
  39. package/dist/runner/postTaskCleanup.js +147 -0
  40. package/dist/runner/runTask.js +50 -10
  41. package/dist/security/discoveryTokenStore.d.ts +62 -0
  42. package/dist/security/discoveryTokenStore.js +100 -0
  43. package/dist/security/toolInvocationGuard.d.ts +35 -0
  44. package/dist/security/toolInvocationGuard.js +153 -0
  45. package/dist/smoke-test.js +18 -10
  46. package/dist/taskRuntime.d.ts +17 -0
  47. package/dist/test/unit/acceptance-engine.test.d.ts +1 -0
  48. package/dist/test/unit/acceptance-engine.test.js +228 -0
  49. package/dist/test/unit/agent-router.test.d.ts +1 -0
  50. package/dist/test/unit/agent-router.test.js +287 -0
  51. package/dist/test/unit/audit-checks.test.d.ts +1 -0
  52. package/dist/test/unit/audit-checks.test.js +350 -0
  53. package/dist/test/unit/diagnose-task.test.d.ts +1 -0
  54. package/dist/test/unit/diagnose-task.test.js +457 -0
  55. package/dist/test/unit/discovery-token-store.test.d.ts +1 -0
  56. package/dist/test/unit/discovery-token-store.test.js +139 -0
  57. package/dist/test/unit/goal-graph.test.d.ts +1 -0
  58. package/dist/test/unit/goal-graph.test.js +298 -0
  59. package/dist/test/unit/goal-progress.test.d.ts +1 -0
  60. package/dist/test/unit/goal-progress.test.js +381 -0
  61. package/dist/test/unit/goal-status.test.d.ts +1 -0
  62. package/dist/test/unit/goal-status.test.js +215 -0
  63. package/dist/test/unit/goal-store.test.d.ts +1 -0
  64. package/dist/test/unit/goal-store.test.js +253 -0
  65. package/dist/test/unit/goal-subgoal-task.test.d.ts +1 -0
  66. package/dist/test/unit/goal-subgoal-task.test.js +55 -0
  67. package/dist/test/unit/goal-tools-registry.test.d.ts +1 -0
  68. package/dist/test/unit/goal-tools-registry.test.js +190 -0
  69. package/dist/test/unit/handoff-export.test.d.ts +1 -0
  70. package/dist/test/unit/handoff-export.test.js +263 -0
  71. package/dist/test/unit/invoke-discovered-tool.test.d.ts +1 -0
  72. package/dist/test/unit/invoke-discovered-tool.test.js +167 -0
  73. package/dist/test/unit/logging.test.js +127 -5
  74. package/dist/test/unit/post-task-cleanup.test.d.ts +1 -0
  75. package/dist/test/unit/post-task-cleanup.test.js +48 -0
  76. package/dist/test/unit/reconcile-tasks.test.d.ts +1 -0
  77. package/dist/test/unit/reconcile-tasks.test.js +456 -0
  78. package/dist/test/unit/release-gate.test.d.ts +1 -0
  79. package/dist/test/unit/release-gate.test.js +242 -0
  80. package/dist/test/unit/safe-views.test.d.ts +1 -0
  81. package/dist/test/unit/safe-views.test.js +171 -0
  82. package/dist/test/unit/schema-drift-check.test.d.ts +1 -0
  83. package/dist/test/unit/schema-drift-check.test.js +175 -0
  84. package/dist/test/unit/subgoal-sync.test.d.ts +1 -0
  85. package/dist/test/unit/subgoal-sync.test.js +183 -0
  86. package/dist/test/unit/tool-invocation-guard.test.d.ts +1 -0
  87. package/dist/test/unit/tool-invocation-guard.test.js +432 -0
  88. package/dist/test/unit/tool-usage-stats.test.d.ts +1 -0
  89. package/dist/test/unit/tool-usage-stats.test.js +300 -0
  90. package/dist/test/unit/toolSearch.test.d.ts +1 -0
  91. package/dist/test/unit/toolSearch.test.js +571 -0
  92. package/dist/test/unit/worktree-manager.test.d.ts +1 -0
  93. package/dist/test/unit/worktree-manager.test.js +176 -0
  94. package/dist/tools/auditTask.d.ts +103 -1
  95. package/dist/tools/auditTask.js +461 -8
  96. package/dist/tools/cancelTask.js +1 -1
  97. package/dist/tools/checkReleaseGate.d.ts +21 -0
  98. package/dist/tools/checkReleaseGate.js +22 -0
  99. package/dist/tools/createTask.d.ts +18 -2
  100. package/dist/tools/createTask.js +29 -1
  101. package/dist/tools/diagnoseTask.d.ts +45 -0
  102. package/dist/tools/diagnoseTask.js +347 -0
  103. package/dist/tools/discardWorktree.d.ts +24 -0
  104. package/dist/tools/discardWorktree.js +27 -0
  105. package/dist/tools/discoverTools.d.ts +11 -0
  106. package/dist/tools/discoverTools.js +39 -0
  107. package/dist/tools/explainTool.d.ts +11 -0
  108. package/dist/tools/explainTool.js +21 -0
  109. package/dist/tools/getTaskSummary.js +2 -1
  110. package/dist/tools/goalSubgoalTask.d.ts +51 -0
  111. package/dist/tools/goalSubgoalTask.js +110 -0
  112. package/dist/tools/healthCheck.d.ts +1 -0
  113. package/dist/tools/healthCheck.js +5 -1
  114. package/dist/tools/invokeDiscoveredTool.d.ts +37 -0
  115. package/dist/tools/invokeDiscoveredTool.js +112 -0
  116. package/dist/tools/listTasks.d.ts +3 -1
  117. package/dist/tools/listTasks.js +14 -1
  118. package/dist/tools/mergeWorktree.d.ts +24 -0
  119. package/dist/tools/mergeWorktree.js +27 -0
  120. package/dist/tools/reconcileTasks.d.ts +41 -0
  121. package/dist/tools/reconcileTasks.js +352 -0
  122. package/dist/tools/registry.js +550 -0
  123. package/dist/tools/safeStatus.d.ts +31 -1
  124. package/dist/tools/safeStatus.js +43 -1
  125. package/dist/tools/safeViews.d.ts +256 -0
  126. package/dist/tools/safeViews.js +250 -0
  127. package/dist/tools/schemaDriftCheck.d.ts +46 -0
  128. package/dist/tools/schemaDriftCheck.js +80 -0
  129. package/dist/tools/toolCatalog.d.ts +4 -3
  130. package/dist/tools/toolCatalog.js +33 -11
  131. package/dist/tools/toolRegistry.d.ts +61 -0
  132. package/dist/tools/toolRegistry.js +724 -0
  133. package/dist/tools/toolSearch.d.ts +110 -0
  134. package/dist/tools/toolSearch.js +331 -0
  135. package/dist/tools/toolUsageStats.d.ts +41 -0
  136. package/dist/tools/toolUsageStats.js +116 -0
  137. package/dist/tools/waitForTask.js +1 -0
  138. package/dist/version.d.ts +2 -2
  139. package/dist/version.js +2 -2
  140. package/dist/watcherStatus.d.ts +29 -0
  141. package/dist/watcherStatus.js +92 -1
  142. package/docs/control-center/README.md +33 -0
  143. package/docs/control-center/control-center-daily-driver.md +211 -0
  144. package/docs/control-center/control-center-mvp.md +205 -0
  145. package/docs/control-center/control-center-phase2.md +159 -0
  146. package/docs/demo.md +3 -0
  147. package/docs/release-v0.6.4.md +45 -0
  148. package/examples/openai-tunnel/README.md +5 -5
  149. package/examples/openai-tunnel/tunnel-client.example.yaml +3 -3
  150. package/package.json +25 -16
  151. package/scripts/README.md +47 -0
  152. package/scripts/{brand-check.js → checks/brand-check.js} +2 -2
  153. package/scripts/checks/control-center-smoke.js +1098 -0
  154. package/scripts/{control-smoke.js → checks/control-smoke.js} +19 -5
  155. package/scripts/{doctor-smoke.js → checks/doctor-smoke.js} +2 -1
  156. package/scripts/{http-mcp-smoke.js → checks/http-mcp-smoke.js} +2 -2
  157. package/scripts/{lifecycle-smoke.js → checks/lifecycle-smoke.js} +25 -21
  158. package/scripts/{mcp-manifest-check.js → checks/mcp-manifest-check.js} +54 -7
  159. package/scripts/{mcp-smoke.js → checks/mcp-smoke.js} +40 -11
  160. package/scripts/{package-manifest-check.js → checks/package-manifest-check.js} +2 -1
  161. package/scripts/{tunnel-supervisor-smoke.js → checks/tunnel-supervisor-smoke.js} +2 -2
  162. package/scripts/{unit-tests.js → checks/unit-tests.js} +1 -1
  163. package/scripts/{watcher-supervisor-smoke.js → checks/watcher-supervisor-smoke.js} +13 -7
  164. package/scripts/control/control-center-tray.ps1 +281 -0
  165. package/scripts/{get-patchwarden-health.ps1 → control/get-patchwarden-health.ps1} +3 -3
  166. package/scripts/{manage-patchwarden.ps1 → control/manage-patchwarden.ps1} +30 -4
  167. package/scripts/control/restart-control-center.ps1 +173 -0
  168. package/scripts/{restart-patchwarden.ps1 → control/restart-patchwarden.ps1} +1 -1
  169. package/scripts/control/start-control-center.ps1 +263 -0
  170. package/scripts/{start-patchwarden-tunnel.ps1 → control/start-patchwarden-tunnel.ps1} +48 -6
  171. package/scripts/control/stop-patchwarden.ps1 +114 -0
  172. package/scripts/launchers/Check-PatchWarden-Health.cmd +1 -1
  173. package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +1 -1
  174. package/scripts/launchers/Restart-PatchWarden.cmd +1 -1
  175. package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +1 -1
  176. package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +1 -1
  177. package/scripts/{patchwarden-mcp-direct.cmd → mcp/patchwarden-mcp-direct.cmd} +1 -1
  178. package/scripts/{patchwarden-mcp-stdio.cmd → mcp/patchwarden-mcp-stdio.cmd} +1 -1
  179. package/scripts/{pack-clean.js → release/pack-clean.js} +9 -1
  180. package/src/agents/agentRouter.ts +149 -0
  181. package/src/config.ts +9 -3
  182. package/src/controlCenter.ts +2166 -0
  183. package/src/doctor.ts +40 -5
  184. package/src/goal/acceptanceEngine.ts +160 -0
  185. package/src/goal/acceptanceTemplate.ts +121 -0
  186. package/src/goal/goalGraph.ts +225 -0
  187. package/src/goal/goalProgress.ts +301 -0
  188. package/src/goal/goalStatus.ts +234 -0
  189. package/src/goal/goalStore.ts +306 -0
  190. package/src/goal/handoffExport.ts +211 -0
  191. package/src/goal/subgoalSync.ts +82 -0
  192. package/src/goal/worktreeManager.ts +404 -0
  193. package/src/logging.ts +91 -0
  194. package/src/release/releaseGate.ts +567 -0
  195. package/src/runner/postTaskCleanup.ts +154 -0
  196. package/src/runner/runTask.ts +49 -10
  197. package/src/security/discoveryTokenStore.ts +157 -0
  198. package/src/security/toolInvocationGuard.ts +251 -0
  199. package/src/smoke-test.ts +15 -7
  200. package/src/taskRuntime.ts +17 -0
  201. package/src/test/unit/acceptance-engine.test.ts +261 -0
  202. package/src/test/unit/agent-router.test.ts +342 -0
  203. package/src/test/unit/audit-checks.test.ts +567 -0
  204. package/src/test/unit/diagnose-task.test.ts +544 -0
  205. package/src/test/unit/discovery-token-store.test.ts +181 -0
  206. package/src/test/unit/goal-graph.test.ts +347 -0
  207. package/src/test/unit/goal-progress.test.ts +538 -0
  208. package/src/test/unit/goal-status.test.ts +270 -0
  209. package/src/test/unit/goal-store.test.ts +318 -0
  210. package/src/test/unit/goal-subgoal-task.test.ts +72 -0
  211. package/src/test/unit/goal-tools-registry.test.ts +243 -0
  212. package/src/test/unit/handoff-export.test.ts +295 -0
  213. package/src/test/unit/invoke-discovered-tool.test.ts +216 -0
  214. package/src/test/unit/logging.test.ts +177 -5
  215. package/src/test/unit/post-task-cleanup.test.ts +53 -0
  216. package/src/test/unit/reconcile-tasks.test.ts +551 -0
  217. package/src/test/unit/release-gate.test.ts +314 -0
  218. package/src/test/unit/safe-views.test.ts +184 -0
  219. package/src/test/unit/schema-drift-check.test.ts +258 -0
  220. package/src/test/unit/subgoal-sync.test.ts +236 -0
  221. package/src/test/unit/tool-invocation-guard.test.ts +542 -0
  222. package/src/test/unit/tool-usage-stats.test.ts +384 -0
  223. package/src/test/unit/toolSearch.test.ts +756 -0
  224. package/src/test/unit/worktree-manager.test.ts +247 -0
  225. package/src/tools/auditTask.ts +831 -402
  226. package/src/tools/cancelTask.ts +1 -1
  227. package/src/tools/checkReleaseGate.ts +45 -0
  228. package/src/tools/createTask.ts +64 -6
  229. package/src/tools/diagnoseTask.ts +460 -0
  230. package/src/tools/discardWorktree.ts +42 -0
  231. package/src/tools/discoverTools.ts +51 -0
  232. package/src/tools/explainTool.ts +26 -0
  233. package/src/tools/getTaskSummary.ts +2 -1
  234. package/src/tools/goalSubgoalTask.ts +170 -0
  235. package/src/tools/healthCheck.ts +4 -1
  236. package/src/tools/invokeDiscoveredTool.ts +163 -0
  237. package/src/tools/listTasks.ts +16 -2
  238. package/src/tools/mergeWorktree.ts +42 -0
  239. package/src/tools/reconcileTasks.ts +464 -0
  240. package/src/tools/registry.ts +618 -1
  241. package/src/tools/safeStatus.ts +73 -2
  242. package/src/tools/safeViews.ts +271 -0
  243. package/src/tools/schemaDriftCheck.ts +120 -0
  244. package/src/tools/toolCatalog.ts +34 -11
  245. package/src/tools/toolRegistry.ts +786 -0
  246. package/src/tools/toolSearch.ts +464 -0
  247. package/src/tools/toolUsageStats.ts +151 -0
  248. package/src/tools/waitForTask.ts +1 -0
  249. package/src/version.ts +2 -2
  250. package/src/watcherStatus.ts +97 -1
  251. package/ui/colors_and_type.css +141 -0
  252. package/ui/pages/audit.html +743 -0
  253. package/ui/pages/dashboard.html +1154 -0
  254. package/ui/pages/direct-sessions.html +652 -0
  255. package/ui/pages/logs.html +502 -0
  256. package/ui/pages/task-detail.html +1229 -0
  257. package/ui/pages/tasks.html +702 -0
  258. package/ui/pages/workspace.html +947 -0
  259. package/ui/partials/project-shell.html +362 -0
  260. package/ui/vendor/lucide.js +12 -0
  261. package/ui/vendor/tailwindcss-browser.js +947 -0
package/dist/doctor.js CHANGED
@@ -18,6 +18,9 @@ import { guardPlanContent } from "./security/planGuard.js";
18
18
  import { TASK_READ_ONLY_FILES } from "./tools/getTaskFile.js";
19
19
  import { getToolDefs } from "./tools/registry.js";
20
20
  import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, selectToolsForProfile } from "./tools/toolCatalog.js";
21
+ import { buildToolRegistry } from "./tools/toolRegistry.js";
22
+ import { runAllSchemaDriftChecks } from "./tools/schemaDriftCheck.js";
23
+ import { runReleaseGateCheck } from "./release/releaseGate.js";
21
24
  import { PATCHWARDEN_VERSION } from "./version.js";
22
25
  // ── State ──────────────────────────────────────────────────────────
23
26
  let ok = 0;
@@ -210,7 +213,7 @@ async function main() {
210
213
  check("Read-only task artifact allowlist", requiredReadOnlyFiles.every((name) => TASK_READ_ONLY_FILES.includes(name)), requiredReadOnlyFiles.join(", "));
211
214
  const packageJson = JSON.parse(readFileSync(resolve(process.cwd(), "package.json"), "utf-8"));
212
215
  check("Server version matches package.json", packageJson.version === PATCHWARDEN_VERSION, `${PATCHWARDEN_VERSION} vs ${packageJson.version}`);
213
- check("Manifest preflight script exists", existsSync(resolve(process.cwd(), "scripts/mcp-manifest-check.js")), "scripts/mcp-manifest-check.js");
216
+ check("Manifest preflight script exists", existsSync(resolve(process.cwd(), "scripts/checks/mcp-manifest-check.js")), "scripts/checks/mcp-manifest-check.js");
214
217
  const previousProfile = process.env.PATCHWARDEN_TOOL_PROFILE;
215
218
  try {
216
219
  process.env.PATCHWARDEN_TOOL_PROFILE = "full";
@@ -218,7 +221,7 @@ async function main() {
218
221
  const coreTools = selectToolsForProfile(fullTools, "chatgpt_core", config?.enableDirectProfile);
219
222
  const createSchema = coreTools.find((tool) => tool.name === "create_task")?.inputSchema;
220
223
  const waitSchema = coreTools.find((tool) => tool.name === "wait_for_task")?.inputSchema;
221
- check("Full tool profile exposes 30 tools", fullTools.length === 30, `${fullTools.length} tools`);
224
+ check("Full tool profile exposes 54 tools", fullTools.length === 54, `${fullTools.length} tools`);
222
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(", "));
223
226
  check("Core task schemas expose inline_plan, verify_commands, and wait aliases", Boolean(createSchema?.properties?.inline_plan &&
224
227
  createSchema?.properties?.verify_commands &&
@@ -235,6 +238,25 @@ async function main() {
235
238
  results.push(`[OK] chatgpt_direct enabled check skipped (enableDirectProfile=false)`);
236
239
  ok++;
237
240
  }
241
+ // 16. Schema drift 检查(v0.9.0)—— warn 级别,不阻断 doctor:ci
242
+ // 用 fullTools 同时构建 registry 和 toolDefs Map,确保 schema digest 比对基准一致。
243
+ const driftRegistry = buildToolRegistry(fullTools);
244
+ const driftToolDefs = new Map();
245
+ for (const tool of fullTools) {
246
+ driftToolDefs.set(tool.name, { inputSchema: tool.inputSchema });
247
+ }
248
+ const driftResult = runAllSchemaDriftChecks(driftRegistry, driftToolDefs);
249
+ if (driftResult.ok) {
250
+ results.push(`[OK] Schema drift check — no drift detected`);
251
+ ok++;
252
+ }
253
+ else {
254
+ // 每个 warning 单独输出为 WARN,与其他自检项的多警告模式一致
255
+ for (const w of driftResult.warnings) {
256
+ results.push(`[WARN] Schema drift: ${w}`);
257
+ warn++;
258
+ }
259
+ }
238
260
  }
239
261
  finally {
240
262
  if (previousProfile === undefined)
@@ -242,6 +264,15 @@ async function main() {
242
264
  else
243
265
  process.env.PATCHWARDEN_TOOL_PROFILE = previousProfile;
244
266
  }
267
+ // 17. Release gate module loadable (v1.0.0) — module integrity only.
268
+ // Does NOT execute local_ready (would recurse into doctor:ci).
269
+ try {
270
+ const releaseGateReady = typeof runReleaseGateCheck === "function";
271
+ check("Release gate module loadable", releaseGateReady, releaseGateReady ? "runReleaseGateCheck exported" : "runReleaseGateCheck missing or invalid");
272
+ }
273
+ catch (error) {
274
+ check("Release gate module loadable", false, error instanceof Error ? error.message : String(error));
275
+ }
245
276
  // 9. HTTP port check
246
277
  const httpPort = config?.http?.port || 7331;
247
278
  try {
@@ -403,9 +434,9 @@ async function main() {
403
434
  "examples/openai-tunnel/README.md",
404
435
  "examples/openai-tunnel/tunnel-client.example.yaml",
405
436
  "examples/openai-tunnel/chatgpt-test-prompt.md",
406
- "scripts/patchwarden-mcp-direct.cmd",
437
+ "scripts/mcp/patchwarden-mcp-direct.cmd",
407
438
  "PatchWarden.cmd",
408
- "scripts/manage-patchwarden.ps1",
439
+ "scripts/control/manage-patchwarden.ps1",
409
440
  "scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
410
441
  ];
411
442
  for (const tf of tunnelFiles) {
@@ -0,0 +1,62 @@
1
+ /**
2
+ * v0.7.2: 验收引擎 — 基于证据推导 4 级验收结论。
3
+ *
4
+ * 输入:status.json、task_meta(goal/scope/forbidden/verification/done_evidence)、
5
+ * audit checks(pass/warn/fail)、release claims、artifact 信息
6
+ * 输出:ACCEPTED / NEEDS_FIX / REJECTED / BLOCKED_BY_APPROVAL
7
+ * + reasons + required_evidence + next_suggested_task
8
+ *
9
+ * 决策规则:
10
+ * 1. 存在 fail 级检查项 → REJECTED
11
+ * 2. 存在 release claims 但无法验证远端 → BLOCKED_BY_APPROVAL
12
+ * 3. 存在 warn 级检查项或文档不同步 → NEEDS_FIX
13
+ * 4. 所有检查通过 → ACCEPTED
14
+ */
15
+ import type { AcceptanceStatus } from "../tools/createTask.js";
16
+ export type AcceptanceVerdict = "ACCEPTED" | "NEEDS_FIX" | "REJECTED" | "BLOCKED_BY_APPROVAL";
17
+ export interface AcceptanceCheck {
18
+ name: string;
19
+ result: "pass" | "warn" | "fail";
20
+ detail: string;
21
+ }
22
+ export interface AcceptanceEvidence {
23
+ task_id: string;
24
+ task_status: string;
25
+ result_md_exists: boolean;
26
+ result_json_exists: boolean;
27
+ verify_json_exists: boolean;
28
+ test_log_exists: boolean;
29
+ git_diff_exists: boolean;
30
+ verify_status: "passed" | "failed" | "skipped" | null;
31
+ new_out_of_scope_changes: Array<{
32
+ path: string;
33
+ change: string;
34
+ }>;
35
+ goal: string | null;
36
+ scope: string[] | null;
37
+ forbidden: string[] | null;
38
+ verification: string[] | null;
39
+ done_evidence: string[] | null;
40
+ artifact_status: string | null;
41
+ release_claims_unverified: boolean;
42
+ checks: AcceptanceCheck[];
43
+ }
44
+ export interface AcceptanceResult {
45
+ verdict: AcceptanceVerdict;
46
+ acceptance_status: AcceptanceStatus;
47
+ reason: string;
48
+ reasons: string[];
49
+ required_evidence: string[];
50
+ next_suggested_task: string;
51
+ fail_checks: AcceptanceCheck[];
52
+ warn_checks: AcceptanceCheck[];
53
+ }
54
+ /**
55
+ * 核心验收决策函数。
56
+ * 输入所有证据,输出 4 级验收结论。
57
+ */
58
+ export declare function evaluateAcceptance(evidence: AcceptanceEvidence): AcceptanceResult;
59
+ /**
60
+ * 将 AcceptanceVerdict 映射到 AcceptanceStatus(用于回写 status.json)。
61
+ */
62
+ export declare function verdictToStatus(verdict: AcceptanceVerdict): AcceptanceStatus;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * v0.7.2: 验收引擎 — 基于证据推导 4 级验收结论。
3
+ *
4
+ * 输入:status.json、task_meta(goal/scope/forbidden/verification/done_evidence)、
5
+ * audit checks(pass/warn/fail)、release claims、artifact 信息
6
+ * 输出:ACCEPTED / NEEDS_FIX / REJECTED / BLOCKED_BY_APPROVAL
7
+ * + reasons + required_evidence + next_suggested_task
8
+ *
9
+ * 决策规则:
10
+ * 1. 存在 fail 级检查项 → REJECTED
11
+ * 2. 存在 release claims 但无法验证远端 → BLOCKED_BY_APPROVAL
12
+ * 3. 存在 warn 级检查项或文档不同步 → NEEDS_FIX
13
+ * 4. 所有检查通过 → ACCEPTED
14
+ */
15
+ // ── 验收决策 ──────────────────────────────────────────────────────
16
+ /**
17
+ * 核心验收决策函数。
18
+ * 输入所有证据,输出 4 级验收结论。
19
+ */
20
+ export function evaluateAcceptance(evidence) {
21
+ const reasons = [];
22
+ const requiredEvidence = [];
23
+ const failChecks = [];
24
+ const warnChecks = [];
25
+ // 收集 fail 和 warn 检查项
26
+ for (const check of evidence.checks) {
27
+ if (check.result === "fail") {
28
+ failChecks.push(check);
29
+ }
30
+ else if (check.result === "warn") {
31
+ warnChecks.push(check);
32
+ }
33
+ }
34
+ // ── Rule 1: fail 级检查项 → REJECTED ──────────────────────
35
+ if (failChecks.length > 0) {
36
+ for (const check of failChecks) {
37
+ reasons.push(`[FAIL] ${check.name}: ${check.detail}`);
38
+ }
39
+ return {
40
+ verdict: "REJECTED",
41
+ acceptance_status: "rejected",
42
+ reason: `${failChecks.length} fail-level check(s): ${failChecks.map((c) => c.name).join(", ")}`,
43
+ reasons,
44
+ required_evidence: requiredEvidence.length > 0 ? requiredEvidence : ["Fix all fail-level issues and re-run audit_task"],
45
+ next_suggested_task: `Fix fail-level issues: ${failChecks.map((c) => c.name).join(", ")}`,
46
+ fail_checks: failChecks,
47
+ warn_checks: warnChecks,
48
+ };
49
+ }
50
+ // ── Rule 2: release claims 无法验证 → BLOCKED_BY_APPROVAL ──
51
+ if (evidence.release_claims_unverified) {
52
+ reasons.push("[BLOCKED] Release claims detected but remote state cannot be verified");
53
+ requiredEvidence.push("Verify remote release state (npm publish, GitHub release, git tag)");
54
+ return {
55
+ verdict: "BLOCKED_BY_APPROVAL",
56
+ acceptance_status: "blocked",
57
+ reason: "Release claims require manual verification of remote state",
58
+ reasons,
59
+ required_evidence: requiredEvidence,
60
+ next_suggested_task: "Manually verify remote release state, then re-run audit_task",
61
+ fail_checks: failChecks,
62
+ warn_checks: warnChecks,
63
+ };
64
+ }
65
+ // ── Rule 3: warn 级检查项 → NEEDS_FIX ──────────────────────
66
+ if (warnChecks.length > 0) {
67
+ for (const check of warnChecks) {
68
+ reasons.push(`[WARN] ${check.name}: ${check.detail}`);
69
+ }
70
+ return {
71
+ verdict: "NEEDS_FIX",
72
+ acceptance_status: "needs_fix",
73
+ reason: `${warnChecks.length} warn-level check(s) require fixes: ${warnChecks.map((c) => c.name).join(", ")}`,
74
+ reasons,
75
+ required_evidence: requiredEvidence.length > 0 ? requiredEvidence : ["Resolve all warn-level issues"],
76
+ next_suggested_task: `Fix warn-level issues: ${warnChecks.map((c) => c.name).join(", ")}`,
77
+ fail_checks: failChecks,
78
+ warn_checks: warnChecks,
79
+ };
80
+ }
81
+ // ── Rule 4: 所有检查通过 → ACCEPTED ───────────────────────
82
+ return {
83
+ verdict: "ACCEPTED",
84
+ acceptance_status: "accepted",
85
+ reason: "All acceptance checks passed",
86
+ reasons: ["All checks passed, no warnings or failures"],
87
+ required_evidence: [],
88
+ next_suggested_task: "Task accepted. Proceed to next goal or task.",
89
+ fail_checks: failChecks,
90
+ warn_checks: warnChecks,
91
+ };
92
+ }
93
+ /**
94
+ * 将 AcceptanceVerdict 映射到 AcceptanceStatus(用于回写 status.json)。
95
+ */
96
+ export function verdictToStatus(verdict) {
97
+ switch (verdict) {
98
+ case "ACCEPTED": return "accepted";
99
+ case "REJECTED": return "rejected";
100
+ case "NEEDS_FIX": return "needs_fix";
101
+ case "BLOCKED_BY_APPROVAL": return "blocked";
102
+ }
103
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * v0.7.2: ACCEPTANCE.md 模板生成 — 人类可读的验收报告导出。
3
+ *
4
+ * 将 AcceptanceResult 渲染为 Markdown,落盘到 .patchwarden/tasks/{task_id}/ACCEPTANCE.md。
5
+ */
6
+ import type { AcceptanceResult, AcceptanceEvidence } from "./acceptanceEngine.js";
7
+ /**
8
+ * 生成 ACCEPTANCE.md 内容。
9
+ */
10
+ export declare function renderAcceptanceMarkdown(taskId: string, result: AcceptanceResult, evidence: AcceptanceEvidence): string;
@@ -0,0 +1,104 @@
1
+ /**
2
+ * v0.7.2: ACCEPTANCE.md 模板生成 — 人类可读的验收报告导出。
3
+ *
4
+ * 将 AcceptanceResult 渲染为 Markdown,落盘到 .patchwarden/tasks/{task_id}/ACCEPTANCE.md。
5
+ */
6
+ import { PATCHWARDEN_VERSION } from "../version.js";
7
+ /**
8
+ * 生成 ACCEPTANCE.md 内容。
9
+ */
10
+ export function renderAcceptanceMarkdown(taskId, result, evidence) {
11
+ const lines = [];
12
+ const now = new Date().toISOString();
13
+ lines.push("# Acceptance Report — " + taskId);
14
+ lines.push("");
15
+ lines.push("Generated: " + now);
16
+ lines.push("");
17
+ // ── Verdict ──────────────────────────────────────────────
18
+ lines.push("## Verdict");
19
+ lines.push("");
20
+ lines.push("| Field | Value |");
21
+ lines.push("| --- | --- |");
22
+ lines.push("| Verdict | **" + result.verdict + "** |");
23
+ lines.push("| Acceptance Status | " + (result.acceptance_status ?? "null") + " |");
24
+ lines.push("| Reason | " + result.reason + " |");
25
+ lines.push("| Next Suggested Task | " + result.next_suggested_task + " |");
26
+ lines.push("");
27
+ // ── Evidence Summary ────────────────────────────────────
28
+ lines.push("## Evidence Summary");
29
+ lines.push("");
30
+ lines.push("| Evidence | Present |");
31
+ lines.push("| --- | --- |");
32
+ lines.push("| result.md | " + (evidence.result_md_exists ? "YES" : "NO") + " |");
33
+ lines.push("| result.json | " + (evidence.result_json_exists ? "YES" : "NO") + " |");
34
+ lines.push("| verify.json | " + (evidence.verify_json_exists ? "YES" : "NO") + " |");
35
+ lines.push("| test.log | " + (evidence.test_log_exists ? "YES" : "NO") + " |");
36
+ lines.push("| git.diff | " + (evidence.git_diff_exists ? "YES" : "NO") + " |");
37
+ lines.push("| verify_status | " + (evidence.verify_status ?? "N/A") + " |");
38
+ lines.push("| artifact_status | " + (evidence.artifact_status ?? "N/A") + " |");
39
+ lines.push("| out_of_scope_changes | " + evidence.new_out_of_scope_changes.length + " |");
40
+ lines.push("| release_claims_unverified | " + (evidence.release_claims_unverified ? "YES" : "NO") + " |");
41
+ lines.push("");
42
+ // ── Task Metadata ────────────────────────────────────────
43
+ if (evidence.goal || evidence.scope || evidence.forbidden || evidence.verification || evidence.done_evidence) {
44
+ lines.push("## Task Acceptance Criteria");
45
+ lines.push("");
46
+ if (evidence.goal) {
47
+ lines.push("- **Goal**: " + evidence.goal);
48
+ }
49
+ if (evidence.scope && evidence.scope.length > 0) {
50
+ lines.push("- **Scope**: " + evidence.scope.join(", "));
51
+ }
52
+ if (evidence.forbidden && evidence.forbidden.length > 0) {
53
+ lines.push("- **Forbidden**: " + evidence.forbidden.join(", "));
54
+ }
55
+ if (evidence.verification && evidence.verification.length > 0) {
56
+ lines.push("- **Verification**: " + evidence.verification.join(", "));
57
+ }
58
+ if (evidence.done_evidence && evidence.done_evidence.length > 0) {
59
+ lines.push("- **Done Evidence**: " + evidence.done_evidence.join(", "));
60
+ }
61
+ lines.push("");
62
+ }
63
+ // ── Fail Checks ─────────────────────────────────────────
64
+ if (result.fail_checks.length > 0) {
65
+ lines.push("## Fail-Level Checks");
66
+ lines.push("");
67
+ for (const check of result.fail_checks) {
68
+ lines.push("- **" + check.name + "**: " + check.detail);
69
+ }
70
+ lines.push("");
71
+ }
72
+ // ── Warn Checks ─────────────────────────────────────────
73
+ if (result.warn_checks.length > 0) {
74
+ lines.push("## Warn-Level Checks");
75
+ lines.push("");
76
+ for (const check of result.warn_checks) {
77
+ lines.push("- **" + check.name + "**: " + check.detail);
78
+ }
79
+ lines.push("");
80
+ }
81
+ // ── Reasons ─────────────────────────────────────────────
82
+ if (result.reasons.length > 0) {
83
+ lines.push("## Reasons");
84
+ lines.push("");
85
+ for (const reason of result.reasons) {
86
+ lines.push("- " + reason);
87
+ }
88
+ lines.push("");
89
+ }
90
+ // ── Required Evidence ────────────────────────────────────
91
+ if (result.required_evidence.length > 0) {
92
+ lines.push("## Required Evidence");
93
+ lines.push("");
94
+ for (const reqEvidence of result.required_evidence) {
95
+ lines.push("- " + reqEvidence);
96
+ }
97
+ lines.push("");
98
+ }
99
+ lines.push("---");
100
+ lines.push("");
101
+ lines.push("This report was generated by PatchWarden v" + PATCHWARDEN_VERSION + " acceptanceEngine.");
102
+ lines.push("");
103
+ return lines.join("\n");
104
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * v0.8.0: Goal Session 依赖图 — 就绪/阻塞判定、下一子目标建议、环检测、拓扑排序。
3
+ *
4
+ * 所有函数均为纯函数,不修改传入的 goalStatus 对象。
5
+ * 依赖语义:subgoal.depends_on 引用其他 subgoal 的 id,被引用者 status 必须为
6
+ * "accepted" 才算依赖满足。引用不存在的 id 视为数据不一致,按阻塞处理。
7
+ */
8
+ import type { GoalStatus, Subgoal } from "./goalStatus.js";
9
+ /**
10
+ * 返回所有 status 为 "ready" 且依赖全部满足的子目标。
11
+ * 依赖满足 = depends_on 中所有 id 都存在且对应 subgoal status 为 "accepted"。
12
+ * depends_on 为空数组视为依赖满足。
13
+ * depends_on 引用不存在的 subgoal 时,该子目标不算 ready(跳过)。
14
+ */
15
+ export declare function getReadySubgoals(goalStatus: GoalStatus): Subgoal[];
16
+ /**
17
+ * 返回所有 status 为 "ready" 但依赖未全部满足的子目标,附带阻塞它的依赖 id 列表。
18
+ * blocked_by 包含 depends_on 中 status 不为 "accepted" 的 subgoal_id,
19
+ * 以及引用不存在的 id。
20
+ */
21
+ export declare function getBlockedSubgoals(goalStatus: GoalStatus): Array<{
22
+ subgoal: Subgoal;
23
+ blocked_by: string[];
24
+ }>;
25
+ /**
26
+ * 建议下一个可执行的子目标。
27
+ *
28
+ * 优先级:
29
+ * 1. 有 ready 子目标 → 返回第一个(按 subgoal 添加顺序)的 { subgoal_id, title, depends_on }
30
+ * 2. 无 ready 但有 blocked → 返回 { subgoal_id: null, reason: "dependencies_not_met", blocked_by: [...] }
31
+ * 3. 无 ready 无 blocked → 返回 { subgoal_id: null, reason: "no_ready_subgoal" }
32
+ */
33
+ export declare function suggestNextSubgoal(goalStatus: GoalStatus): {
34
+ subgoal_id: string | null;
35
+ title?: string;
36
+ depends_on?: string[];
37
+ reason?: string;
38
+ blocked_by?: string[];
39
+ };
40
+ /**
41
+ * 检测 subgoal 依赖图是否有环(DFS 三色标记法)。
42
+ *
43
+ * 沿 depends_on 深度优先遍历,遇到当前路径上正在访问的节点即为环。
44
+ * depends_on 引用不存在的 subgoal 不算环(忽略)。
45
+ *
46
+ * @returns 有环时返回环上的 subgoal_id 数组(任一环即可);无环返回 null。
47
+ */
48
+ export declare function detectCycle(goalStatus: GoalStatus): string[] | null;
49
+ /**
50
+ * 对 subgoal 做拓扑排序(依赖在前)。
51
+ *
52
+ * 如果 B depends_on A,则 A 排在 B 之前。
53
+ * depends_on 引用不存在的 id 会被忽略(不影响排序)。
54
+ *
55
+ * @throws {PatchWardenError} reason="dependency_cycle" 当依赖图存在环时。
56
+ * @returns subgoal_id 数组,依赖项在前。
57
+ */
58
+ export declare function topologicalSort(goalStatus: GoalStatus): string[];
@@ -0,0 +1,189 @@
1
+ /**
2
+ * v0.8.0: Goal Session 依赖图 — 就绪/阻塞判定、下一子目标建议、环检测、拓扑排序。
3
+ *
4
+ * 所有函数均为纯函数,不修改传入的 goalStatus 对象。
5
+ * 依赖语义:subgoal.depends_on 引用其他 subgoal 的 id,被引用者 status 必须为
6
+ * "accepted" 才算依赖满足。引用不存在的 id 视为数据不一致,按阻塞处理。
7
+ */
8
+ import { PatchWardenError } from "../errors.js";
9
+ // ── 内部工具 ──────────────────────────────────────────────────────
10
+ /**
11
+ * 构建 subgoal_id → Subgoal 的查找表。
12
+ */
13
+ function buildSubgoalMap(goalStatus) {
14
+ const map = new Map();
15
+ for (const s of goalStatus.subgoals) {
16
+ map.set(s.id, s);
17
+ }
18
+ return map;
19
+ }
20
+ /**
21
+ * 判断单个 subgoal 的依赖是否全部满足(所有 depends_on 引用存在且 status 为 accepted)。
22
+ * depends_on 为空视为依赖满足。引用不存在的 id 视为未满足。
23
+ */
24
+ function areDependenciesMet(subgoal, subgoalMap) {
25
+ for (const depId of subgoal.depends_on) {
26
+ const dep = subgoalMap.get(depId);
27
+ if (!dep)
28
+ return false;
29
+ if (dep.status !== "accepted")
30
+ return false;
31
+ }
32
+ return true;
33
+ }
34
+ // ── 公共 API ──────────────────────────────────────────────────────
35
+ /**
36
+ * 返回所有 status 为 "ready" 且依赖全部满足的子目标。
37
+ * 依赖满足 = depends_on 中所有 id 都存在且对应 subgoal status 为 "accepted"。
38
+ * depends_on 为空数组视为依赖满足。
39
+ * depends_on 引用不存在的 subgoal 时,该子目标不算 ready(跳过)。
40
+ */
41
+ export function getReadySubgoals(goalStatus) {
42
+ const subgoalMap = buildSubgoalMap(goalStatus);
43
+ return goalStatus.subgoals.filter((s) => s.status === "ready" && areDependenciesMet(s, subgoalMap));
44
+ }
45
+ /**
46
+ * 返回所有 status 为 "ready" 但依赖未全部满足的子目标,附带阻塞它的依赖 id 列表。
47
+ * blocked_by 包含 depends_on 中 status 不为 "accepted" 的 subgoal_id,
48
+ * 以及引用不存在的 id。
49
+ */
50
+ export function getBlockedSubgoals(goalStatus) {
51
+ const subgoalMap = buildSubgoalMap(goalStatus);
52
+ const result = [];
53
+ for (const s of goalStatus.subgoals) {
54
+ if (s.status !== "ready")
55
+ continue;
56
+ const blockedBy = [];
57
+ for (const depId of s.depends_on) {
58
+ const dep = subgoalMap.get(depId);
59
+ if (!dep) {
60
+ blockedBy.push(depId);
61
+ }
62
+ else if (dep.status !== "accepted") {
63
+ blockedBy.push(depId);
64
+ }
65
+ }
66
+ if (blockedBy.length > 0) {
67
+ result.push({ subgoal: s, blocked_by: blockedBy });
68
+ }
69
+ }
70
+ return result;
71
+ }
72
+ /**
73
+ * 建议下一个可执行的子目标。
74
+ *
75
+ * 优先级:
76
+ * 1. 有 ready 子目标 → 返回第一个(按 subgoal 添加顺序)的 { subgoal_id, title, depends_on }
77
+ * 2. 无 ready 但有 blocked → 返回 { subgoal_id: null, reason: "dependencies_not_met", blocked_by: [...] }
78
+ * 3. 无 ready 无 blocked → 返回 { subgoal_id: null, reason: "no_ready_subgoal" }
79
+ */
80
+ export function suggestNextSubgoal(goalStatus) {
81
+ const ready = getReadySubgoals(goalStatus);
82
+ if (ready.length > 0) {
83
+ const first = ready[0];
84
+ return {
85
+ subgoal_id: first.id,
86
+ title: first.title,
87
+ depends_on: [...first.depends_on],
88
+ };
89
+ }
90
+ const blocked = getBlockedSubgoals(goalStatus);
91
+ if (blocked.length > 0) {
92
+ return {
93
+ subgoal_id: null,
94
+ reason: "dependencies_not_met",
95
+ blocked_by: blocked.map((b) => b.subgoal.id),
96
+ };
97
+ }
98
+ return {
99
+ subgoal_id: null,
100
+ reason: "no_ready_subgoal",
101
+ };
102
+ }
103
+ /**
104
+ * 检测 subgoal 依赖图是否有环(DFS 三色标记法)。
105
+ *
106
+ * 沿 depends_on 深度优先遍历,遇到当前路径上正在访问的节点即为环。
107
+ * depends_on 引用不存在的 subgoal 不算环(忽略)。
108
+ *
109
+ * @returns 有环时返回环上的 subgoal_id 数组(任一环即可);无环返回 null。
110
+ */
111
+ export function detectCycle(goalStatus) {
112
+ const subgoalMap = buildSubgoalMap(goalStatus);
113
+ const WHITE = 0;
114
+ const GRAY = 1;
115
+ const BLACK = 2;
116
+ const color = new Map();
117
+ for (const s of goalStatus.subgoals) {
118
+ color.set(s.id, WHITE);
119
+ }
120
+ let cycle = null;
121
+ const dfs = (id, path) => {
122
+ color.set(id, GRAY);
123
+ path.push(id);
124
+ const node = subgoalMap.get(id);
125
+ if (node) {
126
+ for (const depId of node.depends_on) {
127
+ if (!subgoalMap.has(depId))
128
+ continue; // 引用不存在,忽略
129
+ const depColor = color.get(depId);
130
+ if (depColor === GRAY) {
131
+ // 发现环:截取从依赖节点到当前节点的路径
132
+ const cycleStart = path.indexOf(depId);
133
+ cycle = path.slice(cycleStart);
134
+ return true;
135
+ }
136
+ if (depColor === WHITE) {
137
+ if (dfs(depId, path))
138
+ return true;
139
+ }
140
+ }
141
+ }
142
+ path.pop();
143
+ color.set(id, BLACK);
144
+ return false;
145
+ };
146
+ for (const s of goalStatus.subgoals) {
147
+ if (color.get(s.id) === WHITE) {
148
+ if (dfs(s.id, []))
149
+ return cycle;
150
+ }
151
+ }
152
+ return null;
153
+ }
154
+ /**
155
+ * 对 subgoal 做拓扑排序(依赖在前)。
156
+ *
157
+ * 如果 B depends_on A,则 A 排在 B 之前。
158
+ * depends_on 引用不存在的 id 会被忽略(不影响排序)。
159
+ *
160
+ * @throws {PatchWardenError} reason="dependency_cycle" 当依赖图存在环时。
161
+ * @returns subgoal_id 数组,依赖项在前。
162
+ */
163
+ export function topologicalSort(goalStatus) {
164
+ const cycle = detectCycle(goalStatus);
165
+ if (cycle) {
166
+ throw new PatchWardenError("dependency_cycle", `Dependency cycle detected among subgoals: ${cycle.join(" -> ")}`, "Remove the circular dependency among subgoals before performing topological sort.", true, { cycle });
167
+ }
168
+ const subgoalMap = buildSubgoalMap(goalStatus);
169
+ const visited = new Set();
170
+ const result = [];
171
+ const visit = (id) => {
172
+ if (visited.has(id))
173
+ return;
174
+ visited.add(id);
175
+ const node = subgoalMap.get(id);
176
+ if (node) {
177
+ for (const depId of node.depends_on) {
178
+ if (subgoalMap.has(depId)) {
179
+ visit(depId);
180
+ }
181
+ }
182
+ }
183
+ result.push(id);
184
+ };
185
+ for (const s of goalStatus.subgoals) {
186
+ visit(s.id);
187
+ }
188
+ return result;
189
+ }