gsd-pi 2.17.0 → 2.19.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-dashboard.ts +14 -2
- package/dist/resources/extensions/gsd/auto-prompts.ts +65 -16
- package/dist/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/dist/resources/extensions/gsd/auto.ts +399 -29
- package/dist/resources/extensions/gsd/captures.ts +384 -0
- package/dist/resources/extensions/gsd/commands.ts +382 -23
- package/dist/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- 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/metrics.ts +48 -0
- package/dist/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/dist/resources/extensions/gsd/model-router.ts +256 -0
- package/dist/resources/extensions/gsd/paths.ts +9 -0
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/dist/resources/extensions/gsd/preferences.ts +132 -1
- package/dist/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/dist/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/dist/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/dist/resources/extensions/gsd/prompts/system.md +2 -0
- package/dist/resources/extensions/gsd/prompts/triage-captures.md +62 -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/captures.test.ts +438 -0
- package/dist/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/dist/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -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/milestone-transition-worktree.test.ts +144 -0
- package/dist/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/dist/resources/extensions/gsd/tests/model-router.test.ts +167 -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/remote-questions.test.ts +227 -1
- package/dist/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/dist/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/dist/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/dist/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/dist/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
- package/dist/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
- package/dist/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/dist/resources/extensions/gsd/triage-ui.ts +175 -0
- package/dist/resources/extensions/gsd/visualizer-data.ts +154 -0
- package/dist/resources/extensions/gsd/visualizer-overlay.ts +193 -0
- package/dist/resources/extensions/gsd/visualizer-views.ts +293 -0
- package/dist/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/dist/resources/extensions/gsd/worktree.ts +22 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +33 -0
- package/dist/resources/extensions/remote-questions/format.ts +12 -6
- package/dist/resources/extensions/remote-questions/manager.ts +8 -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-dashboard.ts +14 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +65 -16
- package/src/resources/extensions/gsd/auto-worktree.ts +33 -4
- package/src/resources/extensions/gsd/auto.ts +399 -29
- package/src/resources/extensions/gsd/captures.ts +384 -0
- package/src/resources/extensions/gsd/commands.ts +382 -23
- package/src/resources/extensions/gsd/complexity-classifier.ts +322 -0
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- 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/metrics.ts +48 -0
- package/src/resources/extensions/gsd/model-cost-table.ts +65 -0
- package/src/resources/extensions/gsd/model-router.ts +256 -0
- package/src/resources/extensions/gsd/paths.ts +9 -0
- package/src/resources/extensions/gsd/post-unit-hooks.ts +2 -1
- package/src/resources/extensions/gsd/preferences.ts +132 -1
- package/src/resources/extensions/gsd/prompt-loader.ts +45 -9
- package/src/resources/extensions/gsd/prompts/execute-task.md +6 -5
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +6 -0
- package/src/resources/extensions/gsd/prompts/replan-slice.md +8 -0
- package/src/resources/extensions/gsd/prompts/system.md +2 -0
- package/src/resources/extensions/gsd/prompts/triage-captures.md +62 -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/captures.test.ts +438 -0
- package/src/resources/extensions/gsd/tests/complexity-classifier.test.ts +181 -0
- package/src/resources/extensions/gsd/tests/derive-state-deps.test.ts +99 -0
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +434 -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/milestone-transition-worktree.test.ts +144 -0
- package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +69 -0
- package/src/resources/extensions/gsd/tests/model-router.test.ts +167 -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/remote-questions.test.ts +227 -1
- package/src/resources/extensions/gsd/tests/routing-history.test.ts +215 -62
- package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +139 -0
- package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +224 -0
- package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +215 -0
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +198 -0
- package/src/resources/extensions/gsd/tests/visualizer-views.test.ts +255 -0
- package/src/resources/extensions/gsd/triage-resolution.ts +200 -0
- package/src/resources/extensions/gsd/triage-ui.ts +175 -0
- package/src/resources/extensions/gsd/visualizer-data.ts +154 -0
- package/src/resources/extensions/gsd/visualizer-overlay.ts +193 -0
- package/src/resources/extensions/gsd/visualizer-views.ts +293 -0
- package/src/resources/extensions/gsd/worktree-manager.ts +8 -5
- package/src/resources/extensions/gsd/worktree.ts +22 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +33 -0
- package/src/resources/extensions/remote-questions/format.ts +12 -6
- package/src/resources/extensions/remote-questions/manager.ts +8 -0
- package/src/resources/extensions/shared/next-action-ui.ts +16 -1
|
@@ -11,8 +11,11 @@ import { join, dirname } from "node:path";
|
|
|
11
11
|
import { fileURLToPath } from "node:url";
|
|
12
12
|
import { deriveState } from "./state.js";
|
|
13
13
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
14
|
+
import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
|
|
14
15
|
import { showQueue, showDiscuss } from "./guided-flow.js";
|
|
15
16
|
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote } from "./auto.js";
|
|
17
|
+
import { resolveProjectRoot } from "./worktree.js";
|
|
18
|
+
import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captures.js";
|
|
16
19
|
import {
|
|
17
20
|
getGlobalGSDPreferencesPath,
|
|
18
21
|
getLegacyGlobalGSDPreferencesPath,
|
|
@@ -22,7 +25,7 @@ import {
|
|
|
22
25
|
loadEffectiveGSDPreferences,
|
|
23
26
|
resolveAllSkillReferences,
|
|
24
27
|
} from "./preferences.js";
|
|
25
|
-
import { loadFile, saveFile, appendOverride } from "./files.js";
|
|
28
|
+
import { loadFile, saveFile, appendOverride, appendKnowledge } from "./files.js";
|
|
26
29
|
import {
|
|
27
30
|
formatDoctorIssuesForPrompt,
|
|
28
31
|
formatDoctorReport,
|
|
@@ -56,14 +59,20 @@ function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportT
|
|
|
56
59
|
);
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
/** Resolve the effective project root, accounting for worktree paths. */
|
|
63
|
+
function projectRoot(): string {
|
|
64
|
+
return resolveProjectRoot(process.cwd());
|
|
65
|
+
}
|
|
66
|
+
|
|
59
67
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
60
68
|
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",
|
|
69
|
+
description: "GSD — Get Shit Done: /gsd next|auto|stop|pause|status|visualize|queue|capture|triage|history|undo|skip|export|cleanup|prefs|config|hooks|doctor|migrate|remote|steer|knowledge",
|
|
62
70
|
getArgumentCompletions: (prefix: string) => {
|
|
63
71
|
const subcommands = [
|
|
64
|
-
"next", "auto", "stop", "pause", "status", "queue", "discuss",
|
|
72
|
+
"next", "auto", "stop", "pause", "status", "visualize", "queue", "discuss",
|
|
73
|
+
"capture", "triage",
|
|
65
74
|
"history", "undo", "skip", "export", "cleanup", "prefs",
|
|
66
|
-
"config", "hooks", "doctor", "migrate", "remote", "steer",
|
|
75
|
+
"config", "hooks", "doctor", "migrate", "remote", "steer", "knowledge",
|
|
67
76
|
];
|
|
68
77
|
const parts = prefix.trim().split(/\s+/);
|
|
69
78
|
|
|
@@ -126,6 +135,13 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
126
135
|
.map((cmd) => ({ value: `cleanup ${cmd}`, label: cmd }));
|
|
127
136
|
}
|
|
128
137
|
|
|
138
|
+
if (parts[0] === "knowledge" && parts.length <= 2) {
|
|
139
|
+
const subPrefix = parts[1] ?? "";
|
|
140
|
+
return ["rule", "pattern", "lesson"]
|
|
141
|
+
.filter((cmd) => cmd.startsWith(subPrefix))
|
|
142
|
+
.map((cmd) => ({ value: `knowledge ${cmd}`, label: cmd }));
|
|
143
|
+
}
|
|
144
|
+
|
|
129
145
|
if (parts[0] === "doctor") {
|
|
130
146
|
const modePrefix = parts[1] ?? "";
|
|
131
147
|
const modes = ["fix", "heal", "audit"];
|
|
@@ -150,6 +166,11 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
150
166
|
return;
|
|
151
167
|
}
|
|
152
168
|
|
|
169
|
+
if (trimmed === "visualize") {
|
|
170
|
+
await handleVisualize(ctx);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
153
174
|
if (trimmed === "prefs" || trimmed.startsWith("prefs ")) {
|
|
154
175
|
await handlePrefs(trimmed.replace(/^prefs\s*/, "").trim(), ctx);
|
|
155
176
|
return;
|
|
@@ -162,24 +183,24 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
162
183
|
|
|
163
184
|
if (trimmed === "next" || trimmed.startsWith("next ")) {
|
|
164
185
|
if (trimmed.includes("--dry-run")) {
|
|
165
|
-
await handleDryRun(ctx,
|
|
186
|
+
await handleDryRun(ctx, projectRoot());
|
|
166
187
|
return;
|
|
167
188
|
}
|
|
168
189
|
const verboseMode = trimmed.includes("--verbose");
|
|
169
|
-
await startAuto(ctx, pi,
|
|
190
|
+
await startAuto(ctx, pi, projectRoot(), verboseMode, { step: true });
|
|
170
191
|
return;
|
|
171
192
|
}
|
|
172
193
|
|
|
173
194
|
if (trimmed === "auto" || trimmed.startsWith("auto ")) {
|
|
174
195
|
const verboseMode = trimmed.includes("--verbose");
|
|
175
|
-
await startAuto(ctx, pi,
|
|
196
|
+
await startAuto(ctx, pi, projectRoot(), verboseMode);
|
|
176
197
|
return;
|
|
177
198
|
}
|
|
178
199
|
|
|
179
200
|
if (trimmed === "stop") {
|
|
180
201
|
if (!isAutoActive() && !isAutoPaused()) {
|
|
181
202
|
// Not running in this process — check for a remote auto-mode session
|
|
182
|
-
const result = stopAutoRemote(
|
|
203
|
+
const result = stopAutoRemote(projectRoot());
|
|
183
204
|
if (result.found) {
|
|
184
205
|
ctx.ui.notify(`Sent stop signal to auto-mode session (PID ${result.pid}). It will shut down gracefully.`, "info");
|
|
185
206
|
} else if (result.error) {
|
|
@@ -207,42 +228,52 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
207
228
|
}
|
|
208
229
|
|
|
209
230
|
if (trimmed === "history" || trimmed.startsWith("history ")) {
|
|
210
|
-
await handleHistory(trimmed.replace(/^history\s*/, "").trim(), ctx,
|
|
231
|
+
await handleHistory(trimmed.replace(/^history\s*/, "").trim(), ctx, projectRoot());
|
|
211
232
|
return;
|
|
212
233
|
}
|
|
213
234
|
|
|
214
235
|
if (trimmed === "undo" || trimmed.startsWith("undo ")) {
|
|
215
|
-
await handleUndo(trimmed.replace(/^undo\s*/, "").trim(), ctx, pi,
|
|
236
|
+
await handleUndo(trimmed.replace(/^undo\s*/, "").trim(), ctx, pi, projectRoot());
|
|
216
237
|
return;
|
|
217
238
|
}
|
|
218
239
|
|
|
219
240
|
if (trimmed.startsWith("skip ")) {
|
|
220
|
-
await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx,
|
|
241
|
+
await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx, projectRoot());
|
|
221
242
|
return;
|
|
222
243
|
}
|
|
223
244
|
|
|
224
245
|
if (trimmed === "export" || trimmed.startsWith("export ")) {
|
|
225
|
-
await handleExport(trimmed.replace(/^export\s*/, "").trim(), ctx,
|
|
246
|
+
await handleExport(trimmed.replace(/^export\s*/, "").trim(), ctx, projectRoot());
|
|
226
247
|
return;
|
|
227
248
|
}
|
|
228
249
|
|
|
229
250
|
if (trimmed === "cleanup branches") {
|
|
230
|
-
await handleCleanupBranches(ctx,
|
|
251
|
+
await handleCleanupBranches(ctx, projectRoot());
|
|
231
252
|
return;
|
|
232
253
|
}
|
|
233
254
|
|
|
234
255
|
if (trimmed === "cleanup snapshots") {
|
|
235
|
-
await handleCleanupSnapshots(ctx,
|
|
256
|
+
await handleCleanupSnapshots(ctx, projectRoot());
|
|
236
257
|
return;
|
|
237
258
|
}
|
|
238
259
|
|
|
239
260
|
if (trimmed === "queue") {
|
|
240
|
-
await showQueue(ctx, pi,
|
|
261
|
+
await showQueue(ctx, pi, projectRoot());
|
|
241
262
|
return;
|
|
242
263
|
}
|
|
243
264
|
|
|
244
265
|
if (trimmed === "discuss") {
|
|
245
|
-
await showDiscuss(ctx, pi,
|
|
266
|
+
await showDiscuss(ctx, pi, projectRoot());
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (trimmed.startsWith("capture ") || trimmed === "capture") {
|
|
271
|
+
await handleCapture(trimmed.replace(/^capture\s*/, "").trim(), ctx);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (trimmed === "triage") {
|
|
276
|
+
await handleTriage(ctx, pi, process.cwd());
|
|
246
277
|
return;
|
|
247
278
|
}
|
|
248
279
|
|
|
@@ -266,6 +297,15 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
266
297
|
return;
|
|
267
298
|
}
|
|
268
299
|
|
|
300
|
+
if (trimmed.startsWith("knowledge ")) {
|
|
301
|
+
await handleKnowledge(trimmed.replace(/^knowledge\s+/, "").trim(), ctx);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (trimmed === "knowledge") {
|
|
305
|
+
ctx.ui.notify("Usage: /gsd knowledge <rule|pattern|lesson> <description>. Example: /gsd knowledge rule Use real DB for integration tests", "warning");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
269
309
|
if (trimmed === "migrate" || trimmed.startsWith("migrate ")) {
|
|
270
310
|
const { handleMigrate } = await import("./migrate/command.js");
|
|
271
311
|
await handleMigrate(trimmed.replace(/^migrate\s*/, "").trim(), ctx, pi);
|
|
@@ -279,12 +319,12 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
279
319
|
|
|
280
320
|
if (trimmed === "") {
|
|
281
321
|
// Bare /gsd defaults to step mode
|
|
282
|
-
await startAuto(ctx, pi,
|
|
322
|
+
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
283
323
|
return;
|
|
284
324
|
}
|
|
285
325
|
|
|
286
326
|
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>.`,
|
|
327
|
+
`Unknown: /gsd ${trimmed}. Use /gsd next|auto|stop|pause|status|visualize|queue|capture|triage|discuss|history|undo|skip <unit>|export|cleanup|prefs|config|hooks|doctor|migrate|remote|steer <change>|knowledge <type> <entry>.`,
|
|
288
328
|
"warning",
|
|
289
329
|
);
|
|
290
330
|
},
|
|
@@ -292,7 +332,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
292
332
|
}
|
|
293
333
|
|
|
294
334
|
async function handleStatus(ctx: ExtensionCommandContext): Promise<void> {
|
|
295
|
-
const basePath =
|
|
335
|
+
const basePath = projectRoot();
|
|
296
336
|
const state = await deriveState(basePath);
|
|
297
337
|
|
|
298
338
|
if (state.registry.length === 0) {
|
|
@@ -322,6 +362,28 @@ export async function fireStatusViaCommand(
|
|
|
322
362
|
await handleStatus(ctx as ExtensionCommandContext);
|
|
323
363
|
}
|
|
324
364
|
|
|
365
|
+
async function handleVisualize(ctx: ExtensionCommandContext): Promise<void> {
|
|
366
|
+
if (!ctx.hasUI) {
|
|
367
|
+
ctx.ui.notify("Visualizer requires an interactive terminal.", "warning");
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
await ctx.ui.custom<void>(
|
|
372
|
+
(tui, theme, _kb, done) => {
|
|
373
|
+
return new GSDVisualizerOverlay(tui, theme, () => done());
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
overlay: true,
|
|
377
|
+
overlayOptions: {
|
|
378
|
+
width: "80%",
|
|
379
|
+
minWidth: 80,
|
|
380
|
+
maxHeight: "90%",
|
|
381
|
+
anchor: "center",
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
325
387
|
async function handlePrefs(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
326
388
|
const trimmed = args.trim();
|
|
327
389
|
|
|
@@ -376,9 +438,9 @@ async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: Exte
|
|
|
376
438
|
const parts = trimmed ? trimmed.split(/\s+/) : [];
|
|
377
439
|
const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
|
|
378
440
|
const requestedScope = mode === "doctor" ? parts[0] : parts[1];
|
|
379
|
-
const scope = await selectDoctorScope(
|
|
441
|
+
const scope = await selectDoctorScope(projectRoot(), requestedScope);
|
|
380
442
|
const effectiveScope = mode === "audit" ? requestedScope : scope;
|
|
381
|
-
const report = await runGSDDoctor(
|
|
443
|
+
const report = await runGSDDoctor(projectRoot(), {
|
|
382
444
|
fix: mode === "fix" || mode === "heal",
|
|
383
445
|
scope: effectiveScope,
|
|
384
446
|
});
|
|
@@ -495,8 +557,10 @@ async function handlePrefsWizard(
|
|
|
495
557
|
prefs.auto_supervisor = autoSup;
|
|
496
558
|
}
|
|
497
559
|
|
|
498
|
-
// ─── Git
|
|
560
|
+
// ─── Git settings ───────────────────────────────────────────────────────
|
|
499
561
|
const git: Record<string, unknown> = (prefs.git as Record<string, unknown>) ?? {};
|
|
562
|
+
|
|
563
|
+
// main_branch
|
|
500
564
|
const currentBranch = git.main_branch ? String(git.main_branch) : "";
|
|
501
565
|
const branchInput = await ctx.ui.input(
|
|
502
566
|
`Git main branch${currentBranch ? ` (current: ${currentBranch})` : ""}:`,
|
|
@@ -510,6 +574,90 @@ async function handlePrefsWizard(
|
|
|
510
574
|
delete git.main_branch;
|
|
511
575
|
}
|
|
512
576
|
}
|
|
577
|
+
|
|
578
|
+
// Boolean git toggles
|
|
579
|
+
const gitBooleanFields = [
|
|
580
|
+
{ key: "auto_push", label: "Auto-push commits after committing", defaultVal: false },
|
|
581
|
+
{ key: "push_branches", label: "Push milestone branches to remote", defaultVal: false },
|
|
582
|
+
{ key: "snapshots", label: "Create WIP snapshot commits during long tasks", defaultVal: false },
|
|
583
|
+
] as const;
|
|
584
|
+
|
|
585
|
+
for (const field of gitBooleanFields) {
|
|
586
|
+
const current = git[field.key];
|
|
587
|
+
const currentStr = current !== undefined ? String(current) : "";
|
|
588
|
+
const choice = await ctx.ui.select(
|
|
589
|
+
`${field.label}${currentStr ? ` (current: ${currentStr})` : ` (default: ${field.defaultVal})`}:`,
|
|
590
|
+
["true", "false", "(keep current)"],
|
|
591
|
+
);
|
|
592
|
+
if (choice && choice !== "(keep current)") {
|
|
593
|
+
git[field.key] = choice === "true";
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// remote
|
|
598
|
+
const currentRemote = git.remote ? String(git.remote) : "";
|
|
599
|
+
const remoteInput = await ctx.ui.input(
|
|
600
|
+
`Git remote name${currentRemote ? ` (current: ${currentRemote})` : " (default: origin)"}:`,
|
|
601
|
+
currentRemote || "origin",
|
|
602
|
+
);
|
|
603
|
+
if (remoteInput !== null && remoteInput !== undefined) {
|
|
604
|
+
const val = remoteInput.trim();
|
|
605
|
+
if (val && val !== "origin") {
|
|
606
|
+
git.remote = val;
|
|
607
|
+
} else if (!val && currentRemote) {
|
|
608
|
+
delete git.remote;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// pre_merge_check
|
|
613
|
+
const currentPreMerge = git.pre_merge_check !== undefined ? String(git.pre_merge_check) : "";
|
|
614
|
+
const preMergeChoice = await ctx.ui.select(
|
|
615
|
+
`Pre-merge check${currentPreMerge ? ` (current: ${currentPreMerge})` : " (default: false)"}:`,
|
|
616
|
+
["true", "false", "auto", "(keep current)"],
|
|
617
|
+
);
|
|
618
|
+
if (preMergeChoice && preMergeChoice !== "(keep current)") {
|
|
619
|
+
if (preMergeChoice === "auto") {
|
|
620
|
+
git.pre_merge_check = "auto";
|
|
621
|
+
} else {
|
|
622
|
+
git.pre_merge_check = preMergeChoice === "true";
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// commit_type
|
|
627
|
+
const currentCommitType = git.commit_type ? String(git.commit_type) : "";
|
|
628
|
+
const commitTypes = ["feat", "fix", "refactor", "docs", "test", "chore", "perf", "ci", "build", "style", "(inferred — default)", "(keep current)"];
|
|
629
|
+
const commitChoice = await ctx.ui.select(
|
|
630
|
+
`Default commit type${currentCommitType ? ` (current: ${currentCommitType})` : ""}:`,
|
|
631
|
+
commitTypes,
|
|
632
|
+
);
|
|
633
|
+
if (commitChoice && typeof commitChoice === "string" && commitChoice !== "(keep current)") {
|
|
634
|
+
if ((commitChoice as string).startsWith("(inferred")) {
|
|
635
|
+
delete git.commit_type;
|
|
636
|
+
} else {
|
|
637
|
+
git.commit_type = commitChoice;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// merge_strategy
|
|
642
|
+
const currentMerge = git.merge_strategy ? String(git.merge_strategy) : "";
|
|
643
|
+
const mergeChoice = await ctx.ui.select(
|
|
644
|
+
`Merge strategy${currentMerge ? ` (current: ${currentMerge})` : ""}:`,
|
|
645
|
+
["squash", "merge", "(keep current)"],
|
|
646
|
+
);
|
|
647
|
+
if (mergeChoice && mergeChoice !== "(keep current)") {
|
|
648
|
+
git.merge_strategy = mergeChoice;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// isolation
|
|
652
|
+
const currentIsolation = git.isolation ? String(git.isolation) : "";
|
|
653
|
+
const isolationChoice = await ctx.ui.select(
|
|
654
|
+
`Git isolation strategy${currentIsolation ? ` (current: ${currentIsolation})` : " (default: worktree)"}:`,
|
|
655
|
+
["worktree", "branch", "(keep current)"],
|
|
656
|
+
);
|
|
657
|
+
if (isolationChoice && isolationChoice !== "(keep current)") {
|
|
658
|
+
git.isolation = isolationChoice;
|
|
659
|
+
}
|
|
660
|
+
|
|
513
661
|
// ─── Git commit_docs ────────────────────────────────────────────────────
|
|
514
662
|
const currentCommitDocs = git.commit_docs;
|
|
515
663
|
const commitDocsChoice = await ctx.ui.select(
|
|
@@ -544,6 +692,89 @@ async function handlePrefsWizard(
|
|
|
544
692
|
prefs.unique_milestone_ids = uniqueChoice === "true";
|
|
545
693
|
}
|
|
546
694
|
|
|
695
|
+
// ─── Budget & cost control ────────────────────────────────────────────
|
|
696
|
+
const currentCeiling = prefs.budget_ceiling;
|
|
697
|
+
const ceilingStr = currentCeiling !== undefined ? String(currentCeiling) : "";
|
|
698
|
+
const ceilingInput = await ctx.ui.input(
|
|
699
|
+
`Budget ceiling (USD)${ceilingStr ? ` (current: $${ceilingStr})` : " (default: no limit)"}:`,
|
|
700
|
+
ceilingStr || "",
|
|
701
|
+
);
|
|
702
|
+
if (ceilingInput !== null && ceilingInput !== undefined) {
|
|
703
|
+
const val = ceilingInput.trim().replace(/^\$/, "");
|
|
704
|
+
if (val && !isNaN(Number(val)) && isFinite(Number(val))) {
|
|
705
|
+
prefs.budget_ceiling = Number(val);
|
|
706
|
+
} else if (val && (isNaN(Number(val)) || !isFinite(Number(val)))) {
|
|
707
|
+
ctx.ui.notify(`Invalid budget ceiling "${val}" — must be a number. Keeping previous value.`, "warning");
|
|
708
|
+
} else if (!val && ceilingStr) {
|
|
709
|
+
delete prefs.budget_ceiling;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
const currentEnforcement = (prefs.budget_enforcement as string) ?? "";
|
|
714
|
+
const enforcementChoice = await ctx.ui.select(
|
|
715
|
+
`Budget enforcement${currentEnforcement ? ` (current: ${currentEnforcement})` : " (default: pause)"}:`,
|
|
716
|
+
["warn", "pause", "halt", "(keep current)"],
|
|
717
|
+
);
|
|
718
|
+
if (enforcementChoice && enforcementChoice !== "(keep current)") {
|
|
719
|
+
prefs.budget_enforcement = enforcementChoice;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
const currentContextPause = prefs.context_pause_threshold;
|
|
723
|
+
const contextPauseStr = currentContextPause !== undefined ? String(currentContextPause) : "";
|
|
724
|
+
const contextPauseInput = await ctx.ui.input(
|
|
725
|
+
`Context pause threshold (0-100%, 0=disabled)${contextPauseStr ? ` (current: ${contextPauseStr}%)` : " (default: 0)"}:`,
|
|
726
|
+
contextPauseStr || "0",
|
|
727
|
+
);
|
|
728
|
+
if (contextPauseInput !== null && contextPauseInput !== undefined) {
|
|
729
|
+
const val = contextPauseInput.trim().replace(/%$/, "");
|
|
730
|
+
if (val && !isNaN(Number(val)) && Number(val) >= 0 && Number(val) <= 100) {
|
|
731
|
+
const num = Number(val);
|
|
732
|
+
if (num === 0) {
|
|
733
|
+
delete prefs.context_pause_threshold;
|
|
734
|
+
} else {
|
|
735
|
+
prefs.context_pause_threshold = num;
|
|
736
|
+
}
|
|
737
|
+
} else if (val && (isNaN(Number(val)) || Number(val) < 0 || Number(val) > 100)) {
|
|
738
|
+
ctx.ui.notify(`Invalid context pause threshold "${val}" — must be 0-100. Keeping previous value.`, "warning");
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// ─── Notifications ────────────────────────────────────────────────────
|
|
743
|
+
const notif: Record<string, boolean> = (prefs.notifications as Record<string, boolean>) ?? {};
|
|
744
|
+
const notifFields = [
|
|
745
|
+
{ key: "enabled", label: "Notifications enabled (master toggle)", defaultVal: true },
|
|
746
|
+
{ key: "on_complete", label: "Notify on unit completion", defaultVal: true },
|
|
747
|
+
{ key: "on_error", label: "Notify on errors", defaultVal: true },
|
|
748
|
+
{ key: "on_budget", label: "Notify on budget thresholds", defaultVal: true },
|
|
749
|
+
{ key: "on_milestone", label: "Notify on milestone completion", defaultVal: true },
|
|
750
|
+
{ key: "on_attention", label: "Notify when manual attention needed", defaultVal: true },
|
|
751
|
+
] as const;
|
|
752
|
+
|
|
753
|
+
for (const field of notifFields) {
|
|
754
|
+
const current = notif[field.key];
|
|
755
|
+
const currentStr = current !== undefined ? String(current) : "";
|
|
756
|
+
const choice = await ctx.ui.select(
|
|
757
|
+
`${field.label}${currentStr ? ` (current: ${currentStr})` : ` (default: ${field.defaultVal})`}:`,
|
|
758
|
+
["true", "false", "(keep current)"],
|
|
759
|
+
);
|
|
760
|
+
if (choice && choice !== "(keep current)") {
|
|
761
|
+
notif[field.key] = choice === "true";
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
if (Object.keys(notif).length > 0) {
|
|
765
|
+
prefs.notifications = notif;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
// ─── UAT dispatch ─────────────────────────────────────────────────────
|
|
769
|
+
const currentUat = prefs.uat_dispatch;
|
|
770
|
+
const uatChoice = await ctx.ui.select(
|
|
771
|
+
`UAT dispatch mode${currentUat !== undefined ? ` (current: ${currentUat})` : " (default: false)"}:`,
|
|
772
|
+
["true", "false", "(keep current)"],
|
|
773
|
+
);
|
|
774
|
+
if (uatChoice && uatChoice !== "(keep current)") {
|
|
775
|
+
prefs.uat_dispatch = uatChoice === "true";
|
|
776
|
+
}
|
|
777
|
+
|
|
547
778
|
// ─── Serialize to frontmatter ───────────────────────────────────────────
|
|
548
779
|
prefs.version = prefs.version || 1;
|
|
549
780
|
const frontmatter = serializePreferencesToFrontmatter(prefs);
|
|
@@ -634,7 +865,10 @@ function serializePreferencesToFrontmatter(prefs: Record<string, unknown>): stri
|
|
|
634
865
|
const orderedKeys = [
|
|
635
866
|
"version", "always_use_skills", "prefer_skills", "avoid_skills",
|
|
636
867
|
"skill_rules", "custom_instructions", "models", "skill_discovery",
|
|
637
|
-
"auto_supervisor", "uat_dispatch", "unique_milestone_ids",
|
|
868
|
+
"auto_supervisor", "uat_dispatch", "unique_milestone_ids",
|
|
869
|
+
"budget_ceiling", "budget_enforcement", "context_pause_threshold",
|
|
870
|
+
"notifications", "remote_questions", "git",
|
|
871
|
+
"post_unit_hooks", "pre_dispatch_hooks",
|
|
638
872
|
];
|
|
639
873
|
|
|
640
874
|
const seen = new Set<string>();
|
|
@@ -972,6 +1206,131 @@ async function handleCleanupSnapshots(ctx: ExtensionCommandContext, basePath: st
|
|
|
972
1206
|
ctx.ui.notify(`Pruned ${pruned} old snapshot refs. ${refs.length - pruned} remain.`, "success");
|
|
973
1207
|
}
|
|
974
1208
|
|
|
1209
|
+
async function handleKnowledge(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
1210
|
+
const parts = args.split(/\s+/);
|
|
1211
|
+
const typeArg = parts[0]?.toLowerCase();
|
|
1212
|
+
|
|
1213
|
+
if (!typeArg || !["rule", "pattern", "lesson"].includes(typeArg)) {
|
|
1214
|
+
ctx.ui.notify(
|
|
1215
|
+
"Usage: /gsd knowledge <rule|pattern|lesson> <description>\nExample: /gsd knowledge rule Use real DB for integration tests",
|
|
1216
|
+
"warning",
|
|
1217
|
+
);
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
const entryText = parts.slice(1).join(" ").trim();
|
|
1222
|
+
if (!entryText) {
|
|
1223
|
+
ctx.ui.notify(`Usage: /gsd knowledge ${typeArg} <description>`, "warning");
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
const type = typeArg as "rule" | "pattern" | "lesson";
|
|
1228
|
+
const basePath = process.cwd();
|
|
1229
|
+
const state = await deriveState(basePath);
|
|
1230
|
+
const scope = state.activeMilestone?.id
|
|
1231
|
+
? `${state.activeMilestone.id}${state.activeSlice ? `/${state.activeSlice.id}` : ""}`
|
|
1232
|
+
: "global";
|
|
1233
|
+
|
|
1234
|
+
await appendKnowledge(basePath, type, entryText, scope);
|
|
1235
|
+
ctx.ui.notify(`Added ${type} to KNOWLEDGE.md: "${entryText}"`, "success");
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
// ─── Capture Command ──────────────────────────────────────────────────────────
|
|
1239
|
+
|
|
1240
|
+
/**
|
|
1241
|
+
* Handle `/gsd capture "..."` — fire-and-forget thought capture.
|
|
1242
|
+
* Appends to `.gsd/CAPTURES.md` without interrupting auto-mode.
|
|
1243
|
+
* Works in all modes: auto running, paused, stopped, no project.
|
|
1244
|
+
*/
|
|
1245
|
+
async function handleCapture(args: string, ctx: ExtensionCommandContext): Promise<void> {
|
|
1246
|
+
// Strip surrounding quotes from the argument
|
|
1247
|
+
let text = args.trim();
|
|
1248
|
+
if (!text) {
|
|
1249
|
+
ctx.ui.notify('Usage: /gsd capture "your thought here"', "warning");
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
// Remove wrapping quotes (single or double)
|
|
1253
|
+
if ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'"))) {
|
|
1254
|
+
text = text.slice(1, -1);
|
|
1255
|
+
}
|
|
1256
|
+
if (!text) {
|
|
1257
|
+
ctx.ui.notify('Usage: /gsd capture "your thought here"', "warning");
|
|
1258
|
+
return;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
const basePath = process.cwd();
|
|
1262
|
+
|
|
1263
|
+
// Ensure .gsd/ exists — capture should work even without a milestone
|
|
1264
|
+
const gsdDir = join(basePath, ".gsd");
|
|
1265
|
+
if (!existsSync(gsdDir)) {
|
|
1266
|
+
mkdirSync(gsdDir, { recursive: true });
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const id = appendCapture(basePath, text);
|
|
1270
|
+
ctx.ui.notify(`Captured: ${id} — "${text.length > 60 ? text.slice(0, 57) + "..." : text}"`, "info");
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// ─── Triage Command ───────────────────────────────────────────────────────────
|
|
1274
|
+
|
|
1275
|
+
/**
|
|
1276
|
+
* Handle `/gsd triage` — manually trigger triage of pending captures.
|
|
1277
|
+
* Dispatches the triage prompt to the LLM for classification.
|
|
1278
|
+
* Triage result handling (confirmation UI) is wired in T03.
|
|
1279
|
+
*/
|
|
1280
|
+
async function handleTriage(ctx: ExtensionCommandContext, pi: ExtensionAPI, basePath: string): Promise<void> {
|
|
1281
|
+
if (!hasPendingCaptures(basePath)) {
|
|
1282
|
+
ctx.ui.notify("No pending captures to triage.", "info");
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
const pending = loadPendingCaptures(basePath);
|
|
1287
|
+
ctx.ui.notify(`Triaging ${pending.length} pending capture${pending.length === 1 ? "" : "s"}...`, "info");
|
|
1288
|
+
|
|
1289
|
+
// Build context for the triage prompt
|
|
1290
|
+
const state = await deriveState(basePath);
|
|
1291
|
+
let currentPlan = "";
|
|
1292
|
+
let roadmapContext = "";
|
|
1293
|
+
|
|
1294
|
+
if (state.activeMilestone && state.activeSlice) {
|
|
1295
|
+
const { resolveSliceFile, resolveMilestoneFile } = await import("./paths.js");
|
|
1296
|
+
const planFile = resolveSliceFile(basePath, state.activeMilestone.id, state.activeSlice.id, "PLAN");
|
|
1297
|
+
if (planFile) {
|
|
1298
|
+
const { loadFile: load } = await import("./files.js");
|
|
1299
|
+
currentPlan = (await load(planFile)) ?? "";
|
|
1300
|
+
}
|
|
1301
|
+
const roadmapFile = resolveMilestoneFile(basePath, state.activeMilestone.id, "ROADMAP");
|
|
1302
|
+
if (roadmapFile) {
|
|
1303
|
+
const { loadFile: load } = await import("./files.js");
|
|
1304
|
+
roadmapContext = (await load(roadmapFile)) ?? "";
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// Format pending captures for the prompt
|
|
1309
|
+
const capturesList = pending.map(c =>
|
|
1310
|
+
`- **${c.id}**: "${c.text}" (captured: ${c.timestamp})`
|
|
1311
|
+
).join("\n");
|
|
1312
|
+
|
|
1313
|
+
// Dispatch triage prompt
|
|
1314
|
+
const { loadPrompt } = await import("./prompt-loader.js");
|
|
1315
|
+
const prompt = loadPrompt("triage-captures", {
|
|
1316
|
+
pendingCaptures: capturesList,
|
|
1317
|
+
currentPlan: currentPlan || "(no active slice plan)",
|
|
1318
|
+
roadmapContext: roadmapContext || "(no active roadmap)",
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
|
|
1322
|
+
const workflow = readFileSync(workflowPath, "utf-8");
|
|
1323
|
+
|
|
1324
|
+
pi.sendMessage(
|
|
1325
|
+
{
|
|
1326
|
+
customType: "gsd-triage",
|
|
1327
|
+
content: `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`,
|
|
1328
|
+
display: false,
|
|
1329
|
+
},
|
|
1330
|
+
{ triggerTurn: true },
|
|
1331
|
+
);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
975
1334
|
async function handleSteer(change: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
976
1335
|
const basePath = process.cwd();
|
|
977
1336
|
const state = await deriveState(basePath);
|