gsd-pi 2.33.1-dev.ee47f1b → 2.34.0-dev.bbb5216

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 (135) hide show
  1. package/dist/bundled-resource-path.d.ts +8 -0
  2. package/dist/bundled-resource-path.js +14 -0
  3. package/dist/headless-query.js +6 -6
  4. package/dist/resources/extensions/gsd/auto/session.js +27 -32
  5. package/dist/resources/extensions/gsd/auto-dashboard.js +29 -109
  6. package/dist/resources/extensions/gsd/auto-direct-dispatch.js +6 -1
  7. package/dist/resources/extensions/gsd/auto-dispatch.js +52 -81
  8. package/dist/resources/extensions/gsd/auto-loop.js +956 -0
  9. package/dist/resources/extensions/gsd/auto-observability.js +4 -2
  10. package/dist/resources/extensions/gsd/auto-post-unit.js +75 -185
  11. package/dist/resources/extensions/gsd/auto-prompts.js +133 -101
  12. package/dist/resources/extensions/gsd/auto-recovery.js +59 -97
  13. package/dist/resources/extensions/gsd/auto-start.js +330 -309
  14. package/dist/resources/extensions/gsd/auto-supervisor.js +5 -11
  15. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +7 -7
  16. package/dist/resources/extensions/gsd/auto-timers.js +3 -4
  17. package/dist/resources/extensions/gsd/auto-verification.js +35 -73
  18. package/dist/resources/extensions/gsd/auto-worktree-sync.js +167 -0
  19. package/dist/resources/extensions/gsd/auto-worktree.js +291 -126
  20. package/dist/resources/extensions/gsd/auto.js +283 -1013
  21. package/dist/resources/extensions/gsd/captures.js +10 -4
  22. package/dist/resources/extensions/gsd/dispatch-guard.js +7 -8
  23. package/dist/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  24. package/dist/resources/extensions/gsd/doctor-checks.js +3 -4
  25. package/dist/resources/extensions/gsd/git-service.js +1 -1
  26. package/dist/resources/extensions/gsd/gsd-db.js +296 -151
  27. package/dist/resources/extensions/gsd/index.js +92 -228
  28. package/dist/resources/extensions/gsd/post-unit-hooks.js +13 -13
  29. package/dist/resources/extensions/gsd/progress-score.js +61 -156
  30. package/dist/resources/extensions/gsd/quick.js +98 -122
  31. package/dist/resources/extensions/gsd/session-lock.js +13 -0
  32. package/dist/resources/extensions/gsd/templates/preferences.md +1 -0
  33. package/dist/resources/extensions/gsd/undo.js +43 -48
  34. package/dist/resources/extensions/gsd/unit-runtime.js +16 -15
  35. package/dist/resources/extensions/gsd/verification-evidence.js +0 -1
  36. package/dist/resources/extensions/gsd/verification-gate.js +6 -35
  37. package/dist/resources/extensions/gsd/worktree-command.js +30 -24
  38. package/dist/resources/extensions/gsd/worktree-manager.js +2 -3
  39. package/dist/resources/extensions/gsd/worktree-resolver.js +344 -0
  40. package/dist/resources/extensions/gsd/worktree.js +7 -44
  41. package/dist/tool-bootstrap.js +59 -11
  42. package/dist/worktree-cli.js +7 -7
  43. package/package.json +1 -1
  44. package/packages/pi-ai/dist/models.generated.d.ts +3630 -5483
  45. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  46. package/packages/pi-ai/dist/models.generated.js +735 -2588
  47. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  48. package/packages/pi-ai/src/models.generated.ts +1039 -2892
  49. package/packages/pi-coding-agent/package.json +1 -1
  50. package/pkg/package.json +1 -1
  51. package/src/resources/extensions/gsd/auto/session.ts +47 -30
  52. package/src/resources/extensions/gsd/auto-dashboard.ts +28 -131
  53. package/src/resources/extensions/gsd/auto-direct-dispatch.ts +6 -1
  54. package/src/resources/extensions/gsd/auto-dispatch.ts +135 -91
  55. package/src/resources/extensions/gsd/auto-loop.ts +1665 -0
  56. package/src/resources/extensions/gsd/auto-observability.ts +4 -2
  57. package/src/resources/extensions/gsd/auto-post-unit.ts +85 -228
  58. package/src/resources/extensions/gsd/auto-prompts.ts +138 -109
  59. package/src/resources/extensions/gsd/auto-recovery.ts +124 -118
  60. package/src/resources/extensions/gsd/auto-start.ts +440 -354
  61. package/src/resources/extensions/gsd/auto-supervisor.ts +5 -12
  62. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +8 -8
  63. package/src/resources/extensions/gsd/auto-timers.ts +3 -4
  64. package/src/resources/extensions/gsd/auto-verification.ts +76 -90
  65. package/src/resources/extensions/gsd/auto-worktree-sync.ts +204 -0
  66. package/src/resources/extensions/gsd/auto-worktree.ts +389 -141
  67. package/src/resources/extensions/gsd/auto.ts +515 -1199
  68. package/src/resources/extensions/gsd/captures.ts +10 -4
  69. package/src/resources/extensions/gsd/dispatch-guard.ts +13 -9
  70. package/src/resources/extensions/gsd/docs/preferences-reference.md +25 -18
  71. package/src/resources/extensions/gsd/doctor-checks.ts +3 -4
  72. package/src/resources/extensions/gsd/git-service.ts +8 -1
  73. package/src/resources/extensions/gsd/gitignore.ts +4 -2
  74. package/src/resources/extensions/gsd/gsd-db.ts +375 -180
  75. package/src/resources/extensions/gsd/index.ts +104 -263
  76. package/src/resources/extensions/gsd/post-unit-hooks.ts +13 -13
  77. package/src/resources/extensions/gsd/progress-score.ts +65 -200
  78. package/src/resources/extensions/gsd/quick.ts +121 -125
  79. package/src/resources/extensions/gsd/session-lock.ts +11 -0
  80. package/src/resources/extensions/gsd/templates/preferences.md +1 -0
  81. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +32 -59
  82. package/src/resources/extensions/gsd/tests/all-milestones-complete-merge.test.ts +75 -27
  83. package/src/resources/extensions/gsd/tests/auto-budget-alerts.test.ts +1 -1
  84. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +37 -0
  85. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +1458 -0
  86. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +8 -162
  87. package/src/resources/extensions/gsd/tests/auto-secrets-gate.test.ts +2 -108
  88. package/src/resources/extensions/gsd/tests/auto-session-encapsulation.test.ts +1 -3
  89. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +0 -3
  90. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +58 -0
  91. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +0 -55
  92. package/src/resources/extensions/gsd/tests/headless-query.test.ts +22 -0
  93. package/src/resources/extensions/gsd/tests/milestone-transition-worktree.test.ts +8 -11
  94. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +4 -6
  95. package/src/resources/extensions/gsd/tests/run-uat.test.ts +3 -3
  96. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +64 -0
  97. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +181 -0
  98. package/src/resources/extensions/gsd/tests/stale-worktree-cwd.test.ts +0 -3
  99. package/src/resources/extensions/gsd/tests/token-profile.test.ts +6 -6
  100. package/src/resources/extensions/gsd/tests/triage-dispatch.test.ts +6 -6
  101. package/src/resources/extensions/gsd/tests/undo.test.ts +6 -0
  102. package/src/resources/extensions/gsd/tests/verification-evidence.test.ts +24 -26
  103. package/src/resources/extensions/gsd/tests/verification-gate.test.ts +7 -201
  104. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +205 -0
  105. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +442 -0
  106. package/src/resources/extensions/gsd/tests/worktree-e2e.test.ts +0 -3
  107. package/src/resources/extensions/gsd/tests/worktree-resolver.test.ts +705 -0
  108. package/src/resources/extensions/gsd/tests/worktree-sync-milestones.test.ts +57 -106
  109. package/src/resources/extensions/gsd/tests/worktree.test.ts +5 -1
  110. package/src/resources/extensions/gsd/tests/write-gate.test.ts +43 -132
  111. package/src/resources/extensions/gsd/types.ts +90 -81
  112. package/src/resources/extensions/gsd/undo.ts +42 -46
  113. package/src/resources/extensions/gsd/unit-runtime.ts +14 -18
  114. package/src/resources/extensions/gsd/verification-evidence.ts +1 -3
  115. package/src/resources/extensions/gsd/verification-gate.ts +6 -39
  116. package/src/resources/extensions/gsd/worktree-command.ts +36 -24
  117. package/src/resources/extensions/gsd/worktree-manager.ts +2 -3
  118. package/src/resources/extensions/gsd/worktree-resolver.ts +485 -0
  119. package/src/resources/extensions/gsd/worktree.ts +7 -44
  120. package/dist/resources/extensions/gsd/auto-constants.js +0 -5
  121. package/dist/resources/extensions/gsd/auto-idempotency.js +0 -106
  122. package/dist/resources/extensions/gsd/auto-stuck-detection.js +0 -165
  123. package/dist/resources/extensions/gsd/mechanical-completion.js +0 -351
  124. package/src/resources/extensions/gsd/auto-constants.ts +0 -6
  125. package/src/resources/extensions/gsd/auto-idempotency.ts +0 -151
  126. package/src/resources/extensions/gsd/auto-stuck-detection.ts +0 -221
  127. package/src/resources/extensions/gsd/mechanical-completion.ts +0 -430
  128. package/src/resources/extensions/gsd/tests/auto-dispatch-loop.test.ts +0 -691
  129. package/src/resources/extensions/gsd/tests/auto-reentrancy-guard.test.ts +0 -127
  130. package/src/resources/extensions/gsd/tests/auto-skip-loop.test.ts +0 -123
  131. package/src/resources/extensions/gsd/tests/dispatch-stall-guard.test.ts +0 -126
  132. package/src/resources/extensions/gsd/tests/loop-regression.test.ts +0 -874
  133. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +0 -356
  134. package/src/resources/extensions/gsd/tests/progress-score.test.ts +0 -206
  135. package/src/resources/extensions/gsd/tests/session-lock.test.ts +0 -434
