gsd-pi 2.39.0 → 2.40.0-dev.4a93031
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/resource-loader.js +66 -2
- package/dist/resources/extensions/async-jobs/index.js +10 -0
- package/dist/resources/extensions/get-secrets-from-user.js +1 -1
- package/dist/resources/extensions/gsd/auto-dashboard.js +7 -0
- package/dist/resources/extensions/gsd/auto-loop.js +761 -673
- package/dist/resources/extensions/gsd/auto-post-unit.js +10 -2
- package/dist/resources/extensions/gsd/auto-prompts.js +3 -3
- package/dist/resources/extensions/gsd/auto-start.js +6 -1
- package/dist/resources/extensions/gsd/auto.js +6 -4
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +126 -0
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +233 -0
- package/dist/resources/extensions/gsd/bootstrap/dynamic-tools.js +59 -0
- package/dist/resources/extensions/gsd/bootstrap/register-extension.js +38 -0
- package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +156 -0
- package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +46 -0
- package/dist/resources/extensions/gsd/bootstrap/system-context.js +300 -0
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +38 -0
- package/dist/resources/extensions/gsd/commands/catalog.js +278 -0
- package/dist/resources/extensions/gsd/commands/context.js +84 -0
- package/dist/resources/extensions/gsd/commands/dispatcher.js +21 -0
- package/dist/resources/extensions/gsd/commands/handlers/auto.js +72 -0
- package/dist/resources/extensions/gsd/commands/handlers/core.js +246 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +166 -0
- package/dist/resources/extensions/gsd/commands/handlers/parallel.js +94 -0
- package/dist/resources/extensions/gsd/commands/handlers/workflow.js +102 -0
- package/dist/resources/extensions/gsd/commands/index.js +11 -0
- package/dist/resources/extensions/gsd/commands-handlers.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +8 -1190
- package/dist/resources/extensions/gsd/dashboard-overlay.js +9 -0
- package/dist/resources/extensions/gsd/doctor-proactive.js +80 -10
- package/dist/resources/extensions/gsd/doctor.js +32 -2
- package/dist/resources/extensions/gsd/export-html.js +46 -0
- package/dist/resources/extensions/gsd/files.js +1 -1
- package/dist/resources/extensions/gsd/health-widget.js +1 -1
- package/dist/resources/extensions/gsd/index.js +4 -1115
- package/dist/resources/extensions/gsd/progress-score.js +20 -1
- package/dist/resources/extensions/gsd/prompts/forensics.md +121 -46
- package/dist/resources/extensions/gsd/visualizer-data.js +26 -1
- package/dist/resources/extensions/gsd/visualizer-views.js +52 -0
- package/dist/welcome-screen.d.ts +3 -2
- package/dist/welcome-screen.js +66 -22
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts +12 -0
- package/packages/pi-coding-agent/dist/core/agent-session.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/agent-session.js +107 -24
- package/packages/pi-coding-agent/dist/core/agent-session.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js +70 -0
- package/packages/pi-coding-agent/dist/core/skill-tool.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +2 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts +17 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +244 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts +3 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js +58 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/extension-ui-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +12 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +54 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts +6 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +63 -0
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +38 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -1
- 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 +15 -457
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/agent-session.ts +122 -23
- package/packages/pi-coding-agent/src/core/skill-tool.test.ts +89 -0
- package/packages/pi-coding-agent/src/core/skills.ts +2 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +302 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/extension-ui-controller.ts +59 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +68 -0
- package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +71 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +37 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +18 -510
- package/pkg/package.json +1 -1
- package/src/resources/extensions/async-jobs/index.ts +11 -0
- package/src/resources/extensions/get-secrets-from-user.ts +1 -1
- package/src/resources/extensions/gsd/auto-dashboard.ts +10 -0
- package/src/resources/extensions/gsd/auto-loop.ts +1075 -921
- package/src/resources/extensions/gsd/auto-post-unit.ts +10 -2
- package/src/resources/extensions/gsd/auto-prompts.ts +3 -3
- package/src/resources/extensions/gsd/auto-start.ts +6 -1
- package/src/resources/extensions/gsd/auto.ts +13 -10
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +142 -0
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +238 -0
- package/src/resources/extensions/gsd/bootstrap/dynamic-tools.ts +90 -0
- package/src/resources/extensions/gsd/bootstrap/register-extension.ts +46 -0
- package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +167 -0
- package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +55 -0
- package/src/resources/extensions/gsd/bootstrap/system-context.ts +340 -0
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +51 -0
- package/src/resources/extensions/gsd/commands/catalog.ts +301 -0
- package/src/resources/extensions/gsd/commands/context.ts +101 -0
- package/src/resources/extensions/gsd/commands/dispatcher.ts +32 -0
- package/src/resources/extensions/gsd/commands/handlers/auto.ts +74 -0
- package/src/resources/extensions/gsd/commands/handlers/core.ts +274 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +169 -0
- package/src/resources/extensions/gsd/commands/handlers/parallel.ts +118 -0
- package/src/resources/extensions/gsd/commands/handlers/workflow.ts +109 -0
- package/src/resources/extensions/gsd/commands/index.ts +14 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +10 -1329
- package/src/resources/extensions/gsd/dashboard-overlay.ts +10 -0
- package/src/resources/extensions/gsd/doctor-proactive.ts +106 -10
- package/src/resources/extensions/gsd/doctor.ts +47 -3
- package/src/resources/extensions/gsd/export-html.ts +51 -0
- package/src/resources/extensions/gsd/files.ts +1 -1
- package/src/resources/extensions/gsd/health-widget.ts +2 -1
- package/src/resources/extensions/gsd/index.ts +12 -1314
- package/src/resources/extensions/gsd/progress-score.ts +23 -0
- package/src/resources/extensions/gsd/prompts/forensics.md +121 -46
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +13 -9
- package/src/resources/extensions/gsd/tests/doctor-proactive.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +16 -16
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/visualizer-data.test.ts +10 -10
- package/src/resources/extensions/gsd/visualizer-data.ts +51 -1
- package/src/resources/extensions/gsd/visualizer-views.ts +58 -0
- /package/dist/resources/extensions/{env-utils.js → gsd/env-utils.js} +0 -0
- /package/src/resources/extensions/{env-utils.ts → gsd/env-utils.ts} +0 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { computeProgressScore, formatProgressLine } from "../../progress-score.js";
|
|
2
|
+
import { getGlobalGSDPreferencesPath, getProjectGSDPreferencesPath } from "../../preferences.js";
|
|
3
|
+
import { ensurePreferencesFile, handlePrefs, handlePrefsMode, handlePrefsWizard } from "../../commands-prefs-wizard.js";
|
|
4
|
+
import { runEnvironmentChecks } from "../../doctor-environment.js";
|
|
5
|
+
import { deriveState } from "../../state.js";
|
|
6
|
+
import { handleCmux } from "../../commands-cmux.js";
|
|
7
|
+
import { projectRoot } from "../context.js";
|
|
8
|
+
export function showHelp(ctx) {
|
|
9
|
+
const lines = [
|
|
10
|
+
"GSD — Get Shit Done\n",
|
|
11
|
+
"WORKFLOW",
|
|
12
|
+
" /gsd start <tpl> Start a workflow template (bugfix, spike, feature, hotfix, etc.)",
|
|
13
|
+
" /gsd templates List available workflow templates [info <name>]",
|
|
14
|
+
" /gsd Run next unit in step mode (same as /gsd next)",
|
|
15
|
+
" /gsd next Execute next task, then pause [--dry-run] [--verbose]",
|
|
16
|
+
" /gsd auto Run all queued units continuously [--verbose]",
|
|
17
|
+
" /gsd stop Stop auto-mode gracefully",
|
|
18
|
+
" /gsd pause Pause auto-mode (preserves state, /gsd auto to resume)",
|
|
19
|
+
" /gsd discuss Start guided milestone/slice discussion",
|
|
20
|
+
" /gsd new-milestone Create milestone from headless context (used by gsd headless)",
|
|
21
|
+
"",
|
|
22
|
+
"VISIBILITY",
|
|
23
|
+
" /gsd status Show progress dashboard (Ctrl+Alt+G)",
|
|
24
|
+
" /gsd visualize Interactive 10-tab TUI (progress, timeline, deps, metrics, health, agent, changes, knowledge, captures, export)",
|
|
25
|
+
" /gsd queue Show queued/dispatched units and execution order",
|
|
26
|
+
" /gsd history View execution history [--cost] [--phase] [--model] [N]",
|
|
27
|
+
" /gsd changelog Show categorized release notes [version]",
|
|
28
|
+
"",
|
|
29
|
+
"COURSE CORRECTION",
|
|
30
|
+
" /gsd steer <desc> Apply user override to active work",
|
|
31
|
+
" /gsd capture <text> Quick-capture a thought to CAPTURES.md",
|
|
32
|
+
" /gsd triage Classify and route pending captures",
|
|
33
|
+
" /gsd skip <unit> Prevent a unit from auto-mode dispatch",
|
|
34
|
+
" /gsd undo Revert last completed unit [--force]",
|
|
35
|
+
" /gsd park [id] Park a milestone — skip without deleting [reason]",
|
|
36
|
+
" /gsd unpark [id] Reactivate a parked milestone",
|
|
37
|
+
"",
|
|
38
|
+
"PROJECT KNOWLEDGE",
|
|
39
|
+
" /gsd knowledge <type> <text> Add rule, pattern, or lesson to KNOWLEDGE.md",
|
|
40
|
+
"",
|
|
41
|
+
"SETUP & CONFIGURATION",
|
|
42
|
+
" /gsd init Project init wizard — detect, configure, bootstrap .gsd/",
|
|
43
|
+
" /gsd setup Global setup status [llm|search|remote|keys|prefs]",
|
|
44
|
+
" /gsd mode Set workflow mode (solo/team) [global|project]",
|
|
45
|
+
" /gsd prefs Manage preferences [global|project|status|wizard|setup|import-claude]",
|
|
46
|
+
" /gsd cmux Manage cmux integration [status|on|off|notifications|sidebar|splits|browser]",
|
|
47
|
+
" /gsd config Set API keys for external tools",
|
|
48
|
+
" /gsd keys API key manager [list|add|remove|test|rotate|doctor]",
|
|
49
|
+
" /gsd hooks Show post-unit hook configuration",
|
|
50
|
+
" /gsd extensions Manage extensions [list|enable|disable|info]",
|
|
51
|
+
"",
|
|
52
|
+
"MAINTENANCE",
|
|
53
|
+
" /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
|
|
54
|
+
" /gsd export Export milestone/slice results [--json|--markdown|--html] [--all]",
|
|
55
|
+
" /gsd cleanup Remove merged branches or snapshots [branches|snapshots]",
|
|
56
|
+
" /gsd migrate Migrate .planning/ (v1) to .gsd/ (v2) format",
|
|
57
|
+
" /gsd remote Control remote auto-mode [slack|discord|status|disconnect]",
|
|
58
|
+
" /gsd inspect Show SQLite DB diagnostics (schema, row counts, recent entries)",
|
|
59
|
+
" /gsd update Update GSD to the latest version via npm",
|
|
60
|
+
];
|
|
61
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
62
|
+
}
|
|
63
|
+
export async function handleStatus(ctx) {
|
|
64
|
+
const basePath = projectRoot();
|
|
65
|
+
const state = await deriveState(basePath);
|
|
66
|
+
if (state.registry.length === 0) {
|
|
67
|
+
ctx.ui.notify("No GSD milestones found. Run /gsd to start.", "info");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const { GSDDashboardOverlay } = await import("../../dashboard-overlay.js");
|
|
71
|
+
const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDDashboardOverlay(tui, theme, () => done()), {
|
|
72
|
+
overlay: true,
|
|
73
|
+
overlayOptions: {
|
|
74
|
+
width: "70%",
|
|
75
|
+
minWidth: 60,
|
|
76
|
+
maxHeight: "90%",
|
|
77
|
+
anchor: "center",
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
if (result === undefined) {
|
|
81
|
+
ctx.ui.notify(formatTextStatus(state), "info");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function fireStatusViaCommand(ctx) {
|
|
85
|
+
await handleStatus(ctx);
|
|
86
|
+
}
|
|
87
|
+
export async function handleVisualize(ctx) {
|
|
88
|
+
if (!ctx.hasUI) {
|
|
89
|
+
ctx.ui.notify("Visualizer requires an interactive terminal.", "warning");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const { GSDVisualizerOverlay } = await import("../../visualizer-overlay.js");
|
|
93
|
+
const result = await ctx.ui.custom((tui, theme, _kb, done) => new GSDVisualizerOverlay(tui, theme, () => done()), {
|
|
94
|
+
overlay: true,
|
|
95
|
+
overlayOptions: {
|
|
96
|
+
width: "80%",
|
|
97
|
+
minWidth: 80,
|
|
98
|
+
maxHeight: "90%",
|
|
99
|
+
anchor: "center",
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
if (result === undefined) {
|
|
103
|
+
ctx.ui.notify("Visualizer requires an interactive terminal. Use /gsd status for a text-based overview.", "warning");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export async function handleSetup(args, ctx) {
|
|
107
|
+
const { detectProjectState, hasGlobalSetup } = await import("../../detection.js");
|
|
108
|
+
const globalConfigured = hasGlobalSetup();
|
|
109
|
+
const detection = detectProjectState(projectRoot());
|
|
110
|
+
const statusLines = ["GSD Setup Status\n"];
|
|
111
|
+
statusLines.push(` Global preferences: ${globalConfigured ? "configured" : "not set"}`);
|
|
112
|
+
statusLines.push(` Project state: ${detection.state}`);
|
|
113
|
+
if (detection.projectSignals.primaryLanguage) {
|
|
114
|
+
statusLines.push(` Detected: ${detection.projectSignals.primaryLanguage}`);
|
|
115
|
+
}
|
|
116
|
+
if (args === "llm" || args === "auth") {
|
|
117
|
+
ctx.ui.notify("Use /login to configure LLM authentication.", "info");
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (args === "search") {
|
|
121
|
+
ctx.ui.notify("Use /search-provider to configure web search.", "info");
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (args === "remote") {
|
|
125
|
+
ctx.ui.notify("Use /gsd remote to configure remote questions.", "info");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (args === "keys") {
|
|
129
|
+
const { handleKeys } = await import("../../key-manager.js");
|
|
130
|
+
await handleKeys("", ctx);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (args === "prefs") {
|
|
134
|
+
await ensurePreferencesFile(getGlobalGSDPreferencesPath(), ctx, "global");
|
|
135
|
+
await handlePrefsWizard(ctx, "global");
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
ctx.ui.notify(statusLines.join("\n"), "info");
|
|
139
|
+
ctx.ui.notify("Available setup commands:\n" +
|
|
140
|
+
" /gsd setup llm — LLM authentication\n" +
|
|
141
|
+
" /gsd setup search — Web search provider\n" +
|
|
142
|
+
" /gsd setup remote — Remote questions (Discord/Slack/Telegram)\n" +
|
|
143
|
+
" /gsd setup keys — Tool API keys\n" +
|
|
144
|
+
" /gsd setup prefs — Global preferences wizard", "info");
|
|
145
|
+
}
|
|
146
|
+
export async function handleCoreCommand(trimmed, ctx) {
|
|
147
|
+
if (trimmed === "help" || trimmed === "h" || trimmed === "?") {
|
|
148
|
+
showHelp(ctx);
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
if (trimmed === "status") {
|
|
152
|
+
await handleStatus(ctx);
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
if (trimmed === "visualize") {
|
|
156
|
+
await handleVisualize(ctx);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
if (trimmed === "widget" || trimmed.startsWith("widget ")) {
|
|
160
|
+
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import("../../auto-dashboard.js");
|
|
161
|
+
const arg = trimmed.replace(/^widget\s*/, "").trim();
|
|
162
|
+
if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
|
|
163
|
+
setWidgetMode(arg);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
cycleWidgetMode();
|
|
167
|
+
}
|
|
168
|
+
ctx.ui.notify(`Widget: ${getWidgetMode()}`, "info");
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
if (trimmed === "mode" || trimmed.startsWith("mode ")) {
|
|
172
|
+
const modeArgs = trimmed.replace(/^mode\s*/, "").trim();
|
|
173
|
+
const scope = modeArgs === "project" ? "project" : "global";
|
|
174
|
+
const path = scope === "project" ? getProjectGSDPreferencesPath() : getGlobalGSDPreferencesPath();
|
|
175
|
+
await ensurePreferencesFile(path, ctx, scope);
|
|
176
|
+
await handlePrefsMode(ctx, scope);
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
if (trimmed === "prefs" || trimmed.startsWith("prefs ")) {
|
|
180
|
+
await handlePrefs(trimmed.replace(/^prefs\s*/, "").trim(), ctx);
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
if (trimmed === "cmux" || trimmed.startsWith("cmux ")) {
|
|
184
|
+
await handleCmux(trimmed.replace(/^cmux\s*/, "").trim(), ctx);
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
if (trimmed === "setup" || trimmed.startsWith("setup ")) {
|
|
188
|
+
await handleSetup(trimmed.replace(/^setup\s*/, "").trim(), ctx);
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
export function formatTextStatus(state) {
|
|
194
|
+
const lines = ["GSD Status\n"];
|
|
195
|
+
lines.push(formatProgressLine(computeProgressScore()));
|
|
196
|
+
lines.push("");
|
|
197
|
+
lines.push(`Phase: ${state.phase}`);
|
|
198
|
+
if (state.activeMilestone) {
|
|
199
|
+
lines.push(`Active milestone: ${state.activeMilestone.id} — ${state.activeMilestone.title}`);
|
|
200
|
+
}
|
|
201
|
+
if (state.activeSlice) {
|
|
202
|
+
lines.push(`Active slice: ${state.activeSlice.id} — ${state.activeSlice.title}`);
|
|
203
|
+
}
|
|
204
|
+
if (state.activeTask) {
|
|
205
|
+
lines.push(`Active task: ${state.activeTask.id} — ${state.activeTask.title}`);
|
|
206
|
+
}
|
|
207
|
+
if (state.progress) {
|
|
208
|
+
const { milestones, slices, tasks } = state.progress;
|
|
209
|
+
const parts = [`milestones ${milestones.done}/${milestones.total}`];
|
|
210
|
+
if (slices)
|
|
211
|
+
parts.push(`slices ${slices.done}/${slices.total}`);
|
|
212
|
+
if (tasks)
|
|
213
|
+
parts.push(`tasks ${tasks.done}/${tasks.total}`);
|
|
214
|
+
lines.push(`Progress: ${parts.join(", ")}`);
|
|
215
|
+
}
|
|
216
|
+
if (state.nextAction) {
|
|
217
|
+
lines.push(`Next: ${state.nextAction}`);
|
|
218
|
+
}
|
|
219
|
+
if (state.blockers.length > 0) {
|
|
220
|
+
lines.push(`Blockers: ${state.blockers.join("; ")}`);
|
|
221
|
+
}
|
|
222
|
+
if (state.registry.length > 0) {
|
|
223
|
+
lines.push("");
|
|
224
|
+
lines.push("Milestones:");
|
|
225
|
+
for (const milestone of state.registry) {
|
|
226
|
+
const icon = milestone.status === "complete"
|
|
227
|
+
? "✓"
|
|
228
|
+
: milestone.status === "active"
|
|
229
|
+
? "▶"
|
|
230
|
+
: milestone.status === "parked"
|
|
231
|
+
? "⏸"
|
|
232
|
+
: "○";
|
|
233
|
+
lines.push(` ${icon} ${milestone.id}: ${milestone.title} (${milestone.status})`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
const envResults = runEnvironmentChecks(projectRoot());
|
|
237
|
+
const envIssues = envResults.filter((result) => result.status !== "ok");
|
|
238
|
+
if (envIssues.length > 0) {
|
|
239
|
+
lines.push("");
|
|
240
|
+
lines.push("Environment:");
|
|
241
|
+
for (const issue of envIssues) {
|
|
242
|
+
lines.push(` ${issue.status === "error" ? "✗" : "⚠"} ${issue.message}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return lines.join("\n");
|
|
246
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { dispatchDirectPhase } from "../../auto-direct-dispatch.js";
|
|
2
|
+
import { handleConfig } from "../../commands-config.js";
|
|
3
|
+
import { handleDoctor, handleCapture, handleKnowledge, handleRunHook, handleSkillHealth, handleSteer, handleTriage, handleUpdate } from "../../commands-handlers.js";
|
|
4
|
+
import { handleInspect } from "../../commands-inspect.js";
|
|
5
|
+
import { handleLogs } from "../../commands-logs.js";
|
|
6
|
+
import { handleCleanupBranches, handleCleanupSnapshots, handleSkip } from "../../commands-maintenance.js";
|
|
7
|
+
import { handleExport } from "../../export.js";
|
|
8
|
+
import { handleHistory } from "../../history.js";
|
|
9
|
+
import { handleUndo } from "../../undo.js";
|
|
10
|
+
import { handleRemote } from "../../../remote-questions/mod.js";
|
|
11
|
+
import { projectRoot } from "../context.js";
|
|
12
|
+
export async function handleOpsCommand(trimmed, ctx, pi) {
|
|
13
|
+
if (trimmed === "init") {
|
|
14
|
+
const { detectProjectState } = await import("../../detection.js");
|
|
15
|
+
const { handleReinit, showProjectInit } = await import("../../init-wizard.js");
|
|
16
|
+
const basePath = projectRoot();
|
|
17
|
+
const detection = detectProjectState(basePath);
|
|
18
|
+
if (detection.state === "v2-gsd" || detection.state === "v2-gsd-empty") {
|
|
19
|
+
await handleReinit(ctx, detection);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
await showProjectInit(ctx, pi, basePath, detection);
|
|
23
|
+
}
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (trimmed === "keys" || trimmed.startsWith("keys ")) {
|
|
27
|
+
const { handleKeys } = await import("../../key-manager.js");
|
|
28
|
+
await handleKeys(trimmed.replace(/^keys\s*/, "").trim(), ctx);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
if (trimmed === "doctor" || trimmed.startsWith("doctor ")) {
|
|
32
|
+
await handleDoctor(trimmed.replace(/^doctor\s*/, "").trim(), ctx, pi);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (trimmed === "logs" || trimmed.startsWith("logs ")) {
|
|
36
|
+
await handleLogs(trimmed.replace(/^logs\s*/, "").trim(), ctx);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (trimmed === "forensics" || trimmed.startsWith("forensics ")) {
|
|
40
|
+
const { handleForensics } = await import("../../forensics.js");
|
|
41
|
+
await handleForensics(trimmed.replace(/^forensics\s*/, "").trim(), ctx, pi);
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (trimmed === "changelog" || trimmed.startsWith("changelog ")) {
|
|
45
|
+
const { handleChangelog } = await import("../../changelog.js");
|
|
46
|
+
await handleChangelog(trimmed.replace(/^changelog\s*/, "").trim(), ctx, pi);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (trimmed === "history" || trimmed.startsWith("history ")) {
|
|
50
|
+
await handleHistory(trimmed.replace(/^history\s*/, "").trim(), ctx, projectRoot());
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
if (trimmed === "undo" || trimmed.startsWith("undo ")) {
|
|
54
|
+
await handleUndo(trimmed.replace(/^undo\s*/, "").trim(), ctx, pi, projectRoot());
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
if (trimmed.startsWith("skip ")) {
|
|
58
|
+
await handleSkip(trimmed.replace(/^skip\s*/, "").trim(), ctx, projectRoot());
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
if (trimmed === "export" || trimmed.startsWith("export ")) {
|
|
62
|
+
await handleExport(trimmed.replace(/^export\s*/, "").trim(), ctx, projectRoot());
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
if (trimmed === "cleanup") {
|
|
66
|
+
await handleCleanupBranches(ctx, projectRoot());
|
|
67
|
+
await handleCleanupSnapshots(ctx, projectRoot());
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
if (trimmed === "cleanup branches") {
|
|
71
|
+
await handleCleanupBranches(ctx, projectRoot());
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (trimmed === "cleanup snapshots") {
|
|
75
|
+
await handleCleanupSnapshots(ctx, projectRoot());
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
if (trimmed.startsWith("capture ") || trimmed === "capture") {
|
|
79
|
+
await handleCapture(trimmed.replace(/^capture\s*/, "").trim(), ctx);
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (trimmed === "triage") {
|
|
83
|
+
await handleTriage(ctx, pi, process.cwd());
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
if (trimmed === "config") {
|
|
87
|
+
await handleConfig(ctx);
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (trimmed === "hooks") {
|
|
91
|
+
const { formatHookStatus } = await import("../../post-unit-hooks.js");
|
|
92
|
+
ctx.ui.notify(formatHookStatus(), "info");
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (trimmed === "skill-health" || trimmed.startsWith("skill-health ")) {
|
|
96
|
+
await handleSkillHealth(trimmed.replace(/^skill-health\s*/, "").trim(), ctx);
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
if (trimmed.startsWith("run-hook ")) {
|
|
100
|
+
await handleRunHook(trimmed.replace(/^run-hook\s*/, "").trim(), ctx, pi);
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
if (trimmed === "run-hook") {
|
|
104
|
+
ctx.ui.notify(`Usage: /gsd run-hook <hook-name> <unit-type> <unit-id>
|
|
105
|
+
|
|
106
|
+
Unit types:
|
|
107
|
+
execute-task - Task execution (unit-id: M001/S01/T01)
|
|
108
|
+
plan-slice - Slice planning (unit-id: M001/S01)
|
|
109
|
+
research-milestone - Milestone research (unit-id: M001)
|
|
110
|
+
complete-slice - Slice completion (unit-id: M001/S01)
|
|
111
|
+
complete-milestone - Milestone completion (unit-id: M001)
|
|
112
|
+
|
|
113
|
+
Examples:
|
|
114
|
+
/gsd run-hook code-review execute-task M001/S01/T01
|
|
115
|
+
/gsd run-hook lint-check plan-slice M001/S01`, "warning");
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
if (trimmed.startsWith("steer ")) {
|
|
119
|
+
await handleSteer(trimmed.replace(/^steer\s+/, "").trim(), ctx, pi);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (trimmed === "steer") {
|
|
123
|
+
ctx.ui.notify("Usage: /gsd steer <description of change>. Example: /gsd steer Use Postgres instead of SQLite", "warning");
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (trimmed.startsWith("knowledge ")) {
|
|
127
|
+
await handleKnowledge(trimmed.replace(/^knowledge\s+/, "").trim(), ctx);
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
if (trimmed === "knowledge") {
|
|
131
|
+
ctx.ui.notify("Usage: /gsd knowledge <rule|pattern|lesson> <description>. Example: /gsd knowledge rule Use real DB for integration tests", "warning");
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
if (trimmed === "migrate" || trimmed.startsWith("migrate ")) {
|
|
135
|
+
const { handleMigrate } = await import("../../migrate/command.js");
|
|
136
|
+
await handleMigrate(trimmed.replace(/^migrate\s*/, "").trim(), ctx, pi);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (trimmed === "remote" || trimmed.startsWith("remote ")) {
|
|
140
|
+
await handleRemote(trimmed.replace(/^remote\s*/, "").trim(), ctx, pi);
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
if (trimmed === "dispatch" || trimmed.startsWith("dispatch ")) {
|
|
144
|
+
const phase = trimmed.replace(/^dispatch\s*/, "").trim();
|
|
145
|
+
if (!phase) {
|
|
146
|
+
ctx.ui.notify("Usage: /gsd dispatch <phase> (research|plan|execute|complete|reassess|uat|replan)", "warning");
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
await dispatchDirectPhase(ctx, pi, phase, projectRoot());
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
if (trimmed === "inspect") {
|
|
153
|
+
await handleInspect(ctx);
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (trimmed === "update") {
|
|
157
|
+
await handleUpdate(ctx);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
if (trimmed === "extensions" || trimmed.startsWith("extensions ")) {
|
|
161
|
+
const { handleExtensions } = await import("../../commands-extensions.js");
|
|
162
|
+
await handleExtensions(trimmed.replace(/^extensions\s*/, "").trim(), ctx);
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { getOrchestratorState, getWorkerStatuses, isParallelActive, pauseWorker, prepareParallelStart, resumeWorker, startParallel, stopParallel, } from "../../parallel-orchestrator.js";
|
|
2
|
+
import { formatEligibilityReport } from "../../parallel-eligibility.js";
|
|
3
|
+
import { formatMergeResults, mergeAllCompleted, mergeCompletedMilestone } from "../../parallel-merge.js";
|
|
4
|
+
import { loadEffectiveGSDPreferences, resolveParallelConfig } from "../../preferences.js";
|
|
5
|
+
import { projectRoot } from "../context.js";
|
|
6
|
+
export async function handleParallelCommand(trimmed, _ctx, pi) {
|
|
7
|
+
if (!trimmed.startsWith("parallel"))
|
|
8
|
+
return false;
|
|
9
|
+
const parallelArgs = trimmed.slice("parallel".length).trim();
|
|
10
|
+
const [subcommand = "", ...restParts] = parallelArgs.split(/\s+/);
|
|
11
|
+
const rest = restParts.join(" ");
|
|
12
|
+
if (subcommand === "start" || subcommand === "") {
|
|
13
|
+
const loaded = loadEffectiveGSDPreferences();
|
|
14
|
+
const config = resolveParallelConfig(loaded?.preferences);
|
|
15
|
+
if (!config.enabled) {
|
|
16
|
+
pi.sendMessage({
|
|
17
|
+
customType: "gsd-parallel",
|
|
18
|
+
content: "Parallel mode is not enabled. Set `parallel.enabled: true` in your preferences.",
|
|
19
|
+
display: false,
|
|
20
|
+
});
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
const candidates = await prepareParallelStart(projectRoot(), loaded?.preferences);
|
|
24
|
+
const report = formatEligibilityReport(candidates);
|
|
25
|
+
if (candidates.eligible.length === 0) {
|
|
26
|
+
pi.sendMessage({ customType: "gsd-parallel", content: `${report}\n\nNo milestones are eligible for parallel execution.`, display: false });
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
const result = await startParallel(projectRoot(), candidates.eligible.map((candidate) => candidate.milestoneId), loaded?.preferences);
|
|
30
|
+
const lines = ["Parallel orchestration started.", `Workers: ${result.started.join(", ")}`];
|
|
31
|
+
if (result.errors.length > 0) {
|
|
32
|
+
lines.push(`Errors: ${result.errors.map((entry) => `${entry.mid}: ${entry.error}`).join("; ")}`);
|
|
33
|
+
}
|
|
34
|
+
pi.sendMessage({ customType: "gsd-parallel", content: `${report}\n\n${lines.join("\n")}`, display: false });
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (subcommand === "status") {
|
|
38
|
+
if (!isParallelActive()) {
|
|
39
|
+
pi.sendMessage({ customType: "gsd-parallel", content: "No parallel orchestration is currently active.", display: false });
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
const workers = getWorkerStatuses();
|
|
43
|
+
const lines = ["# Parallel Workers\n"];
|
|
44
|
+
for (const worker of workers) {
|
|
45
|
+
lines.push(`- **${worker.milestoneId}** (${worker.title}) — ${worker.state} — ${worker.completedUnits} units — $${worker.cost.toFixed(2)}`);
|
|
46
|
+
}
|
|
47
|
+
const state = getOrchestratorState();
|
|
48
|
+
if (state) {
|
|
49
|
+
lines.push(`\nTotal cost: $${state.totalCost.toFixed(2)}`);
|
|
50
|
+
}
|
|
51
|
+
pi.sendMessage({ customType: "gsd-parallel", content: lines.join("\n"), display: false });
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
if (subcommand === "stop") {
|
|
55
|
+
const milestoneId = rest.trim() || undefined;
|
|
56
|
+
await stopParallel(projectRoot(), milestoneId);
|
|
57
|
+
pi.sendMessage({ customType: "gsd-parallel", content: milestoneId ? `Stopped worker for ${milestoneId}.` : "All parallel workers stopped.", display: false });
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
if (subcommand === "pause") {
|
|
61
|
+
const milestoneId = rest.trim() || undefined;
|
|
62
|
+
pauseWorker(projectRoot(), milestoneId);
|
|
63
|
+
pi.sendMessage({ customType: "gsd-parallel", content: milestoneId ? `Paused worker for ${milestoneId}.` : "All parallel workers paused.", display: false });
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
if (subcommand === "resume") {
|
|
67
|
+
const milestoneId = rest.trim() || undefined;
|
|
68
|
+
resumeWorker(projectRoot(), milestoneId);
|
|
69
|
+
pi.sendMessage({ customType: "gsd-parallel", content: milestoneId ? `Resumed worker for ${milestoneId}.` : "All parallel workers resumed.", display: false });
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
if (subcommand === "merge") {
|
|
73
|
+
const milestoneId = rest.trim() || undefined;
|
|
74
|
+
if (milestoneId) {
|
|
75
|
+
const result = await mergeCompletedMilestone(projectRoot(), milestoneId);
|
|
76
|
+
pi.sendMessage({ customType: "gsd-parallel", content: formatMergeResults([result]), display: false });
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
const workers = getWorkerStatuses();
|
|
80
|
+
if (workers.length === 0) {
|
|
81
|
+
pi.sendMessage({ customType: "gsd-parallel", content: "No parallel workers to merge.", display: false });
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
const results = await mergeAllCompleted(projectRoot(), workers);
|
|
85
|
+
pi.sendMessage({ customType: "gsd-parallel", content: formatMergeResults(results), display: false });
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
pi.sendMessage({
|
|
89
|
+
customType: "gsd-parallel",
|
|
90
|
+
content: `Unknown parallel subcommand "${subcommand}". Usage: /gsd parallel [start|status|stop|pause|resume|merge]`,
|
|
91
|
+
display: false,
|
|
92
|
+
});
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { handleQuick } from "../../quick.js";
|
|
4
|
+
import { showDiscuss, showHeadlessMilestoneCreation, showQueue } from "../../guided-flow.js";
|
|
5
|
+
import { handleStart, handleTemplates } from "../../commands-workflow-templates.js";
|
|
6
|
+
import { gsdRoot } from "../../paths.js";
|
|
7
|
+
import { deriveState } from "../../state.js";
|
|
8
|
+
import { isParked, parkMilestone, unparkMilestone } from "../../milestone-actions.js";
|
|
9
|
+
import { loadEffectiveGSDPreferences } from "../../preferences.js";
|
|
10
|
+
import { nextMilestoneId } from "../../milestone-ids.js";
|
|
11
|
+
import { findMilestoneIds } from "../../guided-flow.js";
|
|
12
|
+
import { projectRoot } from "../context.js";
|
|
13
|
+
export async function handleWorkflowCommand(trimmed, ctx, pi) {
|
|
14
|
+
if (trimmed === "queue") {
|
|
15
|
+
await showQueue(ctx, pi, projectRoot());
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
if (trimmed === "discuss") {
|
|
19
|
+
await showDiscuss(ctx, pi, projectRoot());
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
if (trimmed === "quick" || trimmed.startsWith("quick ")) {
|
|
23
|
+
await handleQuick(trimmed.replace(/^quick\s*/, "").trim(), ctx, pi);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (trimmed === "new-milestone") {
|
|
27
|
+
const basePath = projectRoot();
|
|
28
|
+
const headlessContextPath = join(gsdRoot(basePath), "runtime", "headless-context.md");
|
|
29
|
+
if (existsSync(headlessContextPath)) {
|
|
30
|
+
const seedContext = readFileSync(headlessContextPath, "utf-8");
|
|
31
|
+
try {
|
|
32
|
+
unlinkSync(headlessContextPath);
|
|
33
|
+
}
|
|
34
|
+
catch { /* non-fatal */ }
|
|
35
|
+
await showHeadlessMilestoneCreation(ctx, pi, basePath, seedContext);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const { showSmartEntry } = await import("../../guided-flow.js");
|
|
39
|
+
await showSmartEntry(ctx, pi, basePath);
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (trimmed === "start" || trimmed.startsWith("start ")) {
|
|
44
|
+
await handleStart(trimmed.replace(/^start\s*/, "").trim(), ctx, pi);
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (trimmed === "templates" || trimmed.startsWith("templates ")) {
|
|
48
|
+
await handleTemplates(trimmed.replace(/^templates\s*/, "").trim(), ctx);
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
if (trimmed === "park" || trimmed.startsWith("park ")) {
|
|
52
|
+
const basePath = projectRoot();
|
|
53
|
+
const arg = trimmed.replace(/^park\s*/, "").trim();
|
|
54
|
+
let targetId = arg;
|
|
55
|
+
if (!targetId) {
|
|
56
|
+
const state = await deriveState(basePath);
|
|
57
|
+
if (!state.activeMilestone) {
|
|
58
|
+
ctx.ui.notify("No active milestone to park.", "warning");
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
targetId = state.activeMilestone.id;
|
|
62
|
+
}
|
|
63
|
+
if (isParked(basePath, targetId)) {
|
|
64
|
+
ctx.ui.notify(`${targetId} is already parked. Use /gsd unpark ${targetId} to reactivate.`, "info");
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
const reasonParts = arg.replace(targetId, "").trim().replace(/^["']|["']$/g, "");
|
|
68
|
+
const reason = reasonParts || "Parked via /gsd park";
|
|
69
|
+
const success = parkMilestone(basePath, targetId, reason);
|
|
70
|
+
ctx.ui.notify(success ? `Parked ${targetId}. Run /gsd unpark ${targetId} to reactivate.` : `Could not park ${targetId} — milestone not found.`, success ? "info" : "warning");
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (trimmed === "unpark" || trimmed.startsWith("unpark ")) {
|
|
74
|
+
const basePath = projectRoot();
|
|
75
|
+
const arg = trimmed.replace(/^unpark\s*/, "").trim();
|
|
76
|
+
let targetId = arg;
|
|
77
|
+
if (!targetId) {
|
|
78
|
+
const state = await deriveState(basePath);
|
|
79
|
+
const parkedEntries = state.registry.filter((entry) => entry.status === "parked");
|
|
80
|
+
if (parkedEntries.length === 0) {
|
|
81
|
+
ctx.ui.notify("No parked milestones.", "info");
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
if (parkedEntries.length === 1) {
|
|
85
|
+
targetId = parkedEntries[0].id;
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
ctx.ui.notify(`Parked milestones: ${parkedEntries.map((entry) => entry.id).join(", ")}. Specify which to unpark: /gsd unpark <id>`, "info");
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const success = unparkMilestone(basePath, targetId);
|
|
93
|
+
ctx.ui.notify(success ? `Unparked ${targetId}. It will resume its normal position in the queue.` : `Could not unpark ${targetId} — milestone not found or not parked.`, success ? "info" : "warning");
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
export function getNextMilestoneId(basePath) {
|
|
99
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
100
|
+
const uniqueIds = !!loadEffectiveGSDPreferences()?.preferences?.unique_milestone_ids;
|
|
101
|
+
return nextMilestoneId(milestoneIds, uniqueIds);
|
|
102
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GSD_COMMAND_DESCRIPTION, getGsdArgumentCompletions } from "./catalog.js";
|
|
2
|
+
export function registerGSDCommand(pi) {
|
|
3
|
+
pi.registerCommand("gsd", {
|
|
4
|
+
description: GSD_COMMAND_DESCRIPTION,
|
|
5
|
+
getArgumentCompletions: getGsdArgumentCompletions,
|
|
6
|
+
handler: async (args, ctx) => {
|
|
7
|
+
const { handleGSDCommand } = await import("./dispatcher.js");
|
|
8
|
+
await handleGSDCommand(args, ctx, pi);
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
}
|
|
@@ -12,7 +12,7 @@ import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captur
|
|
|
12
12
|
import { appendOverride, appendKnowledge } from "./files.js";
|
|
13
13
|
import { formatDoctorIssuesForPrompt, formatDoctorReport, formatDoctorReportJson, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
|
|
14
14
|
import { isAutoActive } from "./auto.js";
|
|
15
|
-
import { projectRoot } from "./commands.js";
|
|
15
|
+
import { projectRoot } from "./commands/context.js";
|
|
16
16
|
import { loadPrompt } from "./prompt-loader.js";
|
|
17
17
|
export function dispatchDoctorHeal(pi, scope, reportText, structuredIssues) {
|
|
18
18
|
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".gsd", "agent", "GSD-WORKFLOW.md");
|