gsd-pi 2.45.0-dev.fdcf73c → 2.46.0-dev.cc9d310

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 (180) hide show
  1. package/dist/resources/extensions/gsd/auto/phases.js +14 -35
  2. package/dist/resources/extensions/gsd/auto/session.js +0 -11
  3. package/dist/resources/extensions/gsd/auto-artifact-paths.js +112 -0
  4. package/dist/resources/extensions/gsd/auto-post-unit.js +25 -96
  5. package/dist/resources/extensions/gsd/auto-start.js +2 -3
  6. package/dist/resources/extensions/gsd/auto.js +8 -52
  7. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +18 -0
  8. package/dist/resources/extensions/gsd/commands/context.js +0 -4
  9. package/dist/resources/extensions/gsd/commands/handlers/parallel.js +1 -1
  10. package/dist/resources/extensions/gsd/crash-recovery.js +2 -4
  11. package/dist/resources/extensions/gsd/dashboard-overlay.js +0 -44
  12. package/dist/resources/extensions/gsd/doctor-checks.js +166 -1
  13. package/dist/resources/extensions/gsd/doctor.js +3 -1
  14. package/dist/resources/extensions/gsd/gsd-db.js +11 -2
  15. package/dist/resources/extensions/gsd/guided-flow.js +1 -2
  16. package/dist/resources/extensions/gsd/parallel-merge.js +1 -1
  17. package/dist/resources/extensions/gsd/parallel-orchestrator.js +5 -18
  18. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  19. package/dist/resources/extensions/gsd/prompts/complete-slice.md +10 -23
  20. package/dist/resources/extensions/gsd/prompts/discuss.md +2 -2
  21. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -15
  22. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  23. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  24. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  25. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  26. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  27. package/dist/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  28. package/dist/resources/extensions/gsd/prompts/queue.md +2 -2
  29. package/dist/resources/extensions/gsd/prompts/quick-task.md +2 -0
  30. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
  31. package/dist/resources/extensions/gsd/prompts/research-slice.md +3 -3
  32. package/dist/resources/extensions/gsd/prompts/rethink.md +7 -2
  33. package/dist/resources/extensions/gsd/prompts/system.md +1 -1
  34. package/dist/resources/extensions/gsd/session-lock.js +1 -3
  35. package/dist/resources/extensions/gsd/state.js +7 -0
  36. package/dist/resources/extensions/gsd/sync-lock.js +89 -0
  37. package/dist/resources/extensions/gsd/tools/complete-milestone.js +58 -12
  38. package/dist/resources/extensions/gsd/tools/complete-slice.js +56 -11
  39. package/dist/resources/extensions/gsd/tools/complete-task.js +50 -2
  40. package/dist/resources/extensions/gsd/tools/plan-milestone.js +37 -1
  41. package/dist/resources/extensions/gsd/tools/plan-slice.js +30 -1
  42. package/dist/resources/extensions/gsd/tools/plan-task.js +27 -1
  43. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +32 -2
  44. package/dist/resources/extensions/gsd/tools/reopen-slice.js +86 -0
  45. package/dist/resources/extensions/gsd/tools/reopen-task.js +90 -0
  46. package/dist/resources/extensions/gsd/tools/replan-slice.js +32 -2
  47. package/dist/resources/extensions/gsd/unit-ownership.js +85 -0
  48. package/dist/resources/extensions/gsd/workflow-events.js +102 -0
  49. package/dist/resources/extensions/gsd/workflow-logger.js +56 -1
  50. package/dist/resources/extensions/gsd/workflow-manifest.js +244 -0
  51. package/dist/resources/extensions/gsd/workflow-migration.js +280 -0
  52. package/dist/resources/extensions/gsd/workflow-projections.js +373 -0
  53. package/dist/resources/extensions/gsd/workflow-reconcile.js +411 -0
  54. package/dist/resources/extensions/gsd/write-intercept.js +84 -0
  55. package/dist/web/standalone/.next/BUILD_ID +1 -1
  56. package/dist/web/standalone/.next/app-path-routes-manifest.json +17 -17
  57. package/dist/web/standalone/.next/build-manifest.json +2 -2
  58. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  59. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  60. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/index.html +1 -1
  76. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app-paths-manifest.json +17 -17
  83. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  84. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  85. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  86. package/package.json +1 -1
  87. package/packages/pi-coding-agent/package.json +1 -1
  88. package/pkg/package.json +1 -1
  89. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
  90. package/src/resources/extensions/gsd/auto/phases.ts +11 -35
  91. package/src/resources/extensions/gsd/auto/session.ts +0 -18
  92. package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
  93. package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
  94. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
  95. package/src/resources/extensions/gsd/auto-start.ts +1 -3
  96. package/src/resources/extensions/gsd/auto.ts +4 -80
  97. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  98. package/src/resources/extensions/gsd/commands/context.ts +0 -5
  99. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
  100. package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
  101. package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
  102. package/src/resources/extensions/gsd/doctor-checks.ts +179 -1
  103. package/src/resources/extensions/gsd/doctor-types.ts +7 -1
  104. package/src/resources/extensions/gsd/doctor.ts +4 -1
  105. package/src/resources/extensions/gsd/gsd-db.ts +11 -2
  106. package/src/resources/extensions/gsd/guided-flow.ts +1 -2
  107. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  108. package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
  109. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  110. package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
  111. package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
  112. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
  113. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  114. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  115. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  116. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  117. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  118. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  119. package/src/resources/extensions/gsd/prompts/queue.md +2 -2
  120. package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
  121. package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
  122. package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
  123. package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
  124. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  125. package/src/resources/extensions/gsd/session-lock.ts +0 -4
  126. package/src/resources/extensions/gsd/state.ts +8 -0
  127. package/src/resources/extensions/gsd/sync-lock.ts +94 -0
  128. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
  129. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
  130. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
  131. package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
  132. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
  133. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
  134. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  135. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
  136. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
  137. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
  138. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  139. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  140. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
  141. package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
  142. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
  143. package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
  144. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
  145. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
  146. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
  147. package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
  148. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
  149. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
  150. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
  151. package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
  152. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
  153. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
  154. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
  155. package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
  156. package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
  157. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
  158. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
  159. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
  160. package/src/resources/extensions/gsd/tools/complete-milestone.ts +70 -13
  161. package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
  162. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
  163. package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
  164. package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
  165. package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
  166. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
  167. package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
  168. package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
  169. package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
  170. package/src/resources/extensions/gsd/types.ts +8 -0
  171. package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
  172. package/src/resources/extensions/gsd/workflow-events.ts +154 -0
  173. package/src/resources/extensions/gsd/workflow-logger.ts +51 -1
  174. package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
  175. package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
  176. package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
  177. package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
  178. package/src/resources/extensions/gsd/write-intercept.ts +90 -0
  179. /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → ZIDqryyYDroh_8AnaAOSG}/_buildManifest.js +0 -0
  180. /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → ZIDqryyYDroh_8AnaAOSG}/_ssgManifest.js +0 -0
