gsd-pi 0.1.0

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.
Files changed (128) hide show
  1. package/README.md +341 -0
  2. package/dist/app-paths.d.ts +4 -0
  3. package/dist/app-paths.js +6 -0
  4. package/dist/cli.d.ts +1 -0
  5. package/dist/cli.js +35 -0
  6. package/dist/loader.d.ts +2 -0
  7. package/dist/loader.js +69 -0
  8. package/dist/modes/interactive/theme/dark.json +85 -0
  9. package/dist/modes/interactive/theme/light.json +84 -0
  10. package/dist/modes/interactive/theme/theme-schema.json +335 -0
  11. package/dist/modes/interactive/theme/theme.d.ts +78 -0
  12. package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  13. package/dist/modes/interactive/theme/theme.js +949 -0
  14. package/dist/modes/interactive/theme/theme.js.map +1 -0
  15. package/dist/resource-loader.d.ts +22 -0
  16. package/dist/resource-loader.js +48 -0
  17. package/dist/wizard.d.ts +20 -0
  18. package/dist/wizard.js +132 -0
  19. package/package.json +39 -0
  20. package/pkg/dist/modes/interactive/theme/dark.json +85 -0
  21. package/pkg/dist/modes/interactive/theme/light.json +84 -0
  22. package/pkg/dist/modes/interactive/theme/theme-schema.json +335 -0
  23. package/pkg/dist/modes/interactive/theme/theme.d.ts +78 -0
  24. package/pkg/dist/modes/interactive/theme/theme.d.ts.map +1 -0
  25. package/pkg/dist/modes/interactive/theme/theme.js +949 -0
  26. package/pkg/dist/modes/interactive/theme/theme.js.map +1 -0
  27. package/pkg/package.json +8 -0
  28. package/scripts/postinstall.js +10 -0
  29. package/src/resources/AGENTS.md +204 -0
  30. package/src/resources/GSD-WORKFLOW.md +661 -0
  31. package/src/resources/agents/researcher.md +29 -0
  32. package/src/resources/agents/scout.md +56 -0
  33. package/src/resources/agents/worker.md +31 -0
  34. package/src/resources/extensions/ask-user-questions.ts +200 -0
  35. package/src/resources/extensions/bg-shell/index.ts +2554 -0
  36. package/src/resources/extensions/browser-tools/BROWSER-TOOLS-V2-PROPOSAL.md +1277 -0
  37. package/src/resources/extensions/browser-tools/core.js +1057 -0
  38. package/src/resources/extensions/browser-tools/index.ts +4916 -0
  39. package/src/resources/extensions/browser-tools/package.json +20 -0
  40. package/src/resources/extensions/context7/index.ts +428 -0
  41. package/src/resources/extensions/context7/package.json +11 -0
  42. package/src/resources/extensions/get-secrets-from-user.ts +352 -0
  43. package/src/resources/extensions/gsd/activity-log.ts +48 -0
  44. package/src/resources/extensions/gsd/auto.ts +2032 -0
  45. package/src/resources/extensions/gsd/commands.ts +292 -0
  46. package/src/resources/extensions/gsd/crash-recovery.ts +85 -0
  47. package/src/resources/extensions/gsd/dashboard-overlay.ts +516 -0
  48. package/src/resources/extensions/gsd/docs/preferences-reference.md +103 -0
  49. package/src/resources/extensions/gsd/doctor.ts +683 -0
  50. package/src/resources/extensions/gsd/files.ts +730 -0
  51. package/src/resources/extensions/gsd/gitignore.ts +104 -0
  52. package/src/resources/extensions/gsd/guided-flow.ts +800 -0
  53. package/src/resources/extensions/gsd/index.ts +418 -0
  54. package/src/resources/extensions/gsd/metrics.ts +372 -0
  55. package/src/resources/extensions/gsd/observability-validator.ts +408 -0
  56. package/src/resources/extensions/gsd/package.json +11 -0
  57. package/src/resources/extensions/gsd/paths.ts +308 -0
  58. package/src/resources/extensions/gsd/preferences.ts +600 -0
  59. package/src/resources/extensions/gsd/prompt-loader.ts +50 -0
  60. package/src/resources/extensions/gsd/prompts/complete-milestone.md +25 -0
  61. package/src/resources/extensions/gsd/prompts/complete-slice.md +27 -0
  62. package/src/resources/extensions/gsd/prompts/discuss.md +151 -0
  63. package/src/resources/extensions/gsd/prompts/doctor-heal.md +29 -0
  64. package/src/resources/extensions/gsd/prompts/execute-task.md +64 -0
  65. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -0
  66. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +3 -0
  67. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +59 -0
  68. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -0
  69. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +23 -0
  70. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -0
  71. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +11 -0
  72. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -0
  73. package/src/resources/extensions/gsd/prompts/plan-milestone.md +47 -0
  74. package/src/resources/extensions/gsd/prompts/plan-slice.md +63 -0
  75. package/src/resources/extensions/gsd/prompts/queue.md +85 -0
  76. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +48 -0
  77. package/src/resources/extensions/gsd/prompts/replan-slice.md +39 -0
  78. package/src/resources/extensions/gsd/prompts/research-milestone.md +37 -0
  79. package/src/resources/extensions/gsd/prompts/research-slice.md +28 -0
  80. package/src/resources/extensions/gsd/prompts/run-uat.md +109 -0
  81. package/src/resources/extensions/gsd/prompts/system.md +220 -0
  82. package/src/resources/extensions/gsd/session-forensics.ts +487 -0
  83. package/src/resources/extensions/gsd/skill-discovery.ts +137 -0
  84. package/src/resources/extensions/gsd/state.ts +439 -0
  85. package/src/resources/extensions/gsd/templates/context.md +76 -0
  86. package/src/resources/extensions/gsd/templates/decisions.md +8 -0
  87. package/src/resources/extensions/gsd/templates/milestone-summary.md +73 -0
  88. package/src/resources/extensions/gsd/templates/plan.md +133 -0
  89. package/src/resources/extensions/gsd/templates/preferences.md +15 -0
  90. package/src/resources/extensions/gsd/templates/project.md +31 -0
  91. package/src/resources/extensions/gsd/templates/reassessment.md +28 -0
  92. package/src/resources/extensions/gsd/templates/requirements.md +81 -0
  93. package/src/resources/extensions/gsd/templates/research.md +46 -0
  94. package/src/resources/extensions/gsd/templates/roadmap.md +118 -0
  95. package/src/resources/extensions/gsd/templates/slice-context.md +58 -0
  96. package/src/resources/extensions/gsd/templates/slice-summary.md +99 -0
  97. package/src/resources/extensions/gsd/templates/state.md +19 -0
  98. package/src/resources/extensions/gsd/templates/task-plan.md +52 -0
  99. package/src/resources/extensions/gsd/templates/task-summary.md +57 -0
  100. package/src/resources/extensions/gsd/templates/uat.md +54 -0
  101. package/src/resources/extensions/gsd/types.ts +159 -0
  102. package/src/resources/extensions/gsd/unit-runtime.ts +162 -0
  103. package/src/resources/extensions/gsd/workspace-index.ts +203 -0
  104. package/src/resources/extensions/gsd/worktree.ts +182 -0
  105. package/src/resources/extensions/plan-mode/README.md +65 -0
  106. package/src/resources/extensions/plan-mode/index.ts +521 -0
  107. package/src/resources/extensions/plan-mode/utils.ts +168 -0
  108. package/src/resources/extensions/search-the-web/cache.ts +70 -0
  109. package/src/resources/extensions/search-the-web/format.ts +134 -0
  110. package/src/resources/extensions/search-the-web/http.ts +147 -0
  111. package/src/resources/extensions/search-the-web/index.ts +46 -0
  112. package/src/resources/extensions/search-the-web/tool-fetch-page.ts +374 -0
  113. package/src/resources/extensions/search-the-web/tool-search.ts +424 -0
  114. package/src/resources/extensions/search-the-web/url-utils.ts +91 -0
  115. package/src/resources/extensions/shared/interview-ui.ts +613 -0
  116. package/src/resources/extensions/shared/next-action-ui.ts +197 -0
  117. package/src/resources/extensions/shared/progress-widget.ts +282 -0
  118. package/src/resources/extensions/shared/thinking-widget.ts +107 -0
  119. package/src/resources/extensions/shared/ui.ts +400 -0
  120. package/src/resources/extensions/shared/wizard-ui.ts +551 -0
  121. package/src/resources/extensions/slash-commands/audit.ts +88 -0
  122. package/src/resources/extensions/slash-commands/create-extension.ts +297 -0
  123. package/src/resources/extensions/slash-commands/create-slash-command.ts +234 -0
  124. package/src/resources/extensions/slash-commands/gsd-run.ts +34 -0
  125. package/src/resources/extensions/slash-commands/index.ts +12 -0
  126. package/src/resources/extensions/subagent/agents.ts +126 -0
  127. package/src/resources/extensions/subagent/index.ts +1021 -0
  128. package/src/resources/extensions/worktree/index.ts +420 -0
