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
@@ -11,8 +11,8 @@ if (process.platform !== "win32") {
11
11
  }
12
12
 
13
13
  const scriptDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
14
- const root = resolve(scriptDir, "..");
15
- const manager = join(scriptDir, "manage-patchwarden.ps1");
14
+ const root = resolve(scriptDir, "..", "..");
15
+ const manager = join(root, "scripts", "control", "manage-patchwarden.ps1");
16
16
  const temp = mkdtempSync(join(tmpdir(), "patchwarden-control-smoke-"));
17
17
  const mockConfig = join(temp, "patchwarden.config.json");
18
18
  writeFileSync(mockConfig, JSON.stringify({
@@ -76,7 +76,7 @@ try {
76
76
  ready: false,
77
77
  pid: null,
78
78
  tool_profile: "chatgpt_direct",
79
- tool_count: 9,
79
+ tool_count: 13,
80
80
  tools_ready: true,
81
81
  }), "utf8");
82
82
  writeFileSync(join(directRuntime, "tunnel-client.pid"), String(fakeTunnel.pid), "utf8");
@@ -114,7 +114,7 @@ try {
114
114
  reason_code: "stale_fixture",
115
115
  last_error: "stale failure",
116
116
  tool_profile: "chatgpt_core",
117
- tool_count: 16,
117
+ tool_count: 21,
118
118
  tools_ready: true,
119
119
  }), "utf8");
