@opengsd/gsd-pi 1.1.1-dev.9f86580 → 1.1.1-dev.b2556262

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 (261) hide show
  1. package/dist/headless-recover.js +56 -1
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/browser-tools/index.js +39 -22
  4. package/dist/resources/extensions/browser-tools/state.js +12 -0
  5. package/dist/resources/extensions/browser-tools/tools/session.js +3 -2
  6. package/dist/resources/extensions/browser-tools/utils.js +3 -3
  7. package/dist/resources/extensions/gsd/auto/loop.js +4 -2
  8. package/dist/resources/extensions/gsd/auto/phases.js +43 -10
  9. package/dist/resources/extensions/gsd/auto/session.js +20 -1
  10. package/dist/resources/extensions/gsd/auto/workflow-kernel.js +1 -0
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +72 -12
  12. package/dist/resources/extensions/gsd/auto-model-selection.js +128 -9
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +19 -2
  14. package/dist/resources/extensions/gsd/auto-prompts.js +24 -19
  15. package/dist/resources/extensions/gsd/auto-recovery.js +4 -2
  16. package/dist/resources/extensions/gsd/auto-runtime-state.js +3 -0
  17. package/dist/resources/extensions/gsd/auto-start.js +1 -1
  18. package/dist/resources/extensions/gsd/auto.js +14 -11
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  20. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +172 -65
  21. package/dist/resources/extensions/gsd/closeout-wizard.js +32 -9
  22. package/dist/resources/extensions/gsd/commands/handlers/ops.js +2 -9
  23. package/dist/resources/extensions/gsd/commands-maintenance.js +93 -15
  24. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +2 -2
  25. package/dist/resources/extensions/gsd/db-writer.js +35 -0
  26. package/dist/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  27. package/dist/resources/extensions/gsd/gsd-db.js +480 -172
  28. package/dist/resources/extensions/gsd/markdown-renderer.js +37 -53
  29. package/dist/resources/extensions/gsd/md-importer.js +38 -3
  30. package/dist/resources/extensions/gsd/migration-auto-check.js +126 -31
  31. package/dist/resources/extensions/gsd/parsers-legacy.js +23 -0
  32. package/dist/resources/extensions/gsd/planning-path-scope.js +22 -4
  33. package/dist/resources/extensions/gsd/pre-execution-checks.js +10 -2
  34. package/dist/resources/extensions/gsd/preferences-models.js +110 -43
  35. package/dist/resources/extensions/gsd/preferences-types.js +13 -0
  36. package/dist/resources/extensions/gsd/preferences-validation.js +68 -3
  37. package/dist/resources/extensions/gsd/preferences.js +4 -1
  38. package/dist/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  41. package/dist/resources/extensions/gsd/roadmap-slices.js +5 -1
  42. package/dist/resources/extensions/gsd/safety/content-validator.js +6 -4
  43. package/dist/resources/extensions/gsd/source-observations.js +306 -0
  44. package/dist/resources/extensions/gsd/state-reconciliation/drift/completion.js +15 -8
  45. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-render.js +33 -5
  46. package/dist/resources/extensions/gsd/state-reconciliation/drift/stale-worker.js +34 -13
  47. package/dist/resources/extensions/gsd/state-reconciliation/index.js +39 -14
  48. package/dist/resources/extensions/gsd/state-reconciliation/spawn-gate.js +4 -4
  49. package/dist/resources/extensions/gsd/state.js +7 -3
  50. package/dist/resources/extensions/gsd/tool-contract.js +14 -0
  51. package/dist/resources/extensions/gsd/tool-presentation-plan.js +1 -9
  52. package/dist/resources/extensions/gsd/tools/complete-slice.js +7 -6
  53. package/dist/resources/extensions/gsd/tools/plan-slice.js +42 -11
  54. package/dist/resources/extensions/gsd/tools/plan-task.js +7 -1
  55. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +57 -429
  56. package/dist/resources/extensions/gsd/uat-policy.js +130 -0
  57. package/dist/resources/extensions/gsd/uat-run.js +414 -0
  58. package/dist/resources/extensions/gsd/unit-context-manifest.js +3 -4
  59. package/dist/resources/extensions/gsd/verdict-parser.js +3 -8
  60. package/dist/resources/extensions/gsd/workflow-manifest.js +132 -5
  61. package/dist/resources/extensions/gsd/workflow-projections.js +8 -0
  62. package/dist/resources/extensions/gsd/worktree-state-projection.js +18 -17
  63. package/dist/resources/extensions/subagent/agents.js +1 -0
  64. package/dist/resources/extensions/subagent/index.js +27 -12
  65. package/dist/resources/extensions/subagent/launch.js +7 -2
  66. package/dist/web/standalone/.next/BUILD_ID +1 -1
  67. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  68. package/dist/web/standalone/.next/build-manifest.json +2 -2
  69. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  70. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  71. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  72. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  73. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  74. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  75. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  76. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  77. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  78. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  79. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  86. package/dist/web/standalone/.next/server/app/index.html +1 -1
  87. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  94. package/dist/web/standalone/.next/server/chunks/8357.js +1 -1
  95. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  97. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  98. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  99. package/dist/web/standalone/node_modules/@gsd/native/dist/native.js +22 -0
  100. package/dist/web/standalone/node_modules/node-pty/build/Makefile +1 -1
  101. package/package.json +4 -4
  102. package/packages/cloud-mcp-gateway/package.json +2 -2
  103. package/packages/contracts/package.json +1 -1
  104. package/packages/daemon/package.json +4 -4
  105. package/packages/gsd-agent-core/package.json +5 -5
  106. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
  107. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js +21 -23
  108. package/packages/gsd-agent-modes/dist/modes/interactive/components/assistant-message.js.map +1 -1
  109. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts +3 -0
  110. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  111. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js +25 -0
  112. package/packages/gsd-agent-modes/dist/modes/interactive/components/tool-execution.js.map +1 -1
  113. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts +1 -0
  114. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  115. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +66 -12
  116. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  117. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
  118. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js +18 -11
  119. package/packages/gsd-agent-modes/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
  120. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.d.ts.map +1 -1
  121. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js +16 -0
  122. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-chat-render.js.map +1 -1
  123. package/packages/gsd-agent-modes/package.json +7 -7
  124. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  125. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  126. package/packages/mcp-server/package.json +3 -3
  127. package/packages/native/dist/native.js +22 -0
  128. package/packages/native/package.json +1 -1
  129. package/packages/pi-agent-core/package.json +1 -1
  130. package/packages/pi-ai/dist/image-models.generated.d.ts +30 -0
  131. package/packages/pi-ai/dist/image-models.generated.d.ts.map +1 -1
  132. package/packages/pi-ai/dist/image-models.generated.js +30 -0
  133. package/packages/pi-ai/dist/image-models.generated.js.map +1 -1
  134. package/packages/pi-ai/dist/models.generated.d.ts +23 -17
  135. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  136. package/packages/pi-ai/dist/models.generated.js +25 -24
  137. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  138. package/packages/pi-ai/package.json +1 -1
  139. package/packages/pi-coding-agent/dist/core/settings-manager.js +1 -1
  140. package/packages/pi-coding-agent/dist/core/settings-manager.js.map +1 -1
  141. package/packages/pi-coding-agent/dist/theme/themes.js +1 -1
  142. package/packages/pi-coding-agent/dist/theme/themes.js.map +1 -1
  143. package/packages/pi-coding-agent/package.json +7 -7
  144. package/packages/pi-tui/dist/utils.d.ts +11 -0
  145. package/packages/pi-tui/dist/utils.d.ts.map +1 -1
  146. package/packages/pi-tui/dist/utils.js +119 -6
  147. package/packages/pi-tui/dist/utils.js.map +1 -1
  148. package/packages/pi-tui/package.json +2 -1
  149. package/packages/rpc-client/package.json +2 -2
  150. package/pkg/dist/theme/themes.js +1 -1
  151. package/pkg/dist/theme/themes.js.map +1 -1
  152. package/pkg/package.json +1 -1
  153. package/src/resources/extensions/browser-tools/index.ts +39 -22
  154. package/src/resources/extensions/browser-tools/state.ts +13 -0
  155. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +57 -0
  156. package/src/resources/extensions/browser-tools/tools/session.ts +4 -2
  157. package/src/resources/extensions/browser-tools/utils.ts +3 -3
  158. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -0
  159. package/src/resources/extensions/gsd/auto/loop.ts +4 -2
  160. package/src/resources/extensions/gsd/auto/phases.ts +42 -10
  161. package/src/resources/extensions/gsd/auto/session.ts +22 -1
  162. package/src/resources/extensions/gsd/auto/workflow-kernel.ts +1 -0
  163. package/src/resources/extensions/gsd/auto-dispatch.ts +85 -12
  164. package/src/resources/extensions/gsd/auto-model-selection.ts +164 -12
  165. package/src/resources/extensions/gsd/auto-post-unit.ts +20 -2
  166. package/src/resources/extensions/gsd/auto-prompts.ts +23 -20
  167. package/src/resources/extensions/gsd/auto-recovery.ts +22 -3
  168. package/src/resources/extensions/gsd/auto-runtime-state.ts +5 -0
  169. package/src/resources/extensions/gsd/auto-start.ts +1 -1
  170. package/src/resources/extensions/gsd/auto.ts +13 -10
  171. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  172. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +225 -72
  173. package/src/resources/extensions/gsd/closeout-wizard.ts +47 -13
  174. package/src/resources/extensions/gsd/commands/handlers/ops.ts +2 -17
  175. package/src/resources/extensions/gsd/commands-maintenance.ts +124 -13
  176. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +2 -2
  177. package/src/resources/extensions/gsd/db-writer.ts +38 -0
  178. package/src/resources/extensions/gsd/docs/preferences-reference.md +50 -1
  179. package/src/resources/extensions/gsd/gsd-db.ts +564 -186
  180. package/src/resources/extensions/gsd/markdown-renderer.ts +44 -66
  181. package/src/resources/extensions/gsd/md-importer.ts +49 -2
  182. package/src/resources/extensions/gsd/migration-auto-check.ts +154 -34
  183. package/src/resources/extensions/gsd/parsers-legacy.ts +20 -0
  184. package/src/resources/extensions/gsd/planning-path-scope.ts +22 -4
  185. package/src/resources/extensions/gsd/pre-execution-checks.ts +9 -2
  186. package/src/resources/extensions/gsd/preferences-models.ts +112 -43
  187. package/src/resources/extensions/gsd/preferences-types.ts +39 -0
  188. package/src/resources/extensions/gsd/preferences-validation.ts +76 -2
  189. package/src/resources/extensions/gsd/preferences.ts +5 -0
  190. package/src/resources/extensions/gsd/prompts/gate-evaluate.md +1 -1
  191. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  192. package/src/resources/extensions/gsd/prompts/refine-slice.md +1 -1
  193. package/src/resources/extensions/gsd/roadmap-slices.ts +6 -1
  194. package/src/resources/extensions/gsd/safety/content-validator.ts +8 -5
  195. package/src/resources/extensions/gsd/source-observations.ts +402 -0
  196. package/src/resources/extensions/gsd/state-reconciliation/drift/completion.ts +20 -8
  197. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-render.ts +44 -5
  198. package/src/resources/extensions/gsd/state-reconciliation/drift/stale-worker.ts +39 -11
  199. package/src/resources/extensions/gsd/state-reconciliation/index.ts +45 -15
  200. package/src/resources/extensions/gsd/state-reconciliation/spawn-gate.ts +4 -4
  201. package/src/resources/extensions/gsd/state.ts +7 -4
  202. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +15 -0
  203. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +299 -1
  204. package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +32 -0
  205. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +75 -3
  206. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +22 -1
  207. package/src/resources/extensions/gsd/tests/before-provider-context-management.test.ts +145 -0
  208. package/src/resources/extensions/gsd/tests/closeout-wizard.test.ts +44 -0
  209. package/src/resources/extensions/gsd/tests/commands-dispatcher-unmerged-milestone.test.ts +26 -1
  210. package/src/resources/extensions/gsd/tests/content-validator.test.ts +74 -0
  211. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +16 -2
  212. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +1 -11
  213. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +64 -0
  214. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +15 -0
  215. package/src/resources/extensions/gsd/tests/gsd-recover.test.ts +62 -1
  216. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +15 -0
  217. package/src/resources/extensions/gsd/tests/markdown-renderer.test.ts +42 -0
  218. package/src/resources/extensions/gsd/tests/migration-auto-check.test.ts +99 -0
  219. package/src/resources/extensions/gsd/tests/plan-slice.test.ts +99 -2
  220. package/src/resources/extensions/gsd/tests/plan-task.test.ts +19 -0
  221. package/src/resources/extensions/gsd/tests/preferences.test.ts +14 -0
  222. package/src/resources/extensions/gsd/tests/prefs-wizard-coverage.test.ts +1 -0
  223. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +9 -0
  224. package/src/resources/extensions/gsd/tests/register-hooks-depth-verification.test.ts +101 -1
  225. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +2 -2
  226. package/src/resources/extensions/gsd/tests/runtime-invariant-modules.test.ts +8 -0
  227. package/src/resources/extensions/gsd/tests/schema-v21-sequence.test.ts +5 -3
  228. package/src/resources/extensions/gsd/tests/schema-v27-v28-sequence.test.ts +162 -18
  229. package/src/resources/extensions/gsd/tests/skipped-validation-db-atomicity.test.ts +8 -0
  230. package/src/resources/extensions/gsd/tests/source-observations.test.ts +275 -0
  231. package/src/resources/extensions/gsd/tests/stale-queued-milestone.test.ts +43 -0
  232. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +76 -21
  233. package/src/resources/extensions/gsd/tests/thinking-level-resolution.test.ts +203 -0
  234. package/src/resources/extensions/gsd/tests/uat-policy.test.ts +170 -0
  235. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +7 -1
  236. package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
  237. package/src/resources/extensions/gsd/tests/workflow-manifest.test.ts +306 -1
  238. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +73 -6
  239. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +511 -1
  240. package/src/resources/extensions/gsd/tests/worktree-state-projection.test.ts +44 -0
  241. package/src/resources/extensions/gsd/tool-contract.ts +28 -0
  242. package/src/resources/extensions/gsd/tool-presentation-plan.ts +1 -11
  243. package/src/resources/extensions/gsd/tools/complete-slice.ts +7 -6
  244. package/src/resources/extensions/gsd/tools/plan-slice.ts +54 -12
  245. package/src/resources/extensions/gsd/tools/plan-task.ts +8 -1
  246. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +66 -526
  247. package/src/resources/extensions/gsd/types.ts +1 -0
  248. package/src/resources/extensions/gsd/uat-policy.ts +191 -0
  249. package/src/resources/extensions/gsd/uat-run.ts +550 -0
  250. package/src/resources/extensions/gsd/unit-context-manifest.ts +3 -4
  251. package/src/resources/extensions/gsd/verdict-parser.ts +3 -10
  252. package/src/resources/extensions/gsd/workflow-manifest.ts +193 -7
  253. package/src/resources/extensions/gsd/workflow-projections.ts +9 -0
  254. package/src/resources/extensions/gsd/worktree-state-projection.ts +22 -22
  255. package/src/resources/extensions/shared/tests/format-utils.test.ts +8 -3
  256. package/src/resources/extensions/subagent/agents.ts +4 -0
  257. package/src/resources/extensions/subagent/index.ts +28 -3
  258. package/src/resources/extensions/subagent/launch.ts +8 -0
  259. package/src/resources/extensions/subagent/tests/model-override.test.ts +31 -0
  260. /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_buildManifest.js +0 -0
  261. /package/dist/web/standalone/.next/static/{zzYMrKpPGfRQRxSFO32Jr → tJOKQbQRO-9MiFDO8DIDS}/_ssgManifest.js +0 -0
@@ -7,6 +7,8 @@ import {
7
7
  invalidateStateCache as defaultInvalidate,
8
8
  } from "../state.js";
9
9
  import { clearParseCache as defaultClearParseCache } from "../files.js";
10
+ import { clearPathCache } from "../paths.js";
11
+ import { logWarning } from "../workflow-logger.js";
10
12
  import type { GSDState } from "../types.js";
11
13
 
12
14
  import {
@@ -68,18 +70,24 @@ export async function reconcileBeforeDispatch(
68
70
  const stateSnapshot = await deps.deriveState(basePath, deps.deriveStateOptions);
69
71
  const ctx: DriftContext = { basePath, state: stateSnapshot };
70
72
 
71
- const drift = await detectAllDrift(stateSnapshot, ctx, registry, pass);
73
+ const detection = await detectAllDrift(stateSnapshot, ctx, registry);
74
+ const drift = detection.records;
72
75
  if (drift.length === 0) {
73
76
  return {
74
77
  ok: true,
75
78
  stateSnapshot,
76
79
  repaired,
77
- blockers: stateSnapshot.blockers ?? [],
80
+ blockers: [
81
+ ...new Set([
82
+ ...(stateSnapshot.blockers ?? []),
83
+ ...detection.detectBlockers,
84
+ ]),
85
+ ],
78
86
  };
79
87
  }
80
88
 
81
89
  const failures: ReconciliationFailureDetail[] = [];
82
- const blockers: string[] = [];
90
+ const blockers: string[] = [...detection.detectBlockers];
83
91
  let repairedThisPass = false;
84
92
  for (const record of drift) {
85
93
  const handler = registry.find((h) => h.kind === record.kind);
@@ -107,7 +115,11 @@ export async function reconcileBeforeDispatch(
107
115
  }
108
116
 
109
117
  if (repairedThisPass) {
118
+ // A repair may have mutated on-disk structure (e.g. quarantined a slice
119
+ // dir). Clear both the parse cache and the path/dir cache centrally so
120
+ // later passes and any subsequent repair see fresh filesystem state.
110
121
  clearParseCache();
122
+ clearPathCache();
111
123
  }
112
124
  if (blockers.length > 0) {
113
125
  let blockerState = stateSnapshot;
@@ -132,10 +144,11 @@ export async function reconcileBeforeDispatch(
132
144
  deps.invalidateStateCache();
133
145
  const finalState = await deps.deriveState(basePath, deps.deriveStateOptions);
134
146
  const finalCtx: DriftContext = { basePath, state: finalState };
135
- const persistent = await detectAllDrift(finalState, finalCtx, registry);
147
+ const finalDetection = await detectAllDrift(finalState, finalCtx, registry);
148
+ const persistent = finalDetection.records;
136
149
 
137
150
  if (persistent.length > 0) {
138
- const blockers: string[] = [];
151
+ const blockers: string[] = [...finalDetection.detectBlockers];
139
152
  const unblockedPersistent: DriftRecord[] = [];
140
153
  for (const record of persistent) {
141
154
  const handler = registry.find((h) => h.kind === record.kind);
@@ -161,28 +174,45 @@ export async function reconcileBeforeDispatch(
161
174
  ok: true,
162
175
  stateSnapshot: finalState,
163
176
  repaired,
164
- blockers: finalState.blockers ?? [],
177
+ blockers: [
178
+ ...new Set([
179
+ ...(finalState.blockers ?? []),
180
+ ...finalDetection.detectBlockers,
181
+ ]),
182
+ ],
165
183
  };
166
184
  }
167
185
 
186
+ interface DetectionOutcome {
187
+ records: DriftRecord[];
188
+ /** One blocker string per handler whose detect() threw. */
189
+ detectBlockers: string[];
190
+ }
191
+
192
+ /**
193
+ * Run every detector. A single detector throwing (e.g. a transient file read
194
+ * error) must NOT abort the whole cycle and hide every later handler's drift —
195
+ * it is collected as a blocker so dispatch is still gated, while the remaining
196
+ * detectors run and their drift gets repaired (graceful degradation, ADR-017).
197
+ */
168
198
  async function detectAllDrift(
169
199
  state: GSDState,
170
200
  ctx: DriftContext,
171
201
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
172
202
  registry: ReadonlyArray<DriftHandler<any>>,
173
- pass?: number,
174
- ): Promise<DriftRecord[]> {
175
- const collected: DriftRecord[] = [];
203
+ ): Promise<DetectionOutcome> {
204
+ const records: DriftRecord[] = [];
205
+ const detectBlockers: string[] = [];
176
206
  for (const handler of registry) {
177
207
  try {
178
208
  const detected = await handler.detect(state, ctx);
179
- collected.push(...detected);
209
+ records.push(...detected);
180
210
  } catch (cause) {
181
- throw new ReconciliationFailedError({
182
- failures: [{ drift: { kind: handler.kind } as DriftRecord, cause }],
183
- pass,
184
- });
211
+ const message = cause instanceof Error ? cause.message : String(cause);
212
+ const blocker = `Drift detection failed for "${handler.kind}": ${message}`;
213
+ logWarning("reconcile", blocker);
214
+ detectBlockers.push(blocker);
185
215
  }
186
216
  }
187
- return collected;
217
+ return { records, detectBlockers };
188
218
  }
@@ -21,10 +21,10 @@ export interface SpawnGateDeps extends Partial<ReconciliationDeps> {
21
21
  }
22
22
 
23
23
  /**
24
- * Run reconciliation before spawning workers. Returns ok=true when the run
25
- * completed without throwing (blockers ride along but don't fail the gate —
26
- * spawn callers can choose how to handle them). On
27
- * ReconciliationFailedError, returns ok=false with the error message so the
24
+ * Run reconciliation before spawning workers. Returns ok=true only when the run
25
+ * completed without throwing AND surfaced no blockers; any blocker fails the
26
+ * gate (ok=false, reason carries the first blocker) so callers must not spawn.
27
+ * On ReconciliationFailedError, returns ok=false with the error message so the
28
28
  * caller can surface it to the user without re-throwing.
29
29
  *
30
30
  * Other unexpected errors propagate; they are not part of the drift
@@ -483,7 +483,7 @@ async function buildRegistryAndFindActive(
483
483
  let activeMilestoneSlices: SliceRow[] = [];
484
484
  let activeMilestoneFound = false;
485
485
  let activeMilestoneHasDraft = false;
486
- let firstDeferredQueuedShell: { id: string; title: string; deps: string[] } | null = null;
486
+ let firstDeferredQueuedShell: { id: string; title: string; deps: string[]; hasDraftContext: boolean } | null = null;
487
487
 
488
488
  for (const m of milestones) {
489
489
  if (parkedMilestoneIds.has(m.id)) {
@@ -505,6 +505,8 @@ async function buildRegistryAndFindActive(
505
505
  const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
506
506
 
507
507
  const title = stripMilestonePrefix(m.title) || m.id;
508
+ const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
509
+ const hasDraftContext = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
508
510
 
509
511
  if (!activeMilestoneFound) {
510
512
  const deps = m.depends_on;
@@ -515,9 +517,9 @@ async function buildRegistryAndFindActive(
515
517
  continue;
516
518
  }
517
519
 
518
- if (m.status === 'queued' && slices.length === 0) {
520
+ if (m.status === 'queued' && slices.length === 0 && !hasContext) {
519
521
  if (!firstDeferredQueuedShell) {
520
- firstDeferredQueuedShell = { id: m.id, title, deps };
522
+ firstDeferredQueuedShell = { id: m.id, title, deps, hasDraftContext };
521
523
  }
522
524
  registry.push({ id: m.id, title, status: 'pending', ...(deps.length > 0 ? { dependsOn: deps } : {}) });
523
525
  continue;
@@ -531,7 +533,7 @@ async function buildRegistryAndFindActive(
531
533
  continue;
532
534
  }
533
535
 
534
- if (m.status === 'needs-discussion') activeMilestoneHasDraft = true;
536
+ if ((m.status === 'needs-discussion' && !hasContext) || hasDraftContext) activeMilestoneHasDraft = true;
535
537
 
536
538
  activeMilestone = { id: m.id, title };
537
539
  activeMilestoneSlices = slices;
@@ -548,6 +550,7 @@ async function buildRegistryAndFindActive(
548
550
  activeMilestone = { id: shell.id, title: shell.title };
549
551
  activeMilestoneSlices = [];
550
552
  activeMilestoneFound = true;
553
+ if (shell.hasDraftContext) activeMilestoneHasDraft = true;
551
554
  const entry = registry.find(e => e.id === shell.id);
552
555
  if (entry) entry.status = 'active';
553
556
  }
@@ -36,6 +36,7 @@ import { registerAutoWorker } from "../db/auto-workers.js";
36
36
  import { claimMilestoneLease } from "../db/milestone-leases.js";
37
37
  import { recordDispatchClaim, markCanceled } from "../db/unit-dispatches.js";
38
38
  import { setRuntimeKv, getRuntimeKv } from "../db/runtime-kv.js";
39
+ import { SourceObservationStore } from "../source-observations.js";
39
40
 
40
41
  // ─── Helpers ─────────────────────────────────────────────────────────────────
41
42
 
@@ -1102,6 +1103,7 @@ function makeLoopSession(overrides?: Partial<Record<string, unknown>>) {
1102
1103
  currentMilestoneId: "M001",
1103
1104
  currentUnit: null,
1104
1105
  currentUnitRouting: null,
1106
+ sourceObservations: new SourceObservationStore(),
1105
1107
  completedUnits: [],
1106
1108
  resourceVersionOnStart: null,
1107
1109
  lastPromptCharCount: undefined,
@@ -1126,6 +1128,19 @@ function makeLoopSession(overrides?: Partial<Record<string, unknown>>) {
1126
1128
  newSession: () => Promise.resolve({ cancelled: false }),
1127
1129
  getContextUsage: () => ({ percent: 10, tokens: 1000, limit: 10000 }),
1128
1130
  },
1131
+ setCurrentUnit(this: any, unit: any) {
1132
+ this.currentUnit = unit;
1133
+ this.sourceObservations.beginUnit({
1134
+ unitType: unit.type,
1135
+ unitId: unit.id,
1136
+ startedAt: unit.startedAt,
1137
+ basePath: unit.workspaceRoot ?? this.basePath,
1138
+ });
1139
+ },
1140
+ clearCurrentUnit(this: any) {
1141
+ this.currentUnit = null;
1142
+ this.sourceObservations.clear();
1143
+ },
1129
1144
  clearTimers: () => {},
1130
1145
  ...overrides,
1131
1146
  } as any;
@@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
 
8
8
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
9
 
10
- import { ModelPolicyDispatchBlockedError, resolvePreferredModelConfig, resolveModelId, selectAndApplyModel } from "../auto-model-selection.js";
10
+ import { ModelPolicyDispatchBlockedError, resolvePreferredModelConfig, resolveModelId, selectAndApplyModel, floorThinkingLevelForUnit } from "../auto-model-selection.js";
11
11
 
12
12
  function makeTempDir(prefix: string): string {
13
13
  return mkdtempSync(join(tmpdir(), prefix));
@@ -534,6 +534,304 @@ test("selectAndApplyModel re-applies captured thinking level after setModel succ
534
534
  assert.deepEqual(thinkingLevels, [{ effort: "high" }]);
535
535
  });
536
536
 
537
+ // ─── floorThinkingLevelForUnit (#read-bash-thrash) ─────────────────────
538
+ test("floorThinkingLevelForUnit raises minimal/low to the floor for execute-task", () => {
539
+ assert.equal(floorThinkingLevelForUnit("execute-task", "off" as any), "medium");
540
+ assert.equal(floorThinkingLevelForUnit("execute-task", "minimal" as any), "medium");
541
+ assert.equal(floorThinkingLevelForUnit("execute-task", "low" as any), "medium");
542
+ });
543
+
544
+ test("floorThinkingLevelForUnit never lowers a level already at/above the floor", () => {
545
+ assert.equal(floorThinkingLevelForUnit("execute-task", "medium" as any), "medium");
546
+ assert.equal(floorThinkingLevelForUnit("execute-task", "high" as any), "high");
547
+ assert.equal(floorThinkingLevelForUnit("execute-task", "xhigh" as any), "xhigh");
548
+ });
549
+
550
+ test("floorThinkingLevelForUnit leaves non-execute-task units untouched", () => {
551
+ for (const unit of ["plan-slice", "plan-milestone", "research-milestone", "complete-slice", "validate-milestone"]) {
552
+ assert.equal(floorThinkingLevelForUnit(unit, "minimal" as any), "minimal");
553
+ }
554
+ });
555
+
556
+ test("floorThinkingLevelForUnit passes through null/undefined and unrecognized shapes", () => {
557
+ assert.equal(floorThinkingLevelForUnit("execute-task", null), null);
558
+ assert.equal(floorThinkingLevelForUnit("execute-task", undefined), undefined);
559
+ // A richer host snapshot object must not be coerced into a bare string.
560
+ const snapshot = { effort: "minimal" } as any;
561
+ assert.deepEqual(floorThinkingLevelForUnit("execute-task", snapshot), snapshot);
562
+ });
563
+
564
+ test("selectAndApplyModel raises minimal thinking to the floor for execute-task", async (t) => {
565
+ const originalCwd = process.cwd();
566
+ const tempProject = makeTempDir("gsd-routing-thinking-floor-");
567
+ const thinkingLevels: unknown[] = [];
568
+ t.after(() => {
569
+ process.chdir(originalCwd);
570
+ rmSync(tempProject, { recursive: true, force: true });
571
+ });
572
+
573
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
574
+ writeFileSync(
575
+ join(tempProject, ".gsd", "PREFERENCES.md"),
576
+ ["---", "models:", " execute-task: claude-sonnet-4-6", "---"].join("\n"),
577
+ "utf-8",
578
+ );
579
+ process.chdir(tempProject);
580
+
581
+ await selectAndApplyModel(
582
+ {
583
+ modelRegistry: { getAvailable: () => [{ id: "claude-sonnet-4-6", provider: "anthropic", api: "anthropic-messages" }] },
584
+ sessionManager: { getSessionId: () => "test-session" },
585
+ ui: { notify: () => {} },
586
+ model: { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" },
587
+ } as any,
588
+ {
589
+ setModel: async () => true,
590
+ setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
591
+ emitBeforeModelSelect: async () => undefined,
592
+ getActiveTools: () => [],
593
+ emitAdjustToolSet: async () => undefined,
594
+ setActiveTools: () => {},
595
+ } as any,
596
+ "execute-task",
597
+ "M001/S01/T01",
598
+ tempProject,
599
+ undefined,
600
+ false,
601
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
602
+ undefined,
603
+ true,
604
+ undefined,
605
+ "minimal" as any,
606
+ );
607
+
608
+ assert.deepEqual(thinkingLevels, ["medium"]);
609
+ });
610
+
611
+ test("selectAndApplyModel capability-clamps an unsupported thinking level (ADR-026)", async (t) => {
612
+ const originalCwd = process.cwd();
613
+ const tempProject = makeTempDir("gsd-routing-thinking-clamp-");
614
+ const thinkingLevels: unknown[] = [];
615
+ t.after(() => {
616
+ process.chdir(originalCwd);
617
+ rmSync(tempProject, { recursive: true, force: true });
618
+ });
619
+
620
+ // Reasoning-capable model whose map omits xhigh → xhigh must clamp to high.
621
+ const model = {
622
+ id: "claude-sonnet-4-6",
623
+ provider: "anthropic",
624
+ api: "anthropic-messages",
625
+ reasoning: true,
626
+ thinkingLevelMap: { low: "low", medium: "medium", high: "high" },
627
+ };
628
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
629
+ writeFileSync(
630
+ join(tempProject, ".gsd", "PREFERENCES.md"),
631
+ ["---", "models:", " planning: claude-sonnet-4-6", "---"].join("\n"),
632
+ "utf-8",
633
+ );
634
+ process.chdir(tempProject);
635
+
636
+ await selectAndApplyModel(
637
+ {
638
+ modelRegistry: { getAvailable: () => [model] },
639
+ sessionManager: { getSessionId: () => "test-session" },
640
+ ui: { notify: () => {} },
641
+ model: { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" },
642
+ } as any,
643
+ {
644
+ setModel: async () => true,
645
+ setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
646
+ emitBeforeModelSelect: async () => undefined,
647
+ getActiveTools: () => [],
648
+ emitAdjustToolSet: async () => undefined,
649
+ setActiveTools: () => {},
650
+ } as any,
651
+ "plan-slice",
652
+ "M001/S01",
653
+ tempProject,
654
+ undefined,
655
+ false,
656
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
657
+ undefined,
658
+ true,
659
+ undefined,
660
+ "xhigh" as any,
661
+ );
662
+
663
+ assert.deepEqual(thinkingLevels, ["high"]);
664
+ });
665
+
666
+ test("selectAndApplyModel applies an explicit per-phase thinking level (ADR-026)", async (t) => {
667
+ const originalCwd = process.cwd();
668
+ const tempProject = makeTempDir("gsd-routing-thinking-explicit-");
669
+ const thinkingLevels: unknown[] = [];
670
+ t.after(() => {
671
+ process.chdir(originalCwd);
672
+ rmSync(tempProject, { recursive: true, force: true });
673
+ });
674
+
675
+ const model = {
676
+ id: "claude-sonnet-4-6",
677
+ provider: "anthropic",
678
+ api: "anthropic-messages",
679
+ reasoning: true,
680
+ thinkingLevelMap: { low: "low", medium: "medium", high: "high", xhigh: "xhigh" },
681
+ };
682
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
683
+ writeFileSync(
684
+ join(tempProject, ".gsd", "PREFERENCES.md"),
685
+ ["---", "models:", " planning:", " model: claude-sonnet-4-6", " thinking: xhigh", "---"].join("\n"),
686
+ "utf-8",
687
+ );
688
+ process.chdir(tempProject);
689
+
690
+ await selectAndApplyModel(
691
+ {
692
+ modelRegistry: { getAvailable: () => [model] },
693
+ sessionManager: { getSessionId: () => "test-session" },
694
+ ui: { notify: () => {} },
695
+ model: { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" },
696
+ } as any,
697
+ {
698
+ setModel: async () => true,
699
+ setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
700
+ emitBeforeModelSelect: async () => undefined,
701
+ getActiveTools: () => [],
702
+ emitAdjustToolSet: async () => undefined,
703
+ setActiveTools: () => {},
704
+ } as any,
705
+ "plan-slice",
706
+ "M001/S01",
707
+ tempProject,
708
+ undefined,
709
+ false,
710
+ { provider: "anthropic", id: "claude-sonnet-4-6" },
711
+ undefined,
712
+ true,
713
+ undefined,
714
+ // Session level is "low"; the explicit planning thinking (xhigh) must win.
715
+ "low" as any,
716
+ );
717
+
718
+ assert.deepEqual(thinkingLevels, ["xhigh"]);
719
+ });
720
+
721
+ test("selectAndApplyModel applies explicit thinking with no model pin (interactive, ADR-026)", async (t) => {
722
+ const originalCwd = process.cwd();
723
+ const tempProject = makeTempDir("gsd-routing-thinking-nomodel-");
724
+ const thinkingLevels: unknown[] = [];
725
+ t.after(() => {
726
+ process.chdir(originalCwd);
727
+ rmSync(tempProject, { recursive: true, force: true });
728
+ });
729
+
730
+ const model = {
731
+ id: "claude-sonnet-4-6",
732
+ provider: "anthropic",
733
+ api: "anthropic-messages",
734
+ reasoning: true,
735
+ thinkingLevelMap: { low: "low", medium: "medium", high: "high", xhigh: "xhigh" },
736
+ };
737
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
738
+ // A `thinking:` block with NO models config — the interactive guided-flow
739
+ // scenario the bug report flagged (no per-phase model, no start model).
740
+ writeFileSync(
741
+ join(tempProject, ".gsd", "PREFERENCES.md"),
742
+ ["---", "thinking:", " planning: high", "---"].join("\n"),
743
+ "utf-8",
744
+ );
745
+ process.chdir(tempProject);
746
+
747
+ await selectAndApplyModel(
748
+ {
749
+ modelRegistry: { getAvailable: () => [model] },
750
+ sessionManager: { getSessionId: () => "test-session" },
751
+ ui: { notify: () => {} },
752
+ model: { provider: "anthropic", id: "claude-sonnet-4-6", api: "anthropic-messages" },
753
+ } as any,
754
+ {
755
+ setModel: async () => true,
756
+ setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
757
+ emitBeforeModelSelect: async () => undefined,
758
+ getActiveTools: () => [],
759
+ emitAdjustToolSet: async () => undefined,
760
+ setActiveTools: () => {},
761
+ } as any,
762
+ "plan-slice",
763
+ "M001/S01",
764
+ tempProject,
765
+ undefined,
766
+ false,
767
+ null, // no autoModeStartModel
768
+ undefined,
769
+ false, // isAutoMode = false (interactive)
770
+ undefined,
771
+ undefined, // no captured session thinking level
772
+ );
773
+
774
+ // No model branch runs, but the explicit block thinking must still apply.
775
+ assert.deepEqual(thinkingLevels, ["high"]);
776
+ });
777
+
778
+ test("selectAndApplyModel clamps explicit no-model thinking via ctx.model when registry lookup fails (ADR-026)", async (t) => {
779
+ const originalCwd = process.cwd();
780
+ const tempProject = makeTempDir("gsd-routing-thinking-ctxmodel-");
781
+ const thinkingLevels: unknown[] = [];
782
+ t.after(() => {
783
+ process.chdir(originalCwd);
784
+ rmSync(tempProject, { recursive: true, force: true });
785
+ });
786
+
787
+ // ctx.model carries reasoning capability (map omits xhigh) but the registry
788
+ // returns nothing, so resolveModelId fails and ctx.model is the clamp source.
789
+ const ctxModel = {
790
+ id: "claude-sonnet-4-6",
791
+ provider: "anthropic",
792
+ api: "anthropic-messages",
793
+ reasoning: true,
794
+ thinkingLevelMap: { low: "low", medium: "medium", high: "high" },
795
+ };
796
+ mkdirSync(join(tempProject, ".gsd"), { recursive: true });
797
+ writeFileSync(
798
+ join(tempProject, ".gsd", "PREFERENCES.md"),
799
+ ["---", "thinking:", " planning: xhigh", "---"].join("\n"),
800
+ "utf-8",
801
+ );
802
+ process.chdir(tempProject);
803
+
804
+ await selectAndApplyModel(
805
+ {
806
+ modelRegistry: { getAvailable: () => [] }, // registry lookup fails
807
+ sessionManager: { getSessionId: () => "test-session" },
808
+ ui: { notify: () => {} },
809
+ model: ctxModel,
810
+ } as any,
811
+ {
812
+ setModel: async () => true,
813
+ setThinkingLevel: (level: unknown) => { thinkingLevels.push(level); },
814
+ emitBeforeModelSelect: async () => undefined,
815
+ getActiveTools: () => [],
816
+ emitAdjustToolSet: async () => undefined,
817
+ setActiveTools: () => {},
818
+ } as any,
819
+ "plan-slice",
820
+ "M001/S01",
821
+ tempProject,
822
+ undefined,
823
+ false,
824
+ null,
825
+ undefined,
826
+ false,
827
+ undefined,
828
+ undefined,
829
+ );
830
+
831
+ // xhigh is unsupported by ctx.model → clamped to high, never sent verbatim.
832
+ assert.deepEqual(thinkingLevels, ["high"]);
833
+ });
834
+
537
835
  test("resolveModelId: anthropic wins over claude-code when session provider is not claude-code", () => {
538
836
  const availableModels = [
539
837
  { id: "claude-sonnet-4-6", provider: "claude-code" },
@@ -173,6 +173,38 @@ test("cleanupAfterLoopExit preserves completion closeout surface after stopAuto
173
173
  }
174
174
  });
175
175
 
176
+ test("cleanupAfterLoopExit clears completionStopInProgress even when preserveStepSurfaceAfterLoopExit is also set", async () => {
177
+ const statusCalls: unknown[] = [];
178
+ const widgetCalls: unknown[] = [];
179
+
180
+ autoSession.reset();
181
+ autoSession.active = true;
182
+ autoSession.paused = false;
183
+ autoSession.completionStopInProgress = true;
184
+ autoSession.preserveStepSurfaceAfterLoopExit = true;
185
+
186
+ try {
187
+ await cleanupAfterLoopExit({
188
+ hasUI: true,
189
+ ui: {
190
+ setStatus: (...args: unknown[]) => statusCalls.push(args),
191
+ setWidget: (...args: unknown[]) => widgetCalls.push(args),
192
+ setHeader: () => {},
193
+ notify: () => {},
194
+ },
195
+ } as any);
196
+
197
+ assert.equal(
198
+ autoSession.completionStopInProgress,
199
+ false,
200
+ "completionStopInProgress must be cleared even when preserveStepSurfaceAfterLoopExit was also set",
201
+ );
202
+ assert.equal(autoSession.preserveStepSurfaceAfterLoopExit, false);
203
+ } finally {
204
+ autoSession.reset();
205
+ }
206
+ });
207
+
176
208
  test("pauseAuto preserves artifact retry counts across pause/resume", async () => {
177
209
  const base = mkdtempSync(join(tmpdir(), "gsd-pause-retry-count-"));
178
210
  const previousCwd = process.cwd();