gsd-pi 2.37.1-dev.d3ace49 → 2.38.0-dev.29edcdc
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/resource-loader.js +34 -1
- 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/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- 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 +8 -9
- package/dist/resources/extensions/gsd/auto-loop.js +597 -588
- package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +23 -43
- 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-worktree.js +3 -3
- 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 +24 -3
- 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 +204 -12
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +6 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +15 -12
- package/dist/resources/extensions/gsd/guided-flow.js +82 -32
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- 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 +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
- package/dist/resources/extensions/gsd/preferences.js +8 -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 +27 -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/roadmap-mutations.js +24 -0
- 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/mcp-client/index.js +14 -1
- 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-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +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/extensions/loader.ts +223 -7
- 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/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
- package/src/resources/extensions/gsd/auto-loop.ts +484 -546
- package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
- package/src/resources/extensions/gsd/auto-prompts.ts +25 -45
- 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-worktree.ts +3 -3
- 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 +26 -4
- 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 +199 -14
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +5 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +20 -10
- package/src/resources/extensions/gsd/guided-flow.ts +110 -38
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
- package/src/resources/extensions/gsd/preferences.ts +8 -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 +27 -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/roadmap-mutations.ts +29 -0
- 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 +122 -68
- 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/mcp-client/index.ts +17 -1
- 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
|
@@ -355,6 +355,63 @@ function checkGitRemote(basePath) {
|
|
|
355
355
|
}
|
|
356
356
|
return { name: "git_remote", status: "ok", message: "Git remote reachable" };
|
|
357
357
|
}
|
|
358
|
+
/**
|
|
359
|
+
* Check if the project build passes (opt-in slow check, use --build flag).
|
|
360
|
+
* Runs npm run build and reports failure as env_build.
|
|
361
|
+
*/
|
|
362
|
+
function checkBuildHealth(basePath) {
|
|
363
|
+
const pkgPath = join(basePath, "package.json");
|
|
364
|
+
if (!existsSync(pkgPath))
|
|
365
|
+
return null;
|
|
366
|
+
try {
|
|
367
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
368
|
+
const buildScript = pkg.scripts?.build;
|
|
369
|
+
if (!buildScript)
|
|
370
|
+
return null;
|
|
371
|
+
const result = tryExec("npm run build 2>&1", basePath);
|
|
372
|
+
if (result === null) {
|
|
373
|
+
return {
|
|
374
|
+
name: "build",
|
|
375
|
+
status: "error",
|
|
376
|
+
message: "Build failed — npm run build exited non-zero",
|
|
377
|
+
detail: "Fix build errors before dispatching work",
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return { name: "build", status: "ok", message: "Build passes" };
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Check if tests pass (opt-in slow check, use --test flag).
|
|
388
|
+
* Runs npm test and reports failures as env_test.
|
|
389
|
+
*/
|
|
390
|
+
function checkTestHealth(basePath) {
|
|
391
|
+
const pkgPath = join(basePath, "package.json");
|
|
392
|
+
if (!existsSync(pkgPath))
|
|
393
|
+
return null;
|
|
394
|
+
try {
|
|
395
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
396
|
+
const testScript = pkg.scripts?.test;
|
|
397
|
+
// Skip if no test script or the default placeholder
|
|
398
|
+
if (!testScript || testScript.includes("no test specified"))
|
|
399
|
+
return null;
|
|
400
|
+
const result = tryExec("npm test 2>&1", basePath);
|
|
401
|
+
if (result === null) {
|
|
402
|
+
return {
|
|
403
|
+
name: "test",
|
|
404
|
+
status: "warning",
|
|
405
|
+
message: "Tests failing — npm test exited non-zero",
|
|
406
|
+
detail: "Fix failing tests before shipping",
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
return { name: "test", status: "ok", message: "Tests pass" };
|
|
410
|
+
}
|
|
411
|
+
catch {
|
|
412
|
+
return null;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
358
415
|
// ── Public API ─────────────────────────────────────────────────────────────
|
|
359
416
|
/**
|
|
360
417
|
* Run all environment health checks. Returns structured results for
|
|
@@ -394,6 +451,24 @@ export function runFullEnvironmentChecks(basePath) {
|
|
|
394
451
|
results.push(remoteCheck);
|
|
395
452
|
return results;
|
|
396
453
|
}
|
|
454
|
+
/**
|
|
455
|
+
* Run slow opt-in checks (build and/or test).
|
|
456
|
+
* These are never run on the pre-dispatch gate — only on explicit /gsd doctor --build/--test.
|
|
457
|
+
*/
|
|
458
|
+
export function runSlowEnvironmentChecks(basePath, options) {
|
|
459
|
+
const results = [];
|
|
460
|
+
if (options?.includeBuild) {
|
|
461
|
+
const buildCheck = checkBuildHealth(basePath);
|
|
462
|
+
if (buildCheck)
|
|
463
|
+
results.push(buildCheck);
|
|
464
|
+
}
|
|
465
|
+
if (options?.includeTests) {
|
|
466
|
+
const testCheck = checkTestHealth(basePath);
|
|
467
|
+
if (testCheck)
|
|
468
|
+
results.push(testCheck);
|
|
469
|
+
}
|
|
470
|
+
return results;
|
|
471
|
+
}
|
|
397
472
|
/**
|
|
398
473
|
* Convert environment check results to DoctorIssue format for the doctor pipeline.
|
|
399
474
|
*/
|
|
@@ -417,6 +492,9 @@ export async function checkEnvironmentHealth(basePath, issues, options) {
|
|
|
417
492
|
const results = options?.includeRemote
|
|
418
493
|
? runFullEnvironmentChecks(basePath)
|
|
419
494
|
: runEnvironmentChecks(basePath);
|
|
495
|
+
if (options?.includeBuild || options?.includeTests) {
|
|
496
|
+
results.push(...runSlowEnvironmentChecks(basePath, options));
|
|
497
|
+
}
|
|
420
498
|
issues.push(...environmentResultsToDoctorIssues(results));
|
|
421
499
|
}
|
|
422
500
|
/**
|
|
@@ -69,3 +69,18 @@ export function formatDoctorIssuesForPrompt(issues) {
|
|
|
69
69
|
return `- [${prefix}] ${issue.unitId} | ${issue.code} | ${issue.message}${issue.file ? ` | file: ${issue.file}` : ""} | fixable: ${issue.fixable ? "yes" : "no"}`;
|
|
70
70
|
}).join("\n");
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Serialize a doctor report to JSON — suitable for CI/tooling integration.
|
|
74
|
+
* Usage: /gsd doctor --json
|
|
75
|
+
*/
|
|
76
|
+
export function formatDoctorReportJson(report) {
|
|
77
|
+
return JSON.stringify({
|
|
78
|
+
ok: report.ok,
|
|
79
|
+
basePath: report.basePath,
|
|
80
|
+
generatedAt: new Date().toISOString(),
|
|
81
|
+
summary: summarizeDoctorIssues(report.issues),
|
|
82
|
+
issues: report.issues,
|
|
83
|
+
fixesApplied: report.fixesApplied,
|
|
84
|
+
...(report.timing ? { timing: report.timing } : {}),
|
|
85
|
+
}, null, 2);
|
|
86
|
+
}
|
|
@@ -28,10 +28,12 @@ function modelToProviderId(model) {
|
|
|
28
28
|
const prefix = model.split("/")[0].toLowerCase();
|
|
29
29
|
// Map known prefixes to registry IDs
|
|
30
30
|
const prefixMap = {
|
|
31
|
+
"anthropic-vertex": "anthropic-vertex",
|
|
31
32
|
openrouter: "openrouter",
|
|
32
33
|
groq: "groq",
|
|
33
34
|
mistral: "mistral",
|
|
34
35
|
google: "google",
|
|
36
|
+
"google-vertex": "google-vertex",
|
|
35
37
|
anthropic: "anthropic",
|
|
36
38
|
openai: "openai",
|
|
37
39
|
"github-copilot": "github-copilot",
|
|
@@ -67,11 +69,19 @@ function collectConfiguredModelProviders() {
|
|
|
67
69
|
}
|
|
68
70
|
const modelEntries = typeof models === "object" ? Object.values(models) : [];
|
|
69
71
|
for (const entry of modelEntries) {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
if (typeof entry === "string") {
|
|
73
|
+
const pid = modelToProviderId(entry);
|
|
74
|
+
if (pid)
|
|
75
|
+
providers.add(pid);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (typeof entry === "object" && entry !== null && "model" in entry) {
|
|
79
|
+
const configuredProvider = "provider" in entry ? entry.provider : undefined;
|
|
80
|
+
if (typeof configuredProvider === "string" && configuredProvider.trim().length > 0) {
|
|
81
|
+
providers.add(configuredProvider);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const modelId = String(entry.model);
|
|
75
85
|
const pid = modelToProviderId(modelId);
|
|
76
86
|
if (pid)
|
|
77
87
|
providers.add(pid);
|
|
@@ -138,7 +148,9 @@ function checkLlmProviders() {
|
|
|
138
148
|
const results = [];
|
|
139
149
|
for (const providerId of required) {
|
|
140
150
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
141
|
-
const label =
|
|
151
|
+
const label = providerId === "anthropic-vertex"
|
|
152
|
+
? "Anthropic Vertex"
|
|
153
|
+
: info?.label ?? providerId;
|
|
142
154
|
const lookup = resolveKey(providerId);
|
|
143
155
|
if (!lookup.found) {
|
|
144
156
|
// Check if a cross-provider can serve this provider's models
|
|
@@ -157,16 +169,20 @@ function checkLlmProviders() {
|
|
|
157
169
|
});
|
|
158
170
|
continue;
|
|
159
171
|
}
|
|
160
|
-
const envVar =
|
|
172
|
+
const envVar = providerId === "anthropic-vertex"
|
|
173
|
+
? "ANTHROPIC_VERTEX_PROJECT_ID"
|
|
174
|
+
: info?.envVar ?? `${providerId.toUpperCase()}_API_KEY`;
|
|
161
175
|
results.push({
|
|
162
176
|
name: providerId,
|
|
163
177
|
label,
|
|
164
178
|
category: "llm",
|
|
165
179
|
status: "error",
|
|
166
|
-
message: `${label} —
|
|
167
|
-
detail:
|
|
168
|
-
?
|
|
169
|
-
:
|
|
180
|
+
message: `${label} — not configured`,
|
|
181
|
+
detail: providerId === "anthropic-vertex"
|
|
182
|
+
? "Set ANTHROPIC_VERTEX_PROJECT_ID and authenticate with Google ADC"
|
|
183
|
+
: info?.hasOAuth
|
|
184
|
+
? `Run /gsd keys to authenticate`
|
|
185
|
+
: `Set ${envVar} or run /gsd keys`,
|
|
170
186
|
required: true,
|
|
171
187
|
});
|
|
172
188
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
|
|
4
|
-
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
|
|
4
|
+
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
|
|
5
5
|
import { deriveState, isMilestoneComplete } from "./state.js";
|
|
6
6
|
import { invalidateAllCaches } from "./cache.js";
|
|
7
7
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
@@ -9,7 +9,7 @@ import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
|
|
|
9
9
|
import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
|
|
10
10
|
import { checkEnvironmentHealth } from "./doctor-environment.js";
|
|
11
11
|
import { runProviderChecks } from "./doctor-providers.js";
|
|
12
|
-
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
|
|
12
|
+
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } from "./doctor-format.js";
|
|
13
13
|
export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport } from "./doctor-environment.js";
|
|
14
14
|
export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport } from "./progress-score.js";
|
|
15
15
|
/**
|
|
@@ -257,10 +257,23 @@ async function markSliceDoneInRoadmap(basePath, milestoneId, sliceId, fixesAppli
|
|
|
257
257
|
fixesApplied.push(`marked ${sliceId} done in ${roadmapPath}`);
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
async function markSliceUndoneInRoadmap(basePath, milestoneId, sliceId, fixesApplied) {
|
|
261
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
262
|
+
if (!roadmapPath)
|
|
263
|
+
return;
|
|
264
|
+
const content = await loadFile(roadmapPath);
|
|
265
|
+
if (!content)
|
|
266
|
+
return;
|
|
267
|
+
const updated = content.replace(new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"), `$1[ ] **${sliceId}:`);
|
|
268
|
+
if (updated !== content) {
|
|
269
|
+
await saveFile(roadmapPath, updated);
|
|
270
|
+
fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
260
273
|
function matchesScope(unitId, scope) {
|
|
261
274
|
if (!scope)
|
|
262
275
|
return true;
|
|
263
|
-
return unitId === scope || unitId.startsWith(`${scope}/`)
|
|
276
|
+
return unitId === scope || unitId.startsWith(`${scope}/`);
|
|
264
277
|
}
|
|
265
278
|
function auditRequirements(content) {
|
|
266
279
|
if (!content)
|
|
@@ -324,10 +337,68 @@ export async function selectDoctorScope(basePath, requestedScope) {
|
|
|
324
337
|
}
|
|
325
338
|
return state.registry[0]?.id;
|
|
326
339
|
}
|
|
340
|
+
// ── Helper: circular dependency detection ──────────────────────────────────
|
|
341
|
+
function detectCircularDependencies(slices) {
|
|
342
|
+
const known = new Set(slices.map(s => s.id));
|
|
343
|
+
const adj = new Map();
|
|
344
|
+
for (const s of slices)
|
|
345
|
+
adj.set(s.id, s.depends.filter(d => known.has(d)));
|
|
346
|
+
const state = new Map();
|
|
347
|
+
for (const s of slices)
|
|
348
|
+
state.set(s.id, "unvisited");
|
|
349
|
+
const cycles = [];
|
|
350
|
+
function dfs(id, path) {
|
|
351
|
+
const st = state.get(id);
|
|
352
|
+
if (st === "done")
|
|
353
|
+
return;
|
|
354
|
+
if (st === "visiting") {
|
|
355
|
+
cycles.push([...path.slice(path.indexOf(id)), id]);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
state.set(id, "visiting");
|
|
359
|
+
for (const dep of adj.get(id) ?? [])
|
|
360
|
+
dfs(dep, [...path, id]);
|
|
361
|
+
state.set(id, "done");
|
|
362
|
+
}
|
|
363
|
+
for (const s of slices)
|
|
364
|
+
if (state.get(s.id) === "unvisited")
|
|
365
|
+
dfs(s.id, []);
|
|
366
|
+
return cycles;
|
|
367
|
+
}
|
|
368
|
+
async function appendDoctorHistory(basePath, report) {
|
|
369
|
+
try {
|
|
370
|
+
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
371
|
+
const entry = JSON.stringify({
|
|
372
|
+
ts: new Date().toISOString(),
|
|
373
|
+
ok: report.ok,
|
|
374
|
+
errors: report.issues.filter(i => i.severity === "error").length,
|
|
375
|
+
warnings: report.issues.filter(i => i.severity === "warning").length,
|
|
376
|
+
fixes: report.fixesApplied.length,
|
|
377
|
+
codes: [...new Set(report.issues.map(i => i.code))],
|
|
378
|
+
});
|
|
379
|
+
const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
|
|
380
|
+
await saveFile(historyPath, existing + entry + "\n");
|
|
381
|
+
}
|
|
382
|
+
catch { /* non-fatal */ }
|
|
383
|
+
}
|
|
384
|
+
/** Read the last N doctor history entries. Returns most-recent-first. */
|
|
385
|
+
export async function readDoctorHistory(basePath, lastN = 50) {
|
|
386
|
+
try {
|
|
387
|
+
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
388
|
+
if (!existsSync(historyPath))
|
|
389
|
+
return [];
|
|
390
|
+
const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
|
|
391
|
+
return lines.slice(-lastN).reverse().map(l => JSON.parse(l));
|
|
392
|
+
}
|
|
393
|
+
catch {
|
|
394
|
+
return [];
|
|
395
|
+
}
|
|
396
|
+
}
|
|
327
397
|
export async function runGSDDoctor(basePath, options) {
|
|
328
398
|
const issues = [];
|
|
329
399
|
const fixesApplied = [];
|
|
330
400
|
const fix = options?.fix === true;
|
|
401
|
+
const dryRun = options?.dryRun === true;
|
|
331
402
|
const fixLevel = options?.fixLevel ?? "all";
|
|
332
403
|
// Issue codes that represent completion state transitions — creating summary
|
|
333
404
|
// stubs, marking slices/milestones done in the roadmap. These belong to the
|
|
@@ -336,12 +407,18 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
336
407
|
// detected and reported but never auto-fixed.
|
|
337
408
|
/** Whether a given issue code should be auto-fixed at the current fixLevel. */
|
|
338
409
|
const shouldFix = (code) => {
|
|
339
|
-
if (!fix)
|
|
410
|
+
if (!fix || dryRun)
|
|
340
411
|
return false;
|
|
341
412
|
if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))
|
|
342
413
|
return false;
|
|
343
414
|
return true;
|
|
344
415
|
};
|
|
416
|
+
/** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
|
|
417
|
+
const dryRunCanFix = (code, message) => {
|
|
418
|
+
if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
|
|
419
|
+
fixesApplied.push(`[dry-run] would fix: ${message}`);
|
|
420
|
+
}
|
|
421
|
+
};
|
|
345
422
|
const prefs = loadEffectiveGSDPreferences();
|
|
346
423
|
if (prefs) {
|
|
347
424
|
const prefIssues = validatePreferenceShape(prefs.preferences);
|
|
@@ -357,18 +434,30 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
357
434
|
});
|
|
358
435
|
}
|
|
359
436
|
}
|
|
360
|
-
// Git health checks
|
|
437
|
+
// Git health checks — timed
|
|
438
|
+
const t0git = Date.now();
|
|
361
439
|
const isolationMode = options?.isolationMode ??
|
|
362
440
|
(prefs?.preferences?.git?.isolation === "none" ? "none" :
|
|
363
441
|
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
|
|
364
442
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
|
|
365
|
-
|
|
443
|
+
const gitMs = Date.now() - t0git;
|
|
444
|
+
// Runtime health checks — timed
|
|
445
|
+
const t0runtime = Date.now();
|
|
366
446
|
await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
|
|
367
|
-
|
|
368
|
-
|
|
447
|
+
const runtimeMs = Date.now() - t0runtime;
|
|
448
|
+
// Environment health checks — timed
|
|
449
|
+
const t0env = Date.now();
|
|
450
|
+
await checkEnvironmentHealth(basePath, issues, {
|
|
451
|
+
includeRemote: !options?.scope,
|
|
452
|
+
includeBuild: options?.includeBuild,
|
|
453
|
+
includeTests: options?.includeTests,
|
|
454
|
+
});
|
|
455
|
+
const envMs = Date.now() - t0env;
|
|
369
456
|
const milestonesPath = milestonesDir(basePath);
|
|
370
457
|
if (!existsSync(milestonesPath)) {
|
|
371
|
-
|
|
458
|
+
const report = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
|
|
459
|
+
await appendDoctorHistory(basePath, report);
|
|
460
|
+
return report;
|
|
372
461
|
}
|
|
373
462
|
const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
|
|
374
463
|
const requirementsContent = await loadFile(requirementsPath);
|
|
@@ -432,6 +521,46 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
432
521
|
if (!roadmapContent)
|
|
433
522
|
continue;
|
|
434
523
|
const roadmap = parseRoadmap(roadmapContent);
|
|
524
|
+
// ── Circular dependency detection ──────────────────────────────────────
|
|
525
|
+
for (const cycle of detectCircularDependencies(roadmap.slices)) {
|
|
526
|
+
issues.push({
|
|
527
|
+
severity: "error",
|
|
528
|
+
code: "circular_slice_dependency",
|
|
529
|
+
scope: "milestone",
|
|
530
|
+
unitId: milestoneId,
|
|
531
|
+
message: `Circular dependency detected: ${cycle.join(" → ")}`,
|
|
532
|
+
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
533
|
+
fixable: false,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
// ── Orphaned slice directories ─────────────────────────────────────────
|
|
537
|
+
try {
|
|
538
|
+
const slicesDir = join(milestonePath, "slices");
|
|
539
|
+
if (existsSync(slicesDir)) {
|
|
540
|
+
const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
|
|
541
|
+
for (const entry of readdirSync(slicesDir)) {
|
|
542
|
+
try {
|
|
543
|
+
if (!lstatSync(join(slicesDir, entry)).isDirectory())
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (!knownSliceIds.has(entry)) {
|
|
550
|
+
issues.push({
|
|
551
|
+
severity: "warning",
|
|
552
|
+
code: "orphaned_slice_directory",
|
|
553
|
+
scope: "milestone",
|
|
554
|
+
unitId: milestoneId,
|
|
555
|
+
message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
|
|
556
|
+
file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
|
|
557
|
+
fixable: false,
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
catch { /* non-fatal */ }
|
|
435
564
|
for (const slice of roadmap.slices) {
|
|
436
565
|
const unitId = `${milestoneId}/${slice.id}`;
|
|
437
566
|
if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId)
|
|
@@ -502,6 +631,34 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
502
631
|
}
|
|
503
632
|
continue;
|
|
504
633
|
}
|
|
634
|
+
// ── Duplicate task IDs ───────────────────────────────────────────────
|
|
635
|
+
const taskIdCounts = new Map();
|
|
636
|
+
for (const task of plan.tasks)
|
|
637
|
+
taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
|
|
638
|
+
for (const [taskId, count] of taskIdCounts) {
|
|
639
|
+
if (count > 1) {
|
|
640
|
+
issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
|
|
641
|
+
message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
|
|
642
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
// ── Task files on disk not in plan ────────────────────────────────────
|
|
646
|
+
try {
|
|
647
|
+
if (tasksDir) {
|
|
648
|
+
const planTaskIds = new Set(plan.tasks.map(t => t.id));
|
|
649
|
+
for (const f of readdirSync(tasksDir)) {
|
|
650
|
+
if (!f.endsWith("-SUMMARY.md"))
|
|
651
|
+
continue;
|
|
652
|
+
const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
|
|
653
|
+
if (!planTaskIds.has(diskTaskId)) {
|
|
654
|
+
issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
|
|
655
|
+
message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
|
|
656
|
+
file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
catch { /* non-fatal */ }
|
|
505
662
|
let allTasksDone = plan.tasks.length > 0;
|
|
506
663
|
for (const task of plan.tasks) {
|
|
507
664
|
const taskUnitId = `${unitId}/${task.id}`;
|
|
@@ -517,6 +674,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
517
674
|
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
|
|
518
675
|
fixable: true,
|
|
519
676
|
});
|
|
677
|
+
dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
|
|
520
678
|
if (shouldFix("task_done_missing_summary")) {
|
|
521
679
|
const stubPath = join(basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks", `${task.id}-SUMMARY.md`);
|
|
522
680
|
const stubContent = [
|
|
@@ -575,6 +733,22 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
575
733
|
}
|
|
576
734
|
}
|
|
577
735
|
}
|
|
736
|
+
// ── Future timestamp check ─────────────────────────────────────
|
|
737
|
+
if (task.done && hasSummary && summaryPath) {
|
|
738
|
+
try {
|
|
739
|
+
const rawSummary = await loadFile(summaryPath);
|
|
740
|
+
const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
|
|
741
|
+
if (m) {
|
|
742
|
+
const ts = new Date(m[1].trim());
|
|
743
|
+
if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
|
|
744
|
+
issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
|
|
745
|
+
message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
|
|
746
|
+
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
catch { /* non-fatal */ }
|
|
751
|
+
}
|
|
578
752
|
allTasksDone = allTasksDone && task.done;
|
|
579
753
|
}
|
|
580
754
|
// Blocker-without-replan detection
|
|
@@ -604,6 +778,12 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
604
778
|
}
|
|
605
779
|
}
|
|
606
780
|
}
|
|
781
|
+
// ── Stale REPLAN: exists but all tasks done ────────────────────────
|
|
782
|
+
if (replanPath && allTasksDone) {
|
|
783
|
+
issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
|
|
784
|
+
message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
|
|
785
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
|
|
786
|
+
}
|
|
607
787
|
const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
|
|
608
788
|
const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
|
|
609
789
|
const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
|
|
@@ -618,6 +798,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
618
798
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
619
799
|
fixable: true,
|
|
620
800
|
});
|
|
801
|
+
dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
|
|
621
802
|
if (shouldFix("all_tasks_done_missing_slice_summary"))
|
|
622
803
|
await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
623
804
|
}
|
|
@@ -631,6 +812,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
631
812
|
file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
|
|
632
813
|
fixable: true,
|
|
633
814
|
});
|
|
815
|
+
dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
|
|
634
816
|
if (shouldFix("all_tasks_done_missing_slice_uat"))
|
|
635
817
|
await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
636
818
|
}
|
|
@@ -644,6 +826,7 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
644
826
|
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
645
827
|
fixable: true,
|
|
646
828
|
});
|
|
829
|
+
dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
|
|
647
830
|
if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
|
|
648
831
|
await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
649
832
|
}
|
|
@@ -658,6 +841,12 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
658
841
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
659
842
|
fixable: true,
|
|
660
843
|
});
|
|
844
|
+
if (!allTasksDone) {
|
|
845
|
+
dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
|
|
846
|
+
if (shouldFix("slice_checked_missing_summary")) {
|
|
847
|
+
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
661
850
|
}
|
|
662
851
|
if (slice.done && !hasSliceUat) {
|
|
663
852
|
issues.push({
|
|
@@ -696,13 +885,16 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
696
885
|
});
|
|
697
886
|
}
|
|
698
887
|
}
|
|
699
|
-
if (fix && fixesApplied.length > 0) {
|
|
888
|
+
if (fix && !dryRun && fixesApplied.length > 0) {
|
|
700
889
|
await updateStateFile(basePath, fixesApplied);
|
|
701
890
|
}
|
|
702
|
-
|
|
891
|
+
const report = {
|
|
703
892
|
ok: issues.every(issue => issue.severity !== "error"),
|
|
704
893
|
basePath,
|
|
705
894
|
issues,
|
|
706
895
|
fixesApplied,
|
|
896
|
+
timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
|
|
707
897
|
};
|
|
898
|
+
await appendDoctorHistory(basePath, report);
|
|
899
|
+
return report;
|
|
708
900
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
1
2
|
export function registerExitCommand(pi, deps = {}) {
|
|
2
3
|
pi.registerCommand("exit", {
|
|
3
4
|
description: "Exit GSD gracefully",
|
|
4
5
|
handler: async (_args, ctx) => {
|
|
5
6
|
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
6
|
-
const stopAuto = deps.stopAuto ?? (await import
|
|
7
|
+
const stopAuto = deps.stopAuto ?? (await importExtensionModule(import.meta.url, "./auto.js")).stopAuto;
|
|
7
8
|
await stopAuto(ctx, pi, "Graceful exit");
|
|
8
9
|
ctx.shutdown();
|
|
9
10
|
},
|
|
@@ -5,7 +5,7 @@ import { join, basename } from "node:path";
|
|
|
5
5
|
import { exec } from "node:child_process";
|
|
6
6
|
import { getLedger, getProjectTotals, aggregateByPhase, aggregateBySlice, aggregateByModel, formatCost, formatTokenCount, loadLedgerFromDisk, } from "./metrics.js";
|
|
7
7
|
import { gsdRoot } from "./paths.js";
|
|
8
|
-
import { formatDuration, fileLink } from "../shared/
|
|
8
|
+
import { formatDuration, fileLink } from "../shared/format-utils.js";
|
|
9
9
|
import { getErrorMessage } from "./error-utils.js";
|
|
10
10
|
/**
|
|
11
11
|
* Open a file in the user's default browser.
|
|
@@ -6,8 +6,8 @@ import { promises as fs } from 'node:fs';
|
|
|
6
6
|
import { resolve } from 'node:path';
|
|
7
7
|
import { atomicWriteAsync } from './atomic-write.js';
|
|
8
8
|
import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
|
|
9
|
-
import { findMilestoneIds } from './
|
|
10
|
-
import { checkExistingEnvKeys } from '../
|
|
9
|
+
import { findMilestoneIds } from './milestone-ids.js';
|
|
10
|
+
import { checkExistingEnvKeys } from '../env-utils.js';
|
|
11
11
|
import { parseRoadmapSlices } from './roadmap-slices.js';
|
|
12
12
|
import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
|
|
13
13
|
import { debugTime, debugCount } from './debug-logger.js';
|
|
@@ -692,6 +692,10 @@ export function extractUatType(content) {
|
|
|
692
692
|
const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
|
|
693
693
|
if (rawValue.startsWith('artifact-driven'))
|
|
694
694
|
return 'artifact-driven';
|
|
695
|
+
if (rawValue.startsWith('browser-executable'))
|
|
696
|
+
return 'browser-executable';
|
|
697
|
+
if (rawValue.startsWith('runtime-executable'))
|
|
698
|
+
return 'runtime-executable';
|
|
695
699
|
if (rawValue.startsWith('live-runtime'))
|
|
696
700
|
return 'live-runtime';
|
|
697
701
|
if (rawValue.startsWith('human-experience'))
|
|
@@ -21,7 +21,7 @@ import { deriveState } from "./state.js";
|
|
|
21
21
|
import { isAutoActive } from "./auto.js";
|
|
22
22
|
import { loadPrompt } from "./prompt-loader.js";
|
|
23
23
|
import { gsdRoot } from "./paths.js";
|
|
24
|
-
import { formatDuration } from "../shared/
|
|
24
|
+
import { formatDuration } from "../shared/format-utils.js";
|
|
25
25
|
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
26
26
|
// ─── Entry Point ──────────────────────────────────────────────────────────────
|
|
27
27
|
export async function handleForensics(args, ctx, pi) {
|
|
@@ -14,7 +14,7 @@ import { gsdRoot } from "./paths.js";
|
|
|
14
14
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
15
15
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
16
16
|
import { detectWorktreeName, SLICE_BRANCH_RE, } from "./worktree.js";
|
|
17
|
-
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchExists, nativeHasChanges,
|
|
17
|
+
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchExists, nativeHasChanges, nativeAddAllWithExclusions, nativeHasStagedChanges, nativeCommit, nativeRmCached, nativeUpdateRef, } from "./native-git-bridge.js";
|
|
18
18
|
import { GSDError, GSD_MERGE_CONFLICT, GSD_GIT_ERROR } from "./errors.js";
|
|
19
19
|
import { getErrorMessage } from "./error-utils.js";
|
|
20
20
|
export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;
|
|
@@ -36,12 +36,19 @@ export function buildTaskCommitMessage(ctx) {
|
|
|
36
36
|
: description;
|
|
37
37
|
const subject = `${type}(${scope}): ${truncated}`;
|
|
38
38
|
// Build body with key files if available
|
|
39
|
+
const bodyParts = [];
|
|
39
40
|
if (ctx.keyFiles && ctx.keyFiles.length > 0) {
|
|
40
41
|
const fileLines = ctx.keyFiles
|
|
41
42
|
.slice(0, 8) // cap at 8 files to keep commit concise
|
|
42
43
|
.map(f => `- ${f}`)
|
|
43
44
|
.join("\n");
|
|
44
|
-
|
|
45
|
+
bodyParts.push(fileLines);
|
|
46
|
+
}
|
|
47
|
+
if (ctx.issueNumber) {
|
|
48
|
+
bodyParts.push(`Resolves #${ctx.issueNumber}`);
|
|
49
|
+
}
|
|
50
|
+
if (bodyParts.length > 0) {
|
|
51
|
+
return `${subject}\n\n${bodyParts.join("\n\n")}`;
|
|
45
52
|
}
|
|
46
53
|
return subject;
|
|
47
54
|
}
|
|
@@ -254,7 +261,9 @@ export class GitServiceImpl {
|
|
|
254
261
|
}
|
|
255
262
|
this._runtimeFilesCleanedUp = true;
|
|
256
263
|
}
|
|
257
|
-
// Stage everything
|
|
264
|
+
// Stage everything using pathspec exclusions so excluded paths are never
|
|
265
|
+
// hashed by git. The old approach of `git add -A` followed by unstaging
|
|
266
|
+
// hangs indefinitely on repos with large untracked artifact trees (#1605).
|
|
258
267
|
//
|
|
259
268
|
// Exclude only RUNTIME paths from staging — not the entire .gsd/ directory.
|
|
260
269
|
// When .gsd/milestones/ files are already tracked in the index (projects
|
|
@@ -264,15 +273,9 @@ export class GitServiceImpl {
|
|
|
264
273
|
// the second half of a milestone's artifacts are never committed (#1326).
|
|
265
274
|
//
|
|
266
275
|
// If .gsd/ IS in .gitignore (the default for external state projects),
|
|
267
|
-
// git add -A already skips it and the
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for (const exclusion of runtimeExclusions) {
|
|
271
|
-
try {
|
|
272
|
-
nativeResetPaths(this.basePath, [exclusion]);
|
|
273
|
-
}
|
|
274
|
-
catch { /* path not staged — ignore */ }
|
|
275
|
-
}
|
|
276
|
+
// git add -A already skips it and the exclusions are harmless no-ops.
|
|
277
|
+
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
278
|
+
nativeAddAllWithExclusions(this.basePath, allExclusions);
|
|
276
279
|
}
|
|
277
280
|
/** Tracks whether runtime file cleanup has run this session. */
|
|
278
281
|
_runtimeFilesCleanedUp = false;
|