@@ -4,30 +4,45 @@
4
4
 
5
5
  // ─── Enums & Literal Unions ────────────────────────────────────────────────
6
6
 
7
- export type RiskLevel = 'low' | 'medium' | 'high';
8
- export type Phase = 'pre-planning' | 'needs-discussion' | 'discussing' | 'researching' | 'planning' | 'executing' | 'verifying' | 'summarizing' | 'advancing' | 'validating-milestone' | 'completing-milestone' | 'replanning-slice' | 'complete' | 'paused' | 'blocked';
9
- export type ContinueStatus = 'in_progress' | 'interrupted' | 'compacted';
7
+ export type RiskLevel = "low" | "medium" | "high";
8
+ export type Phase =
9
+ | "pre-planning"
10
+ | "needs-discussion"
11
+ | "discussing"
12
+ | "researching"
13
+ | "planning"
14
+ | "executing"
15
+ | "verifying"
16
+ | "summarizing"
17
+ | "advancing"
18
+ | "validating-milestone"
19
+ | "completing-milestone"
20
+ | "replanning-slice"
21
+ | "complete"
22
+ | "paused"
23
+ | "blocked";
24
+ export type ContinueStatus = "in_progress" | "interrupted" | "compacted";
10
25
 
11
26
  // ─── Roadmap (Milestone-level) ─────────────────────────────────────────────