120
120
  healthServer = spawn(
@@ -139,7 +139,13 @@ try {
139
139
 
140
140
  const expectedFiles = [
141
141
  "PatchWarden.cmd",
142
- "scripts/manage-patchwarden.ps1",
142
+ "PatchWarden-Control.cmd",
143
+ "PatchWarden-Control-Tray.cmd",
144
+ "PatchWarden-Desktop.cmd",
145
+ "Restart-PatchWarden-Control.cmd",
146
+ "Stop-PatchWarden.cmd",
147
+ "scripts/control/manage-patchwarden.ps1",
148
+ "scripts/control/stop-patchwarden.ps1",
143
149
  "scripts/launchers/Start-PatchWarden-Tunnel.cmd",
144
150
  "scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
145
151
  ];
@@ -152,6 +158,14 @@ try {
152
158
  if (!rootEntry.includes("manage-patchwarden.ps1")) {
153
159
  throw new Error("PatchWarden.cmd does not invoke the consolidated manager");
154
160
  }
161
+ const stopEntry = readFileSync(join(root, "Stop-PatchWarden.cmd"), "utf8");
162
+ if (!stopEntry.includes("scripts\\control\\stop-patchwarden.ps1")) {
163
+ throw new Error("Stop-PatchWarden.cmd does not invoke the one-click shutdown script");
164
+ }
165
+ const desktopEntry = readFileSync(join(root, "PatchWarden-Desktop.cmd"), "utf8");
166
+ if (!desktopEntry.includes("control-center-tray.ps1") || !desktopEntry.includes("WindowStyle Hidden")) {
167
+ throw new Error("PatchWarden-Desktop.cmd must launch the tray hidden as the daily desktop entry");
168
+ }
155
169
  console.log("ok - control handles orphan cleanup, scoped kill, port conflicts, health fallback, and Core/Direct lifecycle actions");
156
170
  } finally {
157
171
  if (fakeTunnel?.exitCode === null) fakeTunnel.kill();
@@ -5,7 +5,7 @@ import { tmpdir } from "node:os";
5
5
  import { join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
- const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
8
+ const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
9
9
  const tempRoot = mkdtempSync(join(tmpdir(), "patchwarden-doctor-"));
10
10
  const workspaceRoot = join(tempRoot, "workspace");
11
11
  const configPath = join(tempRoot, "patchwarden.config.json");
@@ -42,6 +42,7 @@ try {
42
42
  "[OK] Task directory writable",
43
43
  "[OK] Example task directory read/write",
44
44
  "[OK] allowedTestCommands is non-empty",
45
+ "[OK] Release gate module loadable",
45
46
  ];
46
47
  for (const line of requiredLines) {
47
48
  if (!result.stdout.includes(line)) throw new Error(`doctor output missing stable line: ${line}`);
@@ -11,10 +11,10 @@ import { tmpdir } from "node:os";
11
11
  import { dirname, join, resolve } from "node:path";
12
12
  import { fileURLToPath } from "node:url";
13
13
  import { setTimeout as sleep } from "node:timers/promises";
14
- import { CHATGPT_CORE_TOOL_NAMES } from "../dist/tools/toolCatalog.js";
14
+ import { CHATGPT_CORE_TOOL_NAMES } from "../../dist/tools/toolCatalog.js";
15
15
 
16
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
- const root = resolve(__dirname, "..");
17
+ const root = resolve(__dirname, "..", "..");
18
18
  const serverPath = resolve(root, "dist", "httpServer.js");
19
19
 
20
20
  const host = "127.0.0.1";
@@ -12,6 +12,10 @@ import { tmpdir } from "node:os";
12
12
  import { join } from "node:path";
13
13
  import { setTimeout as sleep } from "node:timers/promises";
14
14
 
15
+ // v0.7.2: tasks complete with "done_by_agent" (pending acceptance) instead of "done".
16
+ // Both are valid successful completion statuses for lifecycle smoke tests.
17
+ const isDoneStatus = (s) => s === "done" || s === "done_by_agent";
18
+
15
19
  const tempRoot = mkdtempSync(join(tmpdir(), "patchwarden-lifecycle-"));
16
20
  const workspaceRoot = join(tempRoot, "workspace");
17
21
  const repoPath = join(workspaceRoot, "repo");
@@ -148,18 +152,18 @@ try {
148
152
  );
149
153
  process.env.PATCHWARDEN_CONFIG = configPath;
150
154
 
151
- const { savePlan } = await import("../dist/tools/savePlan.js");
152
- const { createTask } = await import("../dist/tools/createTask.js");
153
- const { getTaskStatus } = await import("../dist/tools/getTaskStatus.js");
154
- const { cancelTask } = await import("../dist/tools/cancelTask.js");
155
- const { killTask } = await import("../dist/tools/killTask.js");
156
- const { getDiff } = await import("../dist/tools/taskOutputs.js");
157
- const { listAgents } = await import("../dist/tools/listAgents.js");
158
- const { getTaskSummary } = await import("../dist/tools/getTaskSummary.js");
159
- const { auditTask } = await import("../dist/tools/auditTask.js");
160
- const { waitForTask } = await import("../dist/tools/waitForTask.js");
161
- const { runTask } = await import("../dist/runner/runTask.js");
162
- const { reloadConfig } = await import("../dist/config.js");
155
+ const { savePlan } = await import("../../dist/tools/savePlan.js");
156
+ const { createTask } = await import("../../dist/tools/createTask.js");
157
+ const { getTaskStatus } = await import("../../dist/tools/getTaskStatus.js");
158
+ const { cancelTask } = await import("../../dist/tools/cancelTask.js");
159
+ const { killTask } = await import("../../dist/tools/killTask.js");
160
+ const { getDiff } = await import("../../dist/tools/taskOutputs.js");
161
+ const { listAgents } = await import("../../dist/tools/listAgents.js");
162
+ const { getTaskSummary } = await import("../../dist/tools/getTaskSummary.js");
163
+ const { auditTask } = await import("../../dist/tools/auditTask.js");
164
+ const { waitForTask } = await import("../../dist/tools/waitForTask.js");
165
+ const { runTask } = await import("../../dist/runner/runTask.js");
166
+ const { reloadConfig } = await import("../../dist/config.js");
163
167
 
164
168
  await test("list_agents reports configured executables", async () => {
165
169
  const result = listAgents();
@@ -178,7 +182,7 @@ try {
178
182
  verify_commands: ["node --check main.js", "node --check second.js"],
179
183
  });
180
184
  const result = await runTask(task.task_id);
181
- if (result.status !== "done") throw new Error(`Writer task ended ${result.status}: ${result.error}`);
185
+ if (!isDoneStatus(result.status)) throw new Error(`Writer task ended ${result.status}: ${result.error}`);
182
186
  const status = getTaskStatus(task.task_id);
183
187
  const changed = status.changed_files || [];
184
188
  if (!changed.some((file) => file.path === "README.md" && file.change === "modified")) {
@@ -253,7 +257,7 @@ try {
253
257
  });
254
258
  const result = await runTask(task.task_id);
255
259
  const verify = JSON.parse(readFileSync(join(task.path, "verify.json"), "utf-8"));
256
- if (result.status !== "done" || verify.status !== "passed" || verify.commands.length !== 1) {
260
+ if (!isDoneStatus(result.status) || verify.status !== "passed" || verify.commands.length !== 1) {
257
261
  throw new Error(`Legacy verification mismatch: ${JSON.stringify({ result, verify })}`);
258
262
  }
259
263
  if (verify.commands[0].cwd !== repoPath || !("stdout_tail" in verify.commands[0]) || !("stderr_tail" in verify.commands[0])) {
@@ -265,7 +269,7 @@ try {
265
269
  const plan = savePlan({ title: "No diff", content: "Do not change files." });
266
270
  const task = createTask({ plan_id: plan.plan_id, agent: "noop", repo_path: "repo" });
267
271
  const result = await runTask(task.task_id);
268
- if (result.status !== "done") throw new Error(`No-op task failed: ${JSON.stringify(result)}`);
272
+ if (!isDoneStatus(result.status)) throw new Error(`No-op task failed: ${JSON.stringify(result)}`);
269
273
  const diff = getDiff(task.task_id);
270
274
  const verify = JSON.parse(readFileSync(join(task.path, "verify.json"), "utf-8"));
271
275
  if (verify.status !== "skipped") throw new Error(`Expected skipped verification: ${JSON.stringify(verify)}`);
@@ -286,7 +290,7 @@ try {
286
290
  const plan = savePlan({ title: "Large diff", content: "Create a large text file." });
287
291
  const task = createTask({ plan_id: plan.plan_id, agent: "largewriter", repo_path: "repo" });
288
292
  const result = await runTask(task.task_id);
289
- if (result.status !== "done") throw new Error(`Large task failed: ${JSON.stringify(result)}`);
293
+ if (!isDoneStatus(result.status)) throw new Error(`Large task failed: ${JSON.stringify(result)}`);
290
294
  const diff = getDiff(task.task_id);
291
295
  const patchSize = readFileSync(join(task.path, "diff.patch"), "utf-8").length;
292
296
  if (!diff.truncated || !diff.patch_head || !diff.diff_patch_path || patchSize <= diff.content.length) {
@@ -298,7 +302,7 @@ try {
298
302
  const plan = savePlan({ title: "Non-Git evidence", content: "Modify files in a non-Git repository." });
299
303
  const task = createTask({ plan_id: plan.plan_id, agent: "writer", repo_path: "plain-repo" });
300
304
  const result = await runTask(task.task_id);
301
- if (result.status !== "done") throw new Error(`Non-Git task failed: ${JSON.stringify(result)}`);
305
+ if (!isDoneStatus(result.status)) throw new Error(`Non-Git task failed: ${JSON.stringify(result)}`);
302
306
  const diff = getDiff(task.task_id);
303
307
  if (
304
308
  diff.patch_mode !== "hash_only" ||
@@ -313,7 +317,7 @@ try {
313
317
  const plan = savePlan({ title: "Binary evidence", content: "Create a binary fixture." });
314
318
  const task = createTask({ plan_id: plan.plan_id, agent: "binarywriter", repo_path: "repo" });
315
319
  const result = await runTask(task.task_id);
316
- if (result.status !== "done") throw new Error(`Binary task failed: ${JSON.stringify(result)}`);
320
+ if (!isDoneStatus(result.status)) throw new Error(`Binary task failed: ${JSON.stringify(result)}`);
317
321
  const diff = getDiff(task.task_id);
318
322
  if (diff.patch_mode !== "textual" || !diff.content.includes("GIT binary patch")) {
319
323
  throw new Error(`Binary patch evidence mismatch: ${JSON.stringify(diff)}`);
@@ -324,7 +328,7 @@ try {
324
328
  const plan = savePlan({ title: "Artifact hygiene", content: "Generate representative task outputs." });
325
329
  const task = createTask({ plan_id: plan.plan_id, agent: "artifactwriter", repo_path: "repo" });
326
330
  const result = await runTask(task.task_id);
327
- if (result.status !== "done") throw new Error(`Artifact task failed: ${JSON.stringify(result)}`);
331
+ if (!isDoneStatus(result.status)) throw new Error(`Artifact task failed: ${JSON.stringify(result)}`);
328
332
  const standard = getTaskSummary(task.task_id);
329
333
  const compact = getTaskSummary(task.task_id, { view: "compact", max_items: 1 });
330
334
  const counts = standard.artifact_hygiene?.counts || {};
@@ -353,7 +357,7 @@ try {
353
357
  const plan = savePlan({ title: "Delete fixture", content: "Delete the designated fixture file." });
354
358
  const task = createTask({ plan_id: plan.plan_id, agent: "deleter", repo_path: "repo" });
355
359
  const result = await runTask(task.task_id);
356
- if (result.status !== "done") throw new Error(`Delete task failed: ${JSON.stringify(result)}`);
360
+ if (!isDoneStatus(result.status)) throw new Error(`Delete task failed: ${JSON.stringify(result)}`);
357
361
  const diff = getDiff(task.task_id);
358
362
  if (!diff.file_stats?.some((file) => file.path === "delete-me.txt" && file.status === "deleted")) {
359
363
  throw new Error(`Deleted file stats missing: ${JSON.stringify(diff.file_stats)}`);
@@ -504,7 +508,7 @@ try {
504
508
  const task = createTask({ plan_id: plan.plan_id, agent: "noop", repo_path: "repo", verify_commands: ["node --check main.js"] });
505
509
  const result = await runTask(task.task_id);
506
510
 
507
- if (result.status !== "done") {
511
+ if (!isDoneStatus(result.status)) {
508
512
  throw new Error(`Expected done, got: ${JSON.stringify(result)}`);
509
513
  }
510
514
  const structured = JSON.parse(readFileSync(join(task.path, "result.json"), "utf-8"));
@@ -4,18 +4,20 @@ import { resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
6
6
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
7
- import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES } from "../dist/tools/toolCatalog.js";
7
+ import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES, CHATGPT_SEARCH_TOOL_NAMES } from "../../dist/tools/toolCatalog.js";
8
8
 
9
- const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
9
+ const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
10
10
  const jsonOnly = process.argv.includes("--json");
11
11
  const profileIndex = process.argv.indexOf("--profile");
12
12
  const profile = profileIndex >= 0 ? process.argv[profileIndex + 1] : "chatgpt_core";
13
- if (profile !== "chatgpt_core" && profile !== "chatgpt_direct") {
13
+ if (profile !== "chatgpt_core" && profile !== "chatgpt_direct" && profile !== "chatgpt_search") {
14
14
  throw new Error(`Unsupported manifest profile "${profile}".`);
15
15
  }
16
16
  const expectedTools = profile === "chatgpt_direct"
17
17
  ? [...CHATGPT_DIRECT_TOOL_NAMES]
18
- : [...CHATGPT_CORE_TOOL_NAMES];
18
+ : profile === "chatgpt_search"
19
+ ? [...CHATGPT_SEARCH_TOOL_NAMES]
20
+ : [...CHATGPT_CORE_TOOL_NAMES];
19
21
  const defaultConfigPath = resolve(root, "patchwarden.config.json");
20
22
  const transportEnv = {
21
23
  ...process.env,
@@ -102,13 +104,31 @@ try {
102
104
  if (!summaryProperties.includes("view") || !summaryProperties.includes("max_items")) {
103
105
  throw new Error("get_task_summary schema must expose view and max_items");
104
106
  }
105
- } else {
107
+ const safeRequirements = {
108
+ safe_result: ["task_id"],
109
+ safe_audit: ["task_id"],
110
+ safe_test_summary: ["task_id"],
111
+ safe_diff_summary: ["task_id"],
112
+ };
113
+ for (const [toolName, requiredProperties] of Object.entries(safeRequirements)) {
114
+ const tool = listed.tools.find((entry) => entry.name === toolName);
115
+ const properties = Object.keys(tool?.inputSchema?.properties || {});
116
+ for (const property of requiredProperties) {
117
+ if (!properties.includes(property)) {
118
+ throw new Error(`${toolName} schema is missing ${property}`);
119
+ }
120
+ }
121
+ }
122
+ } else if (profile === "chatgpt_direct") {
106
123
  const directRequirements = {
107
124
  create_direct_session: ["repo_path"],
108
125
  apply_patch: ["session_id", "path", "expected_sha256", "operations"],
109
126
  run_verification: ["session_id", "command"],
110
127
  finalize_direct_session: ["session_id"],
111
128
  audit_session: ["session_id"],
129
+ safe_direct_summary: ["session_id"],
130
+ safe_finalize_direct_session: ["session_id"],
131
+ safe_audit_direct_session: ["session_id"],
112
132
  };
113
133
  for (const [toolName, requiredProperties] of Object.entries(directRequirements)) {
114
134
  const tool = listed.tools.find((entry) => entry.name === toolName);
@@ -119,6 +139,21 @@ try {
119
139
  }
120
140
  }
121
141
  }
142
+ } else {
143
+ const searchRequirements = {
144
+ discover_tools: ["query"],
145
+ explain_tool: ["name"],
146
+ invoke_discovered_tool: ["toolName", "arguments", "discoveryToken"],
147
+ };
148
+ for (const [toolName, requiredProperties] of Object.entries(searchRequirements)) {
149
+ const tool = listed.tools.find((entry) => entry.name === toolName);
150
+ const properties = Object.keys(tool?.inputSchema?.properties || {});
151
+ for (const property of requiredProperties) {
152
+ if (!properties.includes(property)) {
153
+ throw new Error(`${toolName} schema is missing ${property}`);
154
+ }
155
+ }
156
+ }
122
157
  }
123
158
  const healthResult = await client.callTool({ name: "health_check", arguments: {} });
124
159
  if (healthResult.isError) throw new Error(String(healthResult.content?.[0]?.text || "health_check failed"));
@@ -127,7 +162,8 @@ try {
127
162
  health.tool_profile !== profile ||
128
163
  health.tool_count !== expectedTools.length ||
129
164
  !health.tool_manifest_sha256 ||
130
- (profile === "chatgpt_direct" && health.direct_profile_enabled !== true)
165
+ (profile === "chatgpt_direct" && health.direct_profile_enabled !== true) ||
166
+ (profile === "chatgpt_search" && health.search_tool_count !== expectedTools.length)
131
167
  ) {
132
168
  throw new Error(`health_check catalog mismatch: ${JSON.stringify(health)}`);
133
169
  }
@@ -164,12 +200,23 @@ try {
164
200
  health_check: ["detail"],
165
201
  list_tasks: ["repo_path", "active_only"],
166
202
  get_task_summary: ["view", "max_items"],
167
- } : {
203
+ safe_result: ["task_id"],
204
+ safe_audit: ["task_id"],
205
+ safe_test_summary: ["task_id"],
206
+ safe_diff_summary: ["task_id"],
207
+ } : profile === "chatgpt_direct" ? {
168
208
  create_direct_session: ["repo_path"],
169
209
  apply_patch: ["session_id", "path", "expected_sha256", "operations"],
170
210
  run_verification: ["session_id", "command"],
171
211
  finalize_direct_session: ["session_id"],
172
212
  audit_session: ["session_id"],
213
+ safe_direct_summary: ["session_id"],
214
+ safe_finalize_direct_session: ["session_id"],
215
+ safe_audit_direct_session: ["session_id"],
216
+ } : {
217
+ discover_tools: ["query"],
218
+ explain_tool: ["name"],
219
+ invoke_discovered_tool: ["toolName", "arguments", "discoveryToken"],
173
220
  },
174
221
  };
175
222
  console.log(jsonOnly ? JSON.stringify(output) : JSON.stringify(output, null, 2));
@@ -14,7 +14,9 @@ import { fileURLToPath } from "node:url";
14
14
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
15
15
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
16
16
 
17
- const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
17
+ const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
18
+ const packageJson = JSON.parse(readFileSync(join(root, "package.json"), "utf-8"));
19
+ const expectedServerVersion = packageJson.version;
18
20
  const tempRoot = mkdtempSync(join(tmpdir(), "patchwarden-mcp-"));
19
21
  const workspaceRoot = join(tempRoot, "workspace");
20
22
  const configPath = join(tempRoot, "patchwarden.config.json");
@@ -101,12 +103,21 @@ try {
101
103
  const tools = await client.listTools();
102
104
  const names = tools.tools.map((tool) => tool.name).sort();
103
105
  const expected = [
106
+ "accept_subgoal",
104
107
  "apply_patch",
105
108
  "audit_session",
106
109
  "audit_task",
107
110
  "cancel_task",
111
+ "check_release_gate",
108
112
  "create_direct_session",
113
+ "create_goal",
114
+ "create_subgoal_task",
109
115
  "create_task",
116
+ "diagnose_task",
117
+ "discard_worktree",
118
+ "discover_tools",
119
+ "explain_tool",
120
+ "export_handoff",
110
121
  "finalize_direct_session",
111
122
  "get_diff",
112
123
  "get_plan",
@@ -119,16 +130,31 @@ try {
119
130
  "get_task_summary",
120
131
  "get_test_log",
121
132
  "health_check",
133
+ "invoke_discovered_tool",
122
134
  "kill_task",
123
135
  "list_agents",
136
+ "list_goals",
124
137
  "list_tasks",
125
138
  "list_workspace",
139
+ "merge_worktree",
140
+ "read_goal",
126
141
  "read_workspace_file",
142
+ "reconcile_tasks",
143
+ "reject_subgoal",
127
144
  "retry_task",
128
145
  "run_verification",
146
+ "safe_audit",
147
+ "safe_audit_direct_session",
148
+ "safe_diff_summary",
149
+ "safe_direct_summary",
150
+ "safe_finalize_direct_session",
151
+ "safe_result",
129
152
  "safe_status",
153
+ "safe_test_summary",
130
154
  "save_plan",
131
155
  "search_workspace",
156
+ "suggest_next_subgoal",
157
+ "summarize_goal_progress",
132
158
  "sync_file",
133
159
  "wait_for_task",
134
160
  ];
@@ -141,7 +167,7 @@ try {
141
167
  if (!tools._meta || typeof tools._meta.tool_manifest_sha256 !== "string" || tools._meta.tool_manifest_sha256.length !== 64) {
142
168
  throw new Error(`tools/list _meta missing manifest hash: ${JSON.stringify(tools._meta || null)}`);
143
169
  }
144
- if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !== 30) {
170
+ if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !== 54) {
145
171
  throw new Error(`tools/list _meta profile/count mismatch: ${JSON.stringify(tools._meta)}`);
146
172
  }
147
173
  if (typeof tools._meta.schema_epoch !== "string" || typeof tools._meta.server_version !== "string") {
@@ -212,7 +238,7 @@ try {
212
238
  test_command: "npm test",
213
239
  });
214
240
  if (
215
- task.server_version !== "0.6.1" ||
241
+ task.server_version !== expectedServerVersion ||
216
242
  !/^[a-f0-9]{64}$/.test(task.tool_manifest_sha256 || "") ||
217
243
  task.next_tool_call?.name !== "wait_for_task" ||
218
244
  task.next_tool_call?.arguments?.timeout_seconds !== 25
@@ -294,8 +320,8 @@ try {
294
320
 
295
321
  const statusPath = join(task.path, "status.json");
296
322
  const statusAfter = JSON.parse(readFileSync(statusPath, "utf-8"));
297
- if (statusAfter.status !== "done") {
298
- throw new Error(`runner status should be done, got ${statusAfter.status}`);
323
+ if (statusAfter.status !== "done" && statusAfter.status !== "done_by_agent") {
324
+ throw new Error(`runner status should be done or done_by_agent, got ${statusAfter.status}`);
299
325
  }
300
326
  for (const fileName of ["result.md", "result.json", "diff.patch", "git.diff", "file-stats.json", "test.log", "verify.json", "verify.log"]) {
301
327
  if (!existsSync(join(task.path, fileName))) {
@@ -391,7 +417,7 @@ try {
391
417
  await disabledClient.close();
392
418
  ok("chatgpt_direct disabled exposes only health_check with diagnostic");
393
419
 
394
- // 2. chatgpt_direct enabled: 10 tools + minimal create_direct_session
420
+ // 2. chatgpt_direct enabled: 13 tools + minimal create_direct_session
395
421
  const enabledConfigPath = join(tempRoot, "direct-enabled.json");
396
422
  const directRepo = join(workspaceRoot, "direct-fixture");
397
423
  mkdirSync(join(directRepo, "src"), { recursive: true });
@@ -448,14 +474,17 @@ try {
448
474
  "list_workspace",
449
475
  "read_workspace_file",
450
476
  "run_verification",
477
+ "safe_audit_direct_session",
478
+ "safe_direct_summary",
479
+ "safe_finalize_direct_session",
451
480
  "search_workspace",
452
481
  "sync_file",
453
482
  ];
454
483
  if (JSON.stringify(enabledNames) !== JSON.stringify(expectedDirect)) {
455
484
  throw new Error(`chatgpt_direct enabled tools mismatch: ${enabledNames.join(", ")}`);
456
485
  }
457
- if (enabledTools._meta.tool_count !== 10) {
458
- throw new Error(`chatgpt_direct enabled tool_count should be 10, got ${enabledTools._meta.tool_count}`);
486
+ if (enabledTools._meta.tool_count !== 13) {
487
+ throw new Error(`chatgpt_direct enabled tool_count should be 13, got ${enabledTools._meta.tool_count}`);
459
488
  }
460
489
 
461
490
  // Minimal create_direct_session
@@ -475,12 +504,12 @@ try {
475
504
  if (enabledHealth.direct_profile_enabled !== true) {
476
505
  throw new Error(`direct_profile_enabled should be true, got ${enabledHealth.direct_profile_enabled}`);
477
506
  }
478
- if (enabledHealth.direct_tool_count !== 10) {
479
- throw new Error(`direct_tool_count should be 10, got ${enabledHealth.direct_tool_count}`);
507
+ if (enabledHealth.direct_tool_count !== 13) {
508
+ throw new Error(`direct_tool_count should be 13, got ${enabledHealth.direct_tool_count}`);
480
509
  }
481
510
 
482
511
  await enabledClient.close();
483
- ok("chatgpt_direct enabled exposes 10 tools and create_direct_session works");
512
+ ok("chatgpt_direct enabled exposes 13 tools and create_direct_session works");
484
513
  } catch (error) {
485
514
  fail("MCP smoke test", error);
486
515
  } finally {
@@ -45,7 +45,8 @@ if (leaked.length > 0) {
45
45
 
46
46
  const required = [
47
47
  "PatchWarden.cmd",
48
- "scripts/manage-patchwarden.ps1",
48
+ "PatchWarden-Desktop.cmd",
49
+ "scripts/control/manage-patchwarden.ps1",
49
50
  "scripts/launchers/Start-PatchWarden-Tunnel.cmd",
50
51
  "scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd",
51
52
  ];
@@ -10,7 +10,7 @@ if (process.platform !== "win32") {
10
10
  process.exit(0);
11
11
  }
12
12
 
13
- const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
13
+ const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
14
14
  const temp = mkdtempSync(join(tmpdir(), "patchwarden-tunnel-smoke-"));
15
15
  const mockJs = join(temp, "mock-tunnel-client.js");
16
16
  const mockCmd = join(temp, "mock-tunnel-client.cmd");
@@ -57,7 +57,7 @@ if(command==='run'){
57
57
  MOCK_TUNNEL_STATE: stateFile,
58
58
  };
59
59
  const result = spawnSync("powershell.exe", [
60
- "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(root, "scripts", "start-patchwarden-tunnel.ps1"),
60
+ "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(root, "scripts", "control", "start-patchwarden-tunnel.ps1"),
61
61
  "-TunnelId", "tunnel_smoke_fixture",
62
62
  "-TunnelClientExe", mockCmd,
63
63
  "-ConfigPath", mockConfig,
@@ -5,7 +5,7 @@ import { resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
 
7
7
  const scriptDir = resolve(fileURLToPath(new URL(".", import.meta.url)));
8
- const root = resolve(scriptDir, "..");
8
+ const root = resolve(scriptDir, "..", "..");
9
9
  const unitDir = resolve(root, "dist", "test", "unit");
10
10
 
11
11
  if (!existsSync(unitDir)) {
@@ -19,7 +19,7 @@ if (process.platform !== "win32") {
19
19
  process.exit(0);
20
20
  }
21
21
 
22
- const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
22
+ const root = resolve(fileURLToPath(new URL("../..", import.meta.url)));
23
23
  const temp = mkdtempSync(join(tmpdir(), "patchwarden-watcher-supervisor-"));
24
24
  const children = [];
25
25
 
@@ -87,23 +87,29 @@ async function runExternalScenario() {
87
87
  function createFixture(name) {
88
88
  const project = join(temp, name, "patchwarden-fixture");
89
89
  const scripts = join(project, "scripts");
90
+ const controlScripts = join(scripts, "control");
91
+ const mcpScripts = join(scripts, "mcp");
92
+ const checkScripts = join(scripts, "checks");
90
93
  const runner = join(project, "dist", "runner");
91
94
  const workspace = join(project, "workspace");
92
95
  mkdirSync(scripts, { recursive: true });
96
+ mkdirSync(controlScripts, { recursive: true });
97
+ mkdirSync(mcpScripts, { recursive: true });
98
+ mkdirSync(checkScripts, { recursive: true });
93
99
  mkdirSync(runner, { recursive: true });
94
100
  mkdirSync(workspace, { recursive: true });
95
- cpSync(join(root, "scripts", "start-patchwarden-tunnel.ps1"), join(scripts, "start-patchwarden-tunnel.ps1"));
96
- writeFileSync(join(scripts, "patchwarden-mcp-stdio.cmd"), "@echo off\r\nexit /b 0\r\n", "utf-8");
101
+ cpSync(join(root, "scripts", "control", "start-patchwarden-tunnel.ps1"), join(controlScripts, "start-patchwarden-tunnel.ps1"));
102
+ writeFileSync(join(mcpScripts, "patchwarden-mcp-stdio.cmd"), "@echo off\r\nexit /b 0\r\n", "utf-8");
97
103
  const manifestFixture = JSON.stringify({
98
104
  ok: true,
99
105
  server_version: "0.6.0",
100
106
  schema_epoch: "2026-06-22-v6",
101
107
  tool_profile: "chatgpt_core",
102
- tool_count: 16,
108
+ tool_count: 21,
103
109
  tool_names: [],
104
110
  tool_manifest_sha256: "a".repeat(64),
105
111
  });
106
- writeFileSync(join(scripts, "mcp-manifest-check.js"), `process.stdout.write(${JSON.stringify(manifestFixture)});\n`, "utf-8");
112
+ writeFileSync(join(checkScripts, "mcp-manifest-check.js"), `process.stdout.write(${JSON.stringify(manifestFixture)});\n`, "utf-8");
107
113
  writeFileSync(join(project, "dist", "index.js"), "", "utf-8");
108
114
  const watcherAttemptPath = join(project, "watcher-attempt.txt");
109
115
  const watcherPath = join(runner, "watch.js");
@@ -140,7 +146,7 @@ function createFixture(name) {
140
146
 
141
147
  function runLauncher(fixture, mode, maxRestarts, lifetimeMs) {
142
148
  return spawnSync("powershell.exe", [
143
- "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(fixture.project, "scripts", "start-patchwarden-tunnel.ps1"),
149
+ "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", join(fixture.project, "scripts", "control", "start-patchwarden-tunnel.ps1"),
144
150
  "-TunnelId", "tunnel_watcher_fixture",
145
151
  "-TunnelClientExe", fixture.mockCmd,
146
152
  "-ProxyUrl", "http://127.0.0.1:1",
@@ -161,7 +167,7 @@ function fixtureEnv(fixture, mode, lifetimeMs) {
161
167
  ...process.env,
162
168
  APPDATA: fixture.appData,
163
169
  LOCALAPPDATA: fixture.localAppData,
164
- CONTROL_PLANE_API_KEY: "watcher-supervisor-smoke-secret",
170
+ CONTROL_PLANE_API_KEY: "test-key",
165
171
  WATCHER_FIXTURE_MODE: mode,
166
172
  TUNNEL_FIXTURE_LIFETIME_MS: String(lifetimeMs),
167
173
  };