gsd-pi 2.67.0-dev.1cd1e0f → 2.67.0-dev.2367d7e

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 (257) hide show
  1. package/README.md +1 -1
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +155 -70
  3. package/dist/resources/extensions/gsd/auto/phases.js +17 -0
  4. package/dist/resources/extensions/gsd/auto/session.js +10 -0
  5. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +12 -0
  6. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  7. package/dist/resources/extensions/gsd/auto-start.js +16 -30
  8. package/dist/resources/extensions/gsd/auto-worktree.js +62 -15
  9. package/dist/resources/extensions/gsd/auto.js +121 -59
  10. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +11 -435
  11. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -4
  12. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +7 -64
  13. package/dist/resources/extensions/gsd/bootstrap/system-context.js +7 -2
  14. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +88 -8
  15. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  16. package/dist/resources/extensions/gsd/commands/handlers/core.js +39 -25
  17. package/dist/resources/extensions/gsd/commands/index.js +8 -1
  18. package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
  19. package/dist/resources/extensions/gsd/doctor-git-checks.js +4 -4
  20. package/dist/resources/extensions/gsd/doctor-proactive.js +3 -3
  21. package/dist/resources/extensions/gsd/doctor.js +8 -4
  22. package/dist/resources/extensions/gsd/gsd-db.js +11 -0
  23. package/dist/resources/extensions/gsd/guided-flow.js +56 -31
  24. package/dist/resources/extensions/gsd/init-wizard.js +37 -0
  25. package/dist/resources/extensions/gsd/interrupted-session.js +146 -0
  26. package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
  27. package/dist/resources/extensions/gsd/state.js +7 -2
  28. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +508 -0
  29. package/dist/resources/extensions/gsd/workflow-logger.js +18 -3
  30. package/dist/resources/extensions/gsd/workflow-mcp.js +261 -0
  31. package/dist/web/standalone/.next/BUILD_ID +1 -1
  32. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  33. package/dist/web/standalone/.next/build-manifest.json +3 -3
  34. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  35. package/dist/web/standalone/.next/react-loadable-manifest.json +2 -2
  36. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  47. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/index.html +1 -1
  55. package/dist/web/standalone/.next/server/app/index.rsc +2 -2
  56. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  57. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +2 -2
  58. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  63. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  65. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  66. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  67. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  68. package/dist/web/standalone/.next/static/chunks/2826.821e01b07d92e948.js +9 -0
  69. package/dist/web/standalone/.next/static/chunks/app/{page-0c485498795110d6.js → page-f1e30ab6bb269149.js} +1 -1
  70. package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-6e4d7e9a4f57bed4.js} +1 -1
  71. package/package.json +4 -2
  72. package/packages/mcp-server/README.md +38 -0
  73. package/packages/mcp-server/dist/cli.d.ts +9 -0
  74. package/packages/mcp-server/dist/cli.d.ts.map +1 -0
  75. package/packages/mcp-server/dist/cli.js +58 -0
  76. package/packages/mcp-server/dist/cli.js.map +1 -0
  77. package/packages/mcp-server/dist/index.d.ts +20 -0
  78. package/packages/mcp-server/dist/index.d.ts.map +1 -0
  79. package/packages/mcp-server/dist/index.js +14 -0
  80. package/packages/mcp-server/dist/index.js.map +1 -0
  81. package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
  82. package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
  83. package/packages/mcp-server/dist/readers/captures.js +67 -0
  84. package/packages/mcp-server/dist/readers/captures.js.map +1 -0
  85. package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
  86. package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
  87. package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
  88. package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
  89. package/packages/mcp-server/dist/readers/index.d.ts +14 -0
  90. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
  91. package/packages/mcp-server/dist/readers/index.js +10 -0
  92. package/packages/mcp-server/dist/readers/index.js.map +1 -0
  93. package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
  94. package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
  95. package/packages/mcp-server/dist/readers/knowledge.js +82 -0
  96. package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
  97. package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
  98. package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
  99. package/packages/mcp-server/dist/readers/metrics.js +74 -0
  100. package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
  101. package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
  102. package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
  103. package/packages/mcp-server/dist/readers/paths.js +199 -0
  104. package/packages/mcp-server/dist/readers/paths.js.map +1 -0
  105. package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
  106. package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
  107. package/packages/mcp-server/dist/readers/roadmap.js +194 -0
  108. package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
  109. package/packages/mcp-server/dist/readers/state.d.ts +43 -0
  110. package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
  111. package/packages/mcp-server/dist/readers/state.js +184 -0
  112. package/packages/mcp-server/dist/readers/state.js.map +1 -0
  113. package/packages/mcp-server/dist/server.d.ts +28 -0
  114. package/packages/mcp-server/dist/server.d.ts.map +1 -0
  115. package/packages/mcp-server/dist/server.js +319 -0
  116. package/packages/mcp-server/dist/server.js.map +1 -0
  117. package/packages/mcp-server/dist/session-manager.d.ts +54 -0
  118. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
  119. package/packages/mcp-server/dist/session-manager.js +284 -0
  120. package/packages/mcp-server/dist/session-manager.js.map +1 -0
  121. package/packages/mcp-server/dist/types.d.ts +61 -0
  122. package/packages/mcp-server/dist/types.d.ts.map +1 -0
  123. package/packages/mcp-server/dist/types.js +11 -0
  124. package/packages/mcp-server/dist/types.js.map +1 -0
  125. package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
  126. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
  127. package/packages/mcp-server/dist/workflow-tools.js +532 -0
  128. package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
  129. package/packages/mcp-server/src/server.ts +6 -2
  130. package/packages/mcp-server/src/workflow-tools.test.ts +976 -0
  131. package/packages/mcp-server/src/workflow-tools.ts +997 -0
  132. package/packages/mcp-server/tsconfig.json +1 -1
  133. package/packages/pi-agent-core/dist/agent-loop.js +14 -6
  134. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  135. package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
  136. package/packages/pi-agent-core/src/agent-loop.ts +20 -6
  137. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts +43 -0
  138. package/packages/pi-coding-agent/dist/core/contextual-tips.d.ts.map +1 -0
  139. package/packages/pi-coding-agent/dist/core/contextual-tips.js +208 -0
  140. package/packages/pi-coding-agent/dist/core/contextual-tips.js.map +1 -0
  141. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts +2 -0
  142. package/packages/pi-coding-agent/dist/core/contextual-tips.test.d.ts.map +1 -0
  143. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js +227 -0
  144. package/packages/pi-coding-agent/dist/core/contextual-tips.test.js.map +1 -0
  145. package/packages/pi-coding-agent/dist/core/index.d.ts +1 -0
  146. package/packages/pi-coding-agent/dist/core/index.d.ts.map +1 -1
  147. package/packages/pi-coding-agent/dist/core/index.js +1 -0
  148. package/packages/pi-coding-agent/dist/core/index.js.map +1 -1
  149. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
  150. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
  151. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
  152. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
  153. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  154. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  155. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
  156. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  157. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  158. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
  159. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  160. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +4 -0
  161. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  162. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +14 -0
  163. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +3 -0
  165. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  166. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +15 -12
  167. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  168. package/packages/pi-coding-agent/src/core/contextual-tips.test.ts +259 -0
  169. package/packages/pi-coding-agent/src/core/contextual-tips.ts +232 -0
  170. package/packages/pi-coding-agent/src/core/index.ts +2 -0
  171. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
  172. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
  173. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
  174. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +19 -0
  175. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +19 -15
  176. package/packages/rpc-client/dist/index.d.ts +10 -0
  177. package/packages/rpc-client/dist/index.d.ts.map +1 -0
  178. package/packages/rpc-client/dist/index.js +9 -0
  179. package/packages/rpc-client/dist/index.js.map +1 -0
  180. package/packages/rpc-client/dist/jsonl.d.ts +17 -0
  181. package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
  182. package/packages/rpc-client/dist/jsonl.js +54 -0
  183. package/packages/rpc-client/dist/jsonl.js.map +1 -0
  184. package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
  185. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
  186. package/packages/rpc-client/dist/rpc-client.js +541 -0
  187. package/packages/rpc-client/dist/rpc-client.js.map +1 -0
  188. package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
  189. package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
  190. package/packages/rpc-client/dist/rpc-client.test.js +477 -0
  191. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
  192. package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
  193. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
  194. package/packages/rpc-client/dist/rpc-types.js +12 -0
  195. package/packages/rpc-client/dist/rpc-types.js.map +1 -0
  196. package/scripts/ensure-workspace-builds.cjs +2 -0
  197. package/scripts/link-workspace-packages.cjs +21 -14
  198. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +193 -93
  199. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +173 -79
  200. package/src/resources/extensions/gsd/auto/phases.ts +25 -0
  201. package/src/resources/extensions/gsd/auto/session.ts +10 -0
  202. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +20 -0
  203. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  204. package/src/resources/extensions/gsd/auto-start.ts +23 -55
  205. package/src/resources/extensions/gsd/auto-worktree.ts +59 -15
  206. package/src/resources/extensions/gsd/auto.ts +133 -64
  207. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +22 -435
  208. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -5
  209. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +7 -72
  210. package/src/resources/extensions/gsd/bootstrap/system-context.ts +8 -2
  211. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +122 -6
  212. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  213. package/src/resources/extensions/gsd/commands/handlers/core.ts +53 -26
  214. package/src/resources/extensions/gsd/commands/index.ts +7 -1
  215. package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
  216. package/src/resources/extensions/gsd/doctor-git-checks.ts +4 -4
  217. package/src/resources/extensions/gsd/doctor-proactive.ts +3 -3
  218. package/src/resources/extensions/gsd/doctor.ts +9 -5
  219. package/src/resources/extensions/gsd/gsd-db.ts +12 -0
  220. package/src/resources/extensions/gsd/guided-flow.ts +66 -36
  221. package/src/resources/extensions/gsd/init-wizard.ts +40 -0
  222. package/src/resources/extensions/gsd/interrupted-session.ts +224 -0
  223. package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
  224. package/src/resources/extensions/gsd/state.ts +7 -1
  225. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
  226. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +668 -2
  227. package/src/resources/extensions/gsd/tests/cold-resume-db-reopen.test.ts +14 -4
  228. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +21 -0
  229. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +101 -0
  230. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +380 -2
  231. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +66 -0
  232. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +30 -0
  233. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +12 -0
  234. package/src/resources/extensions/gsd/tests/guided-flow-session-isolation.test.ts +2 -2
  235. package/src/resources/extensions/gsd/tests/integration/doctor-fixlevel.test.ts +52 -1
  236. package/src/resources/extensions/gsd/tests/integration/doctor-git.test.ts +2 -9
  237. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +0 -33
  238. package/src/resources/extensions/gsd/tests/integration/merge-cwd-restore.test.ts +169 -0
  239. package/src/resources/extensions/gsd/tests/interrupted-session-auto.test.ts +146 -0
  240. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +136 -0
  241. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
  242. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
  243. package/src/resources/extensions/gsd/tests/verification-operational-gate.test.ts +11 -0
  244. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +16 -0
  245. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +500 -0
  246. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +625 -0
  247. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +629 -0
  248. package/src/resources/extensions/gsd/workflow-logger.ts +19 -3
  249. package/src/resources/extensions/gsd/workflow-mcp.ts +320 -0
  250. package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
  251. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts +0 -13
  252. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.d.ts.map +0 -1
  253. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js +0 -27
  254. package/packages/pi-coding-agent/dist/modes/interactive/provider-auth-setup.js.map +0 -1
  255. package/packages/pi-coding-agent/src/modes/interactive/provider-auth-setup.ts +0 -40
  256. /package/dist/web/standalone/.next/static/{PHqEommYRR8CRn3i84CGM → WMDT_0C0XDkBKtsAI_AX4}/_buildManifest.js +0 -0
  257. /package/dist/web/standalone/.next/static/{PHqEommYRR8CRn3i84CGM → WMDT_0C0XDkBKtsAI_AX4}/_ssgManifest.js +0 -0
@@ -0,0 +1,625 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { mkdirSync, rmSync, readFileSync, existsSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ import { randomUUID } from "node:crypto";
7
+
8
+ import {
9
+ openDatabase,
10
+ closeDatabase,
11
+ _getAdapter,
12
+ insertGateRow,
13
+ } from "../gsd-db.ts";
14
+ import {
15
+ executeCompleteMilestone,
16
+ executePlanMilestone,
17
+ executePlanSlice,
18
+ executeReplanSlice,
19
+ executeReassessRoadmap,
20
+ executeSaveGateResult,
21
+ executeSummarySave,
22
+ executeTaskComplete,
23
+ executeMilestoneStatus,
24
+ executeSliceComplete,
25
+ executeValidateMilestone,
26
+ } from "../tools/workflow-tool-executors.ts";
27
+
28
+ function makeTmpBase(): string {
29
+ const base = join(tmpdir(), `gsd-workflow-executors-${randomUUID()}`);
30
+ mkdirSync(join(base, ".gsd"), { recursive: true });
31
+ return base;
32
+ }
33
+
34
+ function cleanup(base: string): void {
35
+ try { rmSync(base, { recursive: true, force: true }); } catch { /* swallow */ }
36
+ }
37
+
38
+ function openTestDb(base: string): void {
39
+ openDatabase(join(base, ".gsd", "gsd.db"));
40
+ }
41
+
42
+ async function inProjectDir<T>(dir: string, fn: () => Promise<T>): Promise<T> {
43
+ const originalCwd = process.cwd();
44
+ try {
45
+ process.chdir(dir);
46
+ return await fn();
47
+ } finally {
48
+ process.chdir(originalCwd);
49
+ }
50
+ }
51
+
52
+ function seedMilestone(milestoneId: string, title: string, status = "active"): void {
53
+ const db = _getAdapter();
54
+ if (!db) throw new Error("DB not open");
55
+ db.prepare(
56
+ "INSERT OR REPLACE INTO milestones (id, title, status, created_at) VALUES (?, ?, ?, ?)",
57
+ ).run(milestoneId, title, status, new Date().toISOString());
58
+ }
59
+
60
+ function seedSlice(milestoneId: string, sliceId: string, status: string): void {
61
+ const db = _getAdapter();
62
+ if (!db) throw new Error("DB not open");
63
+ db.prepare(
64
+ "INSERT OR REPLACE INTO slices (milestone_id, id, title, status, created_at) VALUES (?, ?, ?, ?, ?)",
65
+ ).run(milestoneId, sliceId, `Slice ${sliceId}`, status, new Date().toISOString());
66
+ }
67
+
68
+ function writeRoadmap(base: string, milestoneId: string, sliceIds: string[]): void {
69
+ const milestoneDir = join(base, ".gsd", "milestones", milestoneId);
70
+ mkdirSync(milestoneDir, { recursive: true });
71
+ const lines = [
72
+ `# ${milestoneId}: Workflow MCP planning`,
73
+ "",
74
+ "## Slices",
75
+ "",
76
+ ...sliceIds.map((sliceId) => `- [ ] **${sliceId}: Slice ${sliceId}** \`risk:medium\` \`depends:[]\`\n - After this: demo`),
77
+ "",
78
+ ];
79
+ writeFileSync(join(milestoneDir, `${milestoneId}-ROADMAP.md`), lines.join("\n"));
80
+ }
81
+
82
+ test("executeSummarySave persists artifact and returns computed path", async () => {
83
+ const base = makeTmpBase();
84
+ try {
85
+ openTestDb(base);
86
+ const result = await inProjectDir(base, () => executeSummarySave({
87
+ milestone_id: "M001",
88
+ slice_id: "S01",
89
+ artifact_type: "SUMMARY",
90
+ content: "# Summary\n\ncontent",
91
+ }, base));
92
+
93
+ assert.equal(result.details.operation, "save_summary");
94
+ assert.equal(result.details.path, "milestones/M001/slices/S01/S01-SUMMARY.md");
95
+
96
+ const filePath = join(base, ".gsd", "milestones", "M001", "slices", "S01", "S01-SUMMARY.md");
97
+ assert.ok(existsSync(filePath), "summary artifact should be written to disk");
98
+ assert.match(readFileSync(filePath, "utf-8"), /# Summary/);
99
+ } finally {
100
+ closeDatabase();
101
+ cleanup(base);
102
+ }
103
+ });
104
+
105
+ test("executeTaskComplete coerces string verificationEvidence entries", async () => {
106
+ const base = makeTmpBase();
107
+ try {
108
+ openTestDb(base);
109
+ const planDir = join(base, ".gsd", "milestones", "M001", "slices", "S01");
110
+ mkdirSync(planDir, { recursive: true });
111
+ writeFileSync(join(planDir, "S01-PLAN.md"), "# S01\n\n- [ ] **T01: Demo** `est:5m`\n");
112
+
113
+ const result = await inProjectDir(base, () => executeTaskComplete({
114
+ milestoneId: "M001",
115
+ sliceId: "S01",
116
+ taskId: "T01",
117
+ oneLiner: "Completed task",
118
+ narrative: "Did the work",
119
+ verification: "npm test",
120
+ verificationEvidence: ["npm test"],
121
+ }, base));
122
+
123
+ assert.equal(result.details.operation, "complete_task");
124
+ assert.equal(result.details.taskId, "T01");
125
+
126
+ const db = _getAdapter();
127
+ assert.ok(db, "DB should be open");
128
+ const rows = db!.prepare(
129
+ "SELECT command, exit_code, verdict, duration_ms FROM verification_evidence WHERE milestone_id = ? AND slice_id = ? AND task_id = ?",
130
+ ).all("M001", "S01", "T01") as Array<Record<string, unknown>>;
131
+
132
+ assert.equal(rows.length, 1, "one coerced verification evidence row should be inserted");
133
+ assert.equal(rows[0]["command"], "npm test");
134
+ assert.equal(rows[0]["exit_code"], -1);
135
+ assert.match(String(rows[0]["verdict"]), /coerced from string/);
136
+
137
+ const summaryPath = String(result.details.summaryPath);
138
+ assert.ok(existsSync(summaryPath), "task summary should be written to disk");
139
+ } finally {
140
+ closeDatabase();
141
+ cleanup(base);
142
+ }
143
+ });
144
+
145
+ test("executeMilestoneStatus returns milestone metadata and slice counts", async () => {
146
+ const base = makeTmpBase();
147
+ try {
148
+ openTestDb(base);
149
+ seedMilestone("M001", "Milestone One");
150
+ seedSlice("M001", "S01", "active");
151
+ const db = _getAdapter();
152
+ db!.prepare(
153
+ "INSERT OR REPLACE INTO tasks (milestone_id, slice_id, id, title, status) VALUES (?, ?, ?, ?, ?)",
154
+ ).run("M001", "S01", "T01", "Task T01", "pending");
155
+
156
+ const result = await inProjectDir(base, () => executeMilestoneStatus({ milestoneId: "M001" }, base));
157
+ const parsed = JSON.parse(result.content[0].text);
158
+
159
+ assert.equal(parsed.milestoneId, "M001");
160
+ assert.equal(parsed.title, "Milestone One");
161
+ assert.equal(parsed.sliceCount, 1);
162
+ assert.equal(parsed.slices[0].id, "S01");
163
+ assert.equal(parsed.slices[0].taskCounts.pending, 1);
164
+ } finally {
165
+ closeDatabase();
166
+ cleanup(base);
167
+ }
168
+ });
169
+
170
+ test("executePlanMilestone writes roadmap state and rendered roadmap path", async () => {
171
+ const base = makeTmpBase();
172
+ try {
173
+ openTestDb(base);
174
+
175
+ const result = await inProjectDir(base, () => executePlanMilestone({
176
+ milestoneId: "M001",
177
+ title: "Workflow MCP planning",
178
+ vision: "Plan milestone over shared executors.",
179
+ slices: [
180
+ {
181
+ sliceId: "S01",
182
+ title: "Bridge planning",
183
+ risk: "medium",
184
+ depends: [],
185
+ demo: "Milestone plan persists through MCP.",
186
+ goal: "Persist roadmap state.",
187
+ successCriteria: "ROADMAP.md renders from DB.",
188
+ proofLevel: "integration",
189
+ integrationClosure: "Prompts and MCP call the same handler.",
190
+ observabilityImpact: "Executor tests cover output paths.",
191
+ },
192
+ ],
193
+ }, base));
194
+
195
+ assert.equal(result.details.operation, "plan_milestone");
196
+ assert.equal(result.details.milestoneId, "M001");
197
+ const roadmapPath = String(result.details.roadmapPath);
198
+ assert.ok(existsSync(roadmapPath), "roadmap should be rendered to disk");
199
+ assert.match(readFileSync(roadmapPath, "utf-8"), /Workflow MCP planning/);
200
+ } finally {
201
+ closeDatabase();
202
+ cleanup(base);
203
+ }
204
+ });
205
+
206
+ test("executePlanSlice writes task planning state and rendered plan artifacts", async () => {
207
+ const base = makeTmpBase();
208
+ try {
209
+ openTestDb(base);
210
+ await inProjectDir(base, () => executePlanMilestone({
211
+ milestoneId: "M001",
212
+ title: "Workflow MCP planning",
213
+ vision: "Plan milestone over shared executors.",
214
+ slices: [
215
+ {
216
+ sliceId: "S01",
217
+ title: "Bridge planning",
218
+ risk: "medium",
219
+ depends: [],
220
+ demo: "Milestone plan persists through MCP.",
221
+ goal: "Persist roadmap state.",
222
+ successCriteria: "ROADMAP.md renders from DB.",
223
+ proofLevel: "integration",
224
+ integrationClosure: "Prompts and MCP call the same handler.",
225
+ observabilityImpact: "Executor tests cover output paths.",
226
+ },
227
+ ],
228
+ }, base));
229
+
230
+ const result = await inProjectDir(base, () => executePlanSlice({
231
+ milestoneId: "M001",
232
+ sliceId: "S01",
233
+ goal: "Persist slice plan over MCP.",
234
+ tasks: [
235
+ {
236
+ taskId: "T01",
237
+ title: "Add planning bridge",
238
+ description: "Implement the shared executor path.",
239
+ estimate: "15m",
240
+ files: ["src/resources/extensions/gsd/tools/workflow-tool-executors.ts"],
241
+ verify: "node --test",
242
+ inputs: ["ROADMAP.md"],
243
+ expectedOutput: ["S01-PLAN.md", "T01-PLAN.md"],
244
+ },
245
+ ],
246
+ }, base));
247
+
248
+ assert.equal(result.details.operation, "plan_slice");
249
+ assert.equal(result.details.sliceId, "S01");
250
+ const planPath = String(result.details.planPath);
251
+ assert.ok(existsSync(planPath), "slice plan should be rendered to disk");
252
+ assert.match(readFileSync(planPath, "utf-8"), /Persist slice plan over MCP/);
253
+ } finally {
254
+ closeDatabase();
255
+ cleanup(base);
256
+ }
257
+ });
258
+
259
+ test("executeSliceComplete coerces string enrichment entries and writes summary/UAT artifacts", async () => {
260
+ const base = makeTmpBase();
261
+ try {
262
+ openTestDb(base);
263
+ seedMilestone("M001", "Milestone One");
264
+ seedSlice("M001", "S01", "pending");
265
+ writeRoadmap(base, "M001", ["S01"]);
266
+ const db = _getAdapter();
267
+ db!.prepare(
268
+ "INSERT OR REPLACE INTO tasks (milestone_id, slice_id, id, title, status) VALUES (?, ?, ?, ?, ?)",
269
+ ).run("M001", "S01", "T01", "Task T01", "complete");
270
+
271
+ const rawParams = {
272
+ milestoneId: "M001",
273
+ sliceId: "S01",
274
+ sliceTitle: "Slice S01",
275
+ oneLiner: "Completed slice",
276
+ narrative: "Implemented the slice",
277
+ verification: "node --test",
278
+ uatContent: "## UAT\n\nPASS",
279
+ provides: "shared executor path",
280
+ requirementsAdvanced: ["R001 - added slice completion support"],
281
+ filesModified: ["src/file.ts - updated logic"],
282
+ requires: ["S00 - upstream context"],
283
+ } as unknown as Parameters<typeof executeSliceComplete>[0];
284
+
285
+ const result = await inProjectDir(base, () => executeSliceComplete(rawParams, base));
286
+
287
+ assert.equal(result.details.operation, "complete_slice");
288
+ const summaryPath = String(result.details.summaryPath);
289
+ const uatPath = String(result.details.uatPath);
290
+ assert.ok(existsSync(summaryPath), "slice summary should be written to disk");
291
+ assert.ok(existsSync(uatPath), "slice UAT should be written to disk");
292
+ assert.match(readFileSync(summaryPath, "utf-8"), /shared executor path/);
293
+ assert.match(readFileSync(summaryPath, "utf-8"), /R001/);
294
+ } finally {
295
+ closeDatabase();
296
+ cleanup(base);
297
+ }
298
+ });
299
+
300
+ test("executeValidateMilestone persists validation artifact and gate records", async () => {
301
+ const base = makeTmpBase();
302
+ try {
303
+ openTestDb(base);
304
+ seedMilestone("M002", "Milestone Two");
305
+ seedSlice("M002", "S02", "complete");
306
+
307
+ const result = await inProjectDir(base, () => executeValidateMilestone({
308
+ milestoneId: "M002",
309
+ verdict: "pass",
310
+ remediationRound: 0,
311
+ successCriteriaChecklist: "- [x] Works",
312
+ sliceDeliveryAudit: "| Slice | Result |\n| --- | --- |\n| S02 | pass |",
313
+ crossSliceIntegration: "No cross-slice issues.",
314
+ requirementCoverage: "All requirements covered.",
315
+ verdictRationale: "Everything passed.",
316
+ }, base));
317
+
318
+ assert.equal(result.details.operation, "validate_milestone");
319
+ const validationPath = String(result.details.validationPath);
320
+ assert.ok(existsSync(validationPath), "validation file should be written to disk");
321
+
322
+ const db = _getAdapter();
323
+ const gates = db!.prepare(
324
+ "SELECT gate_id, verdict FROM quality_gates WHERE milestone_id = ? ORDER BY gate_id",
325
+ ).all("M002") as Array<Record<string, unknown>>;
326
+ assert.ok(gates.length > 0, "validation should seed milestone quality gates");
327
+ assert.equal(gates[0]["verdict"], "pass");
328
+ } finally {
329
+ closeDatabase();
330
+ cleanup(base);
331
+ }
332
+ });
333
+
334
+ test("executeCompleteMilestone sanitizes raw params and writes milestone summary", async () => {
335
+ const base = makeTmpBase();
336
+ try {
337
+ openTestDb(base);
338
+ seedMilestone("M003", "Milestone Three");
339
+ seedSlice("M003", "S03", "complete");
340
+ writeRoadmap(base, "M003", ["S03"]);
341
+ const db = _getAdapter();
342
+ db!.prepare(
343
+ "INSERT OR REPLACE INTO tasks (milestone_id, slice_id, id, title, status) VALUES (?, ?, ?, ?, ?)",
344
+ ).run("M003", "S03", "T03", "Task T03", "complete");
345
+
346
+ const rawParams = {
347
+ milestoneId: "M003",
348
+ title: "Milestone Three",
349
+ oneLiner: "Completed milestone",
350
+ narrative: "Everything shipped.",
351
+ verificationPassed: "true",
352
+ keyDecisions: ["shared executor path"],
353
+ lessonsLearned: ["MCP transport stays generic"],
354
+ } as unknown as Parameters<typeof executeCompleteMilestone>[0];
355
+
356
+ const result = await inProjectDir(base, () => executeCompleteMilestone(rawParams, base));
357
+
358
+ assert.equal(result.details.operation, "complete_milestone");
359
+ const summaryPath = String(result.details.summaryPath);
360
+ assert.ok(existsSync(summaryPath), "milestone summary should be written to disk");
361
+ assert.match(readFileSync(summaryPath, "utf-8"), /shared executor path/);
362
+ } finally {
363
+ closeDatabase();
364
+ cleanup(base);
365
+ }
366
+ });
367
+
368
+ test("executeReassessRoadmap writes assessment and updates roadmap projection", async () => {
369
+ const base = makeTmpBase();
370
+ try {
371
+ openTestDb(base);
372
+ await inProjectDir(base, () => executePlanMilestone({
373
+ milestoneId: "M004",
374
+ title: "Milestone Four",
375
+ vision: "Exercise roadmap reassessment.",
376
+ slices: [
377
+ {
378
+ sliceId: "S04",
379
+ title: "Completed slice",
380
+ risk: "medium",
381
+ depends: [],
382
+ demo: "Completed slice works",
383
+ goal: "Complete the first slice.",
384
+ successCriteria: "S04 is complete.",
385
+ proofLevel: "integration",
386
+ integrationClosure: "Baseline flow is wired.",
387
+ observabilityImpact: "Executor test covers reassessment.",
388
+ },
389
+ {
390
+ sliceId: "S05",
391
+ title: "Follow-up slice",
392
+ risk: "medium",
393
+ depends: ["S04"],
394
+ demo: "Follow-up slice is adjusted",
395
+ goal: "Handle the follow-up work.",
396
+ successCriteria: "Roadmap gets updated.",
397
+ proofLevel: "integration",
398
+ integrationClosure: "Downstream work stays aligned.",
399
+ observabilityImpact: "Assessment artifact is rendered.",
400
+ },
401
+ ],
402
+ }, base));
403
+ await inProjectDir(base, () => executePlanSlice({
404
+ milestoneId: "M004",
405
+ sliceId: "S04",
406
+ goal: "Complete the first slice.",
407
+ tasks: [
408
+ {
409
+ taskId: "T04",
410
+ title: "Finish slice",
411
+ description: "Close the completed slice.",
412
+ estimate: "5m",
413
+ files: ["src/file.ts"],
414
+ verify: "node --test",
415
+ inputs: ["M004-ROADMAP.md"],
416
+ expectedOutput: ["S04-SUMMARY.md", "S04-UAT.md"],
417
+ },
418
+ ],
419
+ }, base));
420
+ await inProjectDir(base, () => executeTaskComplete({
421
+ milestoneId: "M004",
422
+ sliceId: "S04",
423
+ taskId: "T04",
424
+ oneLiner: "Completed task",
425
+ narrative: "Task finished.",
426
+ verification: "node --test",
427
+ }, base));
428
+ await inProjectDir(base, () => executeSliceComplete({
429
+ milestoneId: "M004",
430
+ sliceId: "S04",
431
+ sliceTitle: "Completed slice",
432
+ oneLiner: "Completed slice",
433
+ narrative: "Slice finished.",
434
+ verification: "node --test",
435
+ uatContent: "## UAT\n\nPASS",
436
+ }, base));
437
+
438
+ const result = await inProjectDir(base, () => executeReassessRoadmap({
439
+ milestoneId: "M004",
440
+ completedSliceId: "S04",
441
+ verdict: "roadmap-adjusted",
442
+ assessment: "Added a remediation slice.",
443
+ sliceChanges: {
444
+ modified: [
445
+ {
446
+ sliceId: "S05",
447
+ title: "Adjusted follow-up slice",
448
+ risk: "high",
449
+ depends: ["S04"],
450
+ demo: "Adjusted follow-up demo",
451
+ },
452
+ ],
453
+ added: [
454
+ {
455
+ sliceId: "S06",
456
+ title: "Remediation slice",
457
+ risk: "medium",
458
+ depends: ["S05"],
459
+ demo: "Remediation slice demo",
460
+ },
461
+ ],
462
+ removed: [],
463
+ },
464
+ }, base));
465
+
466
+ assert.equal(result.details.operation, "reassess_roadmap");
467
+ const assessmentPath = String(result.details.assessmentPath);
468
+ const roadmapPath = String(result.details.roadmapPath);
469
+ assert.ok(existsSync(assessmentPath), "assessment file should be written");
470
+ assert.ok(existsSync(roadmapPath), "roadmap should be re-rendered");
471
+ assert.match(readFileSync(roadmapPath, "utf-8"), /S06/);
472
+ } finally {
473
+ closeDatabase();
474
+ cleanup(base);
475
+ }
476
+ });
477
+
478
+ test("executeSaveGateResult validates inputs and persists verdicts", async () => {
479
+ const base = makeTmpBase();
480
+ try {
481
+ openTestDb(base);
482
+ seedMilestone("M005", "Milestone Five");
483
+ seedSlice("M005", "S05", "pending");
484
+ insertGateRow({
485
+ milestoneId: "M005",
486
+ sliceId: "S05",
487
+ gateId: "Q3",
488
+ scope: "slice",
489
+ });
490
+
491
+ const result = await inProjectDir(base, () => executeSaveGateResult({
492
+ milestoneId: "M005",
493
+ sliceId: "S05",
494
+ gateId: "Q3",
495
+ verdict: "pass",
496
+ rationale: "Looks good.",
497
+ findings: "No issues found.",
498
+ }, base));
499
+
500
+ assert.equal(result.details.operation, "save_gate_result");
501
+ const db = _getAdapter();
502
+ const row = db!.prepare(
503
+ "SELECT status, verdict, rationale FROM quality_gates WHERE milestone_id = ? AND slice_id = ? AND gate_id = ? AND task_id = ''",
504
+ ).get("M005", "S05", "Q3") as Record<string, unknown> | undefined;
505
+ assert.equal(row?.status, "complete");
506
+ assert.equal(row?.verdict, "pass");
507
+ assert.equal(row?.rationale, "Looks good.");
508
+ } finally {
509
+ closeDatabase();
510
+ cleanup(base);
511
+ }
512
+ });
513
+
514
+ test("executeReplanSlice rewrites pending tasks and renders replan artifacts", async () => {
515
+ const base = makeTmpBase();
516
+ try {
517
+ openTestDb(base);
518
+ await inProjectDir(base, () => executePlanMilestone({
519
+ milestoneId: "M006",
520
+ title: "Milestone Six",
521
+ vision: "Exercise slice replanning.",
522
+ slices: [
523
+ {
524
+ sliceId: "S06",
525
+ title: "Replan slice",
526
+ risk: "medium",
527
+ depends: [],
528
+ demo: "Slice can be replanned after a blocker task completes.",
529
+ goal: "Prepare replan state.",
530
+ successCriteria: "PLAN and REPLAN artifacts update.",
531
+ proofLevel: "integration",
532
+ integrationClosure: "Replan shares the workflow executor path.",
533
+ observabilityImpact: "Executor test covers replan output files.",
534
+ },
535
+ ],
536
+ }, base));
537
+ await inProjectDir(base, () => executePlanSlice({
538
+ milestoneId: "M006",
539
+ sliceId: "S06",
540
+ goal: "Plan a slice that will be replanned.",
541
+ tasks: [
542
+ {
543
+ taskId: "T06",
544
+ title: "Blocker task",
545
+ description: "Finish the blocker-discovery task.",
546
+ estimate: "5m",
547
+ files: ["src/blocker.ts"],
548
+ verify: "node --test",
549
+ inputs: ["M006-ROADMAP.md"],
550
+ expectedOutput: ["T06-SUMMARY.md"],
551
+ },
552
+ {
553
+ taskId: "T07",
554
+ title: "Pending task",
555
+ description: "Original follow-up task.",
556
+ estimate: "10m",
557
+ files: ["src/pending.ts"],
558
+ verify: "node --test",
559
+ inputs: ["S06-PLAN.md"],
560
+ expectedOutput: ["Updated plan"],
561
+ },
562
+ ],
563
+ }, base));
564
+ await inProjectDir(base, () => executeTaskComplete({
565
+ milestoneId: "M006",
566
+ sliceId: "S06",
567
+ taskId: "T06",
568
+ oneLiner: "Completed blocker task",
569
+ narrative: "The blocker was identified and documented.",
570
+ verification: "node --test",
571
+ }, base));
572
+
573
+ const result = await inProjectDir(base, () => executeReplanSlice({
574
+ milestoneId: "M006",
575
+ sliceId: "S06",
576
+ blockerTaskId: "T06",
577
+ blockerDescription: "Original approach no longer works.",
578
+ whatChanged: "Adjusted the remaining tasks and added a remediation task.",
579
+ updatedTasks: [
580
+ {
581
+ taskId: "T07",
582
+ title: "Pending task (updated)",
583
+ description: "Updated follow-up task after replanning.",
584
+ estimate: "15m",
585
+ files: ["src/pending.ts", "src/replanned.ts"],
586
+ verify: "node --test",
587
+ inputs: ["S06-PLAN.md"],
588
+ expectedOutput: ["Updated plan"],
589
+ },
590
+ {
591
+ taskId: "T08",
592
+ title: "Remediation task",
593
+ description: "New task introduced by the replan.",
594
+ estimate: "20m",
595
+ files: ["src/remediation.ts"],
596
+ verify: "node --test",
597
+ inputs: ["S06-REPLAN.md"],
598
+ expectedOutput: ["Remediation patch"],
599
+ },
600
+ ],
601
+ removedTaskIds: [],
602
+ }, base));
603
+
604
+ assert.equal(result.details.operation, "replan_slice");
605
+ const planPath = String(result.details.planPath);
606
+ const replanPath = String(result.details.replanPath);
607
+ assert.ok(existsSync(planPath), "replanned plan should exist on disk");
608
+ assert.ok(existsSync(replanPath), "replan artifact should exist on disk");
609
+ assert.match(readFileSync(planPath, "utf-8"), /T08/);
610
+ assert.match(readFileSync(replanPath, "utf-8"), /Adjusted the remaining tasks/);
611
+
612
+ const db = _getAdapter();
613
+ const updatedTask = db!.prepare(
614
+ "SELECT title FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?",
615
+ ).get("M006", "S06", "T07") as Record<string, unknown> | undefined;
616
+ const insertedTask = db!.prepare(
617
+ "SELECT title FROM tasks WHERE milestone_id = ? AND slice_id = ? AND id = ?",
618
+ ).get("M006", "S06", "T08") as Record<string, unknown> | undefined;
619
+ assert.equal(updatedTask?.title, "Pending task (updated)");
620
+ assert.equal(insertedTask?.title, "Remediation task");
621
+ } finally {
622
+ closeDatabase();
623
+ cleanup(base);
624
+ }
625
+ });