12
27
 
13
28
  export interface RoadmapSliceEntry {
14
- id: string; // e.g. "S01"
15
- title: string; // e.g. "Types + File I/O + Git Operations"
29
+ id: string; // e.g. "S01"
30
+ title: string; // e.g. "Types + File I/O + Git Operations"
16
31
  risk: RiskLevel;
17
- depends: string[]; // e.g. ["S01", "S02"]
32
+ depends: string[]; // e.g. ["S01", "S02"]
18
33
  done: boolean;
19
- demo: string; // the "After this:" sentence
34
+ demo: string; // the "After this:" sentence
20
35
  }
21
36
 
22
37
  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")
38
+ fromSlice: string; // e.g. "S01"
39
+ toSlice: string; // e.g. "S02" or "terminal"
40
+ produces: string; // raw text block of what this slice produces
41
+ consumes: string; // raw text block of what it consumes (or "nothing")
27
42
  }
28
43
 
29
44
  export interface Roadmap {
30
- title: string; // e.g. "M001: GSD Extension — Hierarchical Planning with Auto Mode"
45
+ title: string; // e.g. "M001: GSD Extension — Hierarchical Planning with Auto Mode"
31
46
  vision: string;
32
47
  successCriteria: string[];
33
48
  slices: RoadmapSliceEntry[];
@@ -37,29 +52,24 @@ export interface Roadmap {
37
52
  // ─── Slice Plan ────────────────────────────────────────────────────────────
38
53
 
39
54
  export interface TaskPlanEntry {
40
- id: string; // e.g. "T01"
41
- title: string; // e.g. "Core Type Definitions"
55
+ id: string; // e.g. "T01"
56
+ title: string; // e.g. "Core Type Definitions"
42
57
  description: string;
43
58
  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
59
+ estimate: string; // e.g. "30m", "2h" — informational only
60
+ files?: string[]; // e.g. ["types.ts", "files.ts"] — extracted from "- Files:" subline
61
+ verify?: string; // e.g. "run tests" — extracted from "- Verify:" subline
47
62
  }
48
63
 
49
64
  // ─── Verification Gate ─────────────────────────────────────────────────────
50
65
 
51
66
  /** Result of a single verification command execution */
52
67
  export interface VerificationCheck {
53
- command: string; // e.g. "npm run lint"
54
- exitCode: number; // 0 = pass
68
+ command: string; // e.g. "npm run lint"
69
+ exitCode: number; // 0 = pass
55
70
  stdout: string;
56
71
  stderr: string;
57
72
  durationMs: number;
58
- blocking: boolean; // true for preference/task-plan sources, false for package-json (advisory only)
59
- /** True when the failure was a spawn/infra error (ETIMEDOUT, ENOENT, ENOMEM)
60
- * rather than the command itself failing. Infra errors are transient and
61
- * should not trigger auto-fix retries — the agent cannot fix the OS. */
62
- infraError?: boolean;
63
73
  }
64
74
 
65
75
  /** A runtime error captured from bg-shell processes or browser console */
@@ -81,17 +91,17 @@ export interface AuditWarning {
81
91
 
82
92
  /** Aggregate result from the verification gate */
83
93
  export interface VerificationResult {
84
- passed: boolean; // true if all checks passed (or no checks discovered)
85
- checks: VerificationCheck[]; // per-command results
94
+ passed: boolean; // true if all checks passed (or no checks discovered)
95
+ checks: VerificationCheck[]; // per-command results
86
96
  discoverySource: "preference" | "task-plan" | "package-json" | "none";
87
- timestamp: number; // Date.now() at gate start
88
- runtimeErrors?: RuntimeError[]; // optional — populated by captureRuntimeErrors()
89
- auditWarnings?: AuditWarning[]; // optional — populated by runDependencyAudit()
97
+ timestamp: number; // Date.now() at gate start
98
+ runtimeErrors?: RuntimeError[]; // optional — populated by captureRuntimeErrors()
99
+ auditWarnings?: AuditWarning[]; // optional — populated by runDependencyAudit()
90
100
  }
91
101
 
92
102
  export interface SlicePlan {
93
- id: string; // e.g. "S01"
94
- title: string; // from the H1
103
+ id: string; // e.g. "S01"
104
+ title: string; // from the H1
95
105
  goal: string;
96
106
  demo: string;
97
107
  mustHaves: string[]; // top-level must-have bullet points
@@ -161,29 +171,29 @@ export interface Continue {
161
171
 
162
172
  // ─── Secrets Manifest ──────────────────────────────────────────────────────
163
173
 
164
- export type SecretsManifestEntryStatus = 'pending' | 'collected' | 'skipped';
174
+ export type SecretsManifestEntryStatus = "pending" | "collected" | "skipped";
165
175
 
166
176
  export interface SecretsManifestEntry {
167
- key: string; // e.g. "OPENAI_API_KEY"
168
- service: string; // e.g. "OpenAI"
169
- dashboardUrl: string; // e.g. "https://platform.openai.com/api-keys" — empty if unknown
170
- guidance: string[]; // numbered setup steps
171
- formatHint: string; // e.g. "starts with sk-" — empty if unknown
177
+ key: string; // e.g. "OPENAI_API_KEY"
178
+ service: string; // e.g. "OpenAI"
179
+ dashboardUrl: string; // e.g. "https://platform.openai.com/api-keys" — empty if unknown
180
+ guidance: string[]; // numbered setup steps
181
+ formatHint: string; // e.g. "starts with sk-" — empty if unknown
172
182
  status: SecretsManifestEntryStatus;
173
- destination: string; // e.g. "dotenv", "vercel", "convex"
183
+ destination: string; // e.g. "dotenv", "vercel", "convex"
174
184
  }
175
185
 
176
186
  export interface SecretsManifest {
177
- milestone: string; // e.g. "M001"
178
- generatedAt: string; // ISO 8601 timestamp
187
+ milestone: string; // e.g. "M001"
188
+ generatedAt: string; // ISO 8601 timestamp
179
189
  entries: SecretsManifestEntry[];
180
190
  }
181
191
 
182
192
  export interface ManifestStatus {
183
- pending: string[]; // manifest status = pending AND not in env
184
- collected: string[]; // manifest status = collected AND not in env
185
- skipped: string[]; // manifest status = skipped
186
- existing: string[]; // key present in .env or process.env (regardless of manifest status)
193
+ pending: string[]; // manifest status = pending AND not in env
194
+ collected: string[]; // manifest status = collected AND not in env
195
+ skipped: string[]; // manifest status = skipped
196
+ existing: string[]; // key present in .env or process.env (regardless of manifest status)
187
197
  }
188
198
 
189
199
  // ─── GSD State (Derived Dashboard) ────────────────────────────────────────
@@ -196,7 +206,7 @@ export interface ActiveRef {
196
206
  export interface MilestoneRegistryEntry {
197
207
  id: string;
198
208
  title: string;
199
- status: 'complete' | 'active' | 'pending' | 'parked';
209
+ status: "complete" | "active" | "pending" | "parked";
200
210
  /** Milestone IDs that must be complete before this milestone becomes active. Populated from CONTEXT.md YAML frontmatter. */
201
211
  dependsOn?: string[];
202
212
  }
@@ -279,13 +289,13 @@ export interface HookDispatchResult {
279
289
 
280
290
  // ─── Budget & Notification Types ──────────────────────────────────────────
281
291
 
282
- export type BudgetEnforcementMode = 'warn' | 'pause' | 'halt';
292
+ export type BudgetEnforcementMode = "warn" | "pause" | "halt";
283
293
 
284
- export type TokenProfile = 'budget' | 'balanced' | 'quality';
294
+ export type TokenProfile = "budget" | "balanced" | "quality";
285
295
 
286
- export type InlineLevel = 'full' | 'standard' | 'minimal';
296
+ export type InlineLevel = "full" | "standard" | "minimal";
287
297
 
288
- export type ComplexityTier = 'light' | 'standard' | 'heavy';
298
+ export type ComplexityTier = "light" | "standard" | "heavy";
289
299
 
290
300
  export interface ClassificationResult {
291
301
  tier: ComplexityTier;
@@ -308,19 +318,18 @@ export interface PhaseSkipPreferences {
308
318
  skip_reassess?: boolean;
309
319
  skip_slice_research?: boolean;
310
320
  skip_milestone_validation?: boolean;
311
- /** When true, reassess-roadmap fires after each slice completion. Opt-in. */
312
321
  reassess_after_slice?: boolean;
313
322
  /** When true, auto-mode pauses before each slice for discussion (#789). */
314
323
  require_slice_discussion?: boolean;
315
324
  }
316
325
 
317
326
  export interface NotificationPreferences {
318
- enabled?: boolean; // default true
319
- on_complete?: boolean; // notify on each unit completion
320
- on_error?: boolean; // notify on errors
321
- on_budget?: boolean; // notify on budget thresholds
322
- on_milestone?: boolean; // notify when milestone finishes
323
- on_attention?: boolean; // notify when manual attention needed
327
+ enabled?: boolean; // default true
328
+ on_complete?: boolean; // notify on each unit completion
329
+ on_error?: boolean; // notify on errors
330
+ on_budget?: boolean; // notify on budget thresholds
331
+ on_milestone?: boolean; // notify when milestone finishes
332
+ on_attention?: boolean; // notify when manual attention needed
324
333
  }
325
334
 
326
335
  // ─── Pre-Dispatch Hook Types ──────────────────────────────────────────────
@@ -331,7 +340,7 @@ export interface PreDispatchHookConfig {
331
340
  /** Unit types this hook intercepts before dispatch (e.g., ["execute-task"]). */
332
341
  before: string[];
333
342
  /** Action to take: "modify" mutates the prompt, "skip" skips the unit, "replace" swaps it. */
334
- action: 'modify' | 'skip' | 'replace';
343
+ action: "modify" | "skip" | "replace";
335
344
  /** For "modify": text prepended to the unit prompt. Supports {milestoneId}, {sliceId}, {taskId}. */
336
345
  prepend?: string;
337
346
  /** For "modify": text appended to the unit prompt. Supports {milestoneId}, {sliceId}, {taskId}. */
@@ -350,7 +359,7 @@ export interface PreDispatchHookConfig {
350
359
 
351
360
  export interface PreDispatchResult {
352
361
  /** What happened: the unit proceeds with modifications, was skipped, or was replaced. */
353
- action: 'proceed' | 'skip' | 'replace';
362
+ action: "proceed" | "skip" | "replace";
354
363
  /** Modified/replacement prompt (for "proceed" and "replace"). */
355
364
  prompt?: string;
356
365
  /** Override unit type (for "replace"). */
@@ -374,7 +383,7 @@ export interface HookStatusEntry {
374
383
  /** Hook name. */
375
384
  name: string;
376
385
  /** Hook type: "post" or "pre". */
377
- type: 'post' | 'pre';
386
+ type: "post" | "pre";
378
387
  /** Whether hook is enabled. */
379
388
  enabled: boolean;
380
389
  /** What unit types it targets. */
@@ -386,36 +395,36 @@ export interface HookStatusEntry {
386
395
  // ─── Database Types (Decisions & Requirements) ────────────────────────────
387
396
 
388
397
  export interface Decision {
389
- seq: number; // auto-increment primary key
390
- id: string; // e.g. "D001"
391
- when_context: string; // when/context of the decision
392
- scope: string; // scope (milestone, slice, global, etc.)
393
- decision: string; // what was decided
394
- choice: string; // the specific choice made
395
- rationale: string; // why this choice
396
- revisable: string; // whether/when revisable
397
- superseded_by: string | null; // ID of superseding decision, or null
398
+ seq: number; // auto-increment primary key
399
+ id: string; // e.g. "D001"
400
+ when_context: string; // when/context of the decision
401
+ scope: string; // scope (milestone, slice, global, etc.)
402
+ decision: string; // what was decided
403
+ choice: string; // the specific choice made
404
+ rationale: string; // why this choice
405
+ revisable: string; // whether/when revisable
406
+ superseded_by: string | null; // ID of superseding decision, or null
398
407
  }
399
408
 
400
409
  export interface Requirement {
401
- id: string; // e.g. "R001"
402
- class: string; // requirement class (functional, non-functional, etc.)
403
- status: string; // active, validated, deferred, etc.
404
- description: string; // short description
405
- why: string; // rationale
406
- source: string; // origin (milestone, user, etc.)
407
- primary_owner: string; // owning slice/milestone
410
+ id: string; // e.g. "R001"
411
+ class: string; // requirement class (functional, non-functional, etc.)
412
+ status: string; // active, validated, deferred, etc.
413
+ description: string; // short description
414
+ why: string; // rationale
415
+ source: string; // origin (milestone, user, etc.)
416
+ primary_owner: string; // owning slice/milestone
408
417
  supporting_slices: string; // other slices that touch this
409
- validation: string; // how to validate
410
- notes: string; // additional notes
411
- full_content: string; // full requirement text
412
- superseded_by: string | null; // ID of superseding requirement, or null
418
+ validation: string; // how to validate
419
+ notes: string; // additional notes
420
+ full_content: string; // full requirement text
421
+ superseded_by: string | null; // ID of superseding requirement, or null
413
422
  }
414
423
 
415
424
  // ─── Parallel Orchestration Types ────────────────────────────────────────
416
425
 
417
- export type CompressionStrategy = 'truncate' | 'compress';
418
- export type ContextSelectionMode = 'full' | 'smart';
426
+ export type CompressionStrategy = "truncate" | "compress";
427
+ export type ContextSelectionMode = "full" | "smart";
419
428
 
420
429
  export type MergeStrategy = "per-slice" | "per-milestone";
421
430
  export type AutoMergeMode = "auto" | "confirm" | "manual";
@@ -9,46 +9,48 @@ import { deriveState } from "./state.js";
9
9
  import { invalidateAllCaches } from "./cache.js";
10
10
  import { gsdRoot, resolveTasksDir, resolveSlicePath, buildTaskFileName } from "./paths.js";
11
11
  import { sendDesktopNotification } from "./notifications.js";
12
- import { parseUnitId } from "./unit-id.js";
13
12
 
14
13
  /**
15
- * Undo the last completed unit: revert git commits, remove from completed-units,
14
+ * Undo the last completed unit: revert git commits,
16
15
  * delete summary artifacts, and uncheck the task in PLAN.
16
+ * deriveState() handles re-derivation after revert.
17
17
  */
18
18
  export async function handleUndo(args: string, ctx: ExtensionCommandContext, _pi: ExtensionAPI, basePath: string): Promise<void> {
19
19
  const force = args.includes("--force");
20
20
 
21
- // 1. Load completed-units.json
22
- const completedKeysFile = join(gsdRoot(basePath), "completed-units.json");
23
- if (!existsSync(completedKeysFile)) {
24
- ctx.ui.notify("Nothing to undo — no completed units found.", "info");
21
+ // Find the last GSD-related commit from git activity logs
22
+ const activityDir = join(gsdRoot(basePath), "activity");
23
+ if (!existsSync(activityDir)) {
24
+ ctx.ui.notify("Nothing to undo — no activity logs found.", "info");
25
25
  return;
26
26
  }
27
27
 
28
- let keys: string[];
29
- try {
30
- keys = JSON.parse(readFileSync(completedKeysFile, "utf-8"));
31
- } catch {
32
- ctx.ui.notify("Nothing to undo — completed-units.json is corrupt.", "warning");
28
+ // Parse activity logs to find the most recent unit
29
+ const files = readdirSync(activityDir)
30
+ .filter(f => f.endsWith(".jsonl"))
31
+ .sort()
32
+ .reverse();
33
+
34
+ if (files.length === 0) {
35
+ ctx.ui.notify("Nothing to undo — no activity logs found.", "info");
33
36
  return;
34
37
  }
35
38
 
36
- if (keys.length === 0) {
37
- ctx.ui.notify("Nothing to undo — no completed units.", "info");
39
+ // Extract unit type and ID from the most recent activity log filename
40
+ // Format: <seq>-<unitType>-<unitId>.jsonl
41
+ const match = files[0].match(/^\d+-(.+?)-(.+)\.jsonl$/);
42
+ if (!match) {
43
+ ctx.ui.notify("Nothing to undo — could not parse latest activity log.", "warning");
38
44
  return;
39
45
  }
40
46
 
41
- // Get the last completed unit
42
- const lastKey = keys[keys.length - 1];
43
- const sepIdx = lastKey.indexOf("/");
44
- const unitType = sepIdx >= 0 ? lastKey.slice(0, sepIdx) : lastKey;
45
- const unitId = sepIdx >= 0 ? lastKey.slice(sepIdx + 1) : lastKey;
47
+ const unitType = match[1];
48
+ const unitId = match[2].replace(/-/g, "/");
46
49
 
47
50
  if (!force) {
48
51
  ctx.ui.notify(
49
52
  `Will undo: ${unitType} (${unitId})\n` +
50
53
  `This will:\n` +
51
- ` - Remove from completed-units.json\n` +
52
54
  ` - Delete summary artifacts\n` +
53
55
  ` - Uncheck task in PLAN (if execute-task)\n` +
54
56
  ` - Attempt to revert associated git commits\n\n` +
@@ -58,15 +60,12 @@ export async function handleUndo(args: string, ctx: ExtensionCommandContext, _pi
58
60
  return;
59
61
  }
60
62
 
61
- // 2. Remove from completed-units.json
62
- keys = keys.filter(k => k !== lastKey);
63
- writeFileSync(completedKeysFile, JSON.stringify(keys), "utf-8");
64
-
65
- // 3. Delete summary artifact
66
- const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
63
+ // 1. Delete summary artifact
64
+ const parts = unitId.split("/");
67
65
  let summaryRemoved = false;
68
- if (mid && sid && tid) {
66
+ if (parts.length === 3) {
69
67
  // Task-level: M001/S01/T01
68
+ const [mid, sid, tid] = parts;
70
69
  const tasksDir = resolveTasksDir(basePath, mid, sid);
71
70
  if (tasksDir) {
72
71
  const summaryFile = join(tasksDir, buildTaskFileName(tid, "SUMMARY"));
@@ -75,11 +74,11 @@ export async function handleUndo(args: string, ctx: ExtensionCommandContext, _pi
75
74
  summaryRemoved = true;
76
75
  }
77
76
  }
78
- } else if (mid && sid) {
77
+ } else if (parts.length === 2) {
79
78
  // Slice-level: M001/S01
79
+ const [mid, sid] = parts;
80
80
  const slicePath = resolveSlicePath(basePath, mid, sid);
81
81
  if (slicePath) {
82
- // Try common summary filenames
83
82
  for (const suffix of ["SUMMARY", "COMPLETE"]) {
84
83
  const candidates = findFileWithPrefix(slicePath, sid, suffix);
85
84
  for (const f of candidates) {
@@ -90,40 +89,37 @@ export async function handleUndo(args: string, ctx: ExtensionCommandContext, _pi
90
89
  }
91
90
  }
92
91
 
93
- // 4. Uncheck task in PLAN if execute-task
92
+ // 2. Uncheck task in PLAN if execute-task
94
93
  let planUpdated = false;
95
- if (unitType === "execute-task" && mid && sid && tid) {
94
+ if (unitType === "execute-task" && parts.length === 3) {
95
+ const [mid, sid, tid] = parts;
96
96
  planUpdated = uncheckTaskInPlan(basePath, mid, sid, tid);
97
97
  }
98
98
 
99
- // 5. Try to revert git commits from activity log
99
+ // 3. Try to revert git commits from activity log
100
100
  let commitsReverted = 0;
101
- const activityDir = join(gsdRoot(basePath), "activity");
102
101
  try {
103
- if (existsSync(activityDir)) {
104
- const commits = findCommitsForUnit(activityDir, unitType, unitId);
105
- if (commits.length > 0) {
106
- for (const sha of commits.reverse()) {
107
- try {
108
- nativeRevertCommit(basePath, sha);
109
- commitsReverted++;
110
- } catch {
111
- // Revert conflict or already reverted skip
112
- try { nativeRevertAbort(basePath); } catch { /* no-op */ }
113
- break;
114
- }
102
+ const commits = findCommitsForUnit(activityDir, unitType, unitId);
103
+ if (commits.length > 0) {
104
+ for (const sha of commits.reverse()) {
105
+ try {
106
+ nativeRevertCommit(basePath, sha);
107
+ commitsReverted++;
108
+ } catch {
109
+ // Revert conflict or already reverted — skip
110
+ try { nativeRevertAbort(basePath); } catch { /* no-op */ }
111
+ break;
115
112
  }
116
113
  }
117
114
  }
118
115
  } finally {
119
- // 6. Re-derive state — always invalidate caches even if git operations fail
116
+ // 4. Re-derive state — always invalidate caches even if git operations fail
120
117
  invalidateAllCaches();
121
118
  await deriveState(basePath);
122
119
  }
123
120
 
124
121
  // Build result message
125
122
  const results: string[] = [`Undone: ${unitType} (${unitId})`];
126
- results.push(` - Removed from completed-units.json`);
127
123
  if (summaryRemoved) results.push(` - Deleted summary artifact`);
128
124
  if (planUpdated) results.push(` - Unchecked task in PLAN`);
129
125
  if (commitsReverted > 0) {
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import {
4
4
  gsdRoot,
@@ -8,8 +8,6 @@ import {
8
8
  resolveTaskFile,
9
9
  } from "./paths.js";
10
10
  import { loadFile, parseTaskPlanMustHaves, countMustHavesMentionedInSummary } from "./files.js";
11
- import { loadJsonFileOrNull, saveJsonFile } from "./json-persistence.js";
12
- import { parseUnitId } from "./unit-id.js";
13
11
 
14
12
  export type UnitRuntimePhase =
15
13
  | "dispatched"
@@ -48,23 +46,13 @@ export interface AutoUnitRuntimeRecord {
48
46
  lastRecoveryReason?: "idle" | "hard";
49
47
  }
50
48
 
51
- function isAutoUnitRuntimeRecord(data: unknown): data is AutoUnitRuntimeRecord {
52
- return (
53
- typeof data === "object" &&
54
- data !== null &&
55
- (data as AutoUnitRuntimeRecord).version === 1 &&
56
- typeof (data as AutoUnitRuntimeRecord).unitType === "string" &&
57
- typeof (data as AutoUnitRuntimeRecord).unitId === "string"
58
- );
59
- }
60
-
61
49
  function runtimeDir(basePath: string): string {
62
50
  return join(gsdRoot(basePath), "runtime", "units");
63
51
  }
64
52
 
65
53
  function runtimePath(basePath: string, unitType: string, unitId: string): string {
66
- const sanitizedUnitType = unitType.replace(/[^a-zA-Z0-9._-]+/g, "-");
67
- const sanitizedUnitId = unitId.replace(/[^a-zA-Z0-9._-]+/g, "-");
54
+ const sanitizedUnitType = unitType.replace(/[\/]/g, "-");
55
+ const sanitizedUnitId = unitId.replace(/[\/]/g, "-");
68
56
  return join(runtimeDir(basePath), `${sanitizedUnitType}-${sanitizedUnitId}.json`);
69
57
  }
70
58
 
@@ -75,6 +63,8 @@ export function writeUnitRuntimeRecord(
75
63
  startedAt: number,
76
64
  updates: Partial<AutoUnitRuntimeRecord> = {},
77
65
  ): AutoUnitRuntimeRecord {
66
+ const dir = runtimeDir(basePath);
67
+ mkdirSync(dir, { recursive: true });
78
68
  const path = runtimePath(basePath, unitType, unitId);
79
69
  const prev = readUnitRuntimeRecord(basePath, unitType, unitId);
80
70
  const next: AutoUnitRuntimeRecord = {
@@ -94,12 +84,18 @@ export function writeUnitRuntimeRecord(
94
84
  recoveryAttempts: updates.recoveryAttempts ?? prev?.recoveryAttempts ?? 0,
95
85
  lastRecoveryReason: updates.lastRecoveryReason ?? prev?.lastRecoveryReason,
96
86
  };
97
- saveJsonFile(path, next);
87
+ writeFileSync(path, JSON.stringify(next, null, 2) + "\n", "utf-8");
98
88
  return next;
99
89
  }
100
90
 
101
91
  export function readUnitRuntimeRecord(basePath: string, unitType: string, unitId: string): AutoUnitRuntimeRecord | null {
102
- return loadJsonFileOrNull(runtimePath(basePath, unitType, unitId), isAutoUnitRuntimeRecord);
92
+ const path = runtimePath(basePath, unitType, unitId);
93
+ if (!existsSync(path)) return null;
94
+ try {
95
+ return JSON.parse(readFileSync(path, "utf-8")) as AutoUnitRuntimeRecord;
96
+ } catch {
97
+ return null;
98
+ }
103
99
  }
104
100
 
105
101
  export function clearUnitRuntimeRecord(basePath: string, unitType: string, unitId: string): void {
@@ -132,7 +128,7 @@ export async function inspectExecuteTaskDurability(
132
128
  basePath: string,
133
129
  unitId: string,
134
130
  ): Promise<ExecuteTaskRecoveryStatus | null> {
135
- const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
131
+ const [mid, sid, tid] = unitId.split("/");
136
132
  if (!mid || !sid || !tid) return null;
137
133
 
138
134
  const planAbs = resolveSliceFile(basePath, mid, sid, "PLAN");
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { mkdirSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
- import type { VerificationResult } from "./types.js";
14
+ import type { VerificationResult } from "./types.ts";
15
15
 
16
16
  // ─── JSON Evidence Artifact ──────────────────────────────────────────────────
17
17
 
@@ -20,7 +20,6 @@ export interface EvidenceCheckJSON {
20
20
  exitCode: number;
21
21
  durationMs: number;
22
22
  verdict: "pass" | "fail";
23
- blocking: boolean;
24
23
  }
25
24
 
26
25
  export interface RuntimeErrorJSON {
@@ -81,7 +80,6 @@ export function writeVerificationJSON(
81
80
  exitCode: check.exitCode,
82
81
  durationMs: check.durationMs,
83
82
  verdict: check.exitCode === 0 ? "pass" : "fail",
84
- blocking: check.blocking,
85
83
  })),
86
84
  ...(retryAttempt !== undefined ? { retryAttempt } : {}),
87
85
  ...(maxRetries !== undefined ? { maxRetries } : {}),