gsd-pi 2.69.0-dev.232905b → 2.69.0-dev.87298d1
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.
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +2 -6
- package/dist/resources/extensions/gsd/commands-cmux.js +1 -30
- package/dist/resources/extensions/gsd/workflow-mcp.js +6 -53
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +9 -9
- package/dist/web/standalone/.next/build-manifest.json +2 -2
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +9 -9
- package/dist/web/standalone/.next/server/chunks/6897.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/package.json +1 -1
- package/packages/daemon/src/orchestrator.ts +84 -9
- package/packages/mcp-server/README.md +3 -25
- package/packages/mcp-server/dist/cli.d.ts +1 -0
- package/packages/mcp-server/dist/cli.d.ts.map +1 -1
- package/packages/mcp-server/dist/cli.js +2 -4
- package/packages/mcp-server/dist/cli.js.map +1 -1
- package/packages/mcp-server/dist/server.d.ts +1 -32
- package/packages/mcp-server/dist/server.d.ts.map +1 -1
- package/packages/mcp-server/dist/server.js +1 -118
- package/packages/mcp-server/dist/server.js.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts +0 -1
- package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
- package/packages/mcp-server/dist/workflow-tools.js +2 -274
- package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
- package/packages/mcp-server/src/cli.ts +3 -5
- package/packages/mcp-server/src/mcp-server.test.ts +1 -85
- package/packages/mcp-server/src/server.ts +1 -188
- package/packages/mcp-server/src/workflow-tools.test.ts +25 -32
- package/packages/mcp-server/src/workflow-tools.ts +2 -365
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +23 -1
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts +17 -0
- package/packages/pi-ai/dist/utils/oauth/anthropic.d.ts.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +106 -0
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -0
- package/packages/pi-ai/dist/utils/oauth/index.d.ts +2 -3
- package/packages/pi-ai/dist/utils/oauth/index.d.ts.map +1 -1
- package/packages/pi-ai/dist/utils/oauth/index.js +5 -3
- package/packages/pi-ai/dist/utils/oauth/index.js.map +1 -1
- package/packages/pi-ai/src/providers/anthropic.ts +31 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +140 -0
- package/packages/pi-ai/src/utils/oauth/index.ts +5 -3
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +5 -9
- package/src/resources/extensions/gsd/commands-cmux.ts +1 -32
- package/src/resources/extensions/gsd/tests/cmux.test.ts +1 -67
- package/src/resources/extensions/gsd/tests/mcp-project-config.test.ts +2 -6
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +7 -23
- package/src/resources/extensions/gsd/workflow-mcp.ts +5 -59
- package/packages/mcp-server/dist/tool-credentials.d.ts +0 -6
- package/packages/mcp-server/dist/tool-credentials.d.ts.map +0 -1
- package/packages/mcp-server/dist/tool-credentials.js +0 -90
- package/packages/mcp-server/dist/tool-credentials.js.map +0 -1
- package/packages/mcp-server/src/tool-credentials.test.ts +0 -95
- package/packages/mcp-server/src/tool-credentials.ts +0 -97
- /package/dist/web/standalone/.next/static/{CHeIRZw-E97oO51g7zDvv → PYwFks7YuKWnl48wsvjo4}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{CHeIRZw-E97oO51g7zDvv → PYwFks7YuKWnl48wsvjo4}/_ssgManifest.js +0 -0
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* MCP Server — registers GSD orchestration, project-state, and workflow tools.
|
|
3
3
|
*
|
|
4
4
|
* Session tools (6): gsd_execute, gsd_status, gsd_result, gsd_cancel, gsd_query, gsd_resolve_blocker
|
|
5
|
-
* Interactive tools (1): ask_user_questions via MCP form elicitation
|
|
6
5
|
* Read-only tools (6): gsd_progress, gsd_roadmap, gsd_history, gsd_doctor, gsd_captures, gsd_knowledge
|
|
7
|
-
* Workflow tools (
|
|
6
|
+
* Workflow tools (17): planning, replanning, completion, validation, reassessment, gate result, and milestone status tools
|
|
8
7
|
*
|
|
9
8
|
* Uses dynamic imports for @modelcontextprotocol/sdk because TS Node16
|
|
10
9
|
* cannot resolve the SDK's subpath exports statically (same pattern as
|
|
@@ -45,11 +44,6 @@ function errorContent(message: string): { isError: true; content: Array<{ type:
|
|
|
45
44
|
return { isError: true, content: [{ type: 'text' as const, text: message }] };
|
|
46
45
|
}
|
|
47
46
|
|
|
48
|
-
/** Return raw text content without JSON wrapping. */
|
|
49
|
-
function textContent(text: string): { content: Array<{ type: 'text'; text: string }> } {
|
|
50
|
-
return { content: [{ type: 'text' as const, text }] };
|
|
51
|
-
}
|
|
52
|
-
|
|
53
47
|
// ---------------------------------------------------------------------------
|
|
54
48
|
// gsd_query filesystem reader
|
|
55
49
|
// ---------------------------------------------------------------------------
|
|
@@ -114,155 +108,10 @@ async function fileExists(path: string): Promise<boolean> {
|
|
|
114
108
|
|
|
115
109
|
interface McpServerInstance {
|
|
116
110
|
tool(name: string, description: string, params: Record<string, unknown>, handler: (args: Record<string, unknown>) => Promise<unknown>): unknown;
|
|
117
|
-
server: {
|
|
118
|
-
elicitInput(
|
|
119
|
-
params: AskUserQuestionsElicitRequest,
|
|
120
|
-
options?: unknown,
|
|
121
|
-
): Promise<AskUserQuestionsElicitResult>;
|
|
122
|
-
};
|
|
123
111
|
connect(transport: unknown): Promise<void>;
|
|
124
112
|
close(): Promise<void>;
|
|
125
113
|
}
|
|
126
114
|
|
|
127
|
-
interface AskUserQuestionOption {
|
|
128
|
-
label: string;
|
|
129
|
-
description: string;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
interface AskUserQuestion {
|
|
133
|
-
id: string;
|
|
134
|
-
header: string;
|
|
135
|
-
question: string;
|
|
136
|
-
options: AskUserQuestionOption[];
|
|
137
|
-
allowMultiple?: boolean;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
interface AskUserQuestionsParams {
|
|
141
|
-
questions: AskUserQuestion[];
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
type AskUserQuestionsContentValue = string | number | boolean | string[];
|
|
145
|
-
|
|
146
|
-
interface AskUserQuestionsElicitResult {
|
|
147
|
-
action: 'accept' | 'decline' | 'cancel';
|
|
148
|
-
content?: Record<string, AskUserQuestionsContentValue>;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
interface AskUserQuestionsElicitRequest {
|
|
152
|
-
mode: 'form';
|
|
153
|
-
message: string;
|
|
154
|
-
requestedSchema: {
|
|
155
|
-
type: 'object';
|
|
156
|
-
properties: Record<string, Record<string, unknown>>;
|
|
157
|
-
required?: string[];
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
const OTHER_OPTION_LABEL = 'None of the above';
|
|
162
|
-
|
|
163
|
-
function normalizeAskUserQuestionsNote(value: AskUserQuestionsContentValue | undefined): string {
|
|
164
|
-
return typeof value === 'string' ? value.trim() : '';
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function normalizeAskUserQuestionsAnswers(
|
|
168
|
-
value: AskUserQuestionsContentValue | undefined,
|
|
169
|
-
allowMultiple: boolean,
|
|
170
|
-
): string[] {
|
|
171
|
-
if (allowMultiple) {
|
|
172
|
-
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : [];
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return typeof value === 'string' && value.length > 0 ? [value] : [];
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function validateAskUserQuestionsPayload(questions: AskUserQuestion[]): string | null {
|
|
179
|
-
if (questions.length === 0 || questions.length > 3) {
|
|
180
|
-
return 'Error: questions must contain 1-3 items';
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
for (const question of questions) {
|
|
184
|
-
if (!question.options || question.options.length === 0) {
|
|
185
|
-
return `Error: ask_user_questions requires non-empty options for every question (question "${question.id}" has none)`;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export function buildAskUserQuestionsElicitRequest(questions: AskUserQuestion[]): AskUserQuestionsElicitRequest {
|
|
193
|
-
const properties: Record<string, Record<string, unknown>> = {};
|
|
194
|
-
const required = questions.map((question) => question.id);
|
|
195
|
-
|
|
196
|
-
for (const question of questions) {
|
|
197
|
-
if (question.allowMultiple) {
|
|
198
|
-
properties[question.id] = {
|
|
199
|
-
type: 'array',
|
|
200
|
-
title: question.header,
|
|
201
|
-
description: question.question,
|
|
202
|
-
minItems: 1,
|
|
203
|
-
maxItems: question.options.length,
|
|
204
|
-
items: {
|
|
205
|
-
anyOf: question.options.map((option) => ({
|
|
206
|
-
const: option.label,
|
|
207
|
-
title: option.label,
|
|
208
|
-
})),
|
|
209
|
-
},
|
|
210
|
-
};
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
properties[question.id] = {
|
|
215
|
-
type: 'string',
|
|
216
|
-
title: question.header,
|
|
217
|
-
description: question.question,
|
|
218
|
-
oneOf: [...question.options, { label: OTHER_OPTION_LABEL, description: 'Choose this when the listed options do not fit.' }].map((option) => ({
|
|
219
|
-
const: option.label,
|
|
220
|
-
title: option.label,
|
|
221
|
-
})),
|
|
222
|
-
};
|
|
223
|
-
|
|
224
|
-
properties[`${question.id}__note`] = {
|
|
225
|
-
type: 'string',
|
|
226
|
-
title: `${question.header} Note`,
|
|
227
|
-
description: `Optional note for "${OTHER_OPTION_LABEL}".`,
|
|
228
|
-
maxLength: 500,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
mode: 'form',
|
|
234
|
-
message: 'Please answer the following question(s). For single-select questions, choose "None of the above" and add a note if the provided options do not fit.',
|
|
235
|
-
requestedSchema: {
|
|
236
|
-
type: 'object',
|
|
237
|
-
properties,
|
|
238
|
-
required,
|
|
239
|
-
},
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export function formatAskUserQuestionsElicitResult(
|
|
244
|
-
questions: AskUserQuestion[],
|
|
245
|
-
result: AskUserQuestionsElicitResult,
|
|
246
|
-
): string {
|
|
247
|
-
const answers: Record<string, { answers: string[] }> = {};
|
|
248
|
-
const content = result.content ?? {};
|
|
249
|
-
|
|
250
|
-
for (const question of questions) {
|
|
251
|
-
const answerList = normalizeAskUserQuestionsAnswers(content[question.id], !!question.allowMultiple);
|
|
252
|
-
|
|
253
|
-
if (!question.allowMultiple && answerList[0] === OTHER_OPTION_LABEL) {
|
|
254
|
-
const note = normalizeAskUserQuestionsNote(content[`${question.id}__note`]);
|
|
255
|
-
if (note) {
|
|
256
|
-
answerList.push(`user_note: ${note}`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
answers[question.id] = { answers: answerList };
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return JSON.stringify({ answers });
|
|
264
|
-
}
|
|
265
|
-
|
|
266
115
|
// ---------------------------------------------------------------------------
|
|
267
116
|
// createMcpServer
|
|
268
117
|
// ---------------------------------------------------------------------------
|
|
@@ -436,42 +285,6 @@ export async function createMcpServer(sessionManager: SessionManager): Promise<{
|
|
|
436
285
|
},
|
|
437
286
|
);
|
|
438
287
|
|
|
439
|
-
// -----------------------------------------------------------------------
|
|
440
|
-
// ask_user_questions — structured user input via MCP form elicitation
|
|
441
|
-
// -----------------------------------------------------------------------
|
|
442
|
-
server.tool(
|
|
443
|
-
'ask_user_questions',
|
|
444
|
-
'Request user input for one to three short questions and wait for the response. Single-select questions include a free-form "None of the above" path. Multi-select questions allow multiple choices.',
|
|
445
|
-
{
|
|
446
|
-
questions: z.array(z.object({
|
|
447
|
-
id: z.string().describe('Stable identifier for mapping answers (snake_case)'),
|
|
448
|
-
header: z.string().describe('Short header label shown in the UI (12 or fewer chars)'),
|
|
449
|
-
question: z.string().describe('Single-sentence prompt shown to the user'),
|
|
450
|
-
options: z.array(z.object({
|
|
451
|
-
label: z.string().describe('User-facing label (1-5 words)'),
|
|
452
|
-
description: z.string().describe('One short sentence explaining impact/tradeoff if selected'),
|
|
453
|
-
})).describe('Provide 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with "(Recommended)". Do not include an "Other" option for single-select questions.'),
|
|
454
|
-
allowMultiple: z.boolean().optional().describe('If true, the user can select multiple options. No "None of the above" option is added.'),
|
|
455
|
-
})).describe('Questions to show the user. Prefer 1 and do not exceed 3.'),
|
|
456
|
-
},
|
|
457
|
-
async (args: Record<string, unknown>) => {
|
|
458
|
-
const { questions } = args as unknown as AskUserQuestionsParams;
|
|
459
|
-
try {
|
|
460
|
-
const validationError = validateAskUserQuestionsPayload(questions);
|
|
461
|
-
if (validationError) return errorContent(validationError);
|
|
462
|
-
|
|
463
|
-
const elicitation = await server.server.elicitInput(buildAskUserQuestionsElicitRequest(questions));
|
|
464
|
-
if (elicitation.action !== 'accept' || !elicitation.content) {
|
|
465
|
-
return textContent('ask_user_questions was cancelled before receiving a response');
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
return textContent(formatAskUserQuestionsElicitResult(questions, elicitation));
|
|
469
|
-
} catch (err) {
|
|
470
|
-
return errorContent(err instanceof Error ? err.message : String(err));
|
|
471
|
-
}
|
|
472
|
-
},
|
|
473
|
-
);
|
|
474
|
-
|
|
475
288
|
// =======================================================================
|
|
476
289
|
// READ-ONLY TOOLS — no session required, pure filesystem reads
|
|
477
290
|
// =======================================================================
|
|
@@ -6,7 +6,7 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
import { randomUUID } from "node:crypto";
|
|
7
7
|
|
|
8
8
|
import { _getAdapter, closeDatabase } from "../../../src/resources/extensions/gsd/gsd-db.ts";
|
|
9
|
-
import { registerWorkflowTools
|
|
9
|
+
import { registerWorkflowTools } from "./workflow-tools.ts";
|
|
10
10
|
|
|
11
11
|
function makeTmpBase(): string {
|
|
12
12
|
const base = join(tmpdir(), `gsd-mcp-workflow-${randomUUID()}`);
|
|
@@ -68,12 +68,33 @@ function makeMockServer() {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
describe("workflow MCP tools", () => {
|
|
71
|
-
it("registers the
|
|
71
|
+
it("registers the seventeen workflow tools", () => {
|
|
72
72
|
const server = makeMockServer();
|
|
73
73
|
registerWorkflowTools(server as any);
|
|
74
74
|
|
|
75
|
-
assert.equal(server.tools.length,
|
|
76
|
-
assert.deepEqual(
|
|
75
|
+
assert.equal(server.tools.length, 17);
|
|
76
|
+
assert.deepEqual(
|
|
77
|
+
server.tools.map((t) => t.name),
|
|
78
|
+
[
|
|
79
|
+
"gsd_plan_milestone",
|
|
80
|
+
"gsd_plan_slice",
|
|
81
|
+
"gsd_replan_slice",
|
|
82
|
+
"gsd_slice_replan",
|
|
83
|
+
"gsd_slice_complete",
|
|
84
|
+
"gsd_complete_slice",
|
|
85
|
+
"gsd_complete_milestone",
|
|
86
|
+
"gsd_milestone_complete",
|
|
87
|
+
"gsd_validate_milestone",
|
|
88
|
+
"gsd_milestone_validate",
|
|
89
|
+
"gsd_reassess_roadmap",
|
|
90
|
+
"gsd_roadmap_reassess",
|
|
91
|
+
"gsd_save_gate_result",
|
|
92
|
+
"gsd_summary_save",
|
|
93
|
+
"gsd_task_complete",
|
|
94
|
+
"gsd_complete_task",
|
|
95
|
+
"gsd_milestone_status",
|
|
96
|
+
],
|
|
97
|
+
);
|
|
77
98
|
});
|
|
78
99
|
|
|
79
100
|
it("gsd_summary_save writes artifact through the shared executor", async () => {
|
|
@@ -953,31 +974,3 @@ describe("workflow MCP tools", () => {
|
|
|
953
974
|
}
|
|
954
975
|
});
|
|
955
976
|
});
|
|
956
|
-
|
|
957
|
-
describe("URL scheme regex — Windows drive letter safety", () => {
|
|
958
|
-
// This is the regex used in getWriteGateModuleCandidates() and
|
|
959
|
-
// getWorkflowExecutorModuleCandidates() to reject non-file URL schemes.
|
|
960
|
-
// It must NOT match single-letter Windows drive prefixes (C:, D:, etc.).
|
|
961
|
-
const urlSchemeRegex = /^[a-z]{2,}:/i;
|
|
962
|
-
|
|
963
|
-
it("rejects multi-letter URL schemes", () => {
|
|
964
|
-
assert.ok(urlSchemeRegex.test("http://example.com"), "http: should match");
|
|
965
|
-
assert.ok(urlSchemeRegex.test("https://example.com"), "https: should match");
|
|
966
|
-
assert.ok(urlSchemeRegex.test("ftp://files.example.com"), "ftp: should match");
|
|
967
|
-
assert.ok(urlSchemeRegex.test("file:///C:/Users"), "file: should match");
|
|
968
|
-
assert.ok(urlSchemeRegex.test("node:fs"), "node: should match");
|
|
969
|
-
});
|
|
970
|
-
|
|
971
|
-
it("allows single-letter Windows drive prefixes", () => {
|
|
972
|
-
assert.ok(!urlSchemeRegex.test("C:\\Users\\user\\project"), "C:\\ should not match");
|
|
973
|
-
assert.ok(!urlSchemeRegex.test("D:\\other\\path"), "D:\\ should not match");
|
|
974
|
-
assert.ok(!urlSchemeRegex.test("c:\\lowercase\\drive"), "c:\\ should not match");
|
|
975
|
-
assert.ok(!urlSchemeRegex.test("E:/forward/slash/path"), "E:/ should not match");
|
|
976
|
-
});
|
|
977
|
-
|
|
978
|
-
it("allows bare filesystem paths", () => {
|
|
979
|
-
assert.ok(!urlSchemeRegex.test("/usr/local/lib/module.js"), "unix absolute path should not match");
|
|
980
|
-
assert.ok(!urlSchemeRegex.test("./relative/path.js"), "relative path should not match");
|
|
981
|
-
assert.ok(!urlSchemeRegex.test("../parent/path.js"), "parent relative path should not match");
|
|
982
|
-
});
|
|
983
|
-
});
|