gsd-pi 2.67.0-dev.a5b1d8f → 2.67.0-dev.fe39184

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 (191) hide show
  1. package/README.md +41 -31
  2. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +121 -8
  3. package/dist/resources/extensions/gsd/auto/phases.js +17 -0
  4. package/dist/resources/extensions/gsd/auto/session.js +6 -0
  5. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +12 -0
  6. package/dist/resources/extensions/gsd/auto-start.js +12 -0
  7. package/dist/resources/extensions/gsd/auto.js +27 -0
  8. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +11 -435
  9. package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +1 -4
  10. package/dist/resources/extensions/gsd/bootstrap/query-tools.js +7 -64
  11. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +88 -8
  12. package/dist/resources/extensions/gsd/commands/catalog.js +2 -1
  13. package/dist/resources/extensions/gsd/commands/handlers/core.js +39 -25
  14. package/dist/resources/extensions/gsd/commands/index.js +8 -1
  15. package/dist/resources/extensions/gsd/commands-mcp-status.js +43 -7
  16. package/dist/resources/extensions/gsd/guided-flow.js +16 -0
  17. package/dist/resources/extensions/gsd/init-wizard.js +37 -0
  18. package/dist/resources/extensions/gsd/mcp-project-config.js +83 -0
  19. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +508 -0
  20. package/dist/resources/extensions/gsd/workflow-logger.js +18 -3
  21. package/dist/resources/extensions/gsd/workflow-mcp.js +261 -0
  22. package/dist/web/standalone/.next/BUILD_ID +1 -1
  23. package/dist/web/standalone/.next/app-path-routes-manifest.json +15 -15
  24. package/dist/web/standalone/.next/build-manifest.json +3 -3
  25. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  26. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  27. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/index.html +1 -1
  44. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  45. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  47. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app-paths-manifest.json +15 -15
  51. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  53. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  54. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  55. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  56. package/dist/web/standalone/.next/static/chunks/6502.5dcdcf1e1432e20d.js +9 -0
  57. package/dist/web/standalone/.next/static/chunks/{webpack-b49b09f97429b5d0.js → webpack-42a66876b763aa26.js} +1 -1
  58. package/package.json +4 -2
  59. package/packages/mcp-server/README.md +38 -0
  60. package/packages/mcp-server/dist/cli.d.ts +9 -0
  61. package/packages/mcp-server/dist/cli.d.ts.map +1 -0
  62. package/packages/mcp-server/dist/cli.js +58 -0
  63. package/packages/mcp-server/dist/cli.js.map +1 -0
  64. package/packages/mcp-server/dist/index.d.ts +20 -0
  65. package/packages/mcp-server/dist/index.d.ts.map +1 -0
  66. package/packages/mcp-server/dist/index.js +14 -0
  67. package/packages/mcp-server/dist/index.js.map +1 -0
  68. package/packages/mcp-server/dist/readers/captures.d.ts +25 -0
  69. package/packages/mcp-server/dist/readers/captures.d.ts.map +1 -0
  70. package/packages/mcp-server/dist/readers/captures.js +67 -0
  71. package/packages/mcp-server/dist/readers/captures.js.map +1 -0
  72. package/packages/mcp-server/dist/readers/doctor-lite.d.ts +20 -0
  73. package/packages/mcp-server/dist/readers/doctor-lite.d.ts.map +1 -0
  74. package/packages/mcp-server/dist/readers/doctor-lite.js +173 -0
  75. package/packages/mcp-server/dist/readers/doctor-lite.js.map +1 -0
  76. package/packages/mcp-server/dist/readers/index.d.ts +14 -0
  77. package/packages/mcp-server/dist/readers/index.d.ts.map +1 -0
  78. package/packages/mcp-server/dist/readers/index.js +10 -0
  79. package/packages/mcp-server/dist/readers/index.js.map +1 -0
  80. package/packages/mcp-server/dist/readers/knowledge.d.ts +18 -0
  81. package/packages/mcp-server/dist/readers/knowledge.d.ts.map +1 -0
  82. package/packages/mcp-server/dist/readers/knowledge.js +82 -0
  83. package/packages/mcp-server/dist/readers/knowledge.js.map +1 -0
  84. package/packages/mcp-server/dist/readers/metrics.d.ts +32 -0
  85. package/packages/mcp-server/dist/readers/metrics.d.ts.map +1 -0
  86. package/packages/mcp-server/dist/readers/metrics.js +74 -0
  87. package/packages/mcp-server/dist/readers/metrics.js.map +1 -0
  88. package/packages/mcp-server/dist/readers/paths.d.ts +42 -0
  89. package/packages/mcp-server/dist/readers/paths.d.ts.map +1 -0
  90. package/packages/mcp-server/dist/readers/paths.js +199 -0
  91. package/packages/mcp-server/dist/readers/paths.js.map +1 -0
  92. package/packages/mcp-server/dist/readers/roadmap.d.ts +26 -0
  93. package/packages/mcp-server/dist/readers/roadmap.d.ts.map +1 -0
  94. package/packages/mcp-server/dist/readers/roadmap.js +194 -0
  95. package/packages/mcp-server/dist/readers/roadmap.js.map +1 -0
  96. package/packages/mcp-server/dist/readers/state.d.ts +43 -0
  97. package/packages/mcp-server/dist/readers/state.d.ts.map +1 -0
  98. package/packages/mcp-server/dist/readers/state.js +184 -0
  99. package/packages/mcp-server/dist/readers/state.js.map +1 -0
  100. package/packages/mcp-server/dist/server.d.ts +28 -0
  101. package/packages/mcp-server/dist/server.d.ts.map +1 -0
  102. package/packages/mcp-server/dist/server.js +319 -0
  103. package/packages/mcp-server/dist/server.js.map +1 -0
  104. package/packages/mcp-server/dist/session-manager.d.ts +54 -0
  105. package/packages/mcp-server/dist/session-manager.d.ts.map +1 -0
  106. package/packages/mcp-server/dist/session-manager.js +284 -0
  107. package/packages/mcp-server/dist/session-manager.js.map +1 -0
  108. package/packages/mcp-server/dist/types.d.ts +61 -0
  109. package/packages/mcp-server/dist/types.d.ts.map +1 -0
  110. package/packages/mcp-server/dist/types.js +11 -0
  111. package/packages/mcp-server/dist/types.js.map +1 -0
  112. package/packages/mcp-server/dist/workflow-tools.d.ts +9 -0
  113. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -0
  114. package/packages/mcp-server/dist/workflow-tools.js +532 -0
  115. package/packages/mcp-server/dist/workflow-tools.js.map +1 -0
  116. package/packages/mcp-server/src/server.ts +6 -2
  117. package/packages/mcp-server/src/workflow-tools.test.ts +976 -0
  118. package/packages/mcp-server/src/workflow-tools.ts +997 -0
  119. package/packages/mcp-server/tsconfig.json +1 -1
  120. package/packages/pi-agent-core/dist/agent-loop.js +14 -6
  121. package/packages/pi-agent-core/dist/agent-loop.js.map +1 -1
  122. package/packages/pi-agent-core/src/agent-loop.test.ts +53 -0
  123. package/packages/pi-agent-core/src/agent-loop.ts +20 -6
  124. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts +2 -0
  125. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.d.ts.map +1 -0
  126. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +28 -0
  127. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -0
  128. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts +1 -0
  129. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  130. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +17 -12
  131. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  132. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  133. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +19 -0
  134. package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  135. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +54 -0
  136. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +18 -12
  137. package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +21 -0
  138. package/packages/rpc-client/dist/index.d.ts +10 -0
  139. package/packages/rpc-client/dist/index.d.ts.map +1 -0
  140. package/packages/rpc-client/dist/index.js +9 -0
  141. package/packages/rpc-client/dist/index.js.map +1 -0
  142. package/packages/rpc-client/dist/jsonl.d.ts +17 -0
  143. package/packages/rpc-client/dist/jsonl.d.ts.map +1 -0
  144. package/packages/rpc-client/dist/jsonl.js +54 -0
  145. package/packages/rpc-client/dist/jsonl.js.map +1 -0
  146. package/packages/rpc-client/dist/rpc-client.d.ts +259 -0
  147. package/packages/rpc-client/dist/rpc-client.d.ts.map +1 -0
  148. package/packages/rpc-client/dist/rpc-client.js +541 -0
  149. package/packages/rpc-client/dist/rpc-client.js.map +1 -0
  150. package/packages/rpc-client/dist/rpc-client.test.d.ts +2 -0
  151. package/packages/rpc-client/dist/rpc-client.test.d.ts.map +1 -0
  152. package/packages/rpc-client/dist/rpc-client.test.js +477 -0
  153. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -0
  154. package/packages/rpc-client/dist/rpc-types.d.ts +566 -0
  155. package/packages/rpc-client/dist/rpc-types.d.ts.map +1 -0
  156. package/packages/rpc-client/dist/rpc-types.js +12 -0
  157. package/packages/rpc-client/dist/rpc-types.js.map +1 -0
  158. package/scripts/ensure-workspace-builds.cjs +2 -0
  159. package/scripts/link-workspace-packages.cjs +21 -14
  160. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +157 -8
  161. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +182 -0
  162. package/src/resources/extensions/gsd/auto/phases.ts +25 -0
  163. package/src/resources/extensions/gsd/auto/session.ts +6 -0
  164. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +20 -0
  165. package/src/resources/extensions/gsd/auto-start.ts +15 -1
  166. package/src/resources/extensions/gsd/auto.ts +29 -1
  167. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +22 -435
  168. package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +1 -5
  169. package/src/resources/extensions/gsd/bootstrap/query-tools.ts +7 -72
  170. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +122 -6
  171. package/src/resources/extensions/gsd/commands/catalog.ts +2 -1
  172. package/src/resources/extensions/gsd/commands/handlers/core.ts +53 -26
  173. package/src/resources/extensions/gsd/commands/index.ts +7 -1
  174. package/src/resources/extensions/gsd/commands-mcp-status.ts +53 -7
  175. package/src/resources/extensions/gsd/guided-flow.ts +24 -0
  176. package/src/resources/extensions/gsd/init-wizard.ts +40 -0
  177. package/src/resources/extensions/gsd/mcp-project-config.ts +128 -0
  178. package/src/resources/extensions/gsd/tests/auto-project-root-env.test.ts +29 -0
  179. package/src/resources/extensions/gsd/tests/core-overlay-fallback.test.ts +101 -0
  180. package/src/resources/extensions/gsd/tests/ensure-db-open.test.ts +66 -0
  181. package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +85 -0
  182. package/src/resources/extensions/gsd/tests/mcp-status.test.ts +15 -0
  183. package/src/resources/extensions/gsd/tests/workflow-logger.test.ts +16 -0
  184. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +500 -0
  185. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +625 -0
  186. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +629 -0
  187. package/src/resources/extensions/gsd/workflow-logger.ts +19 -3
  188. package/src/resources/extensions/gsd/workflow-mcp.ts +320 -0
  189. package/dist/web/standalone/.next/static/chunks/6502.b804e48b7919f55e.js +0 -9
  190. /package/dist/web/standalone/.next/static/{NllX5BEOLdTXS9ypf1i3i → gbSATDX4Jt2ufxzUr5nYm}/_buildManifest.js +0 -0
  191. /package/dist/web/standalone/.next/static/{NllX5BEOLdTXS9ypf1i3i → gbSATDX4Jt2ufxzUr5nYm}/_ssgManifest.js +0 -0