@@ -17,12 +17,10 @@ import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
17
17
  import { loadPrompt } from "./prompt-loader.js";
18
18
  import {
19
19
  resolveSliceFile,
20
- resolveSlicePath,
21
20
  resolveTaskFile,
22
21
  resolveMilestoneFile,
23
22
  resolveTasksDir,
24
23
  buildTaskFileName,
25
- gsdRoot,
26
24
  } from "./paths.js";
27
25
  import { invalidateAllCaches } from "./cache.js";
28
26
  import { closeoutUnit, type CloseoutOptions } from "./auto-unit-closeout.js";
@@ -34,9 +32,7 @@ import {
34
32
  verifyExpectedArtifact,
35
33
  resolveExpectedArtifactPath,
36
34
  } from "./auto-recovery.js";
37
- import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.js";
38
- import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
39
- import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
35
+ import { regenerateIfMissing } from "./workflow-projections.js";
40
36
  import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
41
37
  import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
42
38
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
@@ -57,9 +53,8 @@ import {
57
53
  unitVerb,
58
54
  hideFooter,
59
55
  } from "./auto-dashboard.js";
60
- import { existsSync, unlinkSync, readFileSync, writeFileSync } from "node:fs";
56
+ import { existsSync, unlinkSync } from "node:fs";
61
57
  import { join } from "node:path";
62
- import { atomicWriteSync } from "./atomic-write.js";
63
58
  import { _resetHasChangesCache } from "./native-git-bridge.js";
