gsd-pi 2.37.1-dev.d3ace49 → 2.38.0-dev.63ad7e5
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/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/remote-questions-config.js +2 -2
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +7 -8
- package/dist/resources/extensions/gsd/auto-loop.js +68 -97
- package/dist/resources/extensions/gsd/auto-post-unit.js +75 -71
- package/dist/resources/extensions/gsd/auto-prompts.js +7 -31
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +22 -2
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +27 -11
- package/dist/resources/extensions/gsd/doctor.js +184 -11
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +2 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/index.js +2 -1
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +0 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +1 -11
- package/dist/resources/extensions/gsd/preferences.js +5 -5
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +6 -8
- package/src/resources/extensions/gsd/auto-loop.ts +88 -133
- package/src/resources/extensions/gsd/auto-post-unit.ts +52 -42
- package/src/resources/extensions/gsd/auto-prompts.ts +7 -33
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +24 -2
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +26 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +177 -13
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +2 -2
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/index.ts +3 -1
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +0 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +1 -11
- package/src/resources/extensions/gsd/preferences.ts +5 -5
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/run-uat.md +25 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +11 -31
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +0 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -11,6 +11,8 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFile
|
|
|
11
11
|
import { dirname, join } from "node:path";
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
|
|
14
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
15
|
+
|
|
14
16
|
// ─── Types (mirrored from extension-registry.ts) ────────────────────────────
|
|
15
17
|
|
|
16
18
|
interface ExtensionManifest {
|
|
@@ -48,11 +50,11 @@ interface ExtensionRegistry {
|
|
|
48
50
|
// ─── Registry I/O ───────────────────────────────────────────────────────────
|
|
49
51
|
|
|
50
52
|
function getRegistryPath(): string {
|
|
51
|
-
return join(
|
|
53
|
+
return join(gsdHome, "extensions", "registry.json");
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function getAgentExtensionsDir(): string {
|
|
55
|
-
return join(
|
|
57
|
+
return join(gsdHome, "agent", "extensions");
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
function loadRegistry(): ExtensionRegistry {
|
|
@@ -15,6 +15,7 @@ import { appendOverride, appendKnowledge } from "./files.js";
|
|
|
15
15
|
import {
|
|
16
16
|
formatDoctorIssuesForPrompt,
|
|
17
17
|
formatDoctorReport,
|
|
18
|
+
formatDoctorReportJson,
|
|
18
19
|
runGSDDoctor,
|
|
19
20
|
selectDoctorScope,
|
|
20
21
|
filterDoctorIssues,
|
|
@@ -43,16 +44,30 @@ export function dispatchDoctorHeal(pi: ExtensionAPI, scope: string | undefined,
|
|
|
43
44
|
|
|
44
45
|
export async function handleDoctor(args: string, ctx: ExtensionCommandContext, pi: ExtensionAPI): Promise<void> {
|
|
45
46
|
const trimmed = args.trim();
|
|
46
|
-
|
|
47
|
+
// Extract flags before positional parsing
|
|
48
|
+
const jsonMode = trimmed.includes("--json");
|
|
49
|
+
const dryRun = trimmed.includes("--dry-run");
|
|
50
|
+
const includeBuild = trimmed.includes("--build");
|
|
51
|
+
const includeTests = trimmed.includes("--test");
|
|
52
|
+
const stripped = trimmed.replace(/--json|--dry-run|--build|--test/g, "").trim();
|
|
53
|
+
const parts = stripped ? stripped.split(/\s+/) : [];
|
|
47
54
|
const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
|
|
48
55
|
const requestedScope = mode === "doctor" ? parts[0] : parts[1];
|
|
49
56
|
const scope = await selectDoctorScope(projectRoot(), requestedScope);
|
|
50
57
|
const effectiveScope = mode === "audit" ? requestedScope : scope;
|
|
51
58
|
const report = await runGSDDoctor(projectRoot(), {
|
|
52
|
-
fix: mode === "fix" || mode === "heal",
|
|
59
|
+
fix: mode === "fix" || mode === "heal" || dryRun,
|
|
60
|
+
dryRun,
|
|
53
61
|
scope: effectiveScope,
|
|
62
|
+
includeBuild,
|
|
63
|
+
includeTests,
|
|
54
64
|
});
|
|
55
65
|
|
|
66
|
+
if (jsonMode) {
|
|
67
|
+
ctx.ui.notify(formatDoctorReportJson(report), "info");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
56
71
|
const reportText = formatDoctorReport(report, {
|
|
57
72
|
scope: effectiveScope,
|
|
58
73
|
includeWarnings: mode === "audit",
|
|
@@ -745,7 +745,7 @@ export function serializePreferencesToFrontmatter(prefs: Record<string, unknown>
|
|
|
745
745
|
"dynamic_routing", "token_profile", "phases", "parallel",
|
|
746
746
|
"auto_visualize", "auto_report",
|
|
747
747
|
"verification_commands", "verification_auto_fix", "verification_max_retries",
|
|
748
|
-
"search_provider", "
|
|
748
|
+
"search_provider", "context_selection",
|
|
749
749
|
];
|
|
750
750
|
|
|
751
751
|
const seen = new Set<string>();
|
|
@@ -10,6 +10,8 @@ import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { gsdRoot } from "./paths.js";
|
|
13
|
+
|
|
14
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
13
15
|
import { enableDebug } from "./debug-logger.js";
|
|
14
16
|
import { deriveState } from "./state.js";
|
|
15
17
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
@@ -159,7 +161,7 @@ async function guardRemoteSession(
|
|
|
159
161
|
|
|
160
162
|
export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
161
163
|
pi.registerCommand("gsd", {
|
|
162
|
-
description: "GSD — Get Shit Done: /gsd help|start|templates|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|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|update",
|
|
164
|
+
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|capture|triage|dispatch|history|undo|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update",
|
|
163
165
|
getArgumentCompletions: (prefix: string) => {
|
|
164
166
|
const subcommands = [
|
|
165
167
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -210,7 +212,11 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
210
212
|
{ cmd: "templates", desc: "List available workflow templates" },
|
|
211
213
|
{ cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
|
|
212
214
|
];
|
|
215
|
+
const hasTrailingSpace = prefix.endsWith(" ");
|
|
213
216
|
const parts = prefix.trim().split(/\s+/);
|
|
217
|
+
if (hasTrailingSpace && parts.length >= 1) {
|
|
218
|
+
parts.push("");
|
|
219
|
+
}
|
|
214
220
|
|
|
215
221
|
if (parts.length <= 1) {
|
|
216
222
|
return subcommands
|
|
@@ -478,7 +484,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
478
484
|
if (parts.length === 3 && ["enable", "disable", "info"].includes(parts[1])) {
|
|
479
485
|
const idPrefix = parts[2] ?? "";
|
|
480
486
|
try {
|
|
481
|
-
const extDir = join(
|
|
487
|
+
const extDir = join(gsdHome, "agent", "extensions");
|
|
482
488
|
const ids: { id: string; name: string }[] = [];
|
|
483
489
|
for (const entry of readdirSync(extDir, { withFileTypes: true })) {
|
|
484
490
|
if (!entry.isDirectory()) continue;
|
|
@@ -509,6 +515,10 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
509
515
|
{ cmd: "fix", desc: "Auto-fix detected issues" },
|
|
510
516
|
{ cmd: "heal", desc: "AI-driven deep healing" },
|
|
511
517
|
{ cmd: "audit", desc: "Run health audit without fixing" },
|
|
518
|
+
{ cmd: "--dry-run", desc: "Show what --fix would change without applying" },
|
|
519
|
+
{ cmd: "--json", desc: "Output report as JSON (CI/tooling friendly)" },
|
|
520
|
+
{ cmd: "--build", desc: "Include slow build health check (npm run build)" },
|
|
521
|
+
{ cmd: "--test", desc: "Include slow test health check (npm test)" },
|
|
512
522
|
];
|
|
513
523
|
|
|
514
524
|
if (parts.length <= 2) {
|
|
@@ -536,6 +546,18 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
|
|
|
536
546
|
.map((p) => ({ value: `dispatch ${p.cmd}`, label: p.cmd, description: p.desc }));
|
|
537
547
|
}
|
|
538
548
|
|
|
549
|
+
if (parts[0] === "rate" && parts.length <= 2) {
|
|
550
|
+
const tierPrefix = parts[1] ?? "";
|
|
551
|
+
const tiers = [
|
|
552
|
+
{ cmd: "over", desc: "Model was overqualified for this task" },
|
|
553
|
+
{ cmd: "ok", desc: "Model was appropriate for this task" },
|
|
554
|
+
{ cmd: "under", desc: "Model was underqualified for this task" },
|
|
555
|
+
];
|
|
556
|
+
return tiers
|
|
557
|
+
.filter((t) => t.cmd.startsWith(tierPrefix))
|
|
558
|
+
.map((t) => ({ value: `rate ${t.cmd}`, label: t.cmd, description: t.desc }));
|
|
559
|
+
}
|
|
560
|
+
|
|
539
561
|
return [];
|
|
540
562
|
},
|
|
541
563
|
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { type TokenProvider, getCharsPerToken } from "./token-counter.js";
|
|
12
|
-
import { compressToTarget } from "./prompt-compressor.js";
|
|
13
12
|
|
|
14
13
|
// ─── Budget ratio constants ──────────────────────────────────────────────────
|
|
15
14
|
// Percentages of total context window allocated to each budget category.
|
|
@@ -202,22 +201,13 @@ export function resolveExecutorContextWindow(
|
|
|
202
201
|
}
|
|
203
202
|
|
|
204
203
|
/**
|
|
205
|
-
*
|
|
206
|
-
* Returns the content within budget with maximum information preservation.
|
|
204
|
+
* Reduce content to fit within budget using section-boundary truncation.
|
|
207
205
|
*/
|
|
208
206
|
export function reduceToFit(content: string, budgetChars: number): TruncationResult {
|
|
209
207
|
if (!content || content.length <= budgetChars) {
|
|
210
208
|
return { content, droppedSections: 0 };
|
|
211
209
|
}
|
|
212
|
-
|
|
213
|
-
// Step 1: Try compression
|
|
214
|
-
const compressed = compressToTarget(content, budgetChars);
|
|
215
|
-
if (compressed.compressedChars <= budgetChars) {
|
|
216
|
-
return { content: compressed.content, droppedSections: 0 };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Step 2: Truncate the compressed content at section boundaries
|
|
220
|
-
return truncateAtSectionBoundary(compressed.content, budgetChars);
|
|
210
|
+
return truncateAtSectionBoundary(content, budgetChars);
|
|
221
211
|
}
|
|
222
212
|
|
|
223
213
|
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
@@ -11,6 +11,8 @@ import { join } from "node:path";
|
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { gsdRoot } from "./paths.js";
|
|
13
13
|
|
|
14
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
15
|
+
|
|
14
16
|
// ─── Types ──────────────────────────────────────────────────────────────────────
|
|
15
17
|
|
|
16
18
|
export interface ProjectDetection {
|
|
@@ -400,7 +402,6 @@ function detectVerificationCommands(
|
|
|
400
402
|
* Check if global GSD setup exists (has ~/.gsd/ with preferences).
|
|
401
403
|
*/
|
|
402
404
|
export function hasGlobalSetup(): boolean {
|
|
403
|
-
const gsdHome = join(homedir(), ".gsd");
|
|
404
405
|
return (
|
|
405
406
|
existsSync(join(gsdHome, "preferences.md")) ||
|
|
406
407
|
existsSync(join(gsdHome, "PREFERENCES.md"))
|
|
@@ -412,7 +413,6 @@ export function hasGlobalSetup(): boolean {
|
|
|
412
413
|
* Returns true if ~/.gsd/ doesn't exist or has no preferences or auth.
|
|
413
414
|
*/
|
|
414
415
|
export function isFirstEverLaunch(): boolean {
|
|
415
|
-
const gsdHome = join(homedir(), ".gsd");
|
|
416
416
|
if (!existsSync(gsdHome)) return true;
|
|
417
417
|
|
|
418
418
|
// If we have preferences, not first launch
|
|
@@ -194,8 +194,6 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
194
194
|
|
|
195
195
|
- `search_provider`: `"brave"`, `"tavily"`, `"ollama"`, `"native"`, or `"auto"` — selects the search backend for research phases. `"native"` forces Anthropic's built-in web search only; provider values force that backend and disable native search; `"auto"` uses the default heuristic. Default: `"auto"`.
|
|
196
196
|
|
|
197
|
-
- `compression_strategy`: `"truncate"` or `"compress"` — controls how context that exceeds the budget is reduced. `"truncate"` (default) drops sections from the end. `"compress"` applies heuristic compression before truncating, preserving more content at the cost of some fidelity. Default: `"truncate"`.
|
|
198
|
-
|
|
199
197
|
- `context_selection`: `"full"` or `"smart"` — controls how files are inlined into context. `"full"` inlines entire files; `"smart"` uses semantic chunking to include only the most relevant sections. Default is derived from `token_profile`.
|
|
200
198
|
|
|
201
199
|
- `parallel`: configures parallel orchestration for running multiple slices concurrently. Keys:
|
|
@@ -657,6 +657,81 @@ export async function checkRuntimeHealth(
|
|
|
657
657
|
} catch {
|
|
658
658
|
// Non-fatal — external state check failed
|
|
659
659
|
}
|
|
660
|
+
|
|
661
|
+
// ── Metrics ledger integrity ───────────────────────────────────────────
|
|
662
|
+
try {
|
|
663
|
+
const metricsPath = join(root, "metrics.json");
|
|
664
|
+
if (existsSync(metricsPath)) {
|
|
665
|
+
try {
|
|
666
|
+
const raw = readFileSync(metricsPath, "utf-8");
|
|
667
|
+
const ledger = JSON.parse(raw);
|
|
668
|
+
if (ledger.version !== 1 || !Array.isArray(ledger.units)) {
|
|
669
|
+
issues.push({
|
|
670
|
+
severity: "warning",
|
|
671
|
+
code: "metrics_ledger_corrupt",
|
|
672
|
+
scope: "project",
|
|
673
|
+
unitId: "project",
|
|
674
|
+
message: "metrics.json has an unexpected structure (version !== 1 or units is not an array) — metrics data may be unreliable",
|
|
675
|
+
file: ".gsd/metrics.json",
|
|
676
|
+
fixable: false,
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
} catch {
|
|
680
|
+
issues.push({
|
|
681
|
+
severity: "warning",
|
|
682
|
+
code: "metrics_ledger_corrupt",
|
|
683
|
+
scope: "project",
|
|
684
|
+
unitId: "project",
|
|
685
|
+
message: "metrics.json is not valid JSON — metrics data may be corrupt",
|
|
686
|
+
file: ".gsd/metrics.json",
|
|
687
|
+
fixable: false,
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
} catch {
|
|
692
|
+
// Non-fatal — metrics check failed
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// ── Large planning file detection ──────────────────────────────────────
|
|
696
|
+
// Files over 100KB can cause LLM context pressure. Report the worst offenders.
|
|
697
|
+
try {
|
|
698
|
+
const MAX_FILE_BYTES = 100 * 1024; // 100KB
|
|
699
|
+
const milestonesPath = milestonesDir(basePath);
|
|
700
|
+
if (existsSync(milestonesPath)) {
|
|
701
|
+
const largeFiles: Array<{ path: string; sizeKB: number }> = [];
|
|
702
|
+
function scanForLargeFiles(dir: string, depth = 0): void {
|
|
703
|
+
if (depth > 6) return;
|
|
704
|
+
try {
|
|
705
|
+
for (const entry of readdirSync(dir)) {
|
|
706
|
+
const full = join(dir, entry);
|
|
707
|
+
try {
|
|
708
|
+
const s = statSync(full);
|
|
709
|
+
if (s.isDirectory()) { scanForLargeFiles(full, depth + 1); continue; }
|
|
710
|
+
if (entry.endsWith(".md") && s.size > MAX_FILE_BYTES) {
|
|
711
|
+
largeFiles.push({ path: full.replace(basePath + "/", ""), sizeKB: Math.round(s.size / 1024) });
|
|
712
|
+
}
|
|
713
|
+
} catch { /* skip entry */ }
|
|
714
|
+
}
|
|
715
|
+
} catch { /* skip dir */ }
|
|
716
|
+
}
|
|
717
|
+
scanForLargeFiles(milestonesPath);
|
|
718
|
+
if (largeFiles.length > 0) {
|
|
719
|
+
largeFiles.sort((a, b) => b.sizeKB - a.sizeKB);
|
|
720
|
+
const worst = largeFiles[0]!;
|
|
721
|
+
issues.push({
|
|
722
|
+
severity: "warning",
|
|
723
|
+
code: "large_planning_file",
|
|
724
|
+
scope: "project",
|
|
725
|
+
unitId: "project",
|
|
726
|
+
message: `${largeFiles.length} planning file(s) exceed 100KB — largest: ${worst.path} (${worst.sizeKB}KB). Large files cause LLM context pressure.`,
|
|
727
|
+
file: worst.path,
|
|
728
|
+
fixable: false,
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
} catch {
|
|
733
|
+
// Non-fatal — large file scan failed
|
|
734
|
+
}
|
|
660
735
|
}
|
|
661
736
|
|
|
662
737
|
/**
|
|
@@ -407,6 +407,63 @@ function checkGitRemote(basePath: string): EnvironmentCheckResult | null {
|
|
|
407
407
|
return { name: "git_remote", status: "ok", message: "Git remote reachable" };
|
|
408
408
|
}
|
|
409
409
|
|
|
410
|
+
/**
|
|
411
|
+
* Check if the project build passes (opt-in slow check, use --build flag).
|
|
412
|
+
* Runs npm run build and reports failure as env_build.
|
|
413
|
+
*/
|
|
414
|
+
function checkBuildHealth(basePath: string): EnvironmentCheckResult | null {
|
|
415
|
+
const pkgPath = join(basePath, "package.json");
|
|
416
|
+
if (!existsSync(pkgPath)) return null;
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
420
|
+
const buildScript = pkg.scripts?.build;
|
|
421
|
+
if (!buildScript) return null;
|
|
422
|
+
|
|
423
|
+
const result = tryExec("npm run build 2>&1", basePath);
|
|
424
|
+
if (result === null) {
|
|
425
|
+
return {
|
|
426
|
+
name: "build",
|
|
427
|
+
status: "error",
|
|
428
|
+
message: "Build failed — npm run build exited non-zero",
|
|
429
|
+
detail: "Fix build errors before dispatching work",
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
return { name: "build", status: "ok", message: "Build passes" };
|
|
433
|
+
} catch {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Check if tests pass (opt-in slow check, use --test flag).
|
|
440
|
+
* Runs npm test and reports failures as env_test.
|
|
441
|
+
*/
|
|
442
|
+
function checkTestHealth(basePath: string): EnvironmentCheckResult | null {
|
|
443
|
+
const pkgPath = join(basePath, "package.json");
|
|
444
|
+
if (!existsSync(pkgPath)) return null;
|
|
445
|
+
|
|
446
|
+
try {
|
|
447
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
448
|
+
const testScript = pkg.scripts?.test;
|
|
449
|
+
// Skip if no test script or the default placeholder
|
|
450
|
+
if (!testScript || testScript.includes("no test specified")) return null;
|
|
451
|
+
|
|
452
|
+
const result = tryExec("npm test 2>&1", basePath);
|
|
453
|
+
if (result === null) {
|
|
454
|
+
return {
|
|
455
|
+
name: "test",
|
|
456
|
+
status: "warning",
|
|
457
|
+
message: "Tests failing — npm test exited non-zero",
|
|
458
|
+
detail: "Fix failing tests before shipping",
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
return { name: "test", status: "ok", message: "Tests pass" };
|
|
462
|
+
} catch {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
410
467
|
// ── Public API ─────────────────────────────────────────────────────────────
|
|
411
468
|
|
|
412
469
|
/**
|
|
@@ -454,6 +511,26 @@ export function runFullEnvironmentChecks(basePath: string): EnvironmentCheckResu
|
|
|
454
511
|
return results;
|
|
455
512
|
}
|
|
456
513
|
|
|
514
|
+
/**
|
|
515
|
+
* Run slow opt-in checks (build and/or test).
|
|
516
|
+
* These are never run on the pre-dispatch gate — only on explicit /gsd doctor --build/--test.
|
|
517
|
+
*/
|
|
518
|
+
export function runSlowEnvironmentChecks(
|
|
519
|
+
basePath: string,
|
|
520
|
+
options?: { includeBuild?: boolean; includeTests?: boolean },
|
|
521
|
+
): EnvironmentCheckResult[] {
|
|
522
|
+
const results: EnvironmentCheckResult[] = [];
|
|
523
|
+
if (options?.includeBuild) {
|
|
524
|
+
const buildCheck = checkBuildHealth(basePath);
|
|
525
|
+
if (buildCheck) results.push(buildCheck);
|
|
526
|
+
}
|
|
527
|
+
if (options?.includeTests) {
|
|
528
|
+
const testCheck = checkTestHealth(basePath);
|
|
529
|
+
if (testCheck) results.push(testCheck);
|
|
530
|
+
}
|
|
531
|
+
return results;
|
|
532
|
+
}
|
|
533
|
+
|
|
457
534
|
/**
|
|
458
535
|
* Convert environment check results to DoctorIssue format for the doctor pipeline.
|
|
459
536
|
*/
|
|
@@ -477,12 +554,16 @@ export function environmentResultsToDoctorIssues(results: EnvironmentCheckResult
|
|
|
477
554
|
export async function checkEnvironmentHealth(
|
|
478
555
|
basePath: string,
|
|
479
556
|
issues: DoctorIssue[],
|
|
480
|
-
options?: { includeRemote?: boolean },
|
|
557
|
+
options?: { includeRemote?: boolean; includeBuild?: boolean; includeTests?: boolean },
|
|
481
558
|
): Promise<void> {
|
|
482
559
|
const results = options?.includeRemote
|
|
483
560
|
? runFullEnvironmentChecks(basePath)
|
|
484
561
|
: runEnvironmentChecks(basePath);
|
|
485
562
|
|
|
563
|
+
if (options?.includeBuild || options?.includeTests) {
|
|
564
|
+
results.push(...runSlowEnvironmentChecks(basePath, options));
|
|
565
|
+
}
|
|
566
|
+
|
|
486
567
|
issues.push(...environmentResultsToDoctorIssues(results));
|
|
487
568
|
}
|
|
488
569
|
|
|
@@ -76,3 +76,23 @@ export function formatDoctorIssuesForPrompt(issues: DoctorIssue[]): string {
|
|
|
76
76
|
return `- [${prefix}] ${issue.unitId} | ${issue.code} | ${issue.message}${issue.file ? ` | file: ${issue.file}` : ""} | fixable: ${issue.fixable ? "yes" : "no"}`;
|
|
77
77
|
}).join("\n");
|
|
78
78
|
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Serialize a doctor report to JSON — suitable for CI/tooling integration.
|
|
82
|
+
* Usage: /gsd doctor --json
|
|
83
|
+
*/
|
|
84
|
+
export function formatDoctorReportJson(report: DoctorReport): string {
|
|
85
|
+
return JSON.stringify(
|
|
86
|
+
{
|
|
87
|
+
ok: report.ok,
|
|
88
|
+
basePath: report.basePath,
|
|
89
|
+
generatedAt: new Date().toISOString(),
|
|
90
|
+
summary: summarizeDoctorIssues(report.issues),
|
|
91
|
+
issues: report.issues,
|
|
92
|
+
fixesApplied: report.fixesApplied,
|
|
93
|
+
...(report.timing ? { timing: report.timing } : {}),
|
|
94
|
+
},
|
|
95
|
+
null,
|
|
96
|
+
2,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
@@ -51,10 +51,12 @@ function modelToProviderId(model: string): string | null {
|
|
|
51
51
|
const prefix = model.split("/")[0].toLowerCase();
|
|
52
52
|
// Map known prefixes to registry IDs
|
|
53
53
|
const prefixMap: Record<string, string> = {
|
|
54
|
+
"anthropic-vertex": "anthropic-vertex",
|
|
54
55
|
openrouter: "openrouter",
|
|
55
56
|
groq: "groq",
|
|
56
57
|
mistral: "mistral",
|
|
57
58
|
google: "google",
|
|
59
|
+
"google-vertex": "google-vertex",
|
|
58
60
|
anthropic: "anthropic",
|
|
59
61
|
openai: "openai",
|
|
60
62
|
"github-copilot": "github-copilot",
|
|
@@ -88,11 +90,20 @@ function collectConfiguredModelProviders(): Set<string> {
|
|
|
88
90
|
|
|
89
91
|
const modelEntries = typeof models === "object" ? Object.values(models) : [];
|
|
90
92
|
for (const entry of modelEntries) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
if (typeof entry === "string") {
|
|
94
|
+
const pid = modelToProviderId(entry);
|
|
95
|
+
if (pid) providers.add(pid);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (typeof entry === "object" && entry !== null && "model" in entry) {
|
|
100
|
+
const configuredProvider = "provider" in entry ? (entry as { provider?: unknown }).provider : undefined;
|
|
101
|
+
if (typeof configuredProvider === "string" && configuredProvider.trim().length > 0) {
|
|
102
|
+
providers.add(configuredProvider);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const modelId = String((entry as { model: unknown }).model);
|
|
96
107
|
const pid = modelToProviderId(modelId);
|
|
97
108
|
if (pid) providers.add(pid);
|
|
98
109
|
}
|
|
@@ -175,7 +186,9 @@ function checkLlmProviders(): ProviderCheckResult[] {
|
|
|
175
186
|
|
|
176
187
|
for (const providerId of required) {
|
|
177
188
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
178
|
-
const label =
|
|
189
|
+
const label = providerId === "anthropic-vertex"
|
|
190
|
+
? "Anthropic Vertex"
|
|
191
|
+
: info?.label ?? providerId;
|
|
179
192
|
const lookup = resolveKey(providerId);
|
|
180
193
|
|
|
181
194
|
if (!lookup.found) {
|
|
@@ -196,14 +209,18 @@ function checkLlmProviders(): ProviderCheckResult[] {
|
|
|
196
209
|
continue;
|
|
197
210
|
}
|
|
198
211
|
|
|
199
|
-
const envVar =
|
|
212
|
+
const envVar = providerId === "anthropic-vertex"
|
|
213
|
+
? "ANTHROPIC_VERTEX_PROJECT_ID"
|
|
214
|
+
: info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
|
|
200
215
|
results.push({
|
|
201
216
|
name: providerId,
|
|
202
217
|
label,
|
|
203
218
|
category: "llm",
|
|
204
219
|
status: "error",
|
|
205
|
-
message: `${label} —
|
|
206
|
-
detail:
|
|
220
|
+
message: `${label} — not configured`,
|
|
221
|
+
detail: providerId === "anthropic-vertex"
|
|
222
|
+
? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
|
|
223
|
+
: info?.hasOAuth
|
|
207
224
|
? `Run /gsd keys to authenticate`
|
|
208
225
|
: `Set ${envVar} or run /gsd keys`,
|
|
209
226
|
required: true,
|
|
@@ -53,7 +53,20 @@ export type DoctorIssueCode =
|
|
|
53
53
|
| "stranded_lock_directory"
|
|
54
54
|
// Git / worktree integrity checks
|
|
55
55
|
| "integration_branch_missing"
|
|
56
|
-
| "worktree_directory_orphaned"
|
|
56
|
+
| "worktree_directory_orphaned"
|
|
57
|
+
// GSD state structural checks
|
|
58
|
+
| "circular_slice_dependency"
|
|
59
|
+
| "orphaned_slice_directory"
|
|
60
|
+
| "duplicate_task_id"
|
|
61
|
+
| "task_file_not_in_plan"
|
|
62
|
+
| "stale_replan_file"
|
|
63
|
+
| "future_timestamp"
|
|
64
|
+
// Runtime data integrity
|
|
65
|
+
| "metrics_ledger_corrupt"
|
|
66
|
+
| "large_planning_file"
|
|
67
|
+
// Slow environment checks (opt-in via --build / --test flags)
|
|
68
|
+
| "env_build"
|
|
69
|
+
| "env_test";
|
|
57
70
|
|
|
58
71
|
/**
|
|
59
72
|
* Issue codes that represent expected completion-transition states.
|
|
@@ -83,6 +96,8 @@ export interface DoctorReport {
|
|
|
83
96
|
basePath: string;
|
|
84
97
|
issues: DoctorIssue[];
|
|
85
98
|
fixesApplied: string[];
|
|
99
|
+
/** Per-domain check durations in milliseconds. Present on explicit /gsd doctor runs. */
|
|
100
|
+
timing?: { git: number; runtime: number; environment: number; gsdState: number };
|
|
86
101
|
}
|
|
87
102
|
|
|
88
103
|
export interface DoctorSummary {
|