gsd-pi 2.17.0 → 2.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/dist/onboarding.js +2 -2
- package/dist/remote-questions-config.d.ts +10 -0
- package/dist/remote-questions-config.js +36 -0
- package/dist/resources/extensions/gsd/activity-log.ts +37 -7
- package/dist/resources/extensions/gsd/auto-prompts.ts +20 -1
- package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/dist/resources/extensions/gsd/auto.ts +123 -10
- package/dist/resources/extensions/gsd/commands.ts +245 -22
- package/dist/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +201 -2
- package/dist/resources/extensions/gsd/files.ts +123 -1
- package/dist/resources/extensions/gsd/guided-flow.ts +237 -4
- package/dist/resources/extensions/gsd/index.ts +47 -3
- package/dist/resources/extensions/gsd/paths.ts +9 -0
- package/dist/resources/extensions/gsd/preferences.ts +59 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/dist/resources/extensions/gsd/prompts/system.md +2 -0
- package/dist/resources/extensions/gsd/queue-order.ts +231 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/dist/resources/extensions/gsd/state.ts +15 -3
- package/dist/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/dist/resources/extensions/gsd/templates/preferences.md +14 -0
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/dist/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/dist/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/dist/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/dist/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/dist/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/dist/resources/extensions/gsd/worktree.ts +22 -0
- package/dist/resources/extensions/shared/next-action-ui.ts +16 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/cli/args.d.ts +5 -0
- package/packages/pi-coding-agent/dist/cli/args.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/args.js +21 -0
- package/packages/pi-coding-agent/dist/cli/args.js.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts +14 -3
- package/packages/pi-coding-agent/dist/cli/list-models.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/cli/list-models.js +52 -17
- package/packages/pi-coding-agent/dist/cli/list-models.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts +27 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js +79 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +140 -0
- package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts +35 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js +162 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js +100 -0
- package/packages/pi-coding-agent/dist/core/model-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js +113 -0
- package/packages/pi-coding-agent/dist/core/model-registry-discovery.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts +26 -0
- package/packages/pi-coding-agent/dist/core/model-registry.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-registry.js +98 -0
- package/packages/pi-coding-agent/dist/core/model-registry.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts +62 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js +145 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js +118 -0
- package/packages/pi-coding-agent/dist/core/models-json-writer.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts +9 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/settings-manager.js +11 -0
- package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/slash-commands.js +1 -0
- package/packages/pi-coding-agent/dist/core/slash-commands.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +5 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +4 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/main.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/main.js +17 -2
- package/packages/pi-coding-agent/dist/main.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/index.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts +25 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js +121 -0
- package/packages/pi-coding-agent/dist/modes/interactive/components/provider-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +32 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/cli/args.ts +21 -0
- package/packages/pi-coding-agent/src/cli/list-models.ts +70 -17
- package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +170 -0
- package/packages/pi-coding-agent/src/core/discovery-cache.ts +97 -0
- package/packages/pi-coding-agent/src/core/model-discovery.test.ts +125 -0
- package/packages/pi-coding-agent/src/core/model-discovery.ts +231 -0
- package/packages/pi-coding-agent/src/core/model-registry-discovery.test.ts +135 -0
- package/packages/pi-coding-agent/src/core/model-registry.ts +107 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.test.ts +145 -0
- package/packages/pi-coding-agent/src/core/models-json-writer.ts +188 -0
- package/packages/pi-coding-agent/src/core/settings-manager.ts +21 -0
- package/packages/pi-coding-agent/src/core/slash-commands.ts +1 -0
- package/packages/pi-coding-agent/src/index.ts +5 -0
- package/packages/pi-coding-agent/src/main.ts +19 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/index.ts +1 -0
- package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/components/provider-manager.ts +163 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +37 -0
- package/src/resources/extensions/gsd/activity-log.ts +37 -7
- package/src/resources/extensions/gsd/auto-prompts.ts +20 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/src/resources/extensions/gsd/auto.ts +123 -10
- package/src/resources/extensions/gsd/commands.ts +245 -22
- package/src/resources/extensions/gsd/dispatch-guard.ts +7 -19
- package/src/resources/extensions/gsd/docs/preferences-reference.md +201 -2
- package/src/resources/extensions/gsd/files.ts +123 -1
- package/src/resources/extensions/gsd/guided-flow.ts +237 -4
- package/src/resources/extensions/gsd/index.ts +47 -3
- package/src/resources/extensions/gsd/paths.ts +9 -0
- package/src/resources/extensions/gsd/preferences.ts +59 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/src/resources/extensions/gsd/prompts/system.md +2 -0
- package/src/resources/extensions/gsd/queue-order.ts +231 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +263 -0
- package/src/resources/extensions/gsd/state.ts +15 -3
- package/src/resources/extensions/gsd/templates/knowledge.md +19 -0
- package/src/resources/extensions/gsd/templates/preferences.md +14 -0
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +20 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/in-flight-tool-tracking.test.ts +79 -0
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +161 -0
- package/src/resources/extensions/gsd/tests/memory-leak-guards.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/preferences-wizard-fields.test.ts +168 -0
- package/src/resources/extensions/gsd/tests/queue-order.test.ts +204 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +281 -0
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/src/resources/extensions/gsd/worktree.ts +22 -0
- package/src/resources/extensions/shared/next-action-ui.ts +16 -1
|
@@ -13,6 +13,7 @@ import { deriveState } from "./state.js";
|
|
|
13
13
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
14
14
|
import { showQueue, showDiscuss } from "./guided-flow.js";
|
|
15
15
|
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote } from "./auto.js";
|
|
16
|
+
import { resolveProjectRoot } from "./worktree.js";
|
|
16
17
|
import {
|
|
17
18
|
getGlobalGSDPreferencesPath,
|
|
18
19
|
getLegacyGlobalGSDPreferencesPath,
|
|
@@ -22,7 +23,7 @@ import {
|
|
|
22
23
|
loadEffectiveGSDPreferences,
|
|
23
24
|
resolveAllSkillReferences,
|
|
24
25
|
} from "./preferences.js";
|
|
25
|
-
import { loadFile, saveFile, appendOverride } from "./files.js";
|
|
26
|
+
import { loadFile, saveFile, appendOverride, appendKnowledge } from "./files.js";
|
|
26
27
|
import {
|
|
27
28
|
formatDoctorIssuesForPrompt,
|
|
28
29
|
formatDoctorReport,
|
|
@@ -56,14 +57,19 @@ function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportT
|
|
|
56
57
|
);
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/** Resolve the effective project root, accounting for worktree paths. */
|
|
61
|
+
function projectRoot(): string {
|
|
62
|
+
return resolveProjectRoot(process.cwd());
|
|
63
|
+
}
|
|
64
|
+
|
|
59
65
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
60
66
|
pi.registerCommand("gsd", {
|
|
61
|
-
description: "GSD — Get Shit Done: /gsd next|auto|stop|pause|status|queue|history|undo|skip|export|cleanup|prefs|config|hooks|doctor|migrate|remote|steer",
|
|
67
|
+
description: "GSD — Get Shit Done: /gsd next|auto|stop|pause|status|queue|history|undo|skip|export|cleanup|prefs|config|hooks|doctor|migrate|remote|steer|knowledge",
|
|
62
68
|
getArgumentCompletions: (prefix: string) => {
|
|
63
69
|
const subcommands = [
|
|
64
70
|
"next", "auto", "stop", "pause", "status", "queue", "discuss",
|
|
65
71
|
"history", "undo", "skip", "export", "cleanup", "prefs",
|
|
66
|
-
"config", "hooks", "doctor", "migrate", "remote", "steer",
|
|
72
|
+
"config", "hooks", "doctor", "migrate", "remote", "steer", "knowledge",
|
|
67
73
|
];
|
|
68
74
|
const parts = prefix.trim().split(/\s+/);
|
|
69
75
|
|
|
@@ -126,6 +132,13 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
126
132
|
.map((cmd) => ({ value: `cleanup ${cmd}`, label: cmd }));
|
|
127
133
|
}
|
|
128
134
|
|
|
135
|
+
if (parts[0] === "knowledge" && parts.length <= 2) {
|
|
136
|
+
const subPrefix = parts[1] ?? "";
|
|
137
|
+
return ["rule", "pattern", "lesson"]
|
|
138
|
+
.filter((cmd) => cmd.startsWith(subPrefix))
|
|
139
|
+
.map((cmd) => ({ value: `knowledge ${cmd}`, label: cmd }));
|
|
140
|
+
}
|
|
141
|
+
|
|
129
142
|
if (parts[0] === "doctor") {
|
|
130
143
|
const modePrefix = parts[1] ?? "";
|
|
131
144
|
const modes = ["fix", "heal", "audit"];
|
|
@@ -162,24 +175,24 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
162
175
|
|
|
163
176
|
if (trimmed === "next" || trimmed.startsWith("next ")) {
|
|
164
177
|
if (trimmed.includes("--dry-run")) {
|
|
165
|
-
await handleDryRun(ctx,
|
|
178
|
+
await handleDryRun(ctx, projectRoot());
|
|
166
179
|
return;
|
|
167
180
|
}
|
|
168
181
|
const verboseMode = trimmed.includes("--verbose");
|
|
169
|
-
await startAuto(ctx, pi,
|
|
182
|
+
await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
|
|
170
183
|
return;
|
|
171
184
|
}
|
|
172
185
|
|
|
173
186
|
if (trimmed === "auto" || trimmed.startsWith("auto ")) {
|
|
174
187
|
const verboseMode = trimmed.includes("--verbose");
|
|
175
|
-
await startAuto(ctx, pi,
|
|
188
|
+
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
176
189
|
return;
|
|
177
190
|
}
|
|
178
191
|
|
|
179
192
|
if (trimmed === "stop") {
|
|
180
193
|
if (!isAutoActive() && !isAutoPaused()) {
|
|
181
194
|
// Not running in this process — check for a remote auto-mode session
|
|
182
|
-
const result = stopAutoRemote(
|
|
195
|
+
const result = stopAutoRemote(projectRoot());
|
|
183
196
|
if (result.found) {
|
|
184
197
|
ctx.ui.notify(`Sent stop signal to auto-mode session (PID ${result.pid}). It will shut down gracefully.`, "info");
|
|
185
198
|
} else if (result.error) {
|
|
@@ -207,42 +220,42 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
207
220
|
}
|
|
208
221
|
|
|
209
222
|
if (trimmed === "history" || trimmed.startsWith("history ")) {
|
|
210
|
-
await handleHistory(trimmed.replace(/^history\s*/, "").trim(), ctx,
|
|
223
|
+
await handleHistory(trimmed.replace(/^history\s*/, "").trim(), ctx, projectRoot());
|
|
211
224
|
return;
|
|
212
225
|
}
|
|
213
226
|
|
|
214
227
|
if (trimmed === "undo" || trimmed.startsWith("undo ")) {
|
|
215
|
-
await handleUndo(trimmed.replace(/^undo\s*/, "").trim(), ctx, pi,
|
|
228
|
+
await handleUndo(trimmed.replace(/^undo\s*/, "").trim(), ctx, pi, projectRoot());
|
|
216
229
|
return;
|
|
217
230
|
}
|
|
218
231
|
|
|
219
232
|
if (trimmed.startsWith("skip ")) {
|
|
220
|
-
await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx,
|
|
233
|
+
await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx, projectRoot());
|
|
221
234
|
return;
|
|
222
235
|
}
|
|
223
236
|
|
|
224
237
|
if (trimmed === "export" || trimmed.startsWith("export ")) {
|
|
225
|
-
await handleExport(trimmed.replace(/^export\s*/, "").trim(), ctx,
|
|
238
|
+
await handleExport(trimmed.replace(/^export\s*/, "").trim(), ctx, projectRoot());
|
|
226
239
|
return;
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
if (trimmed === "cleanup branches") {
|
|
230
|
-
await handleCleanupBranches(ctx,
|
|
243
|
+
await handleCleanupBranches(ctx, projectRoot());
|
|
231
244
|
return;
|
|
232
245
|
}
|
|
233
246
|
|
|
234
247
|
if (trimmed === "cleanup snapshots") {
|
|
235
|
-
await handleCleanupSnapshots(ctx,
|
|
248
|
+
await handleCleanupSnapshots(ctx, projectRoot());
|
|
236
249
|
return;
|
|
237
250
|
}
|
|
238
251
|
|
|
239
252
|
if (trimmed === "queue") {
|
|
240
|
-
await showQueue(ctx, pi,
|
|
253
|
+
await showQueue(ctx, pi, projectRoot());
|
|
241
254
|
return;
|
|
242
255
|
}
|
|
243
256
|
|
|
244
257
|
if (trimmed === "discuss") {
|
|
245
|
-
await showDiscuss(ctx, pi,
|
|
258
|
+
await showDiscuss(ctx, pi, projectRoot());
|
|
246
259
|
return;
|
|
247
260
|
}
|
|
248
261
|
|
|
@@ -266,6 +279,15 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
266
279
|
return;
|
|
267
280
|
}
|
|
268
281
|
|
|
282
|
+
if (trimmed.startsWith("knowledge ")) {
|
|
283
|
+
await handleKnowledge(trimmed.replace(/^knowledge\s+/, "").trim(), ctx);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (trimmed === "knowledge") {
|
|
287
|
+
ctx.ui.notify("Usage: /gsd knowledge <rule|pattern|lesson> <description>. Example: /gsd knowledge rule Use real DB for integration tests", "warning");
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
269
291
|
if (trimmed === "migrate" || trimmed.startsWith("migrate ")) {
|
|
270
292
|
const { handleMigrate } = await import("./migrate/command.js");
|
|
271
293
|
await handleMigrate(trimmed.replace(/^migrate\s*/, "").trim(), ctx, pi);
|
|
@@ -279,12 +301,12 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
279
301
|
|
|
280
302
|
if (trimmed === "") {
|
|
281
303
|
// Bare /gsd defaults to step mode
|
|
282
|
-
await startAuto(ctx, pi,
|
|
304
|
+
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
283
305
|
return;
|
|
284
306
|
}
|
|
285
307
|
|
|
286
308
|
ctx.ui.notify(
|
|
287
|
-
`Unknown: /gsd ${trimmed}. Use /gsd next|auto|stop|pause|status|queue|discuss|history|undo|skip <unit>|export|cleanup|prefs|config|hooks|doctor|migrate|remote|steer <change>.`,
|
|
309
|
+
`Unknown: /gsd ${trimmed}. Use /gsd next|auto|stop|pause|status|queue|discuss|history|undo|skip <unit>|export|cleanup|prefs|config|hooks|doctor|migrate|remote|steer <change>|knowledge <type> <entry>.`,
|
|
288
310
|
"warning",
|
|
289
311
|
);
|
|
290
312
|
},
|
|
@@ -292,7 +314,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
292
314
|
}
|
|
293
315
|
|
|
294
316
|
async function handleStatus(ctx: ExtensionCommandContext): Promise<void> {
|
|
295
|
-
const basePath =
|
|
317
|
+
const basePath = projectRoot();
|
|
296
318
|
const state = await deriveState(basePath);
|
|
297
319
|
|
|
298
320
|
if (state.registry.length === 0) {
|
|
@@ -376,9 +398,9 @@ async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: Exte
|
|
|
376
398
|
const parts = trimmed ? trimmed.split(/\s+/) : [];
|
|
377
399
|
const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
|
|
378
400
|
const requestedScope = mode === "doctor" ? parts[0] : parts[1];
|
|
379
|
-
const scope = await selectDoctorScope(
|
|
401
|
+
const scope = await selectDoctorScope(projectRoot(), requestedScope);
|
|
380
402
|
const effectiveScope = mode === "audit" ? requestedScope : scope;
|
|
381
|
-
const report = await runGSDDoctor(
|
|
403
|
+
const report = await runGSDDoctor(projectRoot(), {
|
|
382
404
|
fix: mode === "fix" || mode === "heal",
|
|
383
405
|
scope: effectiveScope,
|
|
384
406
|
});
|
|
@@ -495,8 +517,10 @@ async function handlePrefsWizard(
|
|
|
495
517
|
prefs.auto_supervisor = autoSup;
|
|
496
518
|
}
|
|
497
519
|
|
|
498
|
-
// ─── Git
|
|
520
|
+
// ─── Git settings ───────────────────────────────────────────────────────
|
|
499
521
|
const git: Record<string, unknown> = (prefs.git as Record<string, unknown>) ?? {};
|
|
522
|
+
|
|
523
|
+
// main_branch
|
|
500
524
|
const currentBranch = git.main_branch ? String(git.main_branch) : "";
|
|
501
525
|
const branchInput = await ctx.ui.input(
|
|
502
526
|
`Git main branch${currentBranch ? ` (current: ${currentBranch})` : ""}:`,
|
|
@@ -510,6 +534,90 @@ async function handlePrefsWizard(
|
|
|
510
534
|
delete git.main_branch;
|
|
511
535
|
}
|
|
512
536
|
}
|
|
537
|
+
|
|
538
|
+
// Boolean git toggles
|
|
539
|
+
const gitBooleanFields = [
|
|
540
|
+
{ key: "auto_push", label: "Auto-push commits after committing", defaultVal: false },
|
|
541
|
+
{ key: "push_branches", label: "Push milestone branches to remote", defaultVal: false },
|
|
542
|
+
{ key: "snapshots", label: "Create WIP snapshot commits during long tasks", defaultVal: false },
|
|
543
|
+
] as const;
|
|
544
|
+
|
|
545
|
+
for (const field of gitBooleanFields) {
|
|
546
|
+
const current = git[field.key];
|
|
547
|
+
const currentStr = current !== undefined ? String(current) : "";
|
|
548
|
+
const choice = await ctx.ui.select(
|
|
549
|
+
`${field.label}${currentStr ? ` (current: ${currentStr})` : ` (default: ${field.defaultVal})`}:`,
|
|
550
|
+
["true", "false", "(keep current)"],
|
|
551
|
+
);
|
|
552
|
+
if (choice && choice !== "(keep current)") {
|
|
553
|
+
git[field.key] = choice === "true";
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// remote
|
|
558
|
+
const currentRemote = git.remote ? String(git.remote) : "";
|
|
559
|
+
const remoteInput = await ctx.ui.input(
|
|
560
|
+
`Git remote name${currentRemote ? ` (current: ${currentRemote})` : " (default: origin)"}:`,
|
|
561
|
+
currentRemote || "origin",
|
|
562
|
+
);
|
|
563
|
+
if (remoteInput !== null && remoteInput !== undefined) {
|
|
564
|
+
const val = remoteInput.trim();
|
|
565
|
+
if (val && val !== "origin") {
|
|
566
|
+
git.remote = val;
|
|
567
|
+
} else if (!val && currentRemote) {
|
|
568
|
+
delete git.remote;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// pre_merge_check
|
|
573
|
+
const currentPreMerge = git.pre_merge_check !== undefined ? String(git.pre_merge_check) : "";
|
|
574
|
+
const preMergeChoice = await ctx.ui.select(
|
|
575
|
+
`Pre-merge check${currentPreMerge ? ` (current: ${currentPreMerge})` : " (default: false)"}:`,
|
|
576
|
+
["true", "false", "auto", "(keep current)"],
|
|
577
|
+
);
|
|
578
|
+
if (preMergeChoice && preMergeChoice !== "(keep current)") {
|
|
579
|
+
if (preMergeChoice === "auto") {
|
|
580
|
+
git.pre_merge_check = "auto";
|
|
581
|
+
} else {
|
|
582
|
+
git.pre_merge_check = preMergeChoice === "true";
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// commit_type
|
|
587
|
+
const currentCommitType = git.commit_type ? String(git.commit_type) : "";
|
|
588
|
+
const commitTypes = ["feat", "fix", "refactor", "docs", "test", "chore", "perf", "ci", "build", "style", "(inferred — default)", "(keep current)"];
|
|
589
|
+
const commitChoice = await ctx.ui.select(
|
|
590
|
+
`Default commit type${currentCommitType ? ` (current: ${currentCommitType})` : ""}:`,
|
|
591
|
+
commitTypes,
|
|
592
|
+
);
|
|
593
|
+
if (commitChoice && typeof commitChoice === "string" && commitChoice !== "(keep current)") {
|
|
594
|
+
if ((commitChoice as string).startsWith("(inferred")) {
|
|
595
|
+
delete git.commit_type;
|
|
596
|
+
} else {
|
|
597
|
+
git.commit_type = commitChoice;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// merge_strategy
|
|
602
|
+
const currentMerge = git.merge_strategy ? String(git.merge_strategy) : "";
|
|
603
|
+
const mergeChoice = await ctx.ui.select(
|
|
604
|
+
`Merge strategy${currentMerge ? ` (current: ${currentMerge})` : ""}:`,
|
|
605
|
+
["squash", "merge", "(keep current)"],
|
|
606
|
+
);
|
|
607
|
+
if (mergeChoice && mergeChoice !== "(keep current)") {
|
|
608
|
+
git.merge_strategy = mergeChoice;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// isolation
|
|
612
|
+
const currentIsolation = git.isolation ? String(git.isolation) : "";
|
|
613
|
+
const isolationChoice = await ctx.ui.select(
|
|
614
|
+
`Git isolation strategy${currentIsolation ? ` (current: ${currentIsolation})` : " (default: worktree)"}:`,
|
|
615
|
+
["worktree", "branch", "(keep current)"],
|
|
616
|
+
);
|
|
617
|
+
if (isolationChoice && isolationChoice !== "(keep current)") {
|
|
618
|
+
git.isolation = isolationChoice;
|
|
619
|
+
}
|
|
620
|
+
|
|
513
621
|
// ─── Git commit_docs ────────────────────────────────────────────────────
|
|
514
622
|
const currentCommitDocs = git.commit_docs;
|
|
515
623
|
const commitDocsChoice = await ctx.ui.select(
|
|
@@ -544,6 +652,89 @@ async function handlePrefsWizard(
|
|
|
544
652
|
prefs.unique_milestone_ids = uniqueChoice === "true";
|
|
545
653
|
}
|
|
546
654
|
|
|
655
|
+
// ─── Budget & cost control ────────────────────────────────────────────
|
|
656
|
+
const currentCeiling = prefs.budget_ceiling;
|
|
657
|
+
const ceilingStr = currentCeiling !== undefined ? String(currentCeiling) : "";
|
|
658
|
+
const ceilingInput = await ctx.ui.input(
|
|
659
|
+
`Budget ceiling (USD)${ceilingStr ? ` (current: $${ceilingStr})` : " (default: no limit)"}:`,
|
|
660
|
+
ceilingStr || "",
|
|
661
|
+
);
|
|
662
|
+
if (ceilingInput !== null && ceilingInput !== undefined) {
|
|
663
|
+
const val = ceilingInput.trim().replace(/^\$/, "");
|
|
664
|
+
if (val && !isNaN(Number(val)) && isFinite(Number(val))) {
|
|
665
|
+
prefs.budget_ceiling = Number(val);
|
|
666
|
+
} else if (val && (isNaN(Number(val)) || !isFinite(Number(val)))) {
|
|
667
|
+
ctx.ui.notify(`Invalid budget ceiling "${val}" — must be a number. Keeping previous value.`, "warning");
|
|
668
|
+
} else if (!val && ceilingStr) {
|
|
669
|
+
delete prefs.budget_ceiling;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
const currentEnforcement = (prefs.budget_enforcement as string) ?? "";
|
|
674
|
+
const enforcementChoice = await ctx.ui.select(
|
|
675
|
+
`Budget enforcement${currentEnforcement ? ` (current: ${currentEnforcement})` : " (default: pause)"}:`,
|
|
676
|
+
["warn", "pause", "halt", "(keep current)"],
|
|
677
|
+
);
|
|
678
|
+
if (enforcementChoice && enforcementChoice !== "(keep current)") {
|
|
679
|
+
prefs.budget_enforcement = enforcementChoice;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const currentContextPause = prefs.context_pause_threshold;
|
|
683
|
+
const contextPauseStr = currentContextPause !== undefined ? String(currentContextPause) : "";
|
|
684
|
+
const contextPauseInput = await ctx.ui.input(
|
|
685
|
+
`Context pause threshold (0-100%, 0=disabled)${contextPauseStr ? ` (current: ${contextPauseStr}%)` : " (default: 0)"}:`,
|
|
686
|
+
contextPauseStr || "0",
|
|
687
|
+
);
|
|
688
|
+
if (contextPauseInput !== null && contextPauseInput !== undefined) {
|
|
689
|
+
const val = contextPauseInput.trim().replace(/%$/, "");
|
|
690
|
+
if (val && !isNaN(Number(val)) && Number(val) >= 0 && Number(val) <= 100) {
|
|
691
|
+
const num = Number(val);
|
|
692
|
+
if (num === 0) {
|
|
693
|
+
delete prefs.context_pause_threshold;
|
|
694
|
+
} else {
|
|
695
|
+
prefs.context_pause_threshold = num;
|
|
696
|
+
}
|
|
697
|
+
} else if (val && (isNaN(Number(val)) || Number(val) < 0 || Number(val) > 100)) {
|
|
698
|
+
ctx.ui.notify(`Invalid context pause threshold "${val}" — must be 0-100. Keeping previous value.`, "warning");
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// ─── Notifications ────────────────────────────────────────────────────
|
|
703
|
+
const notif: Record<string, boolean> = (prefs.notifications as Record<string, boolean>) ?? {};
|
|
704
|
+
const notifFields = [
|
|
705
|
+
{ key: "enabled", label: "Notifications enabled (master toggle)", defaultVal: true },
|
|
706
|
+
{ key: "on_complete", label: "Notify on unit completion", defaultVal: true },
|
|
707
|
+
{ key: "on_error", label: "Notify on errors", defaultVal: true },
|
|
708
|
+
{ key: "on_budget", label: "Notify on budget thresholds", defaultVal: true },
|
|
709
|
+
{ key: "on_milestone", label: "Notify on milestone completion", defaultVal: true },
|
|
710
|
+
{ key: "on_attention", label: "Notify when manual attention needed", defaultVal: true },
|
|
711
|
+
] as const;
|
|
712
|
+
|
|
713
|
+
for (const field of notifFields) {
|
|
714
|
+
const current = notif[field.key];
|
|
715
|
+
const currentStr = current !== undefined ? String(current) : "";
|
|
716
|
+
const choice = await ctx.ui.select(
|
|
717
|
+
`${field.label}${currentStr ? ` (current: ${currentStr})` : ` (default: ${field.defaultVal})`}:`,
|
|
718
|
+
["true", "false", "(keep current)"],
|
|
719
|
+
);
|
|
720
|
+
if (choice && choice !== "(keep current)") {
|
|
721
|
+
notif[field.key] = choice === "true";
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (Object.keys(notif).length > 0) {
|
|
725
|
+
prefs.notifications = notif;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// ─── UAT dispatch ─────────────────────────────────────────────────────
|
|
729
|
+
const currentUat = prefs.uat_dispatch;
|
|
730
|
+
const uatChoice = await ctx.ui.select(
|
|
731
|
+
`UAT dispatch mode${currentUat !== undefined ? ` (current: ${currentUat})` : " (default: false)"}:`,
|
|
732
|
+
["true", "false", "(keep current)"],
|
|
733
|
+
);
|
|
734
|
+
if (uatChoice && uatChoice !== "(keep current)") {
|
|
735
|
+
prefs.uat_dispatch = uatChoice === "true";
|
|
736
|
+
}
|
|
737
|
+
|
|
547
738
|
// ─── Serialize to frontmatter ───────────────────────────────────────────
|
|
548
739
|
prefs.version = prefs.version || 1;
|
|
549
740
|
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
|
@@ -634,7 +825,10 @@ function serializePreferencesToFrontmatter(prefs: Record<string, unknown>): stri
|
|
|
634
825
|
const orderedKeys = [
|
|
635
826
|
"version", "always_use_skills", "prefer_skills", "avoid_skills",
|
|
636
827
|
"skill_rules", "custom_instructions", "models", "skill_discovery",
|
|
637
|
-
"auto_supervisor", "uat_dispatch", "unique_milestone_ids",
|
|
828
|
+
"auto_supervisor", "uat_dispatch", "unique_milestone_ids",
|
|
829
|
+
"budget_ceiling", "budget_enforcement", "context_pause_threshold",
|
|
830
|
+
"notifications", "remote_questions", "git",
|
|
831
|
+
"post_unit_hooks", "pre_dispatch_hooks",
|
|
638
832
|
];
|
|
639
833
|
|
|
640
834
|
const seen = new Set<string>();
|
|
@@ -972,6 +1166,35 @@ async function handleCleanupSnapshots(ctx: ExtensionCommandContext, basePath: st
|
|
|
972
1166
|
ctx.ui.notify(`Pruned ${pruned} old snapshot refs. ${refs.length - pruned} remain.`, "success");
|
|
973
1167
|
}
|
|
974
1168
|
|
|
1169
|
+
async function handleKnowledge(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
1170
|
+
const parts = args.split(/\s+/);
|
|
1171
|
+
const typeArg = parts[0]?.toLowerCase();
|
|
1172
|
+
|
|
1173
|
+
if (!typeArg || !["rule", "pattern", "lesson"].includes(typeArg)) {
|
|
1174
|
+
ctx.ui.notify(
|
|
1175
|
+
"Usage: /gsd knowledge <rule|pattern|lesson> <description>\nExample: /gsd knowledge rule Use real DB for integration tests",
|
|
1176
|
+
"warning",
|
|
1177
|
+
);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
const entryText = parts.slice(1).join(" ").trim();
|
|
1182
|
+
if (!entryText) {
|
|
1183
|
+
ctx.ui.notify(`Usage: /gsd knowledge ${typeArg} <description>`, "warning");
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
const type = typeArg as "rule" | "pattern" | "lesson";
|
|
1188
|
+
const basePath = process.cwd();
|
|
1189
|
+
const state = await deriveState(basePath);
|
|
1190
|
+
const scope = state.activeMilestone?.id
|
|
1191
|
+
? `${state.activeMilestone.id}${state.activeSlice ? `/${state.activeSlice.id}` : ""}`
|
|
1192
|
+
: "global";
|
|
1193
|
+
|
|
1194
|
+
await appendKnowledge(basePath, type, entryText, scope);
|
|
1195
|
+
ctx.ui.notify(`Added ${type} to KNOWLEDGE.md: "${entryText}"`, "success");
|
|
1196
|
+
}
|
|
1197
|
+
|
|
975
1198
|
async function handleSteer(change: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
976
1199
|
const basePath = process.cwd();
|
|
977
1200
|
const state = await deriveState(basePath);
|
|
@@ -5,7 +5,7 @@ import { readFileSync } from "node:fs";
|
|
|
5
5
|
import { readdirSync } from "node:fs";
|
|
6
6
|
import { resolveMilestoneFile, milestonesDir } from "./paths.js";
|
|
7
7
|
import { parseRoadmapSlices } from "./roadmap-slices.js";
|
|
8
|
-
import {
|
|
8
|
+
import { findMilestoneIds } from "./guided-flow.js";
|
|
9
9
|
|
|
10
10
|
const SLICE_DISPATCH_TYPES = new Set([
|
|
11
11
|
"research-slice",
|
|
@@ -43,24 +43,12 @@ export function getPriorSliceCompletionBlocker(base: string, _mainBranch: string
|
|
|
43
43
|
const [targetMid, targetSid] = unitId.split("/");
|
|
44
44
|
if (!targetMid || !targetSid) return null;
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
milestoneIds = readdirSync(milestonesDir(base), { withFileTypes: true })
|
|
53
|
-
.filter(d => d.isDirectory())
|
|
54
|
-
.map(d => {
|
|
55
|
-
const match = d.name.match(/^(M\d+(?:-[a-z0-9]{6})?)/);
|
|
56
|
-
return match ? match[1] : null;
|
|
57
|
-
})
|
|
58
|
-
.filter((id): id is string => id !== null)
|
|
59
|
-
.sort(milestoneIdSort)
|
|
60
|
-
.filter(id => extractMilestoneSeq(id) <= targetSeq);
|
|
61
|
-
} catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
46
|
+
// Use findMilestoneIds to respect custom queue order.
|
|
47
|
+
// Only check milestones that come BEFORE the target in queue order.
|
|
48
|
+
const allIds = findMilestoneIds(base);
|
|
49
|
+
const targetIdx = allIds.indexOf(targetMid);
|
|
50
|
+
if (targetIdx < 0) return null;
|
|
51
|
+
const milestoneIds = allIds.slice(0, targetIdx + 1);
|
|
64
52
|
|
|
65
53
|
for (const mid of milestoneIds) {
|
|
66
54
|
// Read from disk (working tree) — always has the latest state
|