@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
@@ -152,35 +152,50 @@ test("ADR-017 (#5700): repair failure throws ReconciliationFailedError with shap
152
152
  );
153
153
  });
154
154
 
155
- test("ADR-017 (#5700): detector failure throws ReconciliationFailedError with shape", async () => {
156
- const handler: DriftHandler = {
157
- kind: "stale-sketch-flag",
155
+ test("ADR-017 (#5700): a detector failure degrades to a blocker without aborting other handlers", async () => {
156
+ // A single detector throwing (e.g. a transient file read error) must NOT
157
+ // abort the whole cycle and hide every later handler's drift. It is collected
158
+ // as a blocker (so dispatch is still gated) while the remaining detectors run
159
+ // and their drift is repaired — graceful degradation, not fail-fast.
160
+ const throwingHandler: DriftHandler = {
161
+ kind: "stale-render",
158
162
  detect: () => {
159
163
  throw new Error("simulated detect failure");
160
164
  },
161
165
  repair: () => {
162
- /* detect fails before repair */
166
+ /* never reached: detect throws */
163
167
  },
164
168
  };
165
169
 
166
- await assert.rejects(
167
- () =>
168
- reconcileBeforeDispatch("/project", {
169
- invalidateStateCache: () => {},
170
- deriveState: async () => makeState(),
171
- registry: [handler],
172
- }),
173
- (err: unknown) => {
174
- assert.ok(err instanceof ReconciliationFailedError, "must be ReconciliationFailedError");
175
- assert.equal(err.failures.length, 1);
176
- assert.equal(err.failures[0]?.drift.kind, "stale-sketch-flag");
177
- assert.ok(err.failures[0]?.cause instanceof Error);
178
- assert.equal((err.failures[0]?.cause as Error).message, "simulated detect failure");
179
- assert.equal(err.pass, 0);
180
- assert.equal(err.detectionFailures.length, 0);
181
- assert.equal(err.persistentDrift.length, 0);
182
- return true;
170
+ let repairCount = 0;
171
+ const workingHandler: DriftHandler = {
172
+ kind: "stale-sketch-flag",
173
+ detect: () =>
174
+ repairCount === 0
175
+ ? [{ kind: "stale-sketch-flag", mid: "M001", sid: "S02" }]
176
+ : [],
177
+ repair: () => {
178
+ repairCount++;
183
179
  },
180
+ };
181
+
182
+ const result = await reconcileBeforeDispatch("/project", {
183
+ invalidateStateCache: () => {},
184
+ deriveState: async () => makeState(),
185
+ registry: [throwingHandler, workingHandler],
186
+ });
187
+
188
+ assert.equal(result.ok, true, "cycle must not abort on a single detector failure");
189
+ // The working handler (registered AFTER the thrower) still detected + repaired.
190
+ assert.equal(repairCount, 1, "later handler must still run despite earlier detect failure");
191
+ assert.equal(result.repaired.length, 1);
192
+ assert.equal(result.repaired[0]?.kind, "stale-sketch-flag");
193
+ // The detector failure surfaces as a blocker so dispatch is still gated.
194
+ assert.ok(
195
+ result.blockers.some(
196
+ (b) => b.includes("stale-render") && b.includes("simulated detect failure"),
197
+ ),
198
+ "detect failure must surface as a blocker",
184
199
  );
185
200
  });
186
201
 
@@ -746,6 +761,46 @@ test("ADR-017 (#5702): missing UAT.md clears stale full_uat_md from DB", async (
746
761
  );
747
762
  });
748
763
 
764
+ test("ADR-017 (#5702): stale-render plan repair works with descriptor-layout milestone dir", async (t) => {
765
+ // Regression for bugbot finding: repairStaleRenderFromBasePath was passing the
766
+ // raw dir segment (e.g. M001-DESCRIPTOR) straight to renderPlanCheckboxes, which
767
+ // queries the DB as getSliceTasks("M001-DESCRIPTOR", …) → empty → throws.
768
+ // After the fix it calls canonicalizeMilestoneId() first.
769
+ const base = mkdtempSync(join(tmpdir(), "gsd-adr017-descriptor-"));
770
+ // Use a descriptor-style directory name (M001-DESCRIPTOR → DB milestone M001)
771
+ const milestoneDir = "M001-DESCRIPTOR";
772
+ const sliceDir = join(base, ".gsd", "milestones", milestoneDir, "slices", "S01");
773
+ mkdirSync(join(sliceDir, "tasks"), { recursive: true });
774
+ t.after(() => {
775
+ try { closeDatabase(); } catch { /* noop */ }
776
+ rmTreeQuiet(base);
777
+ });
778
+
779
+ openDatabase(join(base, ".gsd", "gsd.db"));
780
+ clearRendererCaches();
781
+ // DB uses the canonical ID (M001), directory uses the descriptor name.
782
+ insertMilestone({ id: "M001", title: "Descriptor Test", status: "active" });
783
+ insertSlice({ id: "S01", milestoneId: "M001", title: "Slice", status: "pending" });
784
+ insertTask({ id: "T01", sliceId: "S01", milestoneId: "M001", title: "Task One", status: "done" });
785
+
786
+ const planPath = join(sliceDir, "S01-PLAN.md");
787
+ writeFileSync(planPath, makeStalePlanContent("S01", [
788
+ { id: "T01", title: "Task One", done: false },
789
+ ]));
790
+ clearRendererCaches();
791
+
792
+ const result = await reconcileBeforeDispatch(base, {
793
+ invalidateStateCache: () => {},
794
+ deriveState: async () => makeState(),
795
+ });
796
+
797
+ assert.equal(result.ok, true, "reconcile should succeed with descriptor-layout milestone dir");
798
+ const renderRepaired = result.repaired.find((d) => d.kind === "stale-render");
799
+ assert.ok(renderRepaired, "stale-render drift should be repaired");
800
+ const repairedContent = readFileSync(planPath, "utf-8");
801
+ assert.match(repairedContent, /\[x\][^\n]*T01:/, "T01 checkbox should be checked after repair");
802
+ });
803
+
749
804
  // ─── #5703: stale-worker drift ───────────────────────────────────────────────
750
805
 
751
806
  const DEAD_PID = 999_999_999; // far above any realistic system PID; process.kill(pid, 0) → ESRCH
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Per-phase thinking level resolution — behavior tests for ADR-026 (#497).
3
+ *
4
+ * Verifies the (model, thinking) pairing, the hybrid inline/block precedence,
5
+ * sibling-chain fallback, and static validation through exported runtime APIs.
6
+ */
7
+
8
+ import test from "node:test";
9
+ import assert from "node:assert/strict";
10
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { tmpdir } from "node:os";
13
+
14
+ import { resolveThinkingLevelForUnit, resolveModelWithFallbacksForUnit } from "../preferences-models.ts";
15
+ import { validatePreferences } from "../preferences-validation.ts";
16
+
17
+ function withPreferences<T>(frontmatter: string[], fn: () => T): T {
18
+ const oldHome = process.env.GSD_HOME;
19
+ const home = mkdtempSync(join(tmpdir(), "gsd-thinking-"));
20
+ try {
21
+ process.env.GSD_HOME = home;
22
+ writeFileSync(join(home, "preferences.md"), ["---", ...frontmatter, "---", ""].join("\n"));
23
+ return fn();
24
+ } finally {
25
+ if (oldHome === undefined) delete process.env.GSD_HOME;
26
+ else process.env.GSD_HOME = oldHome;
27
+ rmSync(home, { recursive: true, force: true });
28
+ }
29
+ }
30
+
31
+ test("inline models.<phase>.thinking resolves for that phase", () => {
32
+ withPreferences(
33
+ ["models:", " planning:", " model: planning-model", " thinking: xhigh"],
34
+ () => {
35
+ assert.equal(resolveThinkingLevelForUnit("plan-milestone"), "xhigh");
36
+ // Model still resolves normally alongside the paired thinking.
37
+ assert.equal(resolveModelWithFallbacksForUnit("plan-milestone")?.primary, "planning-model");
38
+ },
39
+ );
40
+ });
41
+
42
+ test("separate thinking block resolves without a model pin", () => {
43
+ withPreferences(["thinking:", " execution: low"], () => {
44
+ assert.equal(resolveThinkingLevelForUnit("execute-task"), "low");
45
+ // No model configured — model resolution stays undefined.
46
+ assert.equal(resolveModelWithFallbacksForUnit("execute-task"), undefined);
47
+ });
48
+ });
49
+
50
+ test("inline thinking on a model-less models entry resolves", () => {
51
+ // `models.planning: { thinking: high }` with no model — inline thinking must
52
+ // be honored even though the entry pins no model (resolveWinningPhase skips it).
53
+ withPreferences(["models:", " planning:", " thinking: high"], () => {
54
+ assert.equal(resolveThinkingLevelForUnit("plan-milestone"), "high");
55
+ // Model resolution still yields nothing for that phase.
56
+ assert.equal(resolveModelWithFallbacksForUnit("plan-milestone"), undefined);
57
+ });
58
+ });
59
+
60
+ test("inline thinking wins over the block for the same phase", () => {
61
+ withPreferences(
62
+ [
63
+ "models:",
64
+ " planning:",
65
+ " model: planning-model",
66
+ " thinking: high",
67
+ "thinking:",
68
+ " planning: low",
69
+ ],
70
+ () => {
71
+ assert.equal(resolveThinkingLevelForUnit("plan-slice"), "high");
72
+ },
73
+ );
74
+ });
75
+
76
+ test("a bare-string model bucket is complemented by the block for the same phase", () => {
77
+ withPreferences(
78
+ ["models:", " execution: execution-model", "thinking:", " execution: high"],
79
+ () => {
80
+ assert.equal(resolveModelWithFallbacksForUnit("execute-task")?.primary, "execution-model");
81
+ assert.equal(resolveThinkingLevelForUnit("execute-task"), "high");
82
+ },
83
+ );
84
+ });
85
+
86
+ test("a claimed model bucket does not inherit a sibling's thinking", () => {
87
+ // discuss has its own model (claims the bucket) but no thinking; planning has
88
+ // thinking. discuss must NOT borrow planning's thinking — the pair is anchored
89
+ // to the resolved model phase.
90
+ withPreferences(
91
+ [
92
+ "models:",
93
+ " discuss: discuss-model",
94
+ " planning:",
95
+ " model: planning-model",
96
+ " thinking: xhigh",
97
+ ],
98
+ () => {
99
+ assert.equal(resolveModelWithFallbacksForUnit("discuss-milestone")?.primary, "discuss-model");
100
+ assert.equal(resolveThinkingLevelForUnit("discuss-milestone"), undefined);
101
+ },
102
+ );
103
+ });
104
+
105
+ test("with no model configured, the thinking block follows the sibling chain", () => {
106
+ // discuss-milestone's chain is [discuss, planning]; only planning is set in
107
+ // the block, so discuss inherits it.
108
+ withPreferences(["thinking:", " planning: high"], () => {
109
+ assert.equal(resolveThinkingLevelForUnit("discuss-milestone"), "high");
110
+ });
111
+ });
112
+
113
+ test("execution_simple inherits the execution block when its own is unset", () => {
114
+ withPreferences(["thinking:", " execution: medium"], () => {
115
+ assert.equal(resolveThinkingLevelForUnit("execute-task-simple"), "medium");
116
+ });
117
+ });
118
+
119
+ test("thinking.execution_simple resolves for execute-task-simple even when model falls through to execution", () => {
120
+ // models.execution wins the model chain for execute-task-simple (no execution_simple model).
121
+ // thinking.execution_simple is explicitly set and must be found — it must not
122
+ // be shadowed by the winning model phase (execution).
123
+ withPreferences(
124
+ ["models:", " execution: execution-model", "thinking:", " execution_simple: low"],
125
+ () => {
126
+ assert.equal(resolveModelWithFallbacksForUnit("execute-task-simple")?.primary, "execution-model");
127
+ assert.equal(resolveThinkingLevelForUnit("execute-task-simple"), "low");
128
+ },
129
+ );
130
+ });
131
+
132
+ test("returns undefined when nothing is configured", () => {
133
+ withPreferences(["models:", " planning: planning-model"], () => {
134
+ assert.equal(resolveThinkingLevelForUnit("execute-task"), undefined);
135
+ assert.equal(resolveThinkingLevelForUnit("plan-milestone"), undefined);
136
+ });
137
+ });
138
+
139
+ test("validation warns on an illegal thinking level in the block", () => {
140
+ const result = validatePreferences({ thinking: { planning: "ultra" } } as never);
141
+ assert.ok(result.warnings.some((w) => w.includes("thinking.planning") && w.includes("not a valid thinking level")));
142
+ // Invalid entry is dropped, not kept.
143
+ assert.equal(result.preferences.thinking, undefined);
144
+ });
145
+
146
+ test("validation warns on an unknown phase key in the block", () => {
147
+ const result = validatePreferences({ thinking: { plannning: "high" } } as never);
148
+ assert.ok(result.warnings.some((w) => w.includes("unknown thinking phase") && w.includes("plannning")));
149
+ });
150
+
151
+ test("validation warns on AND strips an illegal inline models thinking level", () => {
152
+ const result = validatePreferences({
153
+ models: { planning: { model: "m", thinking: "max" } },
154
+ } as never);
155
+ assert.ok(result.warnings.some((w) => w.includes("models.planning.thinking") && w.includes("not a valid thinking level")));
156
+ // The bad thinking field must be stripped so it can't reach the resolver,
157
+ // while the rest of the model config survives.
158
+ const planning = (result.preferences.models as Record<string, { model?: string; thinking?: string }>).planning;
159
+ assert.equal(planning.thinking, undefined);
160
+ assert.equal(planning.model, "m");
161
+ });
162
+
163
+ test("an empty-string model is treated as unconfigured (no {primary: ''})", () => {
164
+ withPreferences(["models:", ' planning: ""'], () => {
165
+ assert.equal(resolveModelWithFallbacksForUnit("plan-milestone"), undefined);
166
+ });
167
+ });
168
+
169
+ test("an empty-string model falls through the sibling chain", () => {
170
+ withPreferences(["models:", ' discuss: ""', " planning: planning-model"], () => {
171
+ // discuss is empty → chain falls through to planning.
172
+ assert.equal(resolveModelWithFallbacksForUnit("discuss-milestone")?.primary, "planning-model");
173
+ });
174
+ });
175
+
176
+ test("a model-less object entry is unconfigured and falls through to a sibling", () => {
177
+ withPreferences(
178
+ ["models:", " discuss:", " provider: anthropic", " planning: planning-model"],
179
+ () => {
180
+ // discuss has no `model` → skipped → planning wins.
181
+ assert.equal(resolveModelWithFallbacksForUnit("discuss-milestone")?.primary, "planning-model");
182
+ },
183
+ );
184
+ });
185
+
186
+ test("a sole model-less object entry yields undefined (no {primary: undefined})", () => {
187
+ withPreferences(["models:", " planning:", " provider: anthropic"], () => {
188
+ assert.equal(resolveModelWithFallbacksForUnit("plan-milestone"), undefined);
189
+ });
190
+ });
191
+
192
+ test("validation drops a phase left hollow after stripping invalid thinking", () => {
193
+ const result = validatePreferences({ models: { planning: { thinking: "bad" } } } as never);
194
+ assert.ok(result.warnings.some((w) => w.includes("models.planning.thinking")));
195
+ // No model remained after stripping → phase dropped entirely, not stored as {}.
196
+ assert.equal((result.preferences.models as Record<string, unknown>).planning, undefined);
197
+ });
198
+
199
+ test("validation accepts a valid thinking block", () => {
200
+ const result = validatePreferences({ thinking: { planning: "xhigh", execution: "low" } } as never);
201
+ assert.deepEqual(result.preferences.thinking, { planning: "xhigh", execution: "low" });
202
+ assert.equal(result.errors.length, 0);
203
+ });
@@ -0,0 +1,170 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+
4
+ import {
5
+ classifyUatContent,
6
+ getDeclaredUatType,
7
+ getUatBrowserToolSupportError,
8
+ hasUatBrowserToolSurface,
9
+ isPartialEligibleUatType,
10
+ resolveEffectiveUatType,
11
+ shouldDispatchUatForContent,
12
+ shouldEscalateArtifactUatToBrowser,
13
+ uatTypeIncludesBrowser,
14
+ validateUatModePolicy,
15
+ } from "../uat-policy.ts";
16
+
17
+ describe("uat-policy", () => {
18
+ it("defaults missing UAT mode to artifact-driven", () => {
19
+ assert.equal(getDeclaredUatType("# UAT\n\nCheck generated files."), "artifact-driven");
20
+ });
21
+
22
+ it("escalates artifact-driven UAT to browser-executable when the spec requires browser work", () => {
23
+ const content = [
24
+ "## UAT Type",
25
+ "- UAT mode: artifact-driven",
26
+ "",
27
+ "## Test",
28
+ "Open the page in a browser and verify the submit button is visible.",
29
+ ].join("\n");
30
+
31
+ assert.equal(shouldEscalateArtifactUatToBrowser(content), true);
32
+ assert.equal(resolveEffectiveUatType(content), "browser-executable");
33
+ assert.equal(shouldDispatchUatForContent(content, undefined), true);
34
+ assert.deepEqual(classifyUatContent(content), {
35
+ declaredType: "artifact-driven",
36
+ effectiveType: "browser-executable",
37
+ browserRequired: true,
38
+ shouldDispatchByDefault: true,
39
+ });
40
+ });
41
+
42
+ it("does not escalate disclaimer-only browser mentions", () => {
43
+ const content = [
44
+ "## UAT Type",
45
+ "- UAT mode: artifact-driven",
46
+ "",
47
+ "## Not Proven By This UAT",
48
+ "- No live browser session was run in this artifact check.",
49
+ ].join("\n");
50
+
51
+ assert.equal(shouldEscalateArtifactUatToBrowser(content), false);
52
+ assert.equal(resolveEffectiveUatType(content), "artifact-driven");
53
+ assert.equal(shouldDispatchUatForContent(content, undefined), false);
54
+ });
55
+
56
+ it("centralizes which UAT modes receive browser tools", () => {
57
+ for (const uatType of ["browser-executable", "live-runtime", "mixed", "human-experience"] as const) {
58
+ assert.equal(uatTypeIncludesBrowser(uatType), true, `${uatType} should include browser tools`);
59
+ }
60
+
61
+ for (const uatType of ["artifact-driven", "runtime-executable"] as const) {
62
+ assert.equal(uatTypeIncludesBrowser(uatType), false, `${uatType} should not include browser tools`);
63
+ }
64
+ });
65
+
66
+ it("detects direct and MCP-shaped browser tool surfaces", () => {
67
+ assert.equal(hasUatBrowserToolSurface(["read", "browser_navigate"]), true);
68
+ assert.equal(hasUatBrowserToolSurface(["read", "mcp__gsd-browser__browser_navigate"]), true);
69
+ assert.equal(hasUatBrowserToolSurface(["read", "gsd_uat_exec"]), false);
70
+ assert.equal(hasUatBrowserToolSurface(undefined), false);
71
+ });
72
+
73
+ it("reports missing browser tools only for browser-backed UAT with a known tool snapshot", () => {
74
+ assert.equal(
75
+ getUatBrowserToolSupportError({
76
+ uatType: "artifact-driven",
77
+ activeTools: ["read", "gsd_uat_exec"],
78
+ milestoneId: "M001",
79
+ sliceId: "S01",
80
+ }),
81
+ null,
82
+ );
83
+ assert.equal(
84
+ getUatBrowserToolSupportError({
85
+ uatType: "browser-executable",
86
+ activeTools: undefined,
87
+ milestoneId: "M001",
88
+ sliceId: "S01",
89
+ }),
90
+ null,
91
+ );
92
+
93
+ const error = getUatBrowserToolSupportError({
94
+ uatType: "browser-executable",
95
+ activeTools: ["read", "gsd_uat_exec"],
96
+ milestoneId: "M001",
97
+ sliceId: "S01",
98
+ });
99
+ assert.match(error ?? "", /Cannot dispatch browser-backed run-uat for M001\/S01/);
100
+ });
101
+
102
+ it("centralizes partial verdict eligibility", () => {
103
+ assert.equal(isPartialEligibleUatType("mixed"), true);
104
+ assert.equal(isPartialEligibleUatType("human-experience"), true);
105
+ assert.equal(isPartialEligibleUatType("live-runtime"), true);
106
+ assert.equal(isPartialEligibleUatType("artifact-driven"), false);
107
+ assert.equal(isPartialEligibleUatType("browser-executable"), false);
108
+ assert.equal(isPartialEligibleUatType("runtime-executable"), false);
109
+ });
110
+
111
+ it("requires runtime evidence for runtime-executable UAT", () => {
112
+ assert.equal(
113
+ validateUatModePolicy({
114
+ uatType: "runtime-executable",
115
+ verdict: "PASS",
116
+ checks: [{ mode: "artifact", result: "PASS" }],
117
+ }),
118
+ "runtime-executable UAT requires runtime evidence",
119
+ );
120
+
121
+ assert.equal(
122
+ validateUatModePolicy({
123
+ uatType: "runtime-executable",
124
+ verdict: "PASS",
125
+ checks: [{ mode: "runtime", result: "PASS" }],
126
+ }),
127
+ null,
128
+ );
129
+ });
130
+
131
+ it("requires browser evidence for browser-executable UAT", () => {
132
+ assert.equal(
133
+ validateUatModePolicy({
134
+ uatType: "browser-executable",
135
+ verdict: "PASS",
136
+ checks: [{ mode: "runtime", result: "PASS" }],
137
+ }),
138
+ "browser-executable UAT requires browser evidence",
139
+ );
140
+
141
+ assert.equal(
142
+ validateUatModePolicy({
143
+ uatType: "browser-executable",
144
+ verdict: "PASS",
145
+ checks: [{ mode: "browser", result: "PASS" }],
146
+ }),
147
+ null,
148
+ );
149
+ });
150
+
151
+ it("allows live-runtime evidence through either runtime or browser checks", () => {
152
+ assert.equal(
153
+ validateUatModePolicy({
154
+ uatType: "live-runtime",
155
+ verdict: "PASS",
156
+ checks: [{ mode: "artifact", result: "PASS" }],
157
+ }),
158
+ "live-runtime UAT requires runtime or browser evidence",
159
+ );
160
+
161
+ assert.equal(
162
+ validateUatModePolicy({
163
+ uatType: "live-runtime",
164
+ verdict: "PASS",
165
+ checks: [{ mode: "browser", result: "PASS" }],
166
+ }),
167
+ null,
168
+ );
169
+ });
170
+ });
@@ -85,6 +85,12 @@ test("#4782 phase 1: no manifest has the same artifact key in inline AND excerpt
85
85
  }
86
86
  });
87
87
 
88
+ test("gate-evaluate manifest matches its prompt builder context", () => {
89
+ assert.deepEqual(UNIT_MANIFESTS["gate-evaluate"].artifacts.inline, ["slice-plan"]);
90
+ assert.deepEqual(UNIT_MANIFESTS["gate-evaluate"].artifacts.excerpt, []);
91
+ assert.deepEqual(UNIT_MANIFESTS["gate-evaluate"].artifacts.onDemand, []);
92
+ });
93
+
88
94
  test("#4782 phase 1: every manifest has a positive maxSystemPromptChars", () => {
89
95
  for (const [unitType, manifest] of Object.entries(UNIT_MANIFESTS)) {
90
96
  assert.ok(
@@ -432,7 +438,7 @@ test('Unit Tool Contract exposes subagent dispatch permissions', () => {
432
438
  });
433
439
  assert.deepEqual(resolveSubagentPermissionContract("gate-evaluate"), {
434
440
  allowed: true,
435
- allowedSubagents: ["reviewer", "security", "tester"],
441
+ allowedSubagents: ["tester"],
436
442
  toolsMode: "planning-dispatch",
437
443
  });
438
444
  assert.deepEqual(resolveSubagentPermissionContract("run-uat"), {
@@ -172,6 +172,13 @@ test("decideFinalizeResult maps step-wizard breaks to completed step exits", ()
172
172
  );
173
173
  });
174
174
 
175
+ test("decideFinalizeResult maps milestone-complete breaks to completed exits", () => {
176
+ assert.deepEqual(
177
+ decideFinalizeResult({ action: "break", reason: "milestone-complete" }),
178
+ { action: "complete-and-break" },
179
+ );
180
+ });
181
+
175
182
  test("decideFinalizeResult maps finalize pause breaks to completed exits", () => {
176
183
  for (const reason of [
177
184
  "post-verification-stopped",