gsd-pi 2.45.0-dev.fdcf73c → 2.46.0-dev.5c2b8ed

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 (183) 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/resources/extensions/remote-questions/config.js +42 -0
  56. package/dist/web/standalone/.next/BUILD_ID +1 -1
  57. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  58. package/dist/web/standalone/.next/build-manifest.json +2 -2
  59. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  60. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  61. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  64. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  69. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  71. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/index.html +1 -1
  77. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  84. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  85. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  86. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  87. package/package.json +1 -1
  88. package/packages/pi-coding-agent/package.json +1 -1
  89. package/pkg/package.json +1 -1
  90. package/src/resources/extensions/gsd/auto/loop-deps.ts +0 -19
  91. package/src/resources/extensions/gsd/auto/phases.ts +11 -35
  92. package/src/resources/extensions/gsd/auto/session.ts +0 -18
  93. package/src/resources/extensions/gsd/auto-artifact-paths.ts +131 -0
  94. package/src/resources/extensions/gsd/auto-dashboard.ts +0 -1
  95. package/src/resources/extensions/gsd/auto-post-unit.ts +25 -106
  96. package/src/resources/extensions/gsd/auto-start.ts +1 -3
  97. package/src/resources/extensions/gsd/auto.ts +4 -80
  98. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +22 -0
  99. package/src/resources/extensions/gsd/commands/context.ts +0 -5
  100. package/src/resources/extensions/gsd/commands/handlers/parallel.ts +1 -1
  101. package/src/resources/extensions/gsd/crash-recovery.ts +1 -5
  102. package/src/resources/extensions/gsd/dashboard-overlay.ts +0 -50
  103. package/src/resources/extensions/gsd/doctor-checks.ts +179 -1
  104. package/src/resources/extensions/gsd/doctor-types.ts +7 -1
  105. package/src/resources/extensions/gsd/doctor.ts +4 -1
  106. package/src/resources/extensions/gsd/gsd-db.ts +11 -2
  107. package/src/resources/extensions/gsd/guided-flow.ts +1 -2
  108. package/src/resources/extensions/gsd/parallel-merge.ts +1 -1
  109. package/src/resources/extensions/gsd/parallel-orchestrator.ts +5 -21
  110. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  111. package/src/resources/extensions/gsd/prompts/complete-slice.md +10 -23
  112. package/src/resources/extensions/gsd/prompts/discuss.md +2 -2
  113. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -15
  114. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  115. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +1 -1
  116. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  117. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  118. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  119. package/src/resources/extensions/gsd/prompts/plan-slice.md +4 -2
  120. package/src/resources/extensions/gsd/prompts/queue.md +2 -2
  121. package/src/resources/extensions/gsd/prompts/quick-task.md +2 -0
  122. package/src/resources/extensions/gsd/prompts/reactive-execute.md +1 -1
  123. package/src/resources/extensions/gsd/prompts/research-slice.md +3 -3
  124. package/src/resources/extensions/gsd/prompts/rethink.md +7 -2
  125. package/src/resources/extensions/gsd/prompts/system.md +1 -1
  126. package/src/resources/extensions/gsd/session-lock.ts +0 -4
  127. package/src/resources/extensions/gsd/state.ts +8 -0
  128. package/src/resources/extensions/gsd/sync-lock.ts +94 -0
  129. package/src/resources/extensions/gsd/tests/auto-lock-creation.test.ts +5 -13
  130. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +6 -10
  131. package/src/resources/extensions/gsd/tests/complete-slice.test.ts +264 -228
  132. package/src/resources/extensions/gsd/tests/complete-task.test.ts +317 -250
  133. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +2 -8
  134. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +0 -3
  135. package/src/resources/extensions/gsd/tests/gsd-db.test.ts +1 -1
  136. package/src/resources/extensions/gsd/tests/idle-recovery.test.ts +1 -1
  137. package/src/resources/extensions/gsd/tests/integration-proof.test.ts +15 -24
  138. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +0 -3
  139. package/src/resources/extensions/gsd/tests/md-importer.test.ts +1 -1
  140. package/src/resources/extensions/gsd/tests/memory-store.test.ts +2 -2
  141. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +8 -9
  142. package/src/resources/extensions/gsd/tests/parallel-budget-atomicity.test.ts +0 -1
  143. package/src/resources/extensions/gsd/tests/parallel-crash-recovery.test.ts +0 -7
  144. package/src/resources/extensions/gsd/tests/parallel-merge.test.ts +7 -8
  145. package/src/resources/extensions/gsd/tests/parallel-orchestration.test.ts +20 -24
  146. package/src/resources/extensions/gsd/tests/parallel-worker-monitoring.test.ts +0 -2
  147. package/src/resources/extensions/gsd/tests/plan-milestone.test.ts +9 -6
  148. package/src/resources/extensions/gsd/tests/post-mutation-hook.test.ts +171 -0
  149. package/src/resources/extensions/gsd/tests/projection-regression.test.ts +174 -0
  150. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +15 -14
  151. package/src/resources/extensions/gsd/tests/remote-questions.test.ts +84 -0
  152. package/src/resources/extensions/gsd/tests/reopen-slice.test.ts +155 -0
  153. package/src/resources/extensions/gsd/tests/reopen-task.test.ts +165 -0
  154. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +1 -4
  155. package/src/resources/extensions/gsd/tests/stop-auto-remote.test.ts +2 -3
  156. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +122 -0
  157. package/src/resources/extensions/gsd/tests/unit-ownership.test.ts +175 -0
  158. package/src/resources/extensions/gsd/tests/workflow-events.test.ts +205 -0
  159. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +186 -0
  160. package/src/resources/extensions/gsd/tests/workflow-projections.test.ts +171 -0
  161. package/src/resources/extensions/gsd/tests/write-intercept.test.ts +76 -0
  162. package/src/resources/extensions/gsd/tools/complete-milestone.ts +70 -13
  163. package/src/resources/extensions/gsd/tools/complete-slice.ts +68 -11
  164. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -1
  165. package/src/resources/extensions/gsd/tools/plan-milestone.ts +45 -0
  166. package/src/resources/extensions/gsd/tools/plan-slice.ts +38 -0
  167. package/src/resources/extensions/gsd/tools/plan-task.ts +35 -1
  168. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +39 -1
  169. package/src/resources/extensions/gsd/tools/reopen-slice.ts +125 -0
  170. package/src/resources/extensions/gsd/tools/reopen-task.ts +129 -0
  171. package/src/resources/extensions/gsd/tools/replan-slice.ts +38 -1
  172. package/src/resources/extensions/gsd/types.ts +8 -0
  173. package/src/resources/extensions/gsd/unit-ownership.ts +104 -0
  174. package/src/resources/extensions/gsd/workflow-events.ts +154 -0
  175. package/src/resources/extensions/gsd/workflow-logger.ts +51 -1
  176. package/src/resources/extensions/gsd/workflow-manifest.ts +334 -0
  177. package/src/resources/extensions/gsd/workflow-migration.ts +345 -0
  178. package/src/resources/extensions/gsd/workflow-projections.ts +425 -0
  179. package/src/resources/extensions/gsd/workflow-reconcile.ts +503 -0
  180. package/src/resources/extensions/gsd/write-intercept.ts +90 -0
  181. package/src/resources/extensions/remote-questions/config.ts +45 -0
  182. /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → NtM_pwvGTaXTeqiByOv3A}/_buildManifest.js +0 -0
  183. /package/dist/web/standalone/.next/static/{zWYDSwB-terOjfhmWzqk1 → NtM_pwvGTaXTeqiByOv3A}/_ssgManifest.js +0 -0
@@ -11,13 +11,15 @@ import { MAX_RECOVERY_CHARS, BUDGET_THRESHOLDS, } from "./types.js";
11
11
  import { detectStuck } from "./detect-stuck.js";
12
12
  import { runUnit } from "./run-unit.js";
13
13
  import { debugLog } from "../debug-logger.js";
14
- import { gsdRoot } from "../paths.js";
15
- import { atomicWriteSync } from "../atomic-write.js";
16
14
  import { PROJECT_FILES } from "../detection.js";
17
15
  import { MergeConflictError } from "../git-service.js";
18
16
  import { join } from "node:path";
19
17
  import { existsSync, cpSync } from "node:fs";
20
18
  import { logWarning } from "../workflow-logger.js";
19
+ import { gsdRoot } from "../paths.js";
20
+ import { atomicWriteSync } from "../atomic-write.js";
21
+ import { verifyExpectedArtifact } from "../auto-recovery.js";
22
+ import { writeUnitRuntimeRecord } from "../unit-runtime.js";
21
23
  // ─── generateMilestoneReport ──────────────────────────────────────────────────
22
24
  /**
23
25
  * Generate and write an HTML milestone report snapshot.
@@ -179,11 +181,7 @@ export async function runPreDispatch(ic, loopState) {
179
181
  .filter((m) => m.status !== "complete" && m.status !== "parked")
180
182
  .map((m) => m.id);
181
183
  deps.pruneQueueOrder(s.basePath, pendingIds);
182
- // Reset completed-units tracking for the new milestone — stale entries
183
- // from the previous milestone cause the dispatch loop to skip units
184
- // that haven't actually been completed in the new milestone's context.
185
184
  // Archive the old completed-units.json instead of wiping it (#2313).
186
- s.completedUnits = [];
187
185
  try {
188
186
  const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
189
187
  if (existsSync(completedKeysPath) && s.currentMilestoneId) {
@@ -368,7 +366,7 @@ export async function runDispatch(ic, preData, loopState) {
368
366
  if (loopState.stuckRecoveryAttempts === 0) {
369
367
  // Level 1: try verifying the artifact, then cache invalidation + retry
370
368
  loopState.stuckRecoveryAttempts++;
371
- const artifactExists = deps.verifyExpectedArtifact(unitType, unitId, s.basePath);
369
+ const artifactExists = verifyExpectedArtifact(unitType, unitId, s.basePath);
372
370
  if (artifactExists) {
373
371
  debugLog("autoLoop", {
374
372
  phase: "stuck-recovery",
@@ -588,7 +586,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
588
586
  const unitStartSeq = ic.nextSeq();
589
587
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: unitStartSeq, eventType: "unit-start", data: { unitType, unitId } });
590
588
  deps.captureAvailableSkills();
591
- deps.writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
589
+ writeUnitRuntimeRecord(s.basePath, unitType, unitId, s.currentUnit.startedAt, {
592
590
  phase: "dispatched",
593
591
  wrapupWarningSent: false,
594
592
  timeoutAt: null,
@@ -703,7 +701,7 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
703
701
  });
704
702
  // Write preliminary lock (no session path yet — runUnit creates a new session).
705
703
  // Crash recovery can still identify the in-flight unit from this lock.
706
- deps.writeLock(deps.lockBase(), unitType, unitId, s.completedUnits.length);
704
+ deps.writeLock(deps.lockBase(), unitType, unitId);
707
705
  debugLog("autoLoop", {
708
706
  phase: "runUnit-start",
709
707
  iteration: ic.iteration,
@@ -720,8 +718,8 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
720
718
  });
721
719
  // Now that runUnit has called newSession(), the session file path is correct.
722
720
  const sessionFile = deps.getSessionFile(ctx);
723
- deps.updateSessionLock(deps.lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
724
- deps.writeLock(deps.lockBase(), unitType, unitId, s.completedUnits.length, sessionFile);
721
+ deps.updateSessionLock(deps.lockBase(), unitType, unitId, sessionFile);
722
+ deps.writeLock(deps.lockBase(), unitType, unitId, sessionFile);
725
723
  // Tag the most recent window entry with error info for stuck detection
726
724
  if (unitResult.status === "error" || unitResult.status === "cancelled") {
727
725
  const lastEntry = loopState.recentUnits[loopState.recentUnits.length - 1];
@@ -765,8 +763,8 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
765
763
  warning: "Task completed with 0 tool calls — likely hallucinated, marking as failed",
766
764
  });
767
765
  ctx.ui.notify(`${unitType} ${unitId} completed with 0 tool calls — hallucinated summary, will retry`, "warning");
768
- // Do NOT add to completedUnits fall through to next iteration
769
- // where dispatch will re-derive and re-dispatch this task.
766
+ // Fall through to next iteration where dispatch will re-derive
767
+ // and re-dispatch this task.
770
768
  return { action: "next", data: { unitStartedAt: s.currentUnit.startedAt } };
771
769
  }
772
770
  }
@@ -776,27 +774,8 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
776
774
  }
777
775
  const skipArtifactVerification = unitType.startsWith("hook/") || unitType === "custom-step";
778
776
  const artifactVerified = skipArtifactVerification ||
779
- deps.verifyExpectedArtifact(unitType, unitId, s.basePath);
777
+ verifyExpectedArtifact(unitType, unitId, s.basePath);
780
778
  if (artifactVerified) {
781
- s.completedUnits.push({
782
- type: unitType,
783
- id: unitId,
784
- startedAt: s.currentUnit.startedAt,
785
- finishedAt: Date.now(),
786
- });
787
- if (s.completedUnits.length > 200) {
788
- s.completedUnits = s.completedUnits.slice(-200);
789
- }
790
- // Flush completed-units to disk so the record survives crashes
791
- try {
792
- const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
793
- const keys = s.completedUnits.map((u) => `${u.type}/${u.id}`);
794
- atomicWriteSync(completedKeysPath, JSON.stringify(keys, null, 2));
795
- }
796
- catch (e) {
797
- logWarning("engine", "Failed to flush completed-units to disk", { error: String(e) });
798
- }
799
- deps.clearUnitRuntimeRecord(s.basePath, unitType, unitId);
800
779
  s.unitDispatchCount.delete(`${unitType}/${unitId}`);
801
780
  s.unitRecoveryCount.delete(`${unitType}/${unitId}`);
802
781
  }
@@ -829,8 +808,8 @@ export async function runFinalize(ic, iterData, sidecarItem) {
829
808
  // Sidecar items use lightweight pre-verification opts
830
809
  const preVerificationOpts = sidecarItem
831
810
  ? sidecarItem.kind === "hook"
832
- ? { skipSettleDelay: true, skipDoctor: true, skipStateRebuild: true, skipWorktreeSync: true }
833
- : { skipSettleDelay: true, skipStateRebuild: true }
811
+ ? { skipSettleDelay: true, skipWorktreeSync: true }
812
+ : { skipSettleDelay: true }
834
813
  : undefined;
835
814
  const preResult = await deps.postUnitPreVerification(postUnitCtx, preVerificationOpts);
836
815
  if (preResult === "dispatched") {
@@ -46,7 +46,6 @@ export class AutoSession {
46
46
  // ── Current unit ─────────────────────────────────────────────────────────
47
47
  currentUnit = null;
48
48
  currentUnitRouting = null;
49
- completedUnits = [];
50
49
  currentMilestoneId = null;
51
50
  // ── Model state ──────────────────────────────────────────────────────────
52
51
  autoModeStartModel = null;
@@ -100,14 +99,6 @@ export class AutoSession {
100
99
  get lockBasePath() {
101
100
  return this.originalBasePath || this.basePath;
102
101
  }
103
- completeCurrentUnit() {
104
- if (!this.currentUnit)
105
- return null;
106
- const done = { ...this.currentUnit, finishedAt: Date.now() };
107
- this.completedUnits.push(done);
108
- this.currentUnit = null;
109
- return done;
110
- }
111
102
  reset() {
112
103
  this.clearTimers();
113
104
  // Lifecycle
@@ -129,7 +120,6 @@ export class AutoSession {
129
120
  // Unit
130
121
  this.currentUnit = null;
131
122
  this.currentUnitRouting = null;
132
- this.completedUnits = [];
133
123
  this.currentMilestoneId = null;
134
124
  // Model
135
125
  this.autoModeStartModel = null;
@@ -164,7 +154,6 @@ export class AutoSession {
164
154
  activeRunDir: this.activeRunDir,
165
155
  currentMilestoneId: this.currentMilestoneId,
166
156
  currentUnit: this.currentUnit,
167
- completedUnits: this.completedUnits.length,
168
157
  unitDispatchCount: Object.fromEntries(this.unitDispatchCount),
169
158
  };
170
159
  }
@@ -0,0 +1,112 @@
1
+ // GSD Auto-mode — Artifact Path Resolution
2
+ //
3
+ // resolveExpectedArtifactPath and diagnoseExpectedArtifact moved here from
4
+ // auto-recovery.ts (Phase 5 dead-code cleanup). The artifact verification
5
+ // function was removed entirely — callers now query WorkflowEngine directly.
6
+ import { resolveMilestonePath, resolveSlicePath, relMilestoneFile, relSliceFile, buildMilestoneFileName, buildSliceFileName, buildTaskFileName, } from "./paths.js";
7
+ import { join } from "node:path";
8
+ /**
9
+ * Resolve the expected artifact for a unit to an absolute path.
10
+ */
11
+ export function resolveExpectedArtifactPath(unitType, unitId, base) {
12
+ const parts = unitId.split("/");
13
+ const mid = parts[0];
14
+ const sid = parts[1];
15
+ switch (unitType) {
16
+ case "discuss-milestone": {
17
+ const dir = resolveMilestonePath(base, mid);
18
+ return dir ? join(dir, buildMilestoneFileName(mid, "CONTEXT")) : null;
19
+ }
20
+ case "research-milestone": {
21
+ const dir = resolveMilestonePath(base, mid);
22
+ return dir ? join(dir, buildMilestoneFileName(mid, "RESEARCH")) : null;
23
+ }
24
+ case "plan-milestone": {
25
+ const dir = resolveMilestonePath(base, mid);
26
+ return dir ? join(dir, buildMilestoneFileName(mid, "ROADMAP")) : null;
27
+ }
28
+ case "research-slice": {
29
+ const dir = resolveSlicePath(base, mid, sid);
30
+ return dir ? join(dir, buildSliceFileName(sid, "RESEARCH")) : null;
31
+ }
32
+ case "plan-slice": {
33
+ const dir = resolveSlicePath(base, mid, sid);
34
+ return dir ? join(dir, buildSliceFileName(sid, "PLAN")) : null;
35
+ }
36
+ case "reassess-roadmap": {
37
+ const dir = resolveSlicePath(base, mid, sid);
38
+ return dir ? join(dir, buildSliceFileName(sid, "ASSESSMENT")) : null;
39
+ }
40
+ case "run-uat": {
41
+ const dir = resolveSlicePath(base, mid, sid);
42
+ return dir ? join(dir, buildSliceFileName(sid, "UAT-RESULT")) : null;
43
+ }
44
+ case "execute-task": {
45
+ const tid = parts[2];
46
+ const dir = resolveSlicePath(base, mid, sid);
47
+ return dir && tid
48
+ ? join(dir, "tasks", buildTaskFileName(tid, "SUMMARY"))
49
+ : null;
50
+ }
51
+ case "complete-slice": {
52
+ const dir = resolveSlicePath(base, mid, sid);
53
+ return dir ? join(dir, buildSliceFileName(sid, "SUMMARY")) : null;
54
+ }
55
+ case "validate-milestone": {
56
+ const dir = resolveMilestonePath(base, mid);
57
+ return dir ? join(dir, buildMilestoneFileName(mid, "VALIDATION")) : null;
58
+ }
59
+ case "complete-milestone": {
60
+ const dir = resolveMilestonePath(base, mid);
61
+ return dir ? join(dir, buildMilestoneFileName(mid, "SUMMARY")) : null;
62
+ }
63
+ case "replan-slice": {
64
+ const dir = resolveSlicePath(base, mid, sid);
65
+ return dir ? join(dir, buildSliceFileName(sid, "REPLAN")) : null;
66
+ }
67
+ case "rewrite-docs":
68
+ return null;
69
+ case "reactive-execute":
70
+ // Reactive execute produces multiple task summaries — verified separately
71
+ return null;
72
+ default:
73
+ return null;
74
+ }
75
+ }
76
+ export function diagnoseExpectedArtifact(unitType, unitId, base) {
77
+ const parts = unitId.split("/");
78
+ const mid = parts[0];
79
+ const sid = parts[1];
80
+ switch (unitType) {
81
+ case "discuss-milestone":
82
+ return `${relMilestoneFile(base, mid, "CONTEXT")} (milestone context from discussion)`;
83
+ case "research-milestone":
84
+ return `${relMilestoneFile(base, mid, "RESEARCH")} (milestone research)`;
85
+ case "plan-milestone":
86
+ return `${relMilestoneFile(base, mid, "ROADMAP")} (milestone roadmap)`;
87
+ case "research-slice":
88
+ return `${relSliceFile(base, mid, sid, "RESEARCH")} (slice research)`;
89
+ case "plan-slice":
90
+ return `${relSliceFile(base, mid, sid, "PLAN")} (slice plan)`;
91
+ case "execute-task": {
92
+ const tid = parts[2];
93
+ return `Task ${tid} marked [x] in ${relSliceFile(base, mid, sid, "PLAN")} + summary written`;
94
+ }
95
+ case "complete-slice":
96
+ return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid, "ROADMAP")} + summary + UAT written`;
97
+ case "replan-slice":
98
+ return `${relSliceFile(base, mid, sid, "REPLAN")} + updated ${relSliceFile(base, mid, sid, "PLAN")}`;
99
+ case "rewrite-docs":
100
+ return "Active overrides resolved in .gsd/OVERRIDES.md + plan documents updated";
101
+ case "reassess-roadmap":
102
+ return `${relSliceFile(base, mid, sid, "ASSESSMENT")} (roadmap reassessment)`;
103
+ case "run-uat":
104
+ return `${relSliceFile(base, mid, sid, "UAT-RESULT")} (UAT result)`;
105
+ case "validate-milestone":
106
+ return `${relMilestoneFile(base, mid, "VALIDATION")} (milestone validation report)`;
107
+ case "complete-milestone":
108
+ return `${relMilestoneFile(base, mid, "SUMMARY")} (milestone summary)`;
109
+ default:
110
+ return null;
111
+ }
112
+ }
@@ -13,14 +13,12 @@
13
13
  import { deriveState } from "./state.js";
14
14
  import { loadFile, parseSummary, resolveAllOverrides } from "./files.js";
15
15
  import { loadPrompt } from "./prompt-loader.js";
16
- import { resolveSliceFile, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, gsdRoot, } from "./paths.js";
16
+ import { resolveSliceFile, resolveTaskFile, resolveMilestoneFile, resolveTasksDir, buildTaskFileName, } from "./paths.js";
17
17
  import { invalidateAllCaches } from "./cache.js";
18
18
  import { closeoutUnit } from "./auto-unit-closeout.js";
19
19
  import { autoCommitCurrentBranch, } from "./worktree.js";
20
20
  import { verifyExpectedArtifact, resolveExpectedArtifactPath, } from "./auto-recovery.js";
21
- import { writeUnitRuntimeRecord, clearUnitRuntimeRecord } from "./unit-runtime.js";
22
- import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
23
- import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
21
+ import { regenerateIfMissing } from "./workflow-projections.js";
24
22
  import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
25
23
  import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter } from "./gsd-db.js";
26
24
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
@@ -30,7 +28,6 @@ import { hasPendingCaptures, loadPendingCaptures } from "./captures.js";
30
28
  import { debugLog } from "./debug-logger.js";
31
29
  import { existsSync, unlinkSync } from "node:fs";
32
30
  import { join } from "node:path";
33
- import { atomicWriteSync } from "./atomic-write.js";
34
31
  import { _resetHasChangesCache } from "./native-git-bridge.js";
35
32
  /**
36
33
  * Detect summary files written directly to disk without the LLM calling
@@ -137,8 +134,6 @@ export function detectRogueFileWrites(unitType, unitId, basePath) {
137
134
  }
138
135
  return rogues;
139
136
  }
140
- /** Throttle STATE.md rebuilds — at most once per 30 seconds */
141
- const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
142
137
  /**
143
138
  * Pre-verification processing: parallel worker signal check, cache invalidation,
144
139
  * auto-commit, doctor run, state rebuild, worktree sync, artifact verification.
@@ -232,74 +227,6 @@ export async function postUnitPreVerification(pctx, opts) {
232
227
  catch (e) {
233
228
  debugLog("postUnit", { phase: "github-sync", error: String(e) });
234
229
  }
235
- // Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
236
- if (!opts?.skipDoctor)
237
- try {
238
- const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
239
- const doctorScope = scopeParts.join("/");
240
- const sliceTerminalUnits = new Set(["complete-slice", "run-uat"]);
241
- const effectiveFixLevel = sliceTerminalUnits.has(s.currentUnit.type) ? "all" : "task";
242
- const report = await runGSDDoctor(s.basePath, { fix: true, scope: doctorScope, fixLevel: effectiveFixLevel });
243
- // Human-readable fix notification with details
244
- if (report.fixesApplied.length > 0) {
245
- const fixSummary = report.fixesApplied.length <= 2
246
- ? report.fixesApplied.join("; ")
247
- : `${report.fixesApplied[0]}; +${report.fixesApplied.length - 1} more`;
248
- ctx.ui.notify(`Doctor: ${fixSummary}`, "info");
249
- }
250
- // Proactive health tracking — filter to current milestone to avoid
251
- // cross-milestone stale errors inflating the escalation counter
252
- const currentMilestoneId = s.currentUnit.id.split("/")[0];
253
- const milestoneIssues = currentMilestoneId
254
- ? report.issues.filter(i => i.unitId === currentMilestoneId ||
255
- i.unitId.startsWith(`${currentMilestoneId}/`))
256
- : report.issues;
257
- const summary = summarizeDoctorIssues(milestoneIssues);
258
- // Pass issue details + scope for real-time visibility in the progress widget
259
- const issueDetails = milestoneIssues
260
- .filter(i => i.severity === "error" || i.severity === "warning")
261
- .map(i => ({ code: i.code, message: i.message, severity: i.severity, unitId: i.unitId }));
262
- recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length, issueDetails, report.fixesApplied, doctorScope);
263
- // Check if we should escalate to LLM-assisted heal
264
- if (summary.errors > 0) {
265
- const unresolvedErrors = milestoneIssues
266
- .filter(i => i.severity === "error" && !i.fixable)
267
- .map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
268
- const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
269
- if (escalation.shouldEscalate) {
270
- ctx.ui.notify(`Doctor heal escalation: ${escalation.reason}. Dispatching LLM-assisted heal.`, "warning");
271
- try {
272
- const { formatDoctorIssuesForPrompt, formatDoctorReport } = await import("./doctor.js");
273
- const { dispatchDoctorHeal } = await import("./commands-handlers.js");
274
- const actionable = report.issues.filter(i => i.severity === "error");
275
- const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
276
- const structuredIssues = formatDoctorIssuesForPrompt(actionable);
277
- dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
278
- return "dispatched";
279
- }
280
- catch (e) {
281
- debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
282
- }
283
- }
284
- }
285
- }
286
- catch (e) {
287
- debugLog("postUnit", { phase: "doctor", error: String(e) });
288
- }
289
- // Throttled STATE.md rebuild (skipped for lightweight sidecars)
290
- if (!opts?.skipStateRebuild) {
291
- const now = Date.now();
292
- if (now - s.lastStateRebuildAt >= STATE_REBUILD_MIN_INTERVAL_MS) {
293
- try {
294
- await rebuildState(s.basePath);
295
- s.lastStateRebuildAt = now;
296
- autoCommitCurrentBranch(s.basePath, "state-rebuild", s.currentUnit.id);
297
- }
298
- catch (e) {
299
- debugLog("postUnit", { phase: "state-rebuild", error: String(e) });
300
- }
301
- }
302
- }
303
230
  // Prune dead bg-shell processes
304
231
  try {
305
232
  const { pruneDeadProcesses } = await import("../bg-shell/process-manager.js");
@@ -411,6 +338,27 @@ export async function postUnitPreVerification(pctx, opts) {
411
338
  catch (e) {
412
339
  debugLog("postUnit", { phase: "artifact-verify", error: String(e) });
413
340
  }
341
+ // If verification failed, attempt to regenerate missing projection files
342
+ // from DB data before giving up (e.g. research-slice produces PLAN from engine).
343
+ if (!triggerArtifactVerified) {
344
+ try {
345
+ const parts = s.currentUnit.id.split("/");
346
+ const [mid, sid] = parts;
347
+ if (mid && sid) {
348
+ const regenerated = regenerateIfMissing(s.basePath, mid, sid, "PLAN");
349
+ if (regenerated) {
350
+ // Re-check after regeneration
351
+ triggerArtifactVerified = verifyExpectedArtifact(s.currentUnit.type, s.currentUnit.id, s.basePath);
352
+ if (triggerArtifactVerified) {
353
+ invalidateAllCaches();
354
+ }
355
+ }
356
+ }
357
+ }
358
+ catch (e) {
359
+ debugLog("postUnit", { phase: "regenerate-projection", error: String(e) });
360
+ }
361
+ }
414
362
  // When artifact verification fails for a unit type that has a known expected
415
363
  // artifact, return "retry" so the caller re-dispatches with failure context
416
364
  // instead of blindly re-dispatching the same unit (#1571).
@@ -432,18 +380,7 @@ export async function postUnitPreVerification(pctx, opts) {
432
380
  }
433
381
  }
434
382
  else {
435
- // Hook unit completed — finalize its runtime record
436
- try {
437
- writeUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, {
438
- phase: "finalized",
439
- progressCount: 1,
440
- lastProgressKind: "hook-completed",
441
- });
442
- clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
443
- }
444
- catch (e) {
445
- debugLog("postUnit", { phase: "hook-finalize", error: String(e) });
446
- }
383
+ // Hook unit completed — no additional processing needed
447
384
  }
448
385
  }
449
386
  return "continue";
@@ -517,15 +454,7 @@ export async function postUnitPostVerification(pctx) {
517
454
  }
518
455
  }
519
456
  }
520
- // 3. Remove from s.completedUnits and flush to completed-units.json
521
- s.completedUnits = s.completedUnits.filter(u => !(u.type === trigger.unitType && u.id === trigger.unitId));
522
- try {
523
- const completedKeysPath = join(gsdRoot(s.basePath), "completed-units.json");
524
- const keys = s.completedUnits.map(u => `${u.type}/${u.id}`);
525
- atomicWriteSync(completedKeysPath, JSON.stringify(keys, null, 2));
526
- }
527
- catch { /* non-fatal: disk flush failure */ }
528
- // 4. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
457
+ // 3. Delete the retry_on artifact (e.g. NEEDS-REWORK.md)
529
458
  if (trigger.retryArtifact) {
530
459
  const retryArtifactPath = resolveHookArtifactPath(s.basePath, trigger.unitId, trigger.retryArtifact);
531
460
  if (existsSync(retryArtifactPath)) {
@@ -352,7 +352,6 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
352
352
  });
353
353
  s.autoStartTime = Date.now();
354
354
  s.resourceVersionOnStart = readResourceVersion();
355
- s.completedUnits = [];
356
355
  s.pendingQuickTasks = [];
357
356
  s.currentUnit = null;
358
357
  s.currentMilestoneId = state.activeMilestone?.id ?? null;
@@ -455,8 +454,8 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
455
454
  ? `Will loop through ${pendingCount} milestones.`
456
455
  : "Will loop until milestone complete.";
457
456
  ctx.ui.notify(`${modeLabel} started. ${scopeMsg}`, "info");
458
- updateSessionLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
459
- writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown", 0);
457
+ updateSessionLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown");
458
+ writeLock(lockBase(), "starting", s.currentMilestoneId ?? "unknown");
460
459
  // Secrets collection gate
461
460
  const mid = state.activeMilestone.id;
462
461
  try {
@@ -19,13 +19,11 @@ import { clearActivityLogState } from "./activity-log.js";
19
19
  import { synthesizeCrashRecovery, getDeepDiagnostic, } from "./session-forensics.js";
20
20
  import { writeLock, clearLock, readCrashLock, isLockProcessAlive, } from "./crash-recovery.js";
21
21
  import { acquireSessionLock, getSessionLockStatus, releaseSessionLock, updateSessionLock, } from "./session-lock.js";
22
- import { clearUnitRuntimeRecord, readUnitRuntimeRecord, writeUnitRuntimeRecord, } from "./unit-runtime.js";
23
22
  import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences, getIsolationMode, } from "./preferences.js";
24
23
  import { sendDesktopNotification } from "./notifications.js";
25
24
  import { getBudgetAlertLevel, getNewBudgetAlertLevel, getBudgetEnforcementAction, } from "./auto-budget.js";
26
25
  import { markToolStart as _markToolStart, markToolEnd as _markToolEnd, getOldestInFlightToolAgeMs as _getOldestInFlightToolAgeMs, clearInFlightTools, } from "./auto-tool-tracking.js";
27
26
  import { closeoutUnit } from "./auto-unit-closeout.js";
28
- import { selfHealRuntimeRecords } from "./auto-recovery.js";
29
27
  import { selectAndApplyModel, resolveModelId } from "./auto-model-selection.js";
30
28
  import { syncProjectRootToWorktree, checkResourcesStale, escapeStaleWorktree, } from "./auto-worktree-sync.js";
31
29
  import { resetRoutingHistory, recordOutcome } from "./routing-history.js";
@@ -44,7 +42,7 @@ import { getPriorSliceCompletionBlocker } from "./dispatch-guard.js";
44
42
  import { createAutoWorktree, enterAutoWorktree, teardownAutoWorktree, isInAutoWorktree, getAutoWorktreePath, mergeMilestoneToMain, autoWorktreeBranch, syncWorktreeStateBack, } from "./auto-worktree.js";
45
43
  import { pruneQueueOrder } from "./queue-order.js";
46
44
  import { debugLog, isDebugEnabled, writeDebugSummary } from "./debug-logger.js";
47
- import { verifyExpectedArtifact, reconcileMergeState, } from "./auto-recovery.js";
45
+ import { reconcileMergeState, } from "./auto-recovery.js";
48
46
  import { resolveDispatch, DISPATCH_RULES } from "./auto-dispatch.js";
49
47
  import { initRegistry, convertDispatchRules } from "./rule-registry.js";
50
48
  import { emitJournalEvent as _emitJournalEvent } from "./journal.js";
@@ -145,7 +143,6 @@ export function getAutoDashboardData() {
145
143
  ? (s.autoStartTime > 0 ? Date.now() - s.autoStartTime : 0)
146
144
  : 0,
147
145
  currentUnit: s.currentUnit ? { ...s.currentUnit } : null,
148
- completedUnits: [...s.completedUnits],
149
146
  basePath: s.basePath,
150
147
  totalCost: totals?.cost ?? 0,
151
148
  totalTokens: totals?.tokens.total ?? 0,
@@ -244,7 +241,6 @@ export function checkRemoteAutoSession(projectRoot) {
244
241
  unitType: lock.unitType,
245
242
  unitId: lock.unitId,
246
243
  startedAt: lock.startedAt,
247
- completedUnits: lock.completedUnits,
248
244
  };
249
245
  }
250
246
  export function isStepMode() {
@@ -269,16 +265,12 @@ function clearUnitTimeout() {
269
265
  }
270
266
  clearInFlightTools();
271
267
  }
272
- /** Build snapshot metric opts, enriching with continueHereFired from the runtime record. */
273
- function buildSnapshotOpts(unitType, unitId) {
274
- const runtime = s.currentUnit
275
- ? readUnitRuntimeRecord(s.basePath, unitType, unitId)
276
- : null;
268
+ /** Build snapshot metric opts. */
269
+ function buildSnapshotOpts(_unitType, _unitId) {
277
270
  return {
278
271
  promptCharCount: s.lastPromptCharCount,
279
272
  baselineCharCount: s.lastBaselineCharCount,
280
273
  ...(s.currentUnitRouting ?? {}),
281
- ...(runtime?.continueHereFired ? { continueHereFired: true } : {}),
282
274
  };
283
275
  }
284
276
  function handleLostSessionLock(ctx, lockStatus) {
@@ -586,12 +578,6 @@ export async function pauseAuto(ctx, _pi) {
586
578
  catch {
587
579
  // Non-fatal — best-effort closeout on pause
588
580
  }
589
- try {
590
- clearUnitRuntimeRecord(s.basePath, s.currentUnit.type, s.currentUnit.id);
591
- }
592
- catch {
593
- // Non-fatal
594
- }
595
581
  s.currentUnit = null;
596
582
  }
597
583
  if (lockBase()) {
@@ -710,9 +696,6 @@ function buildLoopDeps() {
710
696
  getMainBranch,
711
697
  // Unit closeout + runtime records
712
698
  closeoutUnit,
713
- verifyExpectedArtifact,
714
- clearUnitRuntimeRecord,
715
- writeUnitRuntimeRecord,
716
699
  recordOutcome,
717
700
  writeLock,
718
701
  captureAvailableSkills,
@@ -861,15 +844,6 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
861
844
  });
862
845
  }
863
846
  invalidateAllCaches();
864
- // Clean stale runtime records left from the paused session
865
- try {
866
- await selfHealRuntimeRecords(s.basePath, ctx);
867
- }
868
- catch (e) {
869
- debugLog("resume-self-heal-runtime-failed", {
870
- error: e instanceof Error ? e.message : String(e),
871
- });
872
- }
873
847
  if (s.pausedSessionFile) {
874
848
  const activityDir = join(gsdRoot(s.basePath), "activity");
875
849
  const recovery = synthesizeCrashRecovery(s.basePath, s.currentUnit?.type ?? "unknown", s.currentUnit?.id ?? "unknown", s.pausedSessionFile ?? undefined, activityDir);
@@ -879,11 +853,9 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
879
853
  }
880
854
  s.pausedSessionFile = null;
881
855
  }
882
- updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
883
- writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown", s.completedUnits.length);
856
+ updateSessionLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
857
+ writeLock(lockBase(), "resuming", s.currentMilestoneId ?? "unknown");
884
858
  logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, s.stepMode ? "Step-mode resumed." : "Auto-mode resumed.", "progress");
885
- // Clear orphaned runtime records from prior process deaths before entering the loop
886
- await selfHealRuntimeRecords(s.basePath, ctx);
887
859
  await autoLoop(ctx, pi, s, buildLoopDeps());
888
860
  cleanupAfterLoopExit(ctx);
889
861
  return;
@@ -905,8 +877,6 @@ export async function startAuto(ctx, pi, base, verboseMode, options) {
905
877
  // Best-effort only — sidebar sync must never block auto-mode startup
906
878
  }
907
879
  logCmuxEvent(loadEffectiveGSDPreferences()?.preferences, requestedStepMode ? "Step-mode started." : "Auto-mode started.", "progress");
908
- // Clear orphaned runtime records from prior process deaths before entering the loop
909
- await selfHealRuntimeRecords(s.basePath, ctx);
910
880
  // Dispatch the first unit
911
881
  await autoLoop(ctx, pi, s, buildLoopDeps());
912
882
  cleanupAfterLoopExit(ctx);
@@ -1005,7 +975,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
1005
975
  s.basePath = targetBasePath;
1006
976
  s.autoStartTime = Date.now();
1007
977
  s.currentUnit = null;
1008
- s.completedUnits = [];
1009
978
  s.pendingQuickTasks = [];
1010
979
  }
1011
980
  const hookUnitType = `hook/${hookName}`;
@@ -1025,14 +994,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
1025
994
  id: triggerUnitId,
1026
995
  startedAt: hookStartedAt,
1027
996
  };
1028
- writeUnitRuntimeRecord(s.basePath, hookUnitType, triggerUnitId, hookStartedAt, {
1029
- phase: "dispatched",
1030
- wrapupWarningSent: false,
1031
- timeoutAt: null,
1032
- lastProgressAt: hookStartedAt,
1033
- progressCount: 0,
1034
- lastProgressKind: "dispatch",
1035
- });
1036
997
  if (hookModel) {
1037
998
  const availableModels = ctx.modelRegistry.getAvailable();
1038
999
  const match = resolveModelId(hookModel, availableModels, ctx.model?.provider);
@@ -1050,7 +1011,7 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
1050
1011
  }
1051
1012
  }
1052
1013
  const sessionFile = ctx.sessionManager.getSessionFile();
1053
- writeLock(lockBase(), hookUnitType, triggerUnitId, s.completedUnits.length, sessionFile);
1014
+ writeLock(lockBase(), hookUnitType, triggerUnitId, sessionFile);
1054
1015
  clearUnitTimeout();
1055
1016
  const supervisor = resolveAutoSupervisorConfig();
1056
1017
  const hookHardTimeoutMs = (supervisor.hard_timeout_minutes ?? 30) * 60 * 1000;
@@ -1058,12 +1019,6 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
1058
1019
  s.unitTimeoutHandle = null;
1059
1020
  if (!s.active)
1060
1021
  return;
1061
- if (s.currentUnit) {
1062
- writeUnitRuntimeRecord(s.basePath, hookUnitType, triggerUnitId, hookStartedAt, {
1063
- phase: "timeout",
1064
- timeoutAt: Date.now(),
1065
- });
1066
- }
1067
1022
  ctx.ui.notify(`Hook ${hookName} exceeded ${supervisor.hard_timeout_minutes ?? 30}min timeout. Pausing auto-mode.`, "warning");
1068
1023
  resetHookState();
1069
1024
  await pauseAuto(ctx, pi);
@@ -1086,4 +1041,5 @@ export async function dispatchHookUnit(ctx, pi, hookName, triggerUnitType, trigg
1086
1041
  // Direct phase dispatch → auto-direct-dispatch.ts
1087
1042
  export { dispatchDirectPhase } from "./auto-direct-dispatch.js";
1088
1043
  // Re-export recovery functions for external consumers
1089
- export { resolveExpectedArtifactPath, verifyExpectedArtifact, writeBlockerPlaceholder, buildLoopRemediationSteps, } from "./auto-recovery.js";
1044
+ export { buildLoopRemediationSteps, } from "./auto-recovery.js";
1045
+ export { resolveExpectedArtifactPath } from "./auto-artifact-paths.js";
@@ -4,6 +4,7 @@ import { buildMilestoneFileName, resolveMilestonePath, resolveSliceFile, resolve
4
4
  import { buildBeforeAgentStartResult } from "./system-context.js";
5
5
  import { handleAgentEnd } from "./agent-end-recovery.js";
6
6
  import { isDepthVerified, isQueuePhaseActive, markDepthVerified, resetWriteGateState, shouldBlockContextWrite } from "./write-gate.js";
7
+ import { isBlockedStateFile, isBashWriteToStateFile, BLOCKED_WRITE_ERROR } from "../write-intercept.js";
7
8
  import { getDiscussionMilestoneId } from "../guided-flow.js";
8
9
  import { loadToolApiKeys } from "../commands-config.js";
9
10
  import { loadFile, saveFile, formatContinue } from "../files.js";
@@ -128,6 +129,23 @@ export function registerHooks(pi) {
128
129
  if (loopCheck.block) {
129
130
  return { block: true, reason: loopCheck.reason };
130
131
  }
132
+ // ── Single-writer engine: block direct writes to STATE.md ──────────
133
+ // Covers write, edit, and bash tools to prevent bypass vectors.
134
+ if (isToolCallEventType("write", event)) {
135
+ if (isBlockedStateFile(event.input.path)) {
136
+ return { block: true, reason: BLOCKED_WRITE_ERROR };
137
+ }
138
+ }
139
+ if (isToolCallEventType("edit", event)) {
140
+ if (isBlockedStateFile(event.input.path)) {
141
+ return { block: true, reason: BLOCKED_WRITE_ERROR };
142
+ }
143
+ }
144
+ if (isToolCallEventType("bash", event)) {
145
+ if (isBashWriteToStateFile(event.input.command)) {
146
+ return { block: true, reason: BLOCKED_WRITE_ERROR };
147
+ }
148
+ }
131
149
  if (!isToolCallEventType("write", event))
132
150
  return;
133
151
  const result = shouldBlockContextWrite(event.toolName, event.input.path, getDiscussionMilestoneId(), isDepthVerified(), isQueuePhaseActive());