64
59
 
65
60
  // ─── Rogue File Detection ──────────────────────────────────────────────────
@@ -186,13 +181,8 @@ export function detectRogueFileWrites(
186
181
  return rogues;
187
182
  }
188
183
 
189
- /** Throttle STATE.md rebuilds — at most once per 30 seconds */
190
- const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
191
-
192
184
  export interface PreVerificationOpts {
193
185
  skipSettleDelay?: boolean;
194
- skipDoctor?: boolean;
195
- skipStateRebuild?: boolean;
196
186
  skipWorktreeSync?: boolean;
197
187
  }
198
188
 
@@ -306,78 +296,6 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
306
296
  debugLog("postUnit", { phase: "github-sync", error: String(e) });
307
297
  }
308
298
 
309
- // Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
310
- if (!opts?.skipDoctor) try {
311
- const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
312
- const doctorScope = scopeParts.join("/");
313
- const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
314
- const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" as const : "task" as const;
315
- const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
316
- // Human-readable fix notification with details
317
- if (report.fixesApplied.length > 0) {
318
- const fixSummary = report.fixesApplied.length <= 2
319
- ? report.fixesApplied.join("; ")
320
- : `${report.fixesApplied[0]}; +${report.fixesApplied.length - 1} more`;
321
- ctx.ui.notify(`Doctor: ${fixSummary}`, "info");
322
- }
323
-
324
- // Proactive health tracking — filter to current milestone to avoid
325
- // cross-milestone stale errors inflating the escalation counter
326
- const currentMilestoneId = s.currentUnit.id.split("/")[0];
327
- const milestoneIssues = currentMilestoneId
328
- ? report.issues.filter(i =>
329
- i.unitId === currentMilestoneId ||
330
- i.unitId.startsWith(`${currentMilestoneId}/`))
331
- : report.issues;
332
- const summary = summarizeDoctorIssues(milestoneIssues);
333
- // Pass issue details + scope for real-time visibility in the progress widget
334
- const issueDetails = milestoneIssues
335
- .filter(i => i.severity === "error" || i.severity === "warning")
336
- .map(i => ({ code: i.code, message: i.message, severity: i.severity, unitId: i.unitId }));
337
- recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length, issueDetails, report.fixesApplied, doctorScope);
338
-
339
- // Check if we should escalate to LLM-assisted heal
340
- if (summary.errors > 0) {
341
- const unresolvedErrors = milestoneIssues
342
- .filter(i => i.severity === "error" && !i.fixable)
343
- .map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
344
- const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
345
- if (escalation.shouldEscalate) {
346
- ctx.ui.notify(
347
- `Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`,
348
- "warning",
349
- );
350
- try {
351
- const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
352
- const { dispatchDoctorHeal } = await import("./commands-handlers.js");
353
- const actionable = report.issues.filter(i => i.severity === "error");
354
- const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
355
- const structuredIssues = formatDoctorIssuesForPrompt(actionable);
356
- dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
357
- return "dispatched";
358
- } catch (e) {
359
- debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
360
- }
361
- }
362
- }
363
- } catch (e) {
364
- debugLog("postUnit", { phase: "doctor", error: String(e) });
365
- }
366
-
367
- // Throttled STATE.md rebuild (skipped for lightweight sidecars)
368
- if (!opts?.skipStateRebuild) {
369
- const now = Date.now();
370
- if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
371
- try {
372
- await rebuildState(s.basePath);
373
- s.lastStateRebuildAt = now;
374
- autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
375
- } catch (e) {
376
- debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
377
- }
378
- }
379
- }
380
-
381
299
  // Prune dead bg-shell processes
382
300
  try {
383
301
  const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
@@ -503,6 +421,27 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
503
421
  debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
504
422
  }
505
423
 
424
+ // If verification failed, attempt to regenerate missing projection files
425
+ // from DB data before giving up (e.g. research-slice produces PLAN from engine).
426
+ if (!triggerArtifactVerified) {
427
+ try {
428
+ const parts = s.currentUnit.id.split("/");
429
+ const [mid, sid] = parts;
430
+ if (mid && sid) {
431
+ const regenerated = regenerateIfMissing(s.basePath, mid, sid, "PLAN");
432
+ if (regenerated) {
433
+ // Re-check after regeneration
434
+ triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
435
+ if (triggerArtifactVerified) {
436
+ invalidateAllCaches();
437
+ }
438
+ }
439
+ }
440
+ } catch (e) {
441
+ debugLog("postUnit", { phase: "regenerate-projection", error: String(e) });
442
+ }
443
+ }
444
+
506
445
  // When artifact verification fails for a unit type that has a known expected
507
446
  // artifact, return "retry" so the caller re-dispatches with failure context
508
447
  // instead of blindly re-dispatching the same unit (#1571).
@@ -526,17 +465,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
526
465
  }
527
466
  }
528
467
  } else {
529
- // Hook unit completed — finalize its runtime record
530
- try {
531
- writeUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, {
532
- phase: "finalized",
533
- progressCount: 1,
534
- lastProgressKind: "hook-completed",
535
- });
536
- clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
537
- } catch (e) {
538
- debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
539
- }
468
+ // Hook unit completed — no additional processing needed
540
469
  }
541
470
  }
542
471
 
@@ -625,17 +554,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
625
554
  }
626
555
  }
627
556
 
628
- // 3. Remove from s.completedUnits and flush to completed-units.json
629
- s.completedUnits = s.completedUnits.filter(
630
- u => !(u.type === trigger.unitType && u.id === trigger.unitId),
631
- );
632
- try {
633
- const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
634
- const keys = s.completedUnits.map(u => `${u.type}/${u.id}`);
635
- atomicWriteSync(completedKeysPath, JSON.stringify(keys, null, 2));
636
- } catch { /* non-fatal: disk flush failure */ }
637
-
638
- // 4. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
557
+ // 3. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
639
558
  if (trigger.retryArtifact) {
640
559
  const retryArtifactPath = resolveHookArtifactPath(s.basePath, trigger.unitId, trigger.retryArtifact);
641
560
  if (existsSync(retryArtifactPath)) {
@@ -494,7 +494,6 @@ export async function bootstrapAutoSession(
494
494
  });
495
495
  s.autoStartTime = Date.now();
496
496
  s.resourceVersionOnStart = readResourceVersion();
497
- s.completedUnits = [];
498
497
  s.pendingQuickTasks = [];
499
498
  s.currentUnit = null;
500
499
  s.currentMilestoneId = state.activeMilestone?.id ?? null;
@@ -624,9 +623,8 @@ export async function bootstrapAutoSession(
624
623
  lockBase(),
625
624
  "starting",
626
625
  s.currentMilestoneId ?? "unknown",
627
- 0,
628
626
  );
629
- writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
627
+ writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown");
630
628
 
631
629
  // Secrets collection gate
632
630
  const mid = state.activeMilestone!.id;
@@ -52,12 +52,6 @@ import {
52
52
  updateSessionLock,
53
53
  } from "./session-lock.js";
54
54
  import type { SessionLockStatus } from "./session-lock.js";
55
- import {
56
- clearUnitRuntimeRecord,
57
- inspectExecuteTaskDurability,
58
- readUnitRuntimeRecord,
59
- writeUnitRuntimeRecord,
60
- } from "./unit-runtime.js";
61
55
  import {
62
56
  resolveAutoSupervisorConfig,
63
57
  loadEffectiveGSDPreferences,
@@ -81,7 +75,6 @@ import {
81
75
  } from "./auto-tool-tracking.js";
82
76
  import { closeoutUnit } from "./auto-unit-closeout.js";
83
77
  import { recoverTimedOutUnit } from "./auto-timeout-recovery.js";
84
- import { selfHealRuntimeRecords } from "./auto-recovery.js";
85
78
  import { selectAndApplyModel, resolveModelId } from "./auto-model-selection.js";
86
79
  import {
87
80
  syncProjectRootToWorktree,
@@ -155,10 +148,6 @@ import { pruneQueueOrder } from "./queue-order.js";
155
148
 
156
149
  import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
157
150
  import {
158
- resolveExpectedArtifactPath,
159
- verifyExpectedArtifact,
160
- writeBlockerPlaceholder,
161
- diagnoseExpectedArtifact,
162
151
  buildLoopRemediationSteps,
163
152
  reconcileMergeState,
164
153
  } from "./auto-recovery.js";
@@ -213,7 +202,6 @@ import {
213
202
  NEW_SESSION_TIMEOUT_MS,
214
203
  } from "./auto/session.js";
215
204
  import type {
216
- CompletedUnit,
217
205
  CurrentUnit,
218
206
  UnitRouting,
219
207
  StartModel,
@@ -225,7 +213,6 @@ export {
225
213
  NEW_SESSION_TIMEOUT_MS,
226
214
  } from "./auto/session.js";
227
215
  export type {
228
- CompletedUnit,
229
216
  CurrentUnit,
230
217
  UnitRouting,
231
218
  StartModel,
@@ -335,7 +322,6 @@ export function getAutoDashboardData(): AutoDashboardData {
335
322
  ? (s.autoStartTime > 0 ? Date.now() - s.autoStartTime : 0)
336
323
  : 0,
337
324
  currentUnit: s.currentUnit ? { ...s.currentUnit } : null,
338
- completedUnits: [...s.completedUnits],
339
325
  basePath: s.basePath,
340
326
  totalCost: totals?.cost ?? 0,
341
327
  totalTokens: totals?.tokens.total ?? 0,
@@ -447,7 +433,6 @@ export function checkRemoteAutoSession(projectRoot: string): {
447
433
  unitType?: string;
448
434
  unitId?: string;
449
435
  startedAt?: string;
450
- completedUnits?: number;
451
436
  } {
452
437
  const lock = readCrashLock(projectRoot);
453
438
  if (!lock) return { running: false };
@@ -463,7 +448,6 @@ export function checkRemoteAutoSession(projectRoot: string): {
463
448
  unitType: lock.unitType,
464
449
  unitId: lock.unitId,
465
450
  startedAt: lock.startedAt,
466
- completedUnits: lock.completedUnits,
467
451
  };
468
452
  }
469
453
 
@@ -491,23 +475,19 @@ function clearUnitTimeout(): void {
491
475
  clearInFlightTools();
492
476
  }
493
477
 
494
- /** Build snapshot metric opts, enriching with continueHereFired from the runtime record. */
478
+ /** Build snapshot metric opts. */
495
479
  function buildSnapshotOpts(
496
- unitType: string,
497
- unitId: string,
480
+ _unitType: string,
481
+ _unitId: string,
498
482
  ): {
499
483
  continueHereFired?: boolean;
500
484
  promptCharCount?: number;
501
485
  baselineCharCount?: number;
502
486
  } & Record<string, unknown> {
503
- const runtime = s.currentUnit
504
- ? readUnitRuntimeRecord(s.basePath, unitType, unitId)
505
- : null;
506
487
  return {
507
488
  promptCharCount: s.lastPromptCharCount,
508
489
  baselineCharCount: s.lastBaselineCharCount,
509
490
  ...(s.currentUnitRouting ?? {}),
510
- ...(runtime?.continueHereFired ? { continueHereFired: true } : {}),
511
491
  };
512
492
  }
513
493
 
@@ -848,11 +828,6 @@ export async function pauseAuto(
848
828
  } catch {
849
829
  // Non-fatal — best-effort closeout on pause
850
830
  }
851
- try {
852
- clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
853
- } catch {
854
- // Non-fatal
855
- }
856
831
  s.currentUnit = null;
857
832
  }
858
833
 
@@ -993,9 +968,6 @@ function buildLoopDeps(): LoopDeps {
993
968
  getMainBranch,
994
969
  // Unit closeout + runtime records
995
970
  closeoutUnit,
996
- verifyExpectedArtifact,
997
- clearUnitRuntimeRecord,
998
- writeUnitRuntimeRecord,
999
971
  recordOutcome,
1000
972
  writeLock,
1001
973
  captureAvailableSkills,
@@ -1168,15 +1140,6 @@ export async function startAuto(
1168
1140
  }
1169
1141
  invalidateAllCaches();
1170
1142
 
1171
- // Clean stale runtime records left from the paused session
1172
- try {
1173
- await selfHealRuntimeRecords(s.basePath, ctx);
1174
- } catch (e) {
1175
- debugLog("resume-self-heal-runtime-failed", {
1176
- error: e instanceof Error ? e.message : String(e),
1177
- });
1178
- }
1179
-
1180
1143
  if (s.pausedSessionFile) {
1181
1144
  const activityDir = join(gsdRoot(s.basePath), "activity");
1182
1145
  const recovery = synthesizeCrashRecovery(
@@ -1200,19 +1163,14 @@ export async function startAuto(
1200
1163
  lockBase(),
1201
1164
  "resuming",
1202
1165
  s.currentMilestoneId ?? "unknown",
1203
- s.completedUnits.length,
1204
1166
  );
1205
1167
  writeLock(
1206
1168
  lockBase(),
1207
1169
  "resuming",
1208
1170
  s.currentMilestoneId ?? "unknown",
1209
- s.completedUnits.length,
1210
1171
  );
1211
1172
  logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
1212
1173
 
1213
- // Clear orphaned runtime records from prior process deaths before entering the loop
1214
- await selfHealRuntimeRecords(s.basePath, ctx);
1215
-
1216
1174
  await autoLoop(ctx, pi, s, buildLoopDeps());
1217
1175
  cleanupAfterLoopExit(ctx);
1218
1176
  return;
@@ -1244,9 +1202,6 @@ export async function startAuto(
1244
1202
  }
1245
1203
  logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
1246
1204
 
1247
- // Clear orphaned runtime records from prior process deaths before entering the loop
1248
- await selfHealRuntimeRecords(s.basePath, ctx);
1249
-
1250
1205
  // Dispatch the first unit
1251
1206
  await autoLoop(ctx, pi, s, buildLoopDeps());
1252
1207
  cleanupAfterLoopExit(ctx);
@@ -1387,7 +1342,6 @@ export async function dispatchHookUnit(
1387
1342
  s.basePath = targetBasePath;
1388
1343
  s.autoStartTime = Date.now();
1389
1344
  s.currentUnit = null;
1390
- s.completedUnits = [];
1391
1345
  s.pendingQuickTasks = [];
1392
1346
  }
1393
1347
 
@@ -1412,21 +1366,6 @@ export async function dispatchHookUnit(
1412
1366
  startedAt: hookStartedAt,
1413
1367
  };
1414
1368
 
1415
- writeUnitRuntimeRecord(
1416
- s.basePath,
1417
- hookUnitType,
1418
- triggerUnitId,
1419
- hookStartedAt,
1420
- {
1421
- phase: "dispatched",
1422
- wrapupWarningSent: false,
1423
- timeoutAt: null,
1424
- lastProgressAt: hookStartedAt,
1425
- progressCount: 0,
1426
- lastProgressKind: "dispatch",
1427
- },
1428
- );
1429
-
1430
1369
  if (hookModel) {
1431
1370
  const availableModels = ctx.modelRegistry.getAvailable();
1432
1371
  const match = resolveModelId(hookModel, availableModels, ctx.model?.provider);
@@ -1450,7 +1389,6 @@ export async function dispatchHookUnit(
1450
1389
  lockBase(),
1451
1390
  hookUnitType,
1452
1391
  triggerUnitId,
1453
- s.completedUnits.length,
1454
1392
  sessionFile,
1455
1393
  );
1456
1394
 
@@ -1460,18 +1398,6 @@ export async function dispatchHookUnit(
1460
1398
  s.unitTimeoutHandle = setTimeout(async () => {
1461
1399
  s.unitTimeoutHandle = null;
1462
1400
  if (!s.active) return;
1463
- if (s.currentUnit) {
1464
- writeUnitRuntimeRecord(
1465
- s.basePath,
1466
- hookUnitType,
1467
- triggerUnitId,
1468
- hookStartedAt,
1469
- {
1470
- phase: "timeout",
1471
- timeoutAt: Date.now(),
1472
- },
1473
- );
1474
- }
1475
1401
  ctx.ui.notify(
1476
1402
  `Hook ${hookName} exceeded ${supervisor.hard_timeout_minutes ?? 30}min timeout. Pausing auto-mode.`,
1477
1403
  "warning",
@@ -1503,8 +1429,6 @@ export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
1503
1429
 
1504
1430
  // Re-export recovery functions for external consumers
1505
1431
  export {
1506
- resolveExpectedArtifactPath,
1507
- verifyExpectedArtifact,
1508
- writeBlockerPlaceholder,
1509
1432
  buildLoopRemediationSteps,
1510
1433
  } from "./auto-recovery.js";
1434
+ export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
@@ -7,6 +7,7 @@ import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolve
7
7
  import { buildBeforeAgentStartResult } from "./system-context.js";
8
8
  import { handleAgentEnd } from "./agent-end-recovery.js";
9
9
  import { clearDiscussionFlowState, isDepthVerified, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite } from "./write-gate.js";
10
+ import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
10
11
  import { getDiscussionMilestoneId } from "../guided-flow.js";
11
12
  import { loadToolApiKeys } from "../commands-config.js";
12
13
  import { loadFile, saveFile, formatContinue } from "../files.js";
@@ -135,7 +136,28 @@ export function registerHooks(pi: ExtensionAPI): void {
135
136
  return { block: true, reason: loopCheck.reason };
136
137
  }
137
138
 
139
+ // ── Single-writer engine: block direct writes to STATE.md ──────────
140
+ // Covers write, edit, and bash tools to prevent bypass vectors.
141
+ if (isToolCallEventType("write", event)) {
142
+ if (isBlockedStateFile(event.input.path)) {
143
+ return { block: true, reason: BLOCKED_WRITE_ERROR };
144
+ }
145
+ }
146
+
147
+ if (isToolCallEventType("edit", event)) {
148
+ if (isBlockedStateFile(event.input.path)) {
149
+ return { block: true, reason: BLOCKED_WRITE_ERROR };
150
+ }
151
+ }
152
+
153
+ if (isToolCallEventType("bash", event)) {
154
+ if (isBashWriteToStateFile(event.input.command)) {
155
+ return { block: true, reason: BLOCKED_WRITE_ERROR };
156
+ }
157
+ }
158
+
138
159
  if (!isToolCallEventType("write", event)) return;
160
+
139
161
  const result = shouldBlockContextWrite(
140
162
  event.toolName,
141
163
  event.input.path,
@@ -47,15 +47,10 @@ export async function guardRemoteSession(
47
47
  return false;
48
48
  }
49
49
 
50
- const unitsMsg = remote.completedUnits != null
51
- ? `${remote.completedUnits} units completed`
52
- : "";
53
-
54
50
  const choice = await showNextAction(ctx, {
55
51
  title: `Auto-mode is running in another terminal (PID ${remote.pid})`,
56
52
  summary: [
57
53
  `Currently executing: ${unitLabel}`,
58
- ...(unitsMsg ? [unitsMsg] : []),
59
54
  ...(remote.startedAt ? [`Started: ${remote.startedAt}`] : []),
60
55
  ],
61
56
  actions: [
@@ -63,7 +63,7 @@ export async function handleParallelCommand(trimmed: string, _ctx: ExtensionComm
63
63
  }
64
64
  const lines = ["# Parallel Workers\n"];
65
65
  for (const worker of workers) {
66
- lines.push(`- **${worker.milestoneId}** (${worker.title}) — ${worker.state} — ${worker.completedUnits} units — $${worker.cost.toFixed(2)}`);
66
+ lines.push(`- **${worker.milestoneId}** (${worker.title}) — ${worker.state} — $${worker.cost.toFixed(2)}`);
67
67
  }
68
68
  const state = getOrchestratorState();
69
69
  if (state) {
@@ -23,7 +23,6 @@ export interface LockData {
23
23
  unitType: string;
24
24
  unitId: string;
25
25
  unitStartedAt: string;
26
- completedUnits: number;
27
26
  /** Path to the pi session JSONL file that was active when this unit started. */
28
27
  sessionFile?: string;
29
28
  }
@@ -37,7 +36,6 @@ export function writeLock(
37
36
  basePath: string,
38
37
  unitType: string,
39
38
  unitId: string,
40
- completedUnits: number,
41
39
  sessionFile?: string,
42
40
  ): void {
43
41
  try {
@@ -47,7 +45,6 @@ export function writeLock(
47
45
  unitType,
48
46
  unitId,
49
47
  unitStartedAt: new Date().toISOString(),
50
- completedUnits,
51
48
  sessionFile,
52
49
  };
53
50
  const lp = lockPath(basePath);
@@ -102,12 +99,11 @@ export function formatCrashInfo(lock: LockData): string {
102
99
  `Previous auto-mode session was interrupted.`,
103
100
  ` Was executing: ${lock.unitType} (${lock.unitId})`,
104
101
  ` Started at: ${lock.unitStartedAt}`,
105
- ` Units completed before crash: ${lock.completedUnits}`,
106
102
  ` PID: ${lock.pid}`,
107
103
  ];
108
104
 
109
105
  // Add recovery guidance based on what was happening when it crashed
110
- if (lock.unitType === "starting" && lock.unitId === "bootstrap" && lock.completedUnits === 0) {
106
+ if (lock.unitType === "starting" && lock.unitId === "bootstrap") {
111
107
  lines.push(`No work was lost. Run /gsd auto to restart.`);
112
108
  } else if (lock.unitType.includes("research") || lock.unitType.includes("plan")) {
113
109
  lines.push(`The ${lock.unitType} unit may be incomplete. Run /gsd auto to re-run it.`);
@@ -99,18 +99,11 @@ export class GSDDashboardOverlay {
99
99
  const currentUnit = dashData.currentUnit
100
100
  ? `${dashData.currentUnit.type}:${dashData.currentUnit.id}:${dashData.currentUnit.startedAt}`
101
101
  : "-";
102
- const lastCompleted = dashData.completedUnits.length > 0
103
- ? dashData.completedUnits[dashData.completedUnits.length - 1]
104
- : null;
105
- const completedKey = lastCompleted
106
- ? `${dashData.completedUnits.length}:${lastCompleted.type}:${lastCompleted.id}:${lastCompleted.finishedAt}`
107
- : "0";
108
102
  return [
109
103
  base,
110
104
  dashData.active ? "1" : "0",
111
105
  dashData.paused ? "1" : "0",
112
106
  currentUnit,
113
- completedKey,
114
107
  ].join("|");
115
108
  }
116
109
 
@@ -458,49 +451,6 @@ export class GSDDashboardOverlay {
458
451
  lines.push(centered(th.fg("dim", "No active milestone.")));
459
452
  }
460
453
 
461
- if (this.dashData.completedUnits.length > 0) {
462
- lines.push(blank());
463
- lines.push(hr());
464
- lines.push(row(th.fg("text", th.bold("Completed"))));
465
- lines.push(blank());
466
-
467
- // Build ledger lookup for budget indicators (last entry wins for retries)
468
- const ledgerLookup = new Map<string, UnitMetrics>();
469
- const currentLedger = getLedger();
470
- if (currentLedger) {
471
- for (const lu of currentLedger.units) {
472
- ledgerLookup.set(`${lu.type}:${lu.id}`, lu);
473
- }
474
- }
475
-
476
- const recent = [...this.dashData.completedUnits].reverse().slice(0, 10);
477
- for (const u of recent) {
478
- // Budget indicators from ledger — use warning glyph for pressured units
479
- const ledgerEntry = ledgerLookup.get(`${u.type}:${u.id}`);
480
- const hadPressure = ledgerEntry?.continueHereFired === true;
481
- const hadTruncation = (ledgerEntry?.truncationSections ?? 0) > 0;
482
- const unitGlyph = hadPressure
483
- ? th.fg(STATUS_COLOR.warning, STATUS_GLYPH.warning)
484
- : th.fg(STATUS_COLOR.done, STATUS_GLYPH.done);
485
- const left = ` ${unitGlyph} ${th.fg("muted", unitLabel(u.type))} ${th.fg("muted", u.id)}`;
486
-
487
- let budgetMarkers = "";
488
- if (hadTruncation) {
489
- budgetMarkers += th.fg("warning", ` ▼${ledgerEntry!.truncationSections}`);
490
- }
491
- if (hadPressure) {
492
- budgetMarkers += th.fg("error", " → wrap-up");
493
- }
494
-
495
- const right = th.fg("dim", formatDuration(u.finishedAt - u.startedAt));
496
- lines.push(row(joinColumns(`${left}${budgetMarkers}`, right, contentWidth)));
497
- }
498
-
499
- if (this.dashData.completedUnits.length > 10) {
500
- lines.push(row(th.fg("dim", ` ...and ${this.dashData.completedUnits.length - 10} more`)));
501
- }
502
- }
503
-
504
454
  const ledger = getLedger();
505
455
  if (ledger && ledger.units.length > 0) {
506
456
  const totals = getProjectTotals(ledger.units);