@@ -1,3 +1,5 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
1
3
  const MILESTONE_CONTEXT_RE = /M\d+(?:-[a-z0-9]{6})?-CONTEXT\.md$/;
2
4
  const CONTEXT_MILESTONE_RE = /(?:^|[/\\])(M\d+(?:-[a-z0-9]{6})?)-CONTEXT\.md$/i;
3
5
  const DEPTH_VERIFICATION_MILESTONE_RE = /depth_verification[_-](M\d+(?:-[a-z0-9]{6})?)/i;
@@ -57,6 +59,61 @@ const GATE_SAFE_TOOLS = new Set([
57
59
  "search-the-web", "resolve_library", "get_library_docs", "fetch_page",
58
60
  "search_and_read",
59
61
  ]);
62
+ function shouldPersistWriteGateSnapshot(env = process.env) {
63
+ return env.GSD_PERSIST_WRITE_GATE_STATE === "1";
64
+ }
65
+ function writeGateSnapshotPath(basePath = process.cwd()) {
66
+ return join(basePath, ".gsd", "runtime", "write-gate-state.json");
67
+ }
68
+ function currentWriteGateSnapshot() {
69
+ return {
70
+ verifiedDepthMilestones: [...verifiedDepthMilestones].sort(),
71
+ activeQueuePhase,
72
+ pendingGateId,
73
+ };
74
+ }
75
+ function persistWriteGateSnapshot(basePath = process.cwd()) {
76
+ if (!shouldPersistWriteGateSnapshot())
77
+ return;
78
+ const path = writeGateSnapshotPath(basePath);
79
+ mkdirSync(join(basePath, ".gsd", "runtime"), { recursive: true });
80
+ const tempPath = `${path}.tmp`;
81
+ writeFileSync(tempPath, JSON.stringify(currentWriteGateSnapshot(), null, 2), "utf-8");
82
+ renameSync(tempPath, path);
83
+ }
84
+ function clearPersistedWriteGateSnapshot(basePath = process.cwd()) {
85
+ if (!shouldPersistWriteGateSnapshot())
86
+ return;
87
+ const path = writeGateSnapshotPath(basePath);
88
+ try {
89
+ unlinkSync(path);
90
+ }
91
+ catch {
92
+ // swallow
93
+ }
94
+ }
95
+ function normalizeWriteGateSnapshot(value) {
96
+ const record = value && typeof value === "object" ? value : {};
97
+ const verified = Array.isArray(record.verifiedDepthMilestones)
98
+ ? record.verifiedDepthMilestones.filter((item) => typeof item === "string")
99
+ : [];
100
+ return {
101
+ verifiedDepthMilestones: [...new Set(verified)].sort(),
102
+ activeQueuePhase: record.activeQueuePhase === true,
103
+ pendingGateId: typeof record.pendingGateId === "string" ? record.pendingGateId : null,
104
+ };
105
+ }
106
+ export function loadWriteGateSnapshot(basePath = process.cwd()) {
107
+ const path = writeGateSnapshotPath(basePath);
108
+ if (!existsSync(path))
109
+ return currentWriteGateSnapshot();
110
+ try {
111
+ return normalizeWriteGateSnapshot(JSON.parse(readFileSync(path, "utf-8")));
112
+ }
113
+ catch {
114
+ return currentWriteGateSnapshot();
115
+ }
116
+ }
60
117
  export function isDepthVerified() {
61
118
  return verifiedDepthMilestones.size > 0;
62
119
  }
@@ -68,25 +125,34 @@ export function isMilestoneDepthVerified(milestoneId) {
68
125
  return false;
69
126
  return verifiedDepthMilestones.has(milestoneId);
70
127
  }
128
+ export function isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId) {
129
+ if (!milestoneId)
130
+ return false;
131
+ return snapshot.verifiedDepthMilestones.includes(milestoneId);
132
+ }
71
133
  export function isQueuePhaseActive() {
72
134
  return activeQueuePhase;
73
135
  }
74
136
  export function setQueuePhaseActive(active) {
75
137
  activeQueuePhase = active;
138
+ persistWriteGateSnapshot();
76
139
  }
77
140
  export function resetWriteGateState() {
78
141
  verifiedDepthMilestones.clear();
79
142
  pendingGateId = null;
143
+ persistWriteGateSnapshot();
80
144
  }
81
145
  export function clearDiscussionFlowState() {
82
146
  verifiedDepthMilestones.clear();
83
147
  activeQueuePhase = false;
84
148
  pendingGateId = null;
149
+ clearPersistedWriteGateSnapshot();
85
150
  }
86
- export function markDepthVerified(milestoneId) {
151
+ export function markDepthVerified(milestoneId, basePath = process.cwd()) {
87
152
  if (!milestoneId)
88
153
  return;
89
154
  verifiedDepthMilestones.add(milestoneId);
155
+ persistWriteGateSnapshot(basePath);
90
156
  }
91
157
  /**
92
158
  * Check whether a question ID matches a recognized gate pattern.
@@ -114,12 +180,14 @@ function extractContextMilestoneId(inputPath) {
114
180
  */
115
181
  export function setPendingGate(gateId) {
116
182
  pendingGateId = gateId;
183
+ persistWriteGateSnapshot();
117
184
  }
118
185
  /**
119
186
  * Clear the pending gate (called when the user confirms).
120
187
  */
121
188
  export function clearPendingGate() {
122
189
  pendingGateId = null;
190
+ persistWriteGateSnapshot();
123
191
  }
124
192
  /**
125
193
  * Get the currently pending gate, if any.
@@ -134,8 +202,11 @@ export function getPendingGate() {
134
202
  * Returns { block: true, reason } if the tool should be blocked.
135
203
  * Read-only tools and ask_user_questions itself are always allowed.
136
204
  */
137
- export function shouldBlockPendingGate(toolName, _milestoneId, _queuePhaseActive) {
138
- if (!pendingGateId)
205
+ export function shouldBlockPendingGate(toolName, milestoneId, queuePhaseActive) {
206
+ return shouldBlockPendingGateInSnapshot(currentWriteGateSnapshot(), toolName, milestoneId, queuePhaseActive);
207
+ }
208
+ export function shouldBlockPendingGateInSnapshot(snapshot, toolName, _milestoneId, _queuePhaseActive) {
209
+ if (!snapshot.pendingGateId)
139
210
  return { block: false };
140
211
  if (GATE_SAFE_TOOLS.has(toolName))
141
212
  return { block: false };
@@ -145,7 +216,7 @@ export function shouldBlockPendingGate(toolName, _milestoneId, _queuePhaseActive
145
216
  return {
146
217
  block: true,
147
218
  reason: [
148
- `HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
219
+ `HARD BLOCK: Discussion gate "${snapshot.pendingGateId}" has not been confirmed by the user.`,
149
220
  `You MUST re-call ask_user_questions with the gate question before making any other tool calls.`,
150
221
  `If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
151
222
  `did not match a provided option, you MUST re-ask — never rationalize past the block.`,
@@ -157,8 +228,11 @@ export function shouldBlockPendingGate(toolName, _milestoneId, _queuePhaseActive
157
228
  * Check whether a bash command should be blocked because a discussion gate is pending.
158
229
  * Read-only bash commands are allowed; mutating commands are blocked.
159
230
  */
160
- export function shouldBlockPendingGateBash(command, _milestoneId, _queuePhaseActive) {
161
- if (!pendingGateId)
231
+ export function shouldBlockPendingGateBash(command, milestoneId, queuePhaseActive) {
232
+ return shouldBlockPendingGateBashInSnapshot(currentWriteGateSnapshot(), command, milestoneId, queuePhaseActive);
233
+ }
234
+ export function shouldBlockPendingGateBashInSnapshot(snapshot, command, _milestoneId, _queuePhaseActive) {
235
+ if (!snapshot.pendingGateId)
162
236
  return { block: false };
163
237
  // Allow read-only bash commands
164
238
  if (BASH_READ_ONLY_RE.test(command))
@@ -166,7 +240,7 @@ export function shouldBlockPendingGateBash(command, _milestoneId, _queuePhaseAct
166
240
  return {
167
241
  block: true,
168
242
  reason: [
169
- `HARD BLOCK: Discussion gate "${pendingGateId}" has not been confirmed by the user.`,
243
+ `HARD BLOCK: Discussion gate "${snapshot.pendingGateId}" has not been confirmed by the user.`,
170
244
  `You MUST re-call ask_user_questions with the gate question before running mutating commands.`,
171
245
  `If the previous ask_user_questions call failed, errored, was cancelled, or the user's response`,
172
246
  `did not match a provided option, you MUST re-ask — never rationalize past the block.`,
@@ -232,6 +306,9 @@ export function shouldBlockContextWrite(toolName, inputPath, milestoneId, _queue
232
306
  * require the milestone to be depth-verified first.
233
307
  */
234
308
  export function shouldBlockContextArtifactSave(artifactType, milestoneId, sliceId) {
309
+ return shouldBlockContextArtifactSaveInSnapshot(currentWriteGateSnapshot(), artifactType, milestoneId, sliceId);
310
+ }
311
+ export function shouldBlockContextArtifactSaveInSnapshot(snapshot, artifactType, milestoneId, sliceId) {
235
312
  if (artifactType !== "CONTEXT")
236
313
  return { block: false };
237
314
  if (sliceId)
@@ -245,7 +322,7 @@ export function shouldBlockContextArtifactSave(artifactType, milestoneId, sliceI
245
322
  ].join(" "),
246
323
  };
247
324
  }
248
- if (isMilestoneDepthVerified(milestoneId))
325
+ if (isMilestoneDepthVerifiedInSnapshot(snapshot, milestoneId))
249
326
  return { block: false };
250
327
  return {
251
328
  block: true,
@@ -271,6 +348,9 @@ export function shouldBlockContextArtifactSave(artifactType, milestoneId, sliceI
271
348
  * @returns { block, reason } — block=true if the call should be rejected.
272
349
  */
273
350
  export function shouldBlockQueueExecution(toolName, input, queuePhaseActive) {
351
+ return shouldBlockQueueExecutionInSnapshot(currentWriteGateSnapshot(), toolName, input, queuePhaseActive);
352
+ }
353
+ export function shouldBlockQueueExecutionInSnapshot(snapshot, toolName, input, queuePhaseActive = snapshot.activeQueuePhase) {
274
354
  if (!queuePhaseActive)
275
355
  return { block: false };
276
356
  // Always-safe tools (read-only, discussion, planning)
@@ -58,7 +58,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
58
58
  { cmd: "templates", desc: "List available workflow templates" },
59
59
  { cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
60
60
  { cmd: "fast", desc: "Toggle OpenAI service tier (on/off/flex/status)" },
61
- { cmd: "mcp", desc: "MCP server status and connectivity check (status, check <server>)" },
61
+ { cmd: "mcp", desc: "MCP server status, connectivity, and local config bootstrap (status, check, init)" },
62
62
  { cmd: "rethink", desc: "Conversational project reorganization — reorder, park, discard, add milestones" },
63
63
  { cmd: "workflow", desc: "Custom workflow lifecycle (new, run, list, validate, pause, resume)" },
64
64
  { cmd: "codebase", desc: "Generate, refresh, and inspect the codebase map cache (.gsd/CODEBASE.md)" },
@@ -188,6 +188,7 @@ const NESTED_COMPLETIONS = {
188
188
  mcp: [
189
189
  { cmd: "status", desc: "Show all MCP server statuses (default)" },
190
190
  { cmd: "check", desc: "Detailed status for a specific server" },
191
+ { cmd: "init", desc: "Write .mcp.json for the local GSD workflow MCP server" },
191
192
  ],
192
193
  doctor: [
193
194
  { cmd: "fix", desc: "Auto-fix detected issues" },
@@ -55,7 +55,7 @@ export function showHelp(ctx) {
55
55
  " /gsd hooks Show post-unit hook configuration",
56
56
  " /gsd extensions Manage extensions [list|enable|disable|info]",
57
57
  " /gsd fast Toggle OpenAI service tier [on|off|flex|status]",
58
- " /gsd mcp MCP server status and connectivity [status|check <server>]",
58
+ " /gsd mcp MCP server status and connectivity [status|check <server>|init [dir]]",
59
59
  "",
60
60
  "MAINTENANCE",
61
61
  " /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
@@ -168,6 +168,42 @@ function sortModelsForSelection(models, currentModel) {
168
168
  return a.id.localeCompare(b.id);
169
169
  });
170
170
  }
171
+ function buildProviderModelGroups(models, currentModel) {
172
+ const byProvider = new Map();
173
+ for (const model of sortModelsForSelection(models, currentModel)) {
174
+ let group = byProvider.get(model.provider);
175
+ if (!group) {
176
+ group = [];
177
+ byProvider.set(model.provider, group);
178
+ }
179
+ group.push(model);
180
+ }
181
+ return byProvider;
182
+ }
183
+ async function selectModelByProvider(title, models, ctx, currentModel) {
184
+ const byProvider = buildProviderModelGroups(models, currentModel);
185
+ const providerOptions = Array.from(byProvider.entries()).map(([provider, group]) => `${provider} (${group.length} model${group.length === 1 ? "" : "s"})`);
186
+ providerOptions.push("(cancel)");
187
+ const providerChoice = await ctx.ui.select(`${title} — choose provider:`, providerOptions);
188
+ if (!providerChoice || typeof providerChoice !== "string" || providerChoice === "(cancel)")
189
+ return undefined;
190
+ const providerName = providerChoice.replace(/ \(\d+ models?\)$/, "");
191
+ const providerModels = byProvider.get(providerName);
192
+ if (!providerModels || providerModels.length === 0)
193
+ return undefined;
194
+ const optionToModel = new Map();
195
+ const modelOptions = providerModels.map((model) => {
196
+ const isCurrent = currentModel && model.provider === currentModel.provider && model.id === currentModel.id;
197
+ const label = `${isCurrent ? "* " : ""}${model.id}`;
198
+ optionToModel.set(label, model);
199
+ return label;
200
+ });
201
+ modelOptions.push("(cancel)");
202
+ const modelChoice = await ctx.ui.select(`${title} — ${providerName}:`, modelOptions);
203
+ if (!modelChoice || typeof modelChoice !== "string" || modelChoice === "(cancel)")
204
+ return undefined;
205
+ return optionToModel.get(modelChoice);
206
+ }
171
207
  async function resolveRequestedModel(query, ctx) {
172
208
  const { resolveModelId } = await import("../../auto-model-selection.js");
173
209
  const models = ctx.modelRegistry.getAvailable();
@@ -181,18 +217,7 @@ async function resolveRequestedModel(query, ctx) {
181
217
  return partialMatches[0];
182
218
  if (partialMatches.length === 0 || !ctx.hasUI)
183
219
  return undefined;
184
- const sorted = sortModelsForSelection(partialMatches, ctx.model);
185
- const optionToModel = new Map();
186
- const options = sorted.map((model) => {
187
- const label = `${model.provider}/${model.id}`;
188
- optionToModel.set(label, model);
189
- return label;
190
- });
191
- options.push("(cancel)");
192
- const choice = await ctx.ui.select(`Multiple models match "${query}" — choose one:`, options);
193
- if (!choice || typeof choice !== "string" || choice === "(cancel)")
194
- return undefined;
195
- return optionToModel.get(choice);
220
+ return selectModelByProvider(`Multiple models match "${query}"`, partialMatches, ctx, ctx.model);
196
221
  }
197
222
  async function handleModel(trimmedArgs, ctx, pi) {
198
223
  const availableModels = ctx.modelRegistry.getAvailable();
@@ -212,18 +237,7 @@ async function handleModel(trimmedArgs, ctx, pi) {
212
237
  ctx.ui.notify(`Current model: ${current}\nUsage: /gsd model <provider/model|model-id>`, "info");
213
238
  return;
214
239
  }
215
- const optionToModel = new Map();
216
- const options = sortModelsForSelection(availableModels, ctx.model).map((model) => {
217
- const isCurrent = ctx.model && model.provider === ctx.model.provider && model.id === ctx.model.id;
218
- const label = `${isCurrent ? "* " : ""}${model.provider}/${model.id}`;
219
- optionToModel.set(label, model);
220
- return label;
221
- });
222
- options.push("(cancel)");
223
- const choice = await ctx.ui.select("Select session model:", options);
224
- if (!choice || typeof choice !== "string" || choice === "(cancel)")
225
- return;
226
- targetModel = optionToModel.get(choice);
240
+ targetModel = await selectModelByProvider("Select session model:", availableModels, ctx, ctx.model);
227
241
  }
228
242
  else {
229
243
  targetModel = await resolveRequestedModel(trimmed, ctx);
@@ -5,7 +5,14 @@ export function registerGSDCommand(pi) {
5
5
  getArgumentCompletions: getGsdArgumentCompletions,
6
6
  handler: async (args, ctx) => {
7
7
  const { handleGSDCommand } = await import("./dispatcher.js");
8
- await handleGSDCommand(args, ctx, pi);
8
+ const { setStderrLoggingEnabled } = await import("../workflow-logger.js");
9
+ const previousStderrSetting = setStderrLoggingEnabled(false);
10
+ try {
11
+ await handleGSDCommand(args, ctx, pi);
12
+ }
13
+ finally {
14
+ setStderrLoggingEnabled(previousStderrSetting);
15
+ }
9
16
  },
10
17
  });
11
18
  }
@@ -7,9 +7,26 @@
7
7
  * /gsd mcp — Overview of all servers (alias: /gsd mcp status)
8
8
  * /gsd mcp status — Same as bare /gsd mcp
9
9
  * /gsd mcp check <srv> — Detailed status for a specific server
10
+ * /gsd mcp init [dir] — Write project-local GSD workflow MCP config
10
11
  */
11
12
  import { existsSync, readFileSync } from "node:fs";
12
- import { join } from "node:path";
13
+ import { join, resolve } from "node:path";
14
+ import { ensureProjectWorkflowMcpConfig } from "./mcp-project-config.js";
15
+ export function formatMcpInitResult(status, configPath, targetPath) {
16
+ const summary = status === "created"
17
+ ? "Created project MCP config."
18
+ : status === "updated"
19
+ ? "Updated project MCP config."
20
+ : "Project MCP config is already up to date.";
21
+ return [
22
+ summary,
23
+ "",
24
+ `Project: ${targetPath}`,
25
+ `Config: ${configPath}`,
26
+ "",
27
+ "Claude Code can now load the GSD workflow MCP server from this folder.",
28
+ ].join("\n");
29
+ }
13
30
  function readMcpConfigs() {
14
31
  const servers = [];
15
32
  const seen = new Set();
@@ -61,6 +78,7 @@ export function formatMcpStatusReport(servers) {
61
78
  "No MCP servers configured.",
62
79
  "",
63
80
  "Add servers to .mcp.json or .gsd/mcp.json to enable MCP integrations.",
81
+ "Tip: run /gsd mcp init . to write the local GSD workflow MCP config.",
64
82
  "See: https://modelcontextprotocol.io/quickstart",
65
83
  ].join("\n");
66
84
  }
@@ -109,11 +127,28 @@ export function formatMcpServerDetail(server) {
109
127
  * Handle `/gsd mcp [status|check <server>]`.
110
128
  */
111
129
  export async function handleMcpStatus(args, ctx) {
112
- const trimmed = args.trim().toLowerCase();
130
+ const trimmed = args.trim();
131
+ const lowered = trimmed.toLowerCase();
113
132
  const configs = readMcpConfigs();
133
+ // /gsd mcp init [dir]
134
+ if (!lowered || lowered === "status") {
135
+ // handled below
136
+ }
137
+ else if (lowered === "init" || lowered.startsWith("init ")) {
138
+ const rawPath = trimmed.slice("init".length).trim();
139
+ const targetPath = resolve(rawPath || ".");
140
+ try {
141
+ const result = ensureProjectWorkflowMcpConfig(targetPath);
142
+ ctx.ui.notify(formatMcpInitResult(result.status, result.configPath, targetPath), "info");
143
+ }
144
+ catch (err) {
145
+ ctx.ui.notify(`Failed to prepare MCP config for ${targetPath}: ${err instanceof Error ? err.message : String(err)}`, "error");
146
+ }
147
+ return;
148
+ }
114
149
  // /gsd mcp check <server>
115
- if (trimmed.startsWith("check ")) {
116
- const serverName = args.trim().slice("check ".length).trim();
150
+ if (lowered.startsWith("check ")) {
151
+ const serverName = trimmed.slice("check ".length).trim();
117
152
  const config = configs.find((c) => c.name === serverName);
118
153
  if (!config) {
119
154
  const available = configs.map((c) => c.name).join(", ") || "(none)";
@@ -149,7 +184,7 @@ export async function handleMcpStatus(args, ctx) {
149
184
  return;
150
185
  }
151
186
  // /gsd mcp or /gsd mcp status
152
- if (!trimmed || trimmed === "status") {
187
+ if (!lowered || lowered === "status") {
153
188
  // Build status for each server
154
189
  const statuses = [];
155
190
  for (const config of configs) {
@@ -181,7 +216,8 @@ export async function handleMcpStatus(args, ctx) {
181
216
  return;
182
217
  }
183
218
  // Unknown subcommand
184
- ctx.ui.notify("Usage: /gsd mcp [status|check <server>]\n\n" +
219
+ ctx.ui.notify("Usage: /gsd mcp [status|check <server>|init [dir]]\n\n" +
185
220
  " status Show all MCP server statuses (default)\n" +
186
- " check <server> Detailed status for a specific server", "warning");
221
+ " check <server> Detailed status for a specific server\n" +
222
+ " init [dir] Write .mcp.json for the local GSD workflow MCP server", "warning");
187
223
  }
@@ -34,6 +34,7 @@ import { findMilestoneIds, nextMilestoneId, reserveMilestoneId, getReservedMiles
34
34
  import { parkMilestone, discardMilestone } from "./milestone-actions.js";
35
35
  import { selectAndApplyModel } from "./auto-model-selection.js";
36
36
  import { DISCUSS_TOOLS_ALLOWLIST } from "./constants.js";
37
+ import { getWorkflowTransportSupportError, getRequiredWorkflowToolsForGuidedUnit, } from "./workflow-mcp.js";
37
38
  import { runPreparation, formatCodebaseBrief, formatPriorContextBrief, formatEcosystemBrief, } from "./preparation.js";
38
39
  // ─── Preparation result storage ─────────────────────────────────────────────
39
40
  // Stores the most recent preparation result for injection into discuss prompts.
@@ -259,6 +260,21 @@ async function dispatchWorkflow(pi, note, customType = "gsd-run", ctx, unitType)
259
260
  routing: result.routing,
260
261
  });
261
262
  }
263
+ const compatibilityError = getWorkflowTransportSupportError(result.appliedModel?.provider ?? ctx.model?.provider, getRequiredWorkflowToolsForGuidedUnit(unitType), {
264
+ projectRoot: process.cwd(),
265
+ surface: "guided flow",
266
+ unitType,
267
+ authMode: result.appliedModel?.provider
268
+ ? ctx.modelRegistry.getProviderAuthMode(result.appliedModel.provider)
269
+ : ctx.model?.provider
270
+ ? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
271
+ : undefined,
272
+ baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
273
+ });
274
+ if (compatibilityError) {
275
+ ctx.ui.notify(compatibilityError, "error");
276
+ return;
277
+ }
262
278
  }
263
279
  // Scope tools for discuss flows (#2949).
264
280
  // Providers with grammar-based constrained decoding (xAI/Grok) return
@@ -187,6 +187,16 @@ export async function showProjectInit(ctx, pi, basePath, detection) {
187
187
  }
188
188
  // ── Step 9: Bootstrap .gsd/ ────────────────────────────────────────────────
189
189
  bootstrapGsdDirectory(basePath, prefs, signals);
190
+ // Initialize SQLite database so GSD starts in full-capability mode (#3880).
191
+ // Without this, isDbAvailable() returns false and GSD enters degraded
192
+ // markdown-only mode until a tool handler happens to call ensureDbOpen().
193
+ try {
194
+ const { ensureDbOpen } = await import("./bootstrap/dynamic-tools.js");
195
+ await ensureDbOpen(basePath);
196
+ }
197
+ catch {
198
+ // Non-fatal — DB creation failure should not block project init
199
+ }
190
200
  // Ensure .gitignore
191
201
  ensureGitignore(basePath);
192
202
  untrackRuntimeFiles(basePath);
@@ -201,6 +211,32 @@ export async function showProjectInit(ctx, pi, basePath, detection) {
201
211
  catch {
202
212
  // Non-fatal — codebase map generation failure should never block project init
203
213
  }
214
+ // Write initial STATE.md so it exists before the first /gsd invocation.
215
+ // The explicit /gsd init path (ops.ts) returns without entering showSmartEntry(),
216
+ // which would otherwise generate STATE.md at guided-flow.ts:1358.
217
+ try {
218
+ const { deriveState } = await import("./state.js");
219
+ const { buildStateMarkdown } = await import("./doctor.js");
220
+ const { saveFile } = await import("./files.js");
221
+ const { resolveGsdRootFile } = await import("./paths.js");
222
+ const state = await deriveState(basePath);
223
+ await saveFile(resolveGsdRootFile(basePath, "STATE"), buildStateMarkdown(state));
224
+ }
225
+ catch {
226
+ // Non-fatal — STATE.md will be regenerated on next /gsd invocation
227
+ }
228
+ if (ctx.model?.provider === "claude-code") {
229
+ try {
230
+ const { ensureProjectWorkflowMcpConfig } = await import("./mcp-project-config.js");
231
+ const result = ensureProjectWorkflowMcpConfig(basePath);
232
+ if (result.status !== "unchanged") {
233
+ ctx.ui.notify(`Claude Code MCP prepared at ${result.configPath}`, "info");
234
+ }
235
+ }
236
+ catch (err) {
237
+ ctx.ui.notify(`Claude Code MCP prep failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
238
+ }
239
+ }
204
240
  ctx.ui.notify("GSD initialized. Starting your first milestone...", "info");
205
241
  return { completed: true, bootstrapped: true };
206
242
  }
@@ -348,6 +384,7 @@ function bootstrapGsdDirectory(basePath, prefs, signals) {
348
384
  assertSafeDirectory(basePath);
349
385
  const gsd = gsdRoot(basePath);
350
386
  mkdirSync(join(gsd, "milestones"), { recursive: true });
387
+ mkdirSync(join(gsd, "runtime"), { recursive: true });
351
388
  // Write PREFERENCES.md from wizard answers
352
389
  const preferencesContent = buildPreferencesFile(prefs);
353
390
  writeFileSync(join(gsd, "PREFERENCES.md"), preferencesContent, "utf-8");
@@ -0,0 +1,83 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { assertSafeDirectory } from "./validate-directory.js";
5
+ import { detectWorkflowMcpLaunchConfig } from "./workflow-mcp.js";
6
+ export const GSD_WORKFLOW_MCP_SERVER_NAME = "gsd-workflow";
7
+ export function resolveBundledGsdCliPath(env = process.env) {
8
+ const explicit = env.GSD_CLI_PATH?.trim() || env.GSD_BIN_PATH?.trim();
9
+ if (explicit)
10
+ return explicit;
11
+ const candidates = [
12
+ resolve(fileURLToPath(new URL("../../../../scripts/dev-cli.js", import.meta.url))),
13
+ resolve(fileURLToPath(new URL("../../../../dist/loader.js", import.meta.url))),
14
+ resolve(fileURLToPath(new URL("../../../loader.js", import.meta.url))),
15
+ ];
16
+ for (const candidate of candidates) {
17
+ if (existsSync(candidate))
18
+ return candidate;
19
+ }
20
+ return null;
21
+ }
22
+ export function buildProjectWorkflowMcpServerConfig(projectRoot, env = process.env) {
23
+ const resolvedProjectRoot = resolve(projectRoot);
24
+ const gsdCliPath = resolveBundledGsdCliPath(env);
25
+ const launch = detectWorkflowMcpLaunchConfig(resolvedProjectRoot, {
26
+ ...env,
27
+ ...(gsdCliPath ? { GSD_CLI_PATH: gsdCliPath, GSD_BIN_PATH: gsdCliPath } : {}),
28
+ });
29
+ if (!launch) {
30
+ throw new Error("Unable to resolve the GSD workflow MCP server. Build this checkout or install gsd-mcp-server on PATH.");
31
+ }
32
+ return {
33
+ command: launch.command,
34
+ ...(launch.args && launch.args.length > 0 ? { args: launch.args } : {}),
35
+ ...(launch.cwd ? { cwd: launch.cwd } : {}),
36
+ ...(launch.env ? { env: launch.env } : {}),
37
+ };
38
+ }
39
+ function readExistingConfig(configPath) {
40
+ if (!existsSync(configPath))
41
+ return {};
42
+ const raw = readFileSync(configPath, "utf-8");
43
+ try {
44
+ const parsed = JSON.parse(raw);
45
+ return parsed && typeof parsed === "object" ? parsed : {};
46
+ }
47
+ catch (err) {
48
+ throw new Error(`Failed to parse ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
49
+ }
50
+ }
51
+ export function ensureProjectWorkflowMcpConfig(projectRoot, env = process.env) {
52
+ const resolvedProjectRoot = resolve(projectRoot);
53
+ assertSafeDirectory(resolvedProjectRoot);
54
+ const configPath = resolve(resolvedProjectRoot, ".mcp.json");
55
+ const existing = readExistingConfig(configPath);
56
+ const desiredServer = buildProjectWorkflowMcpServerConfig(resolvedProjectRoot, env);
57
+ const previousServers = existing.mcpServers ?? {};
58
+ const nextServers = {
59
+ ...previousServers,
60
+ [GSD_WORKFLOW_MCP_SERVER_NAME]: desiredServer,
61
+ };
62
+ const alreadyPresent = existsSync(configPath);
63
+ const unchanged = JSON.stringify(previousServers[GSD_WORKFLOW_MCP_SERVER_NAME] ?? null)
64
+ === JSON.stringify(desiredServer)
65
+ && existing.mcpServers !== undefined;
66
+ if (unchanged) {
67
+ return {
68
+ configPath,
69
+ serverName: GSD_WORKFLOW_MCP_SERVER_NAME,
70
+ status: "unchanged",
71
+ };
72
+ }
73
+ const nextConfig = {
74
+ ...existing,
75
+ mcpServers: nextServers,
76
+ };
77
+ writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf-8");
78
+ return {
79
+ configPath,
80
+ serverName: GSD_WORKFLOW_MCP_SERVER_NAME,
81
+ status: alreadyPresent ? "updated" : "created",
82
+ };
83
+ }