@@ -0,0 +1,57 @@
1
+ ---
2
+ id: {{taskId}}
3
+ parent: {{sliceId}}
4
+ milestone: {{milestoneId}}
5
+ provides:
6
+ - {{whatThisTaskProvides}}
7
+ key_files:
8
+ - {{filePath}}
9
+ key_decisions:
10
+ - {{decision}}
11
+ patterns_established:
12
+ - {{pattern}}
13
+ observability_surfaces:
14
+ - {{status endpoint, structured log, persisted failure state, diagnostic command, or none}}
15
+ duration: {{duration}}
16
+ verification_result: passed
17
+ completed_at: {{date}}
18
+ # Set blocker_discovered: true only if execution revealed the remaining slice plan
19
+ # is fundamentally invalid (wrong API, missing capability, architectural mismatch).
20
+ # Do NOT set true for ordinary bugs, minor deviations, or fixable issues.
21
+ blocker_discovered: false
22
+ ---
23
+
24
+ # {{taskId}}: {{taskTitle}}
25
+
26
+ <!-- One-liner must say what actually shipped, not just that work completed.
27
+ Good: "Added retry-aware worker status logging"
28
+ Bad: "Implemented logging improvements" -->
29
+
30
+ **{{oneLiner}}**
31
+
32
+ ## What Happened
33
+
34
+ {{narrative}}
35
+
36
+ ## Verification
37
+
38
+ {{whatWasVerifiedAndHow — commands run, tests passed, behavior confirmed}}
39
+
40
+ ## Diagnostics
41
+
42
+ {{howToInspectWhatThisTaskBuiltLater — status surfaces, logs, error shapes, failure artifacts, or none}}
43
+
44
+ ## Deviations
45
+
46
+ <!-- Deviations are unplanned changes to the written task plan, not ordinary debugging during implementation. -->
47
+
48
+ {{deviationsFromPlan_OR_none}}
49
+
50
+ ## Known Issues
51
+
52
+ {{issuesDiscoveredButNotFixed_OR_none}}
53
+
54
+ ## Files Created/Modified
55
+
56
+ - `{{filePath}}` — {{description}}
57
+ - `{{filePath}}` — {{description}}
@@ -0,0 +1,54 @@
1
+ # {{sliceId}}: {{sliceTitle}} — UAT
2
+
3
+ **Milestone:** {{milestoneId}}
4
+ **Written:** {{date}}
5
+
6
+ ## UAT Type
7
+
8
+ - UAT mode: {{artifact-driven | live-runtime | human-experience | mixed}}
9
+ - Why this mode is sufficient: {{reason}}
10
+
11
+ ## Preconditions
12
+
13
+ {{whatMustBeTrueBeforeTesting — server running, data seeded, etc.}}
14
+
15
+ ## Smoke Test
16
+
17
+ {{oneQuickCheckThatConfirmsTheSliceBasicallyWorks}}
18
+
19
+ ## Test Cases
20
+
21
+ ### 1. {{testName}}
22
+
23
+ 1. {{step}}
24
+ 2. {{step}}
25
+ 3. **Expected:** {{expected}}
26
+
27
+ ### 2. {{testName}}
28
+
29
+ 1. {{step}}
30
+ 2. **Expected:** {{expected}}
31
+
32
+ ## Edge Cases
33
+
34
+ ### {{edgeCaseName}}
35
+
36
+ 1. {{step}}
37
+ 2. **Expected:** {{expected}}
38
+
39
+ ## Failure Signals
40
+
41
+ - {{whatWouldIndicateSomethingIsBroken — errors, missing UI, wrong data}}
42
+
43
+ ## Requirements Proved By This UAT
44
+
45
+ - {{requirementIdOr_none}} — {{what this UAT proves}}
46
+
47
+ ## Not Proven By This UAT
48
+
49
+ - {{what this UAT intentionally does not prove}}
50
+ - {{remaining live/runtime/operational gaps, if any}}
51
+
52
+ ## Notes for Tester
53
+
54
+ {{anythingTheHumanShouldKnow — known rough edges, things to ignore, areas needing gut check}}
@@ -0,0 +1,159 @@
1
+ // GSD Extension — Core Type Definitions
2
+ // Types consumed by state derivation, file parsing, and status display.
3
+ // Pure interfaces — no logic, no runtime dependencies.
4
+
5
+ // ─── Enums & Literal Unions ────────────────────────────────────────────────
6
+
7
+ export type RiskLevel = 'low' | 'medium' | 'high';
8
+ export type Phase = 'pre-planning' | 'discussing' | 'researching' | 'planning' | 'executing' | 'verifying' | 'summarizing' | 'advancing' | 'completing-milestone' | 'replanning-slice' | 'complete' | 'paused' | 'blocked';
9
+ export type ContinueStatus = 'in_progress' | 'interrupted' | 'compacted';
10
+
11
+ // ─── Roadmap (Milestone-level) ─────────────────────────────────────────────
12
+
13
+ export interface RoadmapSliceEntry {
14
+ id: string; // e.g. "S01"
15
+ title: string; // e.g. "Types + File I/O + Git Operations"
16
+ risk: RiskLevel;
17
+ depends: string[]; // e.g. ["S01", "S02"]
18
+ done: boolean;
19
+ demo: string; // the "After this:" sentence
20
+ }
21
+
22
+ export interface BoundaryMapEntry {
23
+ fromSlice: string; // e.g. "S01"
24
+ toSlice: string; // e.g. "S02" or "terminal"
25
+ produces: string; // raw text block of what this slice produces
26
+ consumes: string; // raw text block of what it consumes (or "nothing")
27
+ }
28
+
29
+ export interface Roadmap {
30
+ title: string; // e.g. "M001: GSD Extension — Hierarchical Planning with Auto Mode"
31
+ vision: string;
32
+ successCriteria: string[];
33
+ slices: RoadmapSliceEntry[];
34
+ boundaryMap: BoundaryMapEntry[];
35
+ }
36
+
37
+ // ─── Slice Plan ────────────────────────────────────────────────────────────
38
+
39
+ export interface TaskPlanEntry {
40
+ id: string; // e.g. "T01"
41
+ title: string; // e.g. "Core Type Definitions"
42
+ description: string;
43
+ done: boolean;
44
+ estimate: string; // e.g. "30m", "2h" — informational only
45
+ files?: string[]; // e.g. ["types.ts", "files.ts"] — extracted from "- Files:" subline
46
+ verify?: string; // e.g. "run tests" — extracted from "- Verify:" subline
47
+ }
48
+
49
+ export interface SlicePlan {
50
+ id: string; // e.g. "S01"
51
+ title: string; // from the H1
52
+ goal: string;
53
+ demo: string;
54
+ mustHaves: string[]; // top-level must-have bullet points
55
+ tasks: TaskPlanEntry[];
56
+ filesLikelyTouched: string[];
57
+ }
58
+
59
+ // ─── Summary (Task & Slice level) ──────────────────────────────────────────
60
+
61
+ export interface SummaryRequires {
62
+ slice: string;
63
+ provides: string;
64
+ }
65
+
66
+ export interface SummaryFrontmatter {
67
+ id: string;
68
+ parent: string;
69
+ milestone: string;
70
+ provides: string[];
71
+ requires: SummaryRequires[];
72
+ affects: string[];
73
+ key_files: string[];
74
+ key_decisions: string[];
75
+ patterns_established: string[];
76
+ drill_down_paths: string[];
77
+ observability_surfaces: string[];
78
+ duration: string;
79
+ verification_result: string;
80
+ completed_at: string;
81
+ blocker_discovered: boolean;
82
+ }
83
+
84
+ export interface FileModified {
85
+ path: string;
86
+ description: string;
87
+ }
88
+
89
+ export interface Summary {
90
+ frontmatter: SummaryFrontmatter;
91
+ title: string;
92
+ oneLiner: string;
93
+ whatHappened: string;
94
+ deviations: string;
95
+ filesModified: FileModified[];
96
+ }
97
+
98
+ // ─── Continue-Here ─────────────────────────────────────────────────────────
99
+
100
+ export interface ContinueFrontmatter {
101
+ milestone: string;
102
+ slice: string;
103
+ task: string;
104
+ step: number;
105
+ totalSteps: number;
106
+ status: ContinueStatus;
107
+ savedAt: string;
108
+ }
109
+
110
+ export interface Continue {
111
+ frontmatter: ContinueFrontmatter;
112
+ completedWork: string;
113
+ remainingWork: string;
114
+ decisions: string;
115
+ context: string;
116
+ nextAction: string;
117
+ }
118
+
119
+ // ─── GSD State (Derived Dashboard) ────────────────────────────────────────
120
+
121
+ export interface ActiveRef {
122
+ id: string;
123
+ title: string;
124
+ }
125
+
126
+ export interface MilestoneRegistryEntry {
127
+ id: string;
128
+ title: string;
129
+ status: 'complete' | 'active' | 'pending';
130
+ /** Milestone IDs that must be complete before this milestone becomes active. Populated from CONTEXT.md YAML frontmatter. */
131
+ dependsOn?: string[];
132
+ }
133
+
134
+ export interface RequirementCounts {
135
+ active: number;
136
+ validated: number;
137
+ deferred: number;
138
+ outOfScope: number;
139
+ blocked: number;
140
+ total: number;
141
+ }
142
+
143
+ export interface GSDState {
144
+ activeMilestone: ActiveRef | null;
145
+ activeSlice: ActiveRef | null;
146
+ activeTask: ActiveRef | null;
147
+ phase: Phase;
148
+ recentDecisions: string[];
149
+ blockers: string[];
150
+ nextAction: string;
151
+ activeBranch?: string;
152
+ registry: MilestoneRegistryEntry[];
153
+ requirements?: RequirementCounts;
154
+ progress?: {
155
+ milestones: { done: number; total: number };
156
+ slices?: { done: number; total: number };
157
+ tasks?: { done: number; total: number };
158
+ };
159
+ }
@@ -0,0 +1,162 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import {
4
+ gsdRoot,
5
+ relSliceFile,
6
+ relTaskFile,
7
+ resolveSliceFile,
8
+ resolveTaskFile,
9
+ } from "./paths.ts";
10
+ import { loadFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.ts";
11
+
12
+ export type UnitRuntimePhase =
13
+ | "dispatched"
14
+ | "wrapup-warning-sent"
15
+ | "timeout"
16
+ | "recovered"
17
+ | "finalized"
18
+ | "paused";
19
+
20
+ export interface ExecuteTaskRecoveryStatus {
21
+ planPath: string;
22
+ summaryPath: string;
23
+ summaryExists: boolean;
24
+ taskChecked: boolean;
25
+ nextActionAdvanced: boolean;
26
+ mustHaveCount: number;
27
+ mustHavesMentionedInSummary: number;
28
+ }
29
+
30
+ export interface AutoUnitRuntimeRecord {
31
+ version: 1;
32
+ unitType: string;
33
+ unitId: string;
34
+ startedAt: number;
35
+ updatedAt: number;
36
+ phase: UnitRuntimePhase;
37
+ wrapupWarningSent: boolean;
38
+ timeoutAt: number | null;
39
+ lastProgressAt: number;
40
+ progressCount: number;
41
+ lastProgressKind: string;
42
+ recovery?: ExecuteTaskRecoveryStatus;
43
+ recoveryAttempts?: number;
44
+ lastRecoveryReason?: "idle" | "hard";
45
+ }
46
+
47
+ function runtimeDir(basePath: string): string {
48
+ return join(gsdRoot(basePath), "runtime", "units");
49
+ }
50
+
51
+ function runtimePath(basePath: string, unitType: string, unitId: string): string {
52
+ return join(runtimeDir(basePath), `${unitType}-${unitId.replace(/[\/]/g, "-")}.json`);
53
+ }
54
+
55
+ export function writeUnitRuntimeRecord(
56
+ basePath: string,
57
+ unitType: string,
58
+ unitId: string,
59
+ startedAt: number,
60
+ updates: Partial<AutoUnitRuntimeRecord> = {},
61
+ ): AutoUnitRuntimeRecord {
62
+ const dir = runtimeDir(basePath);
63
+ mkdirSync(dir, { recursive: true });
64
+ const path = runtimePath(basePath, unitType, unitId);
65
+ const prev = readUnitRuntimeRecord(basePath, unitType, unitId);
66
+ const next: AutoUnitRuntimeRecord = {
67
+ version: 1,
68
+ unitType,
69
+ unitId,
70
+ startedAt,
71
+ updatedAt: Date.now(),
72
+ phase: updates.phase ?? prev?.phase ?? "dispatched",
73
+ wrapupWarningSent: updates.wrapupWarningSent ?? prev?.wrapupWarningSent ?? false,
74
+ timeoutAt: updates.timeoutAt ?? prev?.timeoutAt ?? null,
75
+ lastProgressAt: updates.lastProgressAt ?? prev?.lastProgressAt ?? Date.now(),
76
+ progressCount: updates.progressCount ?? prev?.progressCount ?? 0,
77
+ lastProgressKind: updates.lastProgressKind ?? prev?.lastProgressKind ?? "dispatch",
78
+ recovery: updates.recovery ?? prev?.recovery,
79
+ recoveryAttempts: updates.recoveryAttempts ?? prev?.recoveryAttempts ?? 0,
80
+ lastRecoveryReason: updates.lastRecoveryReason ?? prev?.lastRecoveryReason,
81
+ };
82
+ writeFileSync(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
83
+ return next;
84
+ }
85
+
86
+ export function readUnitRuntimeRecord(basePath: string, unitType: string, unitId: string): AutoUnitRuntimeRecord | null {
87
+ const path = runtimePath(basePath, unitType, unitId);
88
+ if (!existsSync(path)) return null;
89
+ try {
90
+ return JSON.parse(readFileSync(path, "utf-8")) as AutoUnitRuntimeRecord;
91
+ } catch {
92
+ return null;
93
+ }
94
+ }
95
+
96
+ export function clearUnitRuntimeRecord(basePath: string, unitType: string, unitId: string): void {
97
+ const path = runtimePath(basePath, unitType, unitId);
98
+ if (existsSync(path)) unlinkSync(path);
99
+ }
100
+
101
+ export async function inspectExecuteTaskDurability(
102
+ basePath: string,
103
+ unitId: string,
104
+ ): Promise<ExecuteTaskRecoveryStatus | null> {
105
+ const [mid, sid, tid] = unitId.split("/");
106
+ if (!mid || !sid || !tid) return null;
107
+
108
+ const planAbs = resolveSliceFile(basePath, mid, sid, "PLAN");
109
+ const summaryAbs = resolveTaskFile(basePath, mid, sid, tid, "SUMMARY");
110
+ const stateAbs = join(gsdRoot(basePath), "STATE.md");
111
+
112
+ const planPath = relSliceFile(basePath, mid, sid, "PLAN");
113
+ const summaryPath = relTaskFile(basePath, mid, sid, tid, "SUMMARY");
114
+
115
+ const planContent = planAbs ? await loadFile(planAbs) : null;
116
+ const stateContent = existsSync(stateAbs) ? readFileSync(stateAbs, "utf-8") : "";
117
+ const summaryExists = !!(summaryAbs && existsSync(summaryAbs));
118
+
119
+ const escapedTid = tid.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
120
+ const taskChecked = !!planContent && new RegExp(`^- \\[[xX]\\] \\*\\*${escapedTid}:`, "m").test(planContent);
121
+ const nextActionAdvanced = !new RegExp(`Execute ${tid}\\b`).test(stateContent);
122
+
123
+ // Must-have coverage: load task plan and count mentions in summary
124
+ let mustHaveCount = 0;
125
+ let mustHavesMentionedInSummary = 0;
126
+
127
+ const taskPlanAbs = resolveTaskFile(basePath, mid, sid, tid, "PLAN");
128
+ if (taskPlanAbs) {
129
+ const taskPlanContent = await loadFile(taskPlanAbs);
130
+ if (taskPlanContent) {
131
+ const mustHaves = parseTaskPlanMustHaves(taskPlanContent);
132
+ mustHaveCount = mustHaves.length;
133
+ if (mustHaveCount > 0 && summaryExists && summaryAbs) {
134
+ const summaryContent = await loadFile(summaryAbs);
135
+ if (summaryContent) {
136
+ mustHavesMentionedInSummary = countMustHavesMentionedInSummary(mustHaves, summaryContent);
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ return {
143
+ planPath,
144
+ summaryPath,
145
+ summaryExists,
146
+ taskChecked,
147
+ nextActionAdvanced,
148
+ mustHaveCount,
149
+ mustHavesMentionedInSummary,
150
+ };
151
+ }
152
+
153
+ export function formatExecuteTaskRecoveryStatus(status: ExecuteTaskRecoveryStatus): string {
154
+ const missing = [] as string[];
155
+ if (!status.summaryExists) missing.push(`summary missing (${status.summaryPath})`);
156
+ if (!status.taskChecked) missing.push(`task checkbox unchecked in ${status.planPath}`);
157
+ if (!status.nextActionAdvanced) missing.push("state next action still points at the timed-out task");
158
+ if (status.mustHaveCount > 0 && status.mustHavesMentionedInSummary < status.mustHaveCount) {
159
+ missing.push(`must-have gap: ${status.mustHavesMentionedInSummary} of ${status.mustHaveCount} must-haves addressed in summary`);
160
+ }
161
+ return missing.length > 0 ? missing.join("; ") : "all durable task artifacts present";
162
+ }
@@ -0,0 +1,203 @@
1
+ import { readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ import { loadFile, parsePlan, parseRoadmap } from "./files.ts";
5
+ import {
6
+ milestonesDir,
7
+ resolveMilestoneFile,
8
+ resolveSliceFile,
9
+ resolveSlicePath,
10
+ resolveTaskFile,
11
+ resolveTasksDir,
12
+ } from "./paths.ts";
13
+ import { deriveState } from "./state.ts";
14
+ import { type ValidationIssue, validateCompleteBoundary, validatePlanBoundary } from "./observability-validator.ts";
15
+ import { getSliceBranchName } from "./worktree.ts";
16
+
17
+ export interface WorkspaceTaskTarget {
18
+ id: string;
19
+ title: string;
20
+ done: boolean;
21
+ planPath?: string;
22
+ summaryPath?: string;
23
+ }
24
+
25
+ export interface WorkspaceSliceTarget {
26
+ id: string;
27
+ title: string;
28
+ done: boolean;
29
+ planPath?: string;
30
+ summaryPath?: string;
31
+ uatPath?: string;
32
+ tasksDir?: string;
33
+ branch?: string;
34
+ tasks: WorkspaceTaskTarget[];
35
+ }
36
+
37
+ export interface WorkspaceMilestoneTarget {
38
+ id: string;
39
+ title: string;
40
+ roadmapPath?: string;
41
+ slices: WorkspaceSliceTarget[];
42
+ }
43
+
44
+ export interface WorkspaceScopeTarget {
45
+ scope: string;
46
+ label: string;
47
+ kind: "project" | "milestone" | "slice" | "task";
48
+ }
49
+
50
+ export interface GSDWorkspaceIndex {
51
+ milestones: WorkspaceMilestoneTarget[];
52
+ active: {
53
+ milestoneId?: string;
54
+ sliceId?: string;
55
+ taskId?: string;
56
+ phase: string;
57
+ };
58
+ scopes: WorkspaceScopeTarget[];
59
+ validationIssues: ValidationIssue[];
60
+ }
61
+
62
+ function findMilestoneIds(basePath: string): string[] {
63
+ try {
64
+ return readdirSync(milestonesDir(basePath), { withFileTypes: true })
65
+ .filter(entry => entry.isDirectory())
66
+ .map(entry => {
67
+ const match = entry.name.match(/^(M\d+)/);
68
+ return match ? match[1] : entry.name;
69
+ })
70
+ .sort();
71
+ } catch {
72
+ return [];
73
+ }
74
+ }
75
+
76
+ function titleFromRoadmapHeader(content: string, fallbackId: string): string {
77
+ const roadmap = parseRoadmap(content);
78
+ return roadmap.title.replace(/^M\d+[^:]*:\s*/, "") || fallbackId;
79
+ }
80
+
81
+ async function indexSlice(basePath: string, milestoneId: string, sliceId: string, fallbackTitle: string, done: boolean): Promise<WorkspaceSliceTarget> {
82
+ const planPath = resolveSliceFile(basePath, milestoneId, sliceId, "PLAN") ?? undefined;
83
+ const summaryPath = resolveSliceFile(basePath, milestoneId, sliceId, "SUMMARY") ?? undefined;
84
+ const uatPath = resolveSliceFile(basePath, milestoneId, sliceId, "UAT") ?? undefined;
85
+ const tasksDir = resolveTasksDir(basePath, milestoneId, sliceId) ?? undefined;
86
+
87
+ const tasks: WorkspaceTaskTarget[] = [];
88
+ let title = fallbackTitle;
89
+
90
+ if (planPath) {
91
+ const content = await loadFile(planPath);
92
+ if (content) {
93
+ const plan = parsePlan(content);
94
+ title = plan.title || fallbackTitle;
95
+ for (const task of plan.tasks) {
96
+ tasks.push({
97
+ id: task.id,
98
+ title: task.title,
99
+ done: task.done,
100
+ planPath: resolveTaskFile(basePath, milestoneId, sliceId, task.id, "PLAN") ?? undefined,
101
+ summaryPath: resolveTaskFile(basePath, milestoneId, sliceId, task.id, "SUMMARY") ?? undefined,
102
+ });
103
+ }
104
+ }
105
+ }
106
+
107
+ return {
108
+ id: sliceId,
109
+ title,
110
+ done,
111
+ planPath,
112
+ summaryPath,
113
+ uatPath,
114
+ tasksDir,
115
+ branch: getSliceBranchName(milestoneId, sliceId),
116
+ tasks,
117
+ };
118
+ }
119
+
120
+ export async function indexWorkspace(basePath: string): Promise<GSDWorkspaceIndex> {
121
+ const milestoneIds = findMilestoneIds(basePath);
122
+ const milestones: WorkspaceMilestoneTarget[] = [];
123
+ const validationIssues: ValidationIssue[] = [];
124
+
125
+ for (const milestoneId of milestoneIds) {
126
+ const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP") ?? undefined;
127
+ let title = milestoneId;
128
+ const slices: WorkspaceSliceTarget[] = [];
129
+
130
+ if (roadmapPath) {
131
+ const roadmapContent = await loadFile(roadmapPath);
132
+ if (roadmapContent) {
133
+ const roadmap = parseRoadmap(roadmapContent);
134
+ title = titleFromRoadmapHeader(roadmapContent, milestoneId);
135
+ for (const slice of roadmap.slices) {
136
+ const indexedSlice = await indexSlice(basePath, milestoneId, slice.id, slice.title, slice.done);
137
+ slices.push(indexedSlice);
138
+ validationIssues.push(...await validatePlanBoundary(basePath, milestoneId, slice.id));
139
+ validationIssues.push(...await validateCompleteBoundary(basePath, milestoneId, slice.id));
140
+ }
141
+ }
142
+ }
143
+
144
+ milestones.push({ id: milestoneId, title, roadmapPath, slices });
145
+ }
146
+
147
+ const state = await deriveState(basePath);
148
+ const active = {
149
+ milestoneId: state.activeMilestone?.id,
150
+ sliceId: state.activeSlice?.id,
151
+ taskId: state.activeTask?.id,
152
+ phase: state.phase,
153
+ };
154
+
155
+ const scopes: WorkspaceScopeTarget[] = [{ scope: "project", label: "project", kind: "project" }];
156
+ for (const milestone of milestones) {
157
+ scopes.push({ scope: milestone.id, label: `${milestone.id}: ${milestone.title}`, kind: "milestone" });
158
+ for (const slice of milestone.slices) {
159
+ scopes.push({ scope: `${milestone.id}/${slice.id}`, label: `${milestone.id}/${slice.id}: ${slice.title}`, kind: "slice" });
160
+ for (const task of slice.tasks) {
161
+ scopes.push({
162
+ scope: `${milestone.id}/${slice.id}/${task.id}`,
163
+ label: `${milestone.id}/${slice.id}/${task.id}: ${task.title}`,
164
+ kind: "task",
165
+ });
166
+ }
167
+ }
168
+ }
169
+
170
+ return { milestones, active, scopes, validationIssues };
171
+ }
172
+
173
+ export async function listDoctorScopeSuggestions(basePath: string): Promise<Array<{ value: string; label: string }>> {
174
+ const index = await indexWorkspace(basePath);
175
+ const activeSliceScope = index.active.milestoneId && index.active.sliceId
176
+ ? `${index.active.milestoneId}/${index.active.sliceId}`
177
+ : null;
178
+
179
+ const ordered = [...index.scopes].filter(scope => scope.kind !== "project");
180
+ ordered.sort((a, b) => {
181
+ if (activeSliceScope && a.scope === activeSliceScope) return -1;
182
+ if (activeSliceScope && b.scope === activeSliceScope) return 1;
183
+ return a.scope.localeCompare(b.scope);
184
+ });
185
+
186
+ return ordered.map(scope => ({ value: scope.scope, label: scope.label }));
187
+ }
188
+
189
+ export async function getSuggestedNextCommands(basePath: string): Promise<string[]> {
190
+ const index = await indexWorkspace(basePath);
191
+ const scope = index.active.milestoneId && index.active.sliceId
192
+ ? `${index.active.milestoneId}/${index.active.sliceId}`
193
+ : index.active.milestoneId;
194
+
195
+ const commands = new Set<string>();
196
+ if (index.active.phase === "planning") commands.add("/gsd");
197
+ if (index.active.phase === "executing" || index.active.phase === "summarizing") commands.add("/gsd auto");
198
+ if (scope) commands.add(`/gsd doctor ${scope}`);
199
+ if (scope) commands.add(`/gsd doctor fix ${scope}`);
200
+ if (index.validationIssues.length > 0 && scope) commands.add(`/gsd doctor audit ${scope}`);
201
+ commands.add("/gsd status");
202
+ return [...commands];
203
+ }