gsd-pi 2.37.1 → 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/README.md +1 -1
- 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/onboarding.js +1 -0
- 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 +75 -10
- package/dist/resources/extensions/gsd/auto-loop.js +597 -588
- package/dist/resources/extensions/gsd/auto-post-unit.js +111 -68
- package/dist/resources/extensions/gsd/auto-prompts.js +114 -45
- package/dist/resources/extensions/gsd/auto-recovery.js +37 -1
- 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 +62 -12
- 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 +47 -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/observability-validator.js +24 -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 +3 -2
- package/dist/resources/extensions/gsd/preferences-validation.js +101 -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/plan-slice.md +2 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- 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/reactive-graph.js +227 -0
- 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/templates/task-plan.md +11 -3
- 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 +2 -1
- package/packages/pi-ai/dist/env-api-keys.js +13 -0
- package/packages/pi-ai/dist/env-api-keys.js.map +1 -1
- package/packages/pi-ai/dist/models.generated.d.ts +172 -0
- package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/packages/pi-ai/dist/models.generated.js +172 -0
- package/packages/pi-ai/dist/models.generated.js.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts +64 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js +668 -0
- package/packages/pi-ai/dist/providers/anthropic-shared.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts +5 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js +85 -0
- package/packages/pi-ai/dist/providers/anthropic-vertex.js.map +1 -0
- package/packages/pi-ai/dist/providers/anthropic.d.ts +4 -30
- package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/anthropic.js +47 -764
- package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/register-builtins.js +6 -0
- package/packages/pi-ai/dist/providers/register-builtins.js.map +1 -1
- package/packages/pi-ai/dist/types.d.ts +2 -2
- package/packages/pi-ai/dist/types.d.ts.map +1 -1
- package/packages/pi-ai/dist/types.js.map +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/package.json +1 -0
- package/packages/pi-ai/src/env-api-keys.ts +14 -0
- package/packages/pi-ai/src/models.generated.ts +172 -0
- package/packages/pi-ai/src/providers/anthropic-shared.ts +761 -0
- package/packages/pi-ai/src/providers/anthropic-vertex.ts +130 -0
- package/packages/pi-ai/src/providers/anthropic.ts +76 -868
- package/packages/pi-ai/src/providers/register-builtins.ts +7 -0
- package/packages/pi-ai/src/types.ts +2 -0
- 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/model-resolver.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +1 -0
- package/packages/pi-coding-agent/dist/core/model-resolver.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/model-resolver.ts +1 -0
- 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 +100 -9
- package/src/resources/extensions/gsd/auto-loop.ts +484 -546
- package/src/resources/extensions/gsd/auto-post-unit.ts +92 -42
- package/src/resources/extensions/gsd/auto-prompts.ts +150 -48
- package/src/resources/extensions/gsd/auto-recovery.ts +42 -0
- 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 +64 -10
- 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 +50 -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/observability-validator.ts +27 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +9 -5
- package/src/resources/extensions/gsd/preferences-validation.ts +92 -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/plan-slice.md +2 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +44 -0
- 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/reactive-graph.ts +289 -0
- 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/templates/task-plan.md +11 -3
- 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 +191 -3
- package/src/resources/extensions/gsd/tests/plan-quality-validator.test.ts +111 -0
- 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/reactive-executor.test.ts +511 -0
- package/src/resources/extensions/gsd/tests/reactive-graph.test.ts +299 -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 +43 -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
|
@@ -1,14 +1,15 @@
|
|
|
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
|
|
|
4
4
|
import { loadFile, parsePlan, parseRoadmap, parseSummary, saveFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
|
|
5
|
-
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile } from "./paths.js";
|
|
5
|
+
import { resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveSlicePath, resolveTaskFile, resolveTasksDir, milestonesDir, gsdRoot, relMilestoneFile, relSliceFile, relTaskFile, relSlicePath, relGsdRootFile, resolveGsdRootFile, relMilestonePath } from "./paths.js";
|
|
6
6
|
import { deriveState, isMilestoneComplete } from "./state.js";
|
|
7
7
|
import { invalidateAllCaches } from "./cache.js";
|
|
8
8
|
import { loadEffectiveGSDPreferences, type GSDPreferences } from "./preferences.js";
|
|
9
9
|
|
|
10
|
-
import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
|
|
10
|
+
import type { DoctorIssue, DoctorIssueCode, DoctorReport } from "./doctor-types.js";
|
|
11
11
|
import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
|
|
12
|
+
import type { RoadmapSliceEntry } from "./types.js";
|
|
12
13
|
import { checkGitHealth, checkRuntimeHealth } from "./doctor-checks.js";
|
|
13
14
|
import { checkEnvironmentHealth } from "./doctor-environment.js";
|
|
14
15
|
import { runProviderChecks } from "./doctor-providers.js";
|
|
@@ -17,7 +18,7 @@ import { runProviderChecks } from "./doctor-providers.js";
|
|
|
17
18
|
// All public types and functions from extracted modules are re-exported here
|
|
18
19
|
// so that existing imports from "./doctor.js" continue to work unchanged.
|
|
19
20
|
export type { DoctorSeverity, DoctorIssueCode, DoctorIssue, DoctorReport, DoctorSummary } from "./doctor-types.js";
|
|
20
|
-
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt } from "./doctor-format.js";
|
|
21
|
+
export { summarizeDoctorIssues, filterDoctorIssues, formatDoctorReport, formatDoctorIssuesForPrompt, formatDoctorReportJson } from "./doctor-format.js";
|
|
21
22
|
export { runEnvironmentChecks, runFullEnvironmentChecks, formatEnvironmentReport, type EnvironmentCheckResult } from "./doctor-environment.js";
|
|
22
23
|
export { computeProgressScore, computeProgressScoreWithContext, formatProgressLine, formatProgressReport, type ProgressScore, type ProgressLevel } from "./progress-score.js";
|
|
23
24
|
|
|
@@ -279,9 +280,24 @@ async function markSliceDoneInRoadmap(basePath: string, milestoneId: string, sli
|
|
|
279
280
|
}
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
async function markSliceUndoneInRoadmap(basePath: string, milestoneId: string, sliceId: string, fixesApplied: string[]): Promise<void> {
|
|
284
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
285
|
+
if (!roadmapPath) return;
|
|
286
|
+
const content = await loadFile(roadmapPath);
|
|
287
|
+
if (!content) return;
|
|
288
|
+
const updated = content.replace(
|
|
289
|
+
new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"),
|
|
290
|
+
`$1[ ] **${sliceId}:`,
|
|
291
|
+
);
|
|
292
|
+
if (updated !== content) {
|
|
293
|
+
await saveFile(roadmapPath, updated);
|
|
294
|
+
fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
282
298
|
function matchesScope(unitId: string, scope?: string): boolean {
|
|
283
299
|
if (!scope) return true;
|
|
284
|
-
return unitId === scope || unitId.startsWith(`${scope}/`)
|
|
300
|
+
return unitId === scope || unitId.startsWith(`${scope}/`);
|
|
285
301
|
}
|
|
286
302
|
|
|
287
303
|
function auditRequirements(content: string | null): DoctorIssue[] {
|
|
@@ -350,10 +366,60 @@ export async function selectDoctorScope(basePath: string, requestedScope?: strin
|
|
|
350
366
|
return state.registry[0]?.id;
|
|
351
367
|
}
|
|
352
368
|
|
|
353
|
-
|
|
369
|
+
// ── Helper: circular dependency detection ──────────────────────────────────
|
|
370
|
+
function detectCircularDependencies(slices: RoadmapSliceEntry[]): string[][] {
|
|
371
|
+
const known = new Set(slices.map(s => s.id));
|
|
372
|
+
const adj = new Map<string, string[]>();
|
|
373
|
+
for (const s of slices) adj.set(s.id, s.depends.filter(d => known.has(d)));
|
|
374
|
+
const state = new Map<string, "unvisited" | "visiting" | "done">();
|
|
375
|
+
for (const s of slices) state.set(s.id, "unvisited");
|
|
376
|
+
const cycles: string[][] = [];
|
|
377
|
+
function dfs(id: string, path: string[]): void {
|
|
378
|
+
const st = state.get(id);
|
|
379
|
+
if (st === "done") return;
|
|
380
|
+
if (st === "visiting") { cycles.push([...path.slice(path.indexOf(id)), id]); return; }
|
|
381
|
+
state.set(id, "visiting");
|
|
382
|
+
for (const dep of adj.get(id) ?? []) dfs(dep, [...path, id]);
|
|
383
|
+
state.set(id, "done");
|
|
384
|
+
}
|
|
385
|
+
for (const s of slices) if (state.get(s.id) === "unvisited") dfs(s.id, []);
|
|
386
|
+
return cycles;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── Helper: doctor run history ──────────────────────────────────────────────
|
|
390
|
+
interface DoctorHistoryEntry { ts: string; ok: boolean; errors: number; warnings: number; fixes: number; codes: string[] }
|
|
391
|
+
|
|
392
|
+
async function appendDoctorHistory(basePath: string, report: DoctorReport): Promise<void> {
|
|
393
|
+
try {
|
|
394
|
+
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
395
|
+
const entry = JSON.stringify({
|
|
396
|
+
ts: new Date().toISOString(),
|
|
397
|
+
ok: report.ok,
|
|
398
|
+
errors: report.issues.filter(i => i.severity === "error").length,
|
|
399
|
+
warnings: report.issues.filter(i => i.severity === "warning").length,
|
|
400
|
+
fixes: report.fixesApplied.length,
|
|
401
|
+
codes: [...new Set(report.issues.map(i => i.code))],
|
|
402
|
+
} satisfies DoctorHistoryEntry);
|
|
403
|
+
const existing = existsSync(historyPath) ? readFileSync(historyPath, "utf-8") : "";
|
|
404
|
+
await saveFile(historyPath, existing + entry + "\n");
|
|
405
|
+
} catch { /* non-fatal */ }
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Read the last N doctor history entries. Returns most-recent-first. */
|
|
409
|
+
export async function readDoctorHistory(basePath: string, lastN = 50): Promise<DoctorHistoryEntry[]> {
|
|
410
|
+
try {
|
|
411
|
+
const historyPath = join(gsdRoot(basePath), "doctor-history.jsonl");
|
|
412
|
+
if (!existsSync(historyPath)) return [];
|
|
413
|
+
const lines = readFileSync(historyPath, "utf-8").split("\n").filter(l => l.trim());
|
|
414
|
+
return lines.slice(-lastN).reverse().map(l => JSON.parse(l) as DoctorHistoryEntry);
|
|
415
|
+
} catch { return []; }
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
export async function runGSDDoctor(basePath: string, options?: { fix?: boolean; dryRun?: boolean; scope?: string; fixLevel?: "task" | "all"; isolationMode?: "none" | "worktree" | "branch"; includeBuild?: boolean; includeTests?: boolean }): Promise<DoctorReport> {
|
|
354
419
|
const issues: DoctorIssue[] = [];
|
|
355
420
|
const fixesApplied: string[] = [];
|
|
356
421
|
const fix = options?.fix === true;
|
|
422
|
+
const dryRun = options?.dryRun === true;
|
|
357
423
|
const fixLevel = options?.fixLevel ?? "all";
|
|
358
424
|
|
|
359
425
|
// Issue codes that represent completion state transitions — creating summary
|
|
@@ -364,11 +430,18 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
364
430
|
|
|
365
431
|
/** Whether a given issue code should be auto-fixed at the current fixLevel. */
|
|
366
432
|
const shouldFix = (code: DoctorIssueCode): boolean => {
|
|
367
|
-
if (!fix) return false;
|
|
433
|
+
if (!fix || dryRun) return false;
|
|
368
434
|
if (fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code)) return false;
|
|
369
435
|
return true;
|
|
370
436
|
};
|
|
371
437
|
|
|
438
|
+
/** Log a dry-run "would fix" entry when fix=true but dryRun=true. */
|
|
439
|
+
const dryRunCanFix = (code: DoctorIssueCode, message: string): void => {
|
|
440
|
+
if (dryRun && fix && !(fixLevel === "task" && COMPLETION_TRANSITION_CODES.has(code))) {
|
|
441
|
+
fixesApplied.push(`[dry-run] would fix: ${message}`);
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
|
|
372
445
|
const prefs = loadEffectiveGSDPreferences();
|
|
373
446
|
if (prefs) {
|
|
374
447
|
const prefIssues = validatePreferenceShape(prefs.preferences);
|
|
@@ -385,21 +458,33 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
385
458
|
}
|
|
386
459
|
}
|
|
387
460
|
|
|
388
|
-
// Git health checks
|
|
461
|
+
// Git health checks — timed
|
|
462
|
+
const t0git = Date.now();
|
|
389
463
|
const isolationMode: "none" | "worktree" | "branch" = options?.isolationMode ??
|
|
390
464
|
(prefs?.preferences?.git?.isolation === "none" ? "none" :
|
|
391
465
|
prefs?.preferences?.git?.isolation === "branch" ? "branch" : "worktree");
|
|
392
466
|
await checkGitHealth(basePath, issues, fixesApplied, shouldFix, isolationMode);
|
|
467
|
+
const gitMs = Date.now() - t0git;
|
|
393
468
|
|
|
394
|
-
// Runtime health checks
|
|
469
|
+
// Runtime health checks — timed
|
|
470
|
+
const t0runtime = Date.now();
|
|
395
471
|
await checkRuntimeHealth(basePath, issues, fixesApplied, shouldFix);
|
|
472
|
+
const runtimeMs = Date.now() - t0runtime;
|
|
396
473
|
|
|
397
|
-
// Environment health checks
|
|
398
|
-
|
|
474
|
+
// Environment health checks — timed
|
|
475
|
+
const t0env = Date.now();
|
|
476
|
+
await checkEnvironmentHealth(basePath, issues, {
|
|
477
|
+
includeRemote: !options?.scope,
|
|
478
|
+
includeBuild: options?.includeBuild,
|
|
479
|
+
includeTests: options?.includeTests,
|
|
480
|
+
});
|
|
481
|
+
const envMs = Date.now() - t0env;
|
|
399
482
|
|
|
400
483
|
const milestonesPath = milestonesDir(basePath);
|
|
401
484
|
if (!existsSync(milestonesPath)) {
|
|
402
|
-
|
|
485
|
+
const report: DoctorReport = { ok: issues.every(i => i.severity !== "error"), basePath, issues, fixesApplied, timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: 0 } };
|
|
486
|
+
await appendDoctorHistory(basePath, report);
|
|
487
|
+
return report;
|
|
403
488
|
}
|
|
404
489
|
|
|
405
490
|
const requirementsPath = resolveGsdRootFile(basePath, "REQUIREMENTS");
|
|
@@ -465,6 +550,43 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
465
550
|
if (!roadmapContent) continue;
|
|
466
551
|
const roadmap = parseRoadmap(roadmapContent);
|
|
467
552
|
|
|
553
|
+
// ── Circular dependency detection ──────────────────────────────────────
|
|
554
|
+
for (const cycle of detectCircularDependencies(roadmap.slices)) {
|
|
555
|
+
issues.push({
|
|
556
|
+
severity: "error",
|
|
557
|
+
code: "circular_slice_dependency",
|
|
558
|
+
scope: "milestone",
|
|
559
|
+
unitId: milestoneId,
|
|
560
|
+
message: `Circular dependency detected: ${cycle.join(" → ")}`,
|
|
561
|
+
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
562
|
+
fixable: false,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// ── Orphaned slice directories ─────────────────────────────────────────
|
|
567
|
+
try {
|
|
568
|
+
const slicesDir = join(milestonePath, "slices");
|
|
569
|
+
if (existsSync(slicesDir)) {
|
|
570
|
+
const knownSliceIds = new Set(roadmap.slices.map(s => s.id));
|
|
571
|
+
for (const entry of readdirSync(slicesDir)) {
|
|
572
|
+
try {
|
|
573
|
+
if (!lstatSync(join(slicesDir, entry)).isDirectory()) continue;
|
|
574
|
+
} catch { continue; }
|
|
575
|
+
if (!knownSliceIds.has(entry)) {
|
|
576
|
+
issues.push({
|
|
577
|
+
severity: "warning",
|
|
578
|
+
code: "orphaned_slice_directory",
|
|
579
|
+
scope: "milestone",
|
|
580
|
+
unitId: milestoneId,
|
|
581
|
+
message: `Directory "${entry}" exists in ${milestoneId}/slices/ but is not referenced in the roadmap`,
|
|
582
|
+
file: `${relMilestonePath(basePath, milestoneId)}/slices/${entry}`,
|
|
583
|
+
fixable: false,
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
} catch { /* non-fatal */ }
|
|
589
|
+
|
|
468
590
|
for (const slice of roadmap.slices) {
|
|
469
591
|
const unitId = `${milestoneId}/${slice.id}`;
|
|
470
592
|
if (options?.scope && !matchesScope(unitId, options.scope) && options.scope !== milestoneId) continue;
|
|
@@ -539,6 +661,33 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
539
661
|
continue;
|
|
540
662
|
}
|
|
541
663
|
|
|
664
|
+
// ── Duplicate task IDs ───────────────────────────────────────────────
|
|
665
|
+
const taskIdCounts = new Map<string, number>();
|
|
666
|
+
for (const task of plan.tasks) taskIdCounts.set(task.id, (taskIdCounts.get(task.id) ?? 0) + 1);
|
|
667
|
+
for (const [taskId, count] of taskIdCounts) {
|
|
668
|
+
if (count > 1) {
|
|
669
|
+
issues.push({ severity: "error", code: "duplicate_task_id", scope: "slice", unitId,
|
|
670
|
+
message: `Task ID "${taskId}" appears ${count} times in ${slice.id}-PLAN.md — duplicate IDs cause dispatch failures`,
|
|
671
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "PLAN"), fixable: false });
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ── Task files on disk not in plan ────────────────────────────────────
|
|
676
|
+
try {
|
|
677
|
+
if (tasksDir) {
|
|
678
|
+
const planTaskIds = new Set(plan.tasks.map(t => t.id));
|
|
679
|
+
for (const f of readdirSync(tasksDir)) {
|
|
680
|
+
if (!f.endsWith("-SUMMARY.md")) continue;
|
|
681
|
+
const diskTaskId = f.replace(/-SUMMARY\.md$/, "");
|
|
682
|
+
if (!planTaskIds.has(diskTaskId)) {
|
|
683
|
+
issues.push({ severity: "info", code: "task_file_not_in_plan", scope: "slice", unitId,
|
|
684
|
+
message: `Task summary "${f}" exists on disk but "${diskTaskId}" is not in ${slice.id}-PLAN.md`,
|
|
685
|
+
file: relTaskFile(basePath, milestoneId, slice.id, diskTaskId, "SUMMARY"), fixable: false });
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
} catch { /* non-fatal */ }
|
|
690
|
+
|
|
542
691
|
let allTasksDone = plan.tasks.length > 0;
|
|
543
692
|
for (const task of plan.tasks) {
|
|
544
693
|
const taskUnitId = `${unitId}/${task.id}`;
|
|
@@ -555,6 +704,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
555
704
|
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"),
|
|
556
705
|
fixable: true,
|
|
557
706
|
});
|
|
707
|
+
dryRunCanFix("task_done_missing_summary", `create stub summary for ${taskUnitId}`);
|
|
558
708
|
if (shouldFix("task_done_missing_summary")) {
|
|
559
709
|
const stubPath = join(
|
|
560
710
|
basePath, ".gsd", "milestones", milestoneId, "slices", slice.id, "tasks",
|
|
@@ -618,6 +768,22 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
618
768
|
}
|
|
619
769
|
}
|
|
620
770
|
|
|
771
|
+
// ── Future timestamp check ─────────────────────────────────────
|
|
772
|
+
if (task.done && hasSummary && summaryPath) {
|
|
773
|
+
try {
|
|
774
|
+
const rawSummary = await loadFile(summaryPath);
|
|
775
|
+
const m = rawSummary?.match(/^completed_at:\s*(.+)$/m);
|
|
776
|
+
if (m) {
|
|
777
|
+
const ts = new Date(m[1].trim());
|
|
778
|
+
if (!isNaN(ts.getTime()) && ts.getTime() > Date.now() + 24 * 60 * 60 * 1000) {
|
|
779
|
+
issues.push({ severity: "warning", code: "future_timestamp", scope: "task", unitId: taskUnitId,
|
|
780
|
+
message: `Task ${task.id} has completed_at "${m[1].trim()}" which is more than 24h in the future`,
|
|
781
|
+
file: relTaskFile(basePath, milestoneId, slice.id, task.id, "SUMMARY"), fixable: false });
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
} catch { /* non-fatal */ }
|
|
785
|
+
}
|
|
786
|
+
|
|
621
787
|
allTasksDone = allTasksDone && task.done;
|
|
622
788
|
}
|
|
623
789
|
|
|
@@ -646,6 +812,13 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
646
812
|
}
|
|
647
813
|
}
|
|
648
814
|
|
|
815
|
+
// ── Stale REPLAN: exists but all tasks done ────────────────────────
|
|
816
|
+
if (replanPath && allTasksDone) {
|
|
817
|
+
issues.push({ severity: "info", code: "stale_replan_file", scope: "slice", unitId,
|
|
818
|
+
message: `${slice.id} has a REPLAN.md but all tasks are done — REPLAN.md may be stale`,
|
|
819
|
+
file: relSliceFile(basePath, milestoneId, slice.id, "REPLAN"), fixable: false });
|
|
820
|
+
}
|
|
821
|
+
|
|
649
822
|
const sliceSummaryPath = resolveSliceFile(basePath, milestoneId, slice.id, "SUMMARY");
|
|
650
823
|
const sliceUatPath = join(slicePath, `${slice.id}-UAT.md`);
|
|
651
824
|
const hasSliceSummary = !!(sliceSummaryPath && await loadFile(sliceSummaryPath));
|
|
@@ -661,6 +834,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
661
834
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
662
835
|
fixable: true,
|
|
663
836
|
});
|
|
837
|
+
dryRunCanFix("all_tasks_done_missing_slice_summary", `create placeholder summary for ${unitId}`);
|
|
664
838
|
if (shouldFix("all_tasks_done_missing_slice_summary")) await ensureSliceSummaryStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
665
839
|
}
|
|
666
840
|
|
|
@@ -674,6 +848,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
674
848
|
file: `${relSlicePath(basePath, milestoneId, slice.id)}/${slice.id}-UAT.md`,
|
|
675
849
|
fixable: true,
|
|
676
850
|
});
|
|
851
|
+
dryRunCanFix("all_tasks_done_missing_slice_uat", `create placeholder UAT for ${unitId}`);
|
|
677
852
|
if (shouldFix("all_tasks_done_missing_slice_uat")) await ensureSliceUatStub(basePath, milestoneId, slice.id, fixesApplied);
|
|
678
853
|
}
|
|
679
854
|
|
|
@@ -687,6 +862,7 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
687
862
|
file: relMilestoneFile(basePath, milestoneId, "ROADMAP"),
|
|
688
863
|
fixable: true,
|
|
689
864
|
});
|
|
865
|
+
dryRunCanFix("all_tasks_done_roadmap_not_checked", `mark ${slice.id} done in roadmap`);
|
|
690
866
|
if (shouldFix("all_tasks_done_roadmap_not_checked") && (hasSliceSummary || issues.some(issue => issue.code === "all_tasks_done_missing_slice_summary" && issue.unitId === unitId))) {
|
|
691
867
|
await markSliceDoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
692
868
|
}
|
|
@@ -702,6 +878,12 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
702
878
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
703
879
|
fixable: true,
|
|
704
880
|
});
|
|
881
|
+
if (!allTasksDone) {
|
|
882
|
+
dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
|
|
883
|
+
if (shouldFix("slice_checked_missing_summary")) {
|
|
884
|
+
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
705
887
|
}
|
|
706
888
|
|
|
707
889
|
if (slice.done && !hasSliceUat) {
|
|
@@ -744,14 +926,17 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
744
926
|
}
|
|
745
927
|
}
|
|
746
928
|
|
|
747
|
-
if (fix && fixesApplied.length > 0) {
|
|
929
|
+
if (fix && !dryRun && fixesApplied.length > 0) {
|
|
748
930
|
await updateStateFile(basePath, fixesApplied);
|
|
749
931
|
}
|
|
750
932
|
|
|
751
|
-
|
|
933
|
+
const report: DoctorReport = {
|
|
752
934
|
ok: issues.every(issue => issue.severity !== "error"),
|
|
753
935
|
basePath,
|
|
754
936
|
issues,
|
|
755
937
|
fixesApplied,
|
|
938
|
+
timing: { git: gitMs, runtime: runtimeMs, environment: envMs, gsdState: Math.max(0, Date.now() - t0env - envMs) },
|
|
756
939
|
};
|
|
940
|
+
await appendDoctorHistory(basePath, report);
|
|
941
|
+
return report;
|
|
757
942
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { importExtensionModule, type ExtensionAPI, type ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
type StopAutoFn = (ctx: ExtensionCommandContext, pi: ExtensionAPI, reason?: string) => Promise<void>;
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ export function registerExitCommand(
|
|
|
10
10
|
description: "Exit GSD gracefully",
|
|
11
11
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
12
12
|
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
13
|
-
const stopAuto = deps.stopAuto ?? (await import("./auto.js")).stopAuto;
|
|
13
|
+
const stopAuto = deps.stopAuto ?? (await importExtensionModule<typeof import("./auto.js")>(import.meta.url, "./auto.js")).stopAuto;
|
|
14
14
|
await stopAuto(ctx, pi, "Graceful exit");
|
|
15
15
|
ctx.shutdown();
|
|
16
16
|
},
|
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
} from "./metrics.js";
|
|
12
12
|
import type { UnitMetrics } from "./metrics.js";
|
|
13
13
|
import { gsdRoot } from "./paths.js";
|
|
14
|
-
import { formatDuration, fileLink } from "../shared/
|
|
14
|
+
import { formatDuration, fileLink } from "../shared/format-utils.js";
|
|
15
15
|
import { getErrorMessage } from "./error-utils.js";
|
|
16
16
|
|
|
17
17
|
/**
|
|
@@ -7,7 +7,7 @@ import { promises as fs } from 'node:fs';
|
|
|
7
7
|
import { resolve } from 'node:path';
|
|
8
8
|
import { atomicWriteAsync } from './atomic-write.js';
|
|
9
9
|
import { resolveMilestoneFile, relMilestoneFile, resolveGsdRootFile } from './paths.js';
|
|
10
|
-
import { milestoneIdSort, findMilestoneIds } from './
|
|
10
|
+
import { milestoneIdSort, findMilestoneIds } from './milestone-ids.js';
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
Roadmap, BoundaryMapEntry,
|
|
@@ -15,11 +15,12 @@ import type {
|
|
|
15
15
|
Summary, SummaryFrontmatter, SummaryRequires, FileModified,
|
|
16
16
|
Continue, ContinueFrontmatter, ContinueStatus,
|
|
17
17
|
RequirementCounts,
|
|
18
|
+
TaskIO,
|
|
18
19
|
SecretsManifest, SecretsManifestEntry, SecretsManifestEntryStatus,
|
|
19
20
|
ManifestStatus,
|
|
20
21
|
} from './types.js';
|
|
21
22
|
|
|
22
|
-
import { checkExistingEnvKeys } from '../
|
|
23
|
+
import { checkExistingEnvKeys } from '../env-utils.js';
|
|
23
24
|
import { parseRoadmapSlices } from './roadmap-slices.js';
|
|
24
25
|
import { nativeParseRoadmap, nativeExtractSection, nativeParsePlanFile, nativeParseSummaryFile, NATIVE_UNAVAILABLE } from './native-parser-bridge.js';
|
|
25
26
|
import { debugTime, debugCount } from './debug-logger.js';
|
|
@@ -724,13 +725,57 @@ export function countMustHavesMentionedInSummary(
|
|
|
724
725
|
return count;
|
|
725
726
|
}
|
|
726
727
|
|
|
728
|
+
// ─── Task Plan IO Extractor ────────────────────────────────────────────────
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Extract input and output file paths from a task plan's `## Inputs` and
|
|
732
|
+
* `## Expected Output` sections. Looks for backtick-wrapped file paths on
|
|
733
|
+
* each line (e.g. `` `src/foo.ts` ``).
|
|
734
|
+
*
|
|
735
|
+
* Returns empty arrays for missing/empty sections — callers should treat
|
|
736
|
+
* tasks with no IO as ambiguous (sequential fallback trigger).
|
|
737
|
+
*/
|
|
738
|
+
export function parseTaskPlanIO(content: string): { inputFiles: string[]; outputFiles: string[] } {
|
|
739
|
+
const backtickPathRegex = /`([^`]+)`/g;
|
|
740
|
+
|
|
741
|
+
function extractPaths(sectionText: string | null): string[] {
|
|
742
|
+
if (!sectionText) return [];
|
|
743
|
+
const paths: string[] = [];
|
|
744
|
+
for (const line of sectionText.split("\n")) {
|
|
745
|
+
const trimmed = line.trim();
|
|
746
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
747
|
+
let match: RegExpExecArray | null;
|
|
748
|
+
backtickPathRegex.lastIndex = 0;
|
|
749
|
+
while ((match = backtickPathRegex.exec(trimmed)) !== null) {
|
|
750
|
+
const candidate = match[1];
|
|
751
|
+
// Filter out things that look like code tokens rather than file paths
|
|
752
|
+
// (e.g. `true`, `false`, `npm run test`). A file path has at least one
|
|
753
|
+
// dot or slash.
|
|
754
|
+
if (candidate.includes("/") || candidate.includes(".")) {
|
|
755
|
+
paths.push(candidate);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return paths;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
const [, body] = splitFrontmatter(content);
|
|
763
|
+
const inputSection = extractSection(body, "Inputs");
|
|
764
|
+
const outputSection = extractSection(body, "Expected Output");
|
|
765
|
+
|
|
766
|
+
return {
|
|
767
|
+
inputFiles: extractPaths(inputSection),
|
|
768
|
+
outputFiles: extractPaths(outputSection),
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
|
|
727
772
|
// ─── UAT Type Extractor ────────────────────────────────────────────────────
|
|
728
773
|
|
|
729
774
|
/**
|
|
730
775
|
* The four UAT classification types recognised by GSD auto-mode.
|
|
731
776
|
* `undefined` is returned (not this union) when no type can be determined.
|
|
732
777
|
*/
|
|
733
|
-
export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed';
|
|
778
|
+
export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed' | 'browser-executable' | 'runtime-executable';
|
|
734
779
|
|
|
735
780
|
/**
|
|
736
781
|
* Extract the UAT type from a UAT file's raw content.
|
|
@@ -754,6 +799,8 @@ export function extractUatType(content: string): UatType | undefined {
|
|
|
754
799
|
const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
|
|
755
800
|
|
|
756
801
|
if (rawValue.startsWith('artifact-driven')) return 'artifact-driven';
|
|
802
|
+
if (rawValue.startsWith('browser-executable')) return 'browser-executable';
|
|
803
|
+
if (rawValue.startsWith('runtime-executable')) return 'runtime-executable';
|
|
757
804
|
if (rawValue.startsWith('live-runtime')) return 'live-runtime';
|
|
758
805
|
if (rawValue.startsWith('human-experience')) return 'human-experience';
|
|
759
806
|
if (rawValue.startsWith('mixed')) return 'mixed';
|
|
@@ -27,7 +27,7 @@ import { deriveState } from "./state.js";
|
|
|
27
27
|
import { isAutoActive } from "./auto.js";
|
|
28
28
|
import { loadPrompt } from "./prompt-loader.js";
|
|
29
29
|
import { gsdRoot } from "./paths.js";
|
|
30
|
-
import { formatDuration } from "../shared/
|
|
30
|
+
import { formatDuration } from "../shared/format-utils.js";
|
|
31
31
|
import { getAutoWorktreePath } from "./auto-worktree.js";
|
|
32
32
|
|
|
33
33
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
nativeDetectMainBranch,
|
|
25
25
|
nativeBranchExists,
|
|
26
26
|
nativeHasChanges,
|
|
27
|
-
|
|
27
|
+
nativeAddAllWithExclusions,
|
|
28
28
|
nativeResetPaths,
|
|
29
29
|
nativeHasStagedChanges,
|
|
30
30
|
nativeCommit,
|
|
@@ -95,6 +95,8 @@ export interface TaskCommitContext {
|
|
|
95
95
|
oneLiner?: string;
|
|
96
96
|
/** Files modified by this task (from task summary frontmatter) */
|
|
97
97
|
keyFiles?: string[];
|
|
98
|
+
/** GitHub issue number — appends "Resolves #N" trailer when set. */
|
|
99
|
+
issueNumber?: number;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
/**
|
|
@@ -118,12 +120,22 @@ export function buildTaskCommitMessage(ctx: TaskCommitContext): string {
|
|
|
118
120
|
const subject = `${type}(${scope}): ${truncated}`;
|
|
119
121
|
|
|
120
122
|
// Build body with key files if available
|
|
123
|
+
const bodyParts: string[] = [];
|
|
124
|
+
|
|
121
125
|
if (ctx.keyFiles && ctx.keyFiles.length > 0) {
|
|
122
126
|
const fileLines = ctx.keyFiles
|
|
123
127
|
.slice(0, 8) // cap at 8 files to keep commit concise
|
|
124
128
|
.map(f => `- ${f}`)
|
|
125
129
|
.join("\n");
|
|
126
|
-
|
|
130
|
+
bodyParts.push(fileLines);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (ctx.issueNumber) {
|
|
134
|
+
bodyParts.push(`Resolves #${ctx.issueNumber}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (bodyParts.length > 0) {
|
|
138
|
+
return `${subject}\n\n${bodyParts.join("\n\n")}`;
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
return subject;
|
|
@@ -373,7 +385,9 @@ export class GitServiceImpl {
|
|
|
373
385
|
this._runtimeFilesCleanedUp = true;
|
|
374
386
|
}
|
|
375
387
|
|
|
376
|
-
// Stage everything
|
|
388
|
+
// Stage everything using pathspec exclusions so excluded paths are never
|
|
389
|
+
// hashed by git. The old approach of `git add -A` followed by unstaging
|
|
390
|
+
// hangs indefinitely on repos with large untracked artifact trees (#1605).
|
|
377
391
|
//
|
|
378
392
|
// Exclude only RUNTIME paths from staging — not the entire .gsd/ directory.
|
|
379
393
|
// When .gsd/milestones/ files are already tracked in the index (projects
|
|
@@ -383,13 +397,9 @@ export class GitServiceImpl {
|
|
|
383
397
|
// the second half of a milestone's artifacts are never committed (#1326).
|
|
384
398
|
//
|
|
385
399
|
// If .gsd/ IS in .gitignore (the default for external state projects),
|
|
386
|
-
// git add -A already skips it and the
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const runtimeExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
390
|
-
for (const exclusion of runtimeExclusions) {
|
|
391
|
-
try { nativeResetPaths(this.basePath, [exclusion]); } catch { /* path not staged — ignore */ }
|
|
392
|
-
}
|
|
400
|
+
// git add -A already skips it and the exclusions are harmless no-ops.
|
|
401
|
+
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
402
|
+
nativeAddAllWithExclusions(this.basePath, allExclusions);
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
/** Tracks whether runtime file cleanup has run this session. */
|