gsd-pi 2.29.0-dev.77f06e2 → 2.29.0-dev.953d788
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 +17 -24
- package/dist/resources/extensions/bg-shell/process-manager.ts +0 -13
- package/dist/resources/extensions/gsd/auto-dashboard.ts +65 -186
- package/dist/resources/extensions/gsd/auto-post-unit.ts +3 -6
- package/dist/resources/extensions/gsd/auto-recovery.ts +22 -16
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +6 -7
- package/dist/resources/extensions/gsd/auto.ts +15 -0
- package/dist/resources/extensions/gsd/commands-handlers.ts +1 -20
- package/dist/resources/extensions/gsd/commands-logs.ts +14 -13
- package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +14 -44
- package/dist/resources/extensions/gsd/commands.ts +22 -55
- package/dist/resources/extensions/gsd/dashboard-overlay.ts +1 -2
- package/dist/resources/extensions/gsd/json-persistence.ts +1 -16
- package/dist/resources/extensions/gsd/queue-order.ts +11 -10
- package/dist/resources/extensions/gsd/session-status-io.ts +41 -23
- package/dist/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +38 -60
- package/dist/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/dist/resources/extensions/mcporter/index.ts +525 -0
- package/dist/resources/extensions/remote-questions/discord-adapter.ts +19 -8
- package/dist/resources/extensions/remote-questions/slack-adapter.ts +17 -11
- package/dist/resources/extensions/remote-questions/telegram-adapter.ts +19 -8
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +0 -13
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +0 -13
- package/src/resources/extensions/bg-shell/process-manager.ts +0 -13
- package/src/resources/extensions/gsd/auto-dashboard.ts +65 -186
- package/src/resources/extensions/gsd/auto-post-unit.ts +3 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +22 -16
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +6 -7
- package/src/resources/extensions/gsd/auto.ts +15 -0
- package/src/resources/extensions/gsd/commands-handlers.ts +1 -20
- package/src/resources/extensions/gsd/commands-logs.ts +14 -13
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +14 -44
- package/src/resources/extensions/gsd/commands.ts +22 -55
- package/src/resources/extensions/gsd/dashboard-overlay.ts +1 -2
- package/src/resources/extensions/gsd/json-persistence.ts +1 -16
- package/src/resources/extensions/gsd/queue-order.ts +11 -10
- package/src/resources/extensions/gsd/session-status-io.ts +41 -23
- package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +38 -60
- package/src/resources/extensions/gsd/tests/parallel-workers-multi-milestone-e2e.test.ts +1 -1
- package/src/resources/extensions/mcporter/index.ts +525 -0
- package/src/resources/extensions/remote-questions/discord-adapter.ts +19 -8
- package/src/resources/extensions/remote-questions/slack-adapter.ts +17 -11
- package/src/resources/extensions/remote-questions/telegram-adapter.ts +19 -8
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +0 -544
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +0 -28
- package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +0 -173
- package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +0 -87
- package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +0 -74
- package/dist/resources/extensions/gsd/workflow-templates/full-project.md +0 -41
- package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +0 -45
- package/dist/resources/extensions/gsd/workflow-templates/refactor.md +0 -83
- package/dist/resources/extensions/gsd/workflow-templates/registry.json +0 -85
- package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +0 -73
- package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +0 -81
- package/dist/resources/extensions/gsd/workflow-templates/spike.md +0 -69
- package/dist/resources/extensions/gsd/workflow-templates.ts +0 -241
- package/dist/resources/extensions/mcp-client/index.ts +0 -459
- package/dist/resources/extensions/remote-questions/http-client.ts +0 -76
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +0 -544
- package/src/resources/extensions/gsd/prompts/workflow-start.md +0 -28
- package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +0 -173
- package/src/resources/extensions/gsd/workflow-templates/bugfix.md +0 -87
- package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +0 -74
- package/src/resources/extensions/gsd/workflow-templates/full-project.md +0 -41
- package/src/resources/extensions/gsd/workflow-templates/hotfix.md +0 -45
- package/src/resources/extensions/gsd/workflow-templates/refactor.md +0 -83
- package/src/resources/extensions/gsd/workflow-templates/registry.json +0 -85
- package/src/resources/extensions/gsd/workflow-templates/security-audit.md +0 -73
- package/src/resources/extensions/gsd/workflow-templates/small-feature.md +0 -81
- package/src/resources/extensions/gsd/workflow-templates/spike.md +0 -69
- package/src/resources/extensions/gsd/workflow-templates.ts +0 -241
- package/src/resources/extensions/mcp-client/index.ts +0 -459
- package/src/resources/extensions/remote-questions/http-client.ts +0 -76
|
@@ -22,6 +22,7 @@ import { loadFile, getManifestStatus, resolveAllOverrides, parsePlan, parseSumma
|
|
|
22
22
|
import { loadPrompt } from "./prompt-loader.js";
|
|
23
23
|
import { runVerificationGate, formatFailureContext, captureRuntimeErrors, runDependencyAudit } from "./verification-gate.js";
|
|
24
24
|
import { writeVerificationJSON } from "./verification-evidence.js";
|
|
25
|
+
export { inlinePriorMilestoneSummary } from "./files.js";
|
|
25
26
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
26
27
|
import {
|
|
27
28
|
gsdRoot, resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
@@ -191,6 +192,12 @@ import {
|
|
|
191
192
|
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
192
193
|
} from "./auto/session.js";
|
|
193
194
|
import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerificationRetry } from "./auto/session.js";
|
|
195
|
+
export {
|
|
196
|
+
MAX_UNIT_DISPATCHES, STUB_RECOVERY_THRESHOLD, MAX_LIFETIME_DISPATCHES,
|
|
197
|
+
MAX_CONSECUTIVE_SKIPS, DISPATCH_GAP_TIMEOUT_MS, MAX_SKIP_DEPTH,
|
|
198
|
+
NEW_SESSION_TIMEOUT_MS, DISPATCH_HANG_TIMEOUT_MS,
|
|
199
|
+
} from "./auto/session.js";
|
|
200
|
+
export type { CompletedUnit, CurrentUnit, UnitRouting, StartModel } from "./auto/session.js";
|
|
194
201
|
|
|
195
202
|
// ── ENCAPSULATION INVARIANT ─────────────────────────────────────────────────
|
|
196
203
|
// ALL mutable auto-mode state lives in the AutoSession class (auto/session.ts).
|
|
@@ -261,6 +268,8 @@ export function shouldUseWorktreeIsolation(): boolean {
|
|
|
261
268
|
* Maps toolCallId → start timestamp (ms) so the idle watchdog can detect tools that have been
|
|
262
269
|
* running suspiciously long (e.g., a Bash command hung because `&` kept stdout open).
|
|
263
270
|
*/
|
|
271
|
+
// Re-export budget utilities for external consumers
|
|
272
|
+
export { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction } from "./auto-budget.js";
|
|
264
273
|
|
|
265
274
|
/** Wrapper: register SIGTERM handler and store reference. */
|
|
266
275
|
function registerSigtermHandler(currentBasePath: string): void {
|
|
@@ -273,6 +282,8 @@ function deregisterSigtermHandler(): void {
|
|
|
273
282
|
s.sigtermHandler = null;
|
|
274
283
|
}
|
|
275
284
|
|
|
285
|
+
export { type AutoDashboardData } from "./auto-dashboard.js";
|
|
286
|
+
|
|
276
287
|
export function getAutoDashboardData(): AutoDashboardData {
|
|
277
288
|
const ledger = getLedger();
|
|
278
289
|
const totals = ledger ? getProjectTotals(ledger.units) : null;
|
|
@@ -923,6 +934,8 @@ async function showStepWizard(
|
|
|
923
934
|
}
|
|
924
935
|
}
|
|
925
936
|
|
|
937
|
+
// describeNextUnit is imported from auto-dashboard.ts and re-exported
|
|
938
|
+
export { describeNextUnit } from "./auto-dashboard.js";
|
|
926
939
|
|
|
927
940
|
/** Thin wrapper: delegates to auto-dashboard.ts, passing state accessors. */
|
|
928
941
|
function updateProgressWidget(
|
|
@@ -1892,3 +1905,5 @@ export async function dispatchHookUnit(
|
|
|
1892
1905
|
}
|
|
1893
1906
|
|
|
1894
1907
|
|
|
1908
|
+
// Direct phase dispatch → auto-direct-dispatch.ts
|
|
1909
|
+
export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
@@ -19,26 +19,7 @@ import {
|
|
|
19
19
|
filterDoctorIssues,
|
|
20
20
|
} from "./doctor.js";
|
|
21
21
|
import { isAutoActive } from "./auto.js";
|
|
22
|
-
import { projectRoot } from "./commands.js";
|
|
23
|
-
import { loadPrompt } from "./prompt-loader.js";
|
|
24
|
-
|
|
25
|
-
export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
|
|
26
|
-
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
|
|
27
|
-
const workflow = readFileSync(workflowPath, "utf-8");
|
|
28
|
-
const prompt = loadPrompt("doctor-heal", {
|
|
29
|
-
doctorSummary: reportText,
|
|
30
|
-
structuredIssues,
|
|
31
|
-
scopeLabel: scope ?? "active milestone / blocking scope",
|
|
32
|
-
doctorCommandSuffix: scope ? ` ${scope}` : "",
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
const content = `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`;
|
|
36
|
-
|
|
37
|
-
pi.sendMessage(
|
|
38
|
-
{ customType: "gsd-doctor-heal", content, display: false },
|
|
39
|
-
{ triggerTurn: true },
|
|
40
|
-
);
|
|
41
|
-
}
|
|
22
|
+
import { projectRoot, dispatchDoctorHeal } from "./commands.js";
|
|
42
23
|
|
|
43
24
|
export async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
44
25
|
const trimmed = args.trim();
|
|
@@ -14,7 +14,6 @@ import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
|
14
14
|
import { existsSync, readdirSync, readFileSync, statSync, unlinkSync } from "node:fs";
|
|
15
15
|
import { join } from "node:path";
|
|
16
16
|
import { gsdRoot } from "./paths.js";
|
|
17
|
-
import { loadJsonFileOrNull } from "./json-persistence.js";
|
|
18
17
|
|
|
19
18
|
// ─── Types ──────────────────────────────────────────────────────────────────
|
|
20
19
|
|
|
@@ -332,18 +331,20 @@ async function handleLogsList(basePath: string, ctx: ExtensionCommandContext): P
|
|
|
332
331
|
|
|
333
332
|
// Metrics summary
|
|
334
333
|
const metricsPath = join(gsdRoot(basePath), "metrics.json");
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
334
|
+
if (existsSync(metricsPath)) {
|
|
335
|
+
try {
|
|
336
|
+
const metrics = JSON.parse(readFileSync(metricsPath, "utf-8"));
|
|
337
|
+
const units = metrics?.units;
|
|
338
|
+
if (Array.isArray(units) && units.length > 0) {
|
|
339
|
+
const totalCost = units.reduce((sum: number, u: Record<string, unknown>) => sum + ((u.cost as number) ?? 0), 0);
|
|
340
|
+
const totalTokens = units.reduce((sum: number, u: Record<string, unknown>) => {
|
|
341
|
+
const t = u.tokens as Record<string, number> | undefined;
|
|
342
|
+
return sum + (t?.total ?? 0);
|
|
343
|
+
}, 0);
|
|
344
|
+
lines.push("");
|
|
345
|
+
lines.push(`Metrics: ${units.length} units tracked · $${totalCost.toFixed(2)} · ${(totalTokens / 1000).toFixed(0)}K tokens`);
|
|
346
|
+
}
|
|
347
|
+
} catch { /* ignore */ }
|
|
347
348
|
}
|
|
348
349
|
|
|
349
350
|
lines.push("");
|
|
@@ -260,57 +260,27 @@ async function configureModels(ctx: ExtensionCommandContext, prefs: Record<strin
|
|
|
260
260
|
group.push(m);
|
|
261
261
|
}
|
|
262
262
|
const providers = Array.from(byProvider.keys()).sort((a, b) => a.localeCompare(b));
|
|
263
|
-
// Sort models within each provider
|
|
264
|
-
for (const group of byProvider.values()) {
|
|
265
|
-
group.sort((a, b) => a.id.localeCompare(b.id));
|
|
266
|
-
}
|
|
267
263
|
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
264
|
+
const modelOptions: string[] = [];
|
|
265
|
+
for (const provider of providers) {
|
|
266
|
+
const group = byProvider.get(provider)!;
|
|
267
|
+
modelOptions.push(`─── ${provider} (${group.length}) ───`);
|
|
268
|
+
for (const m of group) {
|
|
269
|
+
modelOptions.push(`${m.id} · ${m.provider}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
modelOptions.push("(keep current)", "(clear)");
|
|
274
273
|
|
|
275
274
|
for (const phase of modelPhases) {
|
|
276
275
|
const current = models[phase] ?? "";
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
// Step 1: pick provider
|
|
280
|
-
const providerChoice = await ctx.ui.select(`${phaseLabel} — choose provider:`, providerOptions);
|
|
281
|
-
if (!providerChoice || typeof providerChoice !== "string" || providerChoice === "(keep current)") continue;
|
|
282
|
-
|
|
283
|
-
if (providerChoice === "(clear)") {
|
|
284
|
-
delete models[phase];
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (providerChoice === "(type manually)") {
|
|
289
|
-
const input = await ctx.ui.input(
|
|
290
|
-
`${phaseLabel} — enter model ID:`,
|
|
291
|
-
current || "e.g. claude-sonnet-4-20250514",
|
|
292
|
-
);
|
|
293
|
-
if (input !== null && input !== undefined) {
|
|
294
|
-
const val = input.trim();
|
|
295
|
-
if (val) models[phase] = val;
|
|
296
|
-
}
|
|
297
|
-
continue;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Step 2: pick model within provider
|
|
301
|
-
const providerName = providerChoice.replace(/ \(\d+ models?\)$/, "");
|
|
302
|
-
const group = byProvider.get(providerName);
|
|
303
|
-
if (!group) continue;
|
|
304
|
-
|
|
305
|
-
const modelOptions = group.map(m => m.id);
|
|
306
|
-
modelOptions.push("(keep current)", "(clear)");
|
|
276
|
+
const title = `Model for ${phase} phase${current ? ` (current: ${current})` : ""}:`;
|
|
277
|
+
const choice = await ctx.ui.select(title, modelOptions);
|
|
307
278
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
if (modelChoice === "(clear)") {
|
|
279
|
+
if (choice && typeof choice === "string" && choice !== "(keep current)") {
|
|
280
|
+
if (choice === "(clear)") {
|
|
311
281
|
delete models[phase];
|
|
312
282
|
} else {
|
|
313
|
-
models[phase] =
|
|
283
|
+
models[phase] = choice.split(" · ")[0];
|
|
314
284
|
}
|
|
315
285
|
}
|
|
316
286
|
}
|
|
@@ -13,8 +13,7 @@ import { deriveState } from "./state.js";
|
|
|
13
13
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
14
14
|
import { GSDVisualizerOverlay } from "./visualizer-overlay.js";
|
|
15
15
|
import { showQueue, showDiscuss, showHeadlessMilestoneCreation } from "./guided-flow.js";
|
|
16
|
-
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote } from "./auto.js";
|
|
17
|
-
import { dispatchDirectPhase } from "./auto-direct-dispatch.js";
|
|
16
|
+
import { startAuto, stopAuto, pauseAuto, isAutoActive, isAutoPaused, isStepMode, stopAutoRemote, dispatchDirectPhase } from "./auto.js";
|
|
18
17
|
import { resolveProjectRoot } from "./worktree.js";
|
|
19
18
|
import { assertSafeDirectory } from "./validate-directory.js";
|
|
20
19
|
import {
|
|
@@ -22,6 +21,8 @@ import {
|
|
|
22
21
|
getProjectGSDPreferencesPath,
|
|
23
22
|
loadEffectiveGSDPreferences,
|
|
24
23
|
} from "./preferences.js";
|
|
24
|
+
import { loadPrompt } from "./prompt-loader.js";
|
|
25
|
+
|
|
25
26
|
import { handleRemote } from "../remote-questions/mod.js";
|
|
26
27
|
import { handleQuick } from "./quick.js";
|
|
27
28
|
import { handleHistory } from "./history.js";
|
|
@@ -43,9 +44,26 @@ import { handleInspect } from "./commands-inspect.js";
|
|
|
43
44
|
import { handleCleanupBranches, handleCleanupSnapshots, handleSkip, handleDryRun } from "./commands-maintenance.js";
|
|
44
45
|
import { handleDoctor, handleSteer, handleCapture, handleTriage, handleKnowledge, handleRunHook, handleUpdate, handleSkillHealth } from "./commands-handlers.js";
|
|
45
46
|
import { handleLogs } from "./commands-logs.js";
|
|
46
|
-
import { handleStart, handleTemplates, getTemplateCompletions } from "./commands-workflow-templates.js";
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined, reportText: string, structuredIssues: string): void {
|
|
50
|
+
const workflowPath = process.env.GSD_WORKFLOW_PATH ?? join(process.env.HOME ?? "~", ".pi", "GSD-WORKFLOW.md");
|
|
51
|
+
const workflow = readFileSync(workflowPath, "utf-8");
|
|
52
|
+
const prompt = loadPrompt("doctor-heal", {
|
|
53
|
+
doctorSummary: reportText,
|
|
54
|
+
structuredIssues,
|
|
55
|
+
scopeLabel: scope ?? "active milestone / blocking scope",
|
|
56
|
+
doctorCommandSuffix: scope ? ` ${scope}` : "",
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const content = `Read the following GSD workflow protocol and execute exactly.\n\n${workflow}\n\n## Your Task\n\n${prompt}`;
|
|
60
|
+
|
|
61
|
+
pi.sendMessage(
|
|
62
|
+
{ customType: "gsd-doctor-heal", content, display: false },
|
|
63
|
+
{ triggerTurn: true },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
49
67
|
/** Resolve the effective project root, accounting for worktree paths. */
|
|
50
68
|
export function projectRoot(): string {
|
|
51
69
|
const root = resolveProjectRoot(process.cwd());
|
|
@@ -55,7 +73,7 @@ export function projectRoot(): string {
|
|
|
55
73
|
|
|
56
74
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
57
75
|
pi.registerCommand("gsd", {
|
|
58
|
-
description: "GSD — Get Shit Done: /gsd help|
|
|
76
|
+
description: "GSD — Get Shit Done: /gsd help|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|migrate|remote|steer|knowledge|new-milestone|parallel|update",
|
|
59
77
|
getArgumentCompletions: (prefix: string) => {
|
|
60
78
|
const subcommands = [
|
|
61
79
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -98,8 +116,6 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
98
116
|
{ cmd: "park", desc: "Park a milestone — skip without deleting" },
|
|
99
117
|
{ cmd: "unpark", desc: "Reactivate a parked milestone" },
|
|
100
118
|
{ cmd: "update", desc: "Update GSD to the latest version" },
|
|
101
|
-
{ cmd: "start", desc: "Start a workflow template (bugfix, spike, feature, etc.)" },
|
|
102
|
-
{ cmd: "templates", desc: "List available workflow templates" },
|
|
103
119
|
];
|
|
104
120
|
const parts = prefix.trim().split(/\s+/);
|
|
105
121
|
|
|
@@ -285,42 +301,6 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
285
301
|
.map((s) => ({ value: `knowledge ${s.cmd}`, label: s.cmd, description: s.desc }));
|
|
286
302
|
}
|
|
287
303
|
|
|
288
|
-
if (parts[0] === "start" && parts.length <= 2) {
|
|
289
|
-
const subPrefix = parts[1] ?? "";
|
|
290
|
-
const subs = [
|
|
291
|
-
{ cmd: "bugfix", desc: "Triage, fix, test, and ship a bug fix" },
|
|
292
|
-
{ cmd: "small-feature", desc: "Lightweight feature with optional discussion" },
|
|
293
|
-
{ cmd: "spike", desc: "Research, prototype, and document findings" },
|
|
294
|
-
{ cmd: "hotfix", desc: "Minimal: fix it, test it, ship it" },
|
|
295
|
-
{ cmd: "refactor", desc: "Inventory, plan waves, migrate, verify" },
|
|
296
|
-
{ cmd: "security-audit", desc: "Scan, triage, remediate, re-scan" },
|
|
297
|
-
{ cmd: "dep-upgrade", desc: "Assess, upgrade, fix breaks, verify" },
|
|
298
|
-
{ cmd: "full-project", desc: "Complete GSD workflow with full ceremony" },
|
|
299
|
-
{ cmd: "resume", desc: "Resume an in-progress workflow" },
|
|
300
|
-
{ cmd: "--list", desc: "List all available templates" },
|
|
301
|
-
{ cmd: "--dry-run", desc: "Preview workflow without executing" },
|
|
302
|
-
];
|
|
303
|
-
return subs
|
|
304
|
-
.filter((s) => s.cmd.startsWith(subPrefix))
|
|
305
|
-
.map((s) => ({ value: `start ${s.cmd}`, label: s.cmd, description: s.desc }));
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
if (parts[0] === "templates" && parts.length <= 2) {
|
|
309
|
-
const subPrefix = parts[1] ?? "";
|
|
310
|
-
const subs = [
|
|
311
|
-
{ cmd: "info", desc: "Show detailed template info" },
|
|
312
|
-
];
|
|
313
|
-
return subs
|
|
314
|
-
.filter((s) => s.cmd.startsWith(subPrefix))
|
|
315
|
-
.map((s) => ({ value: `templates ${s.cmd}`, label: s.cmd, description: s.desc }));
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (parts[0] === "templates" && parts[1] === "info" && parts.length <= 3) {
|
|
319
|
-
const namePrefix = parts[2] ?? "";
|
|
320
|
-
return getTemplateCompletions(namePrefix)
|
|
321
|
-
.map((c) => ({ value: `templates ${c.value}`, label: c.label, description: c.description }));
|
|
322
|
-
}
|
|
323
|
-
|
|
324
304
|
if (parts[0] === "doctor") {
|
|
325
305
|
const modePrefix = parts[1] ?? "";
|
|
326
306
|
const modes = [
|
|
@@ -812,17 +792,6 @@ Examples:
|
|
|
812
792
|
return;
|
|
813
793
|
}
|
|
814
794
|
|
|
815
|
-
// ─── Workflow Templates ────────────────────────────────────────
|
|
816
|
-
if (trimmed === "start" || trimmed.startsWith("start ")) {
|
|
817
|
-
await handleStart(trimmed.replace(/^start\s*/, "").trim(), ctx, pi);
|
|
818
|
-
return;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
if (trimmed === "templates" || trimmed.startsWith("templates ")) {
|
|
822
|
-
await handleTemplates(trimmed.replace(/^templates\s*/, "").trim(), ctx);
|
|
823
|
-
return;
|
|
824
|
-
}
|
|
825
|
-
|
|
826
795
|
if (trimmed === "") {
|
|
827
796
|
// Bare /gsd defaults to step mode
|
|
828
797
|
await startAuto(ctx, pi, projectRoot(), false, { step: true });
|
|
@@ -841,8 +810,6 @@ function showHelp(ctx: ExtensionCommandContext): void {
|
|
|
841
810
|
const lines = [
|
|
842
811
|
"GSD — Get Shit Done\n",
|
|
843
812
|
"WORKFLOW",
|
|
844
|
-
" /gsd start <tpl> Start a workflow template (bugfix, spike, feature, hotfix, etc.)",
|
|
845
|
-
" /gsd templates List available workflow templates [info <name>]",
|
|
846
813
|
" /gsd Run next unit in step mode (same as /gsd next)",
|
|
847
814
|
" /gsd next Execute next task, then pause [--dry-run] [--verbose]",
|
|
848
815
|
" /gsd auto Run all queued units continuously [--verbose]",
|
|
@@ -11,8 +11,7 @@ import { truncateToWidth, visibleWidth, matchesKey, Key } from "@gsd/pi-tui";
|
|
|
11
11
|
import { deriveState } from "./state.js";
|
|
12
12
|
import { loadFile, parseRoadmap, parsePlan } from "./files.js";
|
|
13
13
|
import { resolveMilestoneFile, resolveSliceFile } from "./paths.js";
|
|
14
|
-
import { getAutoDashboardData } from "./auto.js";
|
|
15
|
-
import type { AutoDashboardData } from "./auto-dashboard.js";
|
|
14
|
+
import { getAutoDashboardData, type AutoDashboardData } from "./auto.js";
|
|
16
15
|
import {
|
|
17
16
|
getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice,
|
|
18
17
|
aggregateByModel, aggregateCacheHitRate, formatCost, formatTokenCount, formatCostProjection,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -50,18 +50,3 @@ export function saveJsonFile<T>(filePath: string, data: T): void {
|
|
|
50
50
|
// Non-fatal — don't let persistence failures break operation
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Write a JSON file atomically (write to .tmp, then rename).
|
|
56
|
-
* Creates parent directories as needed. Non-fatal on error.
|
|
57
|
-
*/
|
|
58
|
-
export function writeJsonFileAtomic<T>(filePath: string, data: T): void {
|
|
59
|
-
try {
|
|
60
|
-
mkdirSync(dirname(filePath), { recursive: true });
|
|
61
|
-
const tmp = filePath + ".tmp";
|
|
62
|
-
writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
|
|
63
|
-
renameSync(tmp, filePath);
|
|
64
|
-
} catch {
|
|
65
|
-
// Non-fatal — don't let persistence failures break operation
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* survives branch switches and is shared across sessions.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
12
13
|
import { join } from "node:path";
|
|
13
14
|
import { gsdRoot } from "./paths.js";
|
|
14
15
|
import { milestoneIdSort } from "./milestone-ids.js";
|
|
15
|
-
import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
|
|
16
16
|
|
|
17
17
|
// ─── Types ───────────────────────────────────────────────────────────────────
|
|
18
18
|
|
|
@@ -45,12 +45,6 @@ function queueOrderPath(basePath: string): string {
|
|
|
45
45
|
return join(gsdRoot(basePath), "QUEUE-ORDER.json");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// ─── Type Guards ─────────────────────────────────────────────────────────────
|
|
49
|
-
|
|
50
|
-
function isQueueOrderFile(data: unknown): data is QueueOrderFile {
|
|
51
|
-
return data !== null && typeof data === "object" && "order" in data! && Array.isArray((data as QueueOrderFile).order);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
48
|
// ─── Read / Write ────────────────────────────────────────────────────────────
|
|
55
49
|
|
|
56
50
|
/**
|
|
@@ -58,8 +52,15 @@ function isQueueOrderFile(data: unknown): data is QueueOrderFile {
|
|
|
58
52
|
* the file is corrupt/unreadable.
|
|
59
53
|
*/
|
|
60
54
|
export function loadQueueOrder(basePath: string): string[] | null {
|
|
61
|
-
const
|
|
62
|
-
|
|
55
|
+
const p = queueOrderPath(basePath);
|
|
56
|
+
if (!existsSync(p)) return null;
|
|
57
|
+
try {
|
|
58
|
+
const data: QueueOrderFile = JSON.parse(readFileSync(p, "utf-8"));
|
|
59
|
+
if (!Array.isArray(data.order)) return null;
|
|
60
|
+
return data.order;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
|
@@ -70,7 +71,7 @@ export function saveQueueOrder(basePath: string, order: string[]): void {
|
|
|
70
71
|
order,
|
|
71
72
|
updatedAt: new Date().toISOString(),
|
|
72
73
|
};
|
|
73
|
-
|
|
74
|
+
writeFileSync(queueOrderPath(basePath), JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
// ─── Sorting ─────────────────────────────────────────────────────────────────
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
+
writeFileSync,
|
|
15
|
+
readFileSync,
|
|
16
|
+
renameSync,
|
|
14
17
|
unlinkSync,
|
|
15
18
|
readdirSync,
|
|
16
19
|
mkdirSync,
|
|
@@ -18,7 +21,6 @@ import {
|
|
|
18
21
|
} from "node:fs";
|
|
19
22
|
import { join } from "node:path";
|
|
20
23
|
import { gsdRoot } from "./paths.js";
|
|
21
|
-
import { loadJsonFileOrNull, writeJsonFileAtomic } from "./json-persistence.js";
|
|
22
24
|
|
|
23
25
|
// ─── Types ─────────────────────────────────────────────────────────────────
|
|
24
26
|
|
|
@@ -47,16 +49,9 @@ export interface SignalMessage {
|
|
|
47
49
|
const PARALLEL_DIR = "parallel";
|
|
48
50
|
const STATUS_SUFFIX = ".status.json";
|
|
49
51
|
const SIGNAL_SUFFIX = ".signal.json";
|
|
52
|
+
const TMP_SUFFIX = ".tmp";
|
|
50
53
|
const DEFAULT_STALE_TIMEOUT_MS = 30_000;
|
|
51
54
|
|
|
52
|
-
function isSessionStatus(data: unknown): data is SessionStatus {
|
|
53
|
-
return data !== null && typeof data === "object" && "milestoneId" in data && "pid" in data;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function isSignalMessage(data: unknown): data is SignalMessage {
|
|
57
|
-
return data !== null && typeof data === "object" && "signal" in data && "sentAt" in data;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
55
|
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
61
56
|
|
|
62
57
|
function parallelDir(basePath: string): string {
|
|
@@ -91,13 +86,25 @@ function isPidAlive(pid: number): boolean {
|
|
|
91
86
|
|
|
92
87
|
/** Write session status atomically (write to .tmp, then rename). */
|
|
93
88
|
export function writeSessionStatus(basePath: string, status: SessionStatus): void {
|
|
94
|
-
|
|
95
|
-
|
|
89
|
+
try {
|
|
90
|
+
ensureParallelDir(basePath);
|
|
91
|
+
const dest = statusPath(basePath, status.milestoneId);
|
|
92
|
+
const tmp = dest + TMP_SUFFIX;
|
|
93
|
+
writeFileSync(tmp, JSON.stringify(status, null, 2), "utf-8");
|
|
94
|
+
renameSync(tmp, dest);
|
|
95
|
+
} catch { /* non-fatal */ }
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
/** Read a specific milestone's session status. */
|
|
99
99
|
export function readSessionStatus(basePath: string, milestoneId: string): SessionStatus | null {
|
|
100
|
-
|
|
100
|
+
try {
|
|
101
|
+
const p = statusPath(basePath, milestoneId);
|
|
102
|
+
if (!existsSync(p)) return null;
|
|
103
|
+
const raw = readFileSync(p, "utf-8");
|
|
104
|
+
return JSON.parse(raw) as SessionStatus;
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
101
108
|
}
|
|
102
109
|
|
|
103
110
|
/** Read all session status files from .gsd/parallel/. */
|
|
@@ -107,10 +114,13 @@ export function readAllSessionStatuses(basePath: string): SessionStatus[] {
|
|
|
107
114
|
|
|
108
115
|
const results: SessionStatus[] = [];
|
|
109
116
|
try {
|
|
110
|
-
|
|
117
|
+
const entries = readdirSync(dir);
|
|
118
|
+
for (const entry of entries) {
|
|
111
119
|
if (!entry.endsWith(STATUS_SUFFIX)) continue;
|
|
112
|
-
|
|
113
|
-
|
|
120
|
+
try {
|
|
121
|
+
const raw = readFileSync(join(dir, entry), "utf-8");
|
|
122
|
+
results.push(JSON.parse(raw) as SessionStatus);
|
|
123
|
+
} catch { /* skip corrupt files */ }
|
|
114
124
|
}
|
|
115
125
|
} catch { /* non-fatal */ }
|
|
116
126
|
return results;
|
|
@@ -128,19 +138,27 @@ export function removeSessionStatus(basePath: string, milestoneId: string): void
|
|
|
128
138
|
|
|
129
139
|
/** Write a signal file for a worker to consume. */
|
|
130
140
|
export function sendSignal(basePath: string, milestoneId: string, signal: SessionSignal): void {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
141
|
+
try {
|
|
142
|
+
ensureParallelDir(basePath);
|
|
143
|
+
const dest = signalPath(basePath, milestoneId);
|
|
144
|
+
const tmp = dest + TMP_SUFFIX;
|
|
145
|
+
const msg: SignalMessage = { signal, sentAt: Date.now(), from: "coordinator" };
|
|
146
|
+
writeFileSync(tmp, JSON.stringify(msg, null, 2), "utf-8");
|
|
147
|
+
renameSync(tmp, dest);
|
|
148
|
+
} catch { /* non-fatal */ }
|
|
134
149
|
}
|
|
135
150
|
|
|
136
151
|
/** Read and delete a signal file (atomic consume). Returns null if no signal pending. */
|
|
137
152
|
export function consumeSignal(basePath: string, milestoneId: string): SignalMessage | null {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
153
|
+
try {
|
|
154
|
+
const p = signalPath(basePath, milestoneId);
|
|
155
|
+
if (!existsSync(p)) return null;
|
|
156
|
+
const raw = readFileSync(p, "utf-8");
|
|
157
|
+
unlinkSync(p);
|
|
158
|
+
return JSON.parse(raw) as SignalMessage;
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
142
161
|
}
|
|
143
|
-
return msg;
|
|
144
162
|
}
|
|
145
163
|
|
|
146
164
|
// ─── Stale Detection ───────────────────────────────────────────────────────
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
getBudgetAlertLevel,
|
|
6
6
|
getBudgetEnforcementAction,
|
|
7
7
|
getNewBudgetAlertLevel,
|
|
8
|
-
} from "../auto
|
|
8
|
+
} from "../auto.js";
|
|
9
9
|
|
|
10
10
|
test("getBudgetAlertLevel returns the expected threshold bucket", () => {
|
|
11
11
|
assert.equal(getBudgetAlertLevel(0.10), 0);
|
|
@@ -17,8 +17,8 @@ import { tmpdir } from "node:os";
|
|
|
17
17
|
import {
|
|
18
18
|
_getUnitConsecutiveSkips,
|
|
19
19
|
_resetUnitConsecutiveSkips,
|
|
20
|
+
MAX_CONSECUTIVE_SKIPS,
|
|
20
21
|
} from "../auto.ts";
|
|
21
|
-
import { MAX_CONSECUTIVE_SKIPS } from "../auto/session.ts";
|
|
22
22
|
import { persistCompletedKey, removePersistedKey, loadPersistedKeys } from "../auto-recovery.ts";
|
|
23
23
|
import { createTestContext } from "./test-helpers.ts";
|
|
24
24
|
|