gsd-pi 2.59.0 → 2.60.0-dev.d9052f5

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 (219) hide show
  1. package/dist/resources/extensions/gsd/auto/phases.js +54 -1
  2. package/dist/resources/extensions/gsd/auto-model-selection.js +8 -3
  3. package/dist/resources/extensions/gsd/auto-post-unit.js +40 -1
  4. package/dist/resources/extensions/gsd/auto-prompts.js +13 -0
  5. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +70 -0
  6. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +51 -5
  7. package/dist/resources/extensions/gsd/captures.js +54 -1
  8. package/dist/resources/extensions/gsd/complexity-classifier.js +1 -1
  9. package/dist/resources/extensions/gsd/context-masker.js +68 -0
  10. package/dist/resources/extensions/gsd/docs/preferences-reference.md +7 -0
  11. package/dist/resources/extensions/gsd/gsd-db.js +2 -2
  12. package/dist/resources/extensions/gsd/model-router.js +123 -4
  13. package/dist/resources/extensions/gsd/phase-anchor.js +56 -0
  14. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  15. package/dist/resources/extensions/gsd/preferences-validation.js +46 -0
  16. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  17. package/dist/resources/extensions/gsd/prompts/rethink.md +7 -0
  18. package/dist/resources/extensions/gsd/prompts/triage-captures.md +6 -1
  19. package/dist/resources/extensions/gsd/rethink.js +5 -2
  20. package/dist/resources/extensions/gsd/state.js +1 -1
  21. package/dist/resources/extensions/gsd/status-guards.js +4 -3
  22. package/dist/resources/extensions/gsd/triage-resolution.js +128 -1
  23. package/dist/resources/extensions/gsd/triage-ui.js +12 -3
  24. package/dist/resources/skills/btw/SKILL.md +42 -0
  25. package/dist/web/standalone/.next/BUILD_ID +1 -1
  26. package/dist/web/standalone/.next/app-path-routes-manifest.json +13 -13
  27. package/dist/web/standalone/.next/build-manifest.json +3 -3
  28. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  29. package/dist/web/standalone/.next/required-server-files.json +3 -3
  30. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  31. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  33. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  35. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  36. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  37. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  38. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  39. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  41. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  44. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  45. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  46. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  47. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  48. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  51. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  57. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  63. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  69. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  91. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  97. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  103. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  111. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  113. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  115. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  117. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  119. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  121. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  123. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  127. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  128. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  129. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  130. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  131. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  132. package/dist/web/standalone/.next/server/app/index.html +1 -1
  133. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  134. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  135. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  136. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  137. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  138. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/page.js +2 -2
  140. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/app-paths-manifest.json +13 -13
  142. package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
  143. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  144. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  145. package/dist/web/standalone/.next/server/middleware.js +2 -2
  146. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  147. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  148. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  149. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  150. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  151. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-2f24283c162b6ab3.js → page-f2a7482d42a5614b.js} +1 -1
  152. package/dist/web/standalone/.next/static/chunks/app/{layout-9ecfd95f343793f0.js → layout-a16c7a7ecdf0c2cf.js} +1 -1
  153. package/dist/web/standalone/.next/static/chunks/app/page-0c485498795110d6.js +1 -0
  154. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +1 -0
  155. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +1 -0
  156. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  157. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  158. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  159. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  160. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  161. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  162. package/dist/web/standalone/server.js +1 -1
  163. package/package.json +1 -1
  164. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts +1 -0
  165. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  166. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +6 -0
  167. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  168. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts +2 -0
  169. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.d.ts.map +1 -0
  170. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +122 -0
  171. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -0
  172. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +1 -0
  173. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  174. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +30 -0
  175. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  176. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.d.ts.map +1 -1
  177. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js +1 -7
  178. package/packages/pi-coding-agent/dist/modes/interactive/slash-command-handlers.js.map +1 -1
  179. package/packages/pi-coding-agent/package.json +1 -1
  180. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +156 -0
  181. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +7 -0
  182. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +38 -0
  183. package/packages/pi-coding-agent/src/modes/interactive/slash-command-handlers.ts +1 -8
  184. package/pkg/package.json +1 -1
  185. package/src/resources/extensions/gsd/auto/phases.ts +60 -1
  186. package/src/resources/extensions/gsd/auto-model-selection.ts +12 -3
  187. package/src/resources/extensions/gsd/auto-post-unit.ts +48 -1
  188. package/src/resources/extensions/gsd/auto-prompts.ts +17 -0
  189. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +78 -0
  190. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +53 -4
  191. package/src/resources/extensions/gsd/captures.ts +71 -2
  192. package/src/resources/extensions/gsd/complexity-classifier.ts +1 -1
  193. package/src/resources/extensions/gsd/context-masker.ts +74 -0
  194. package/src/resources/extensions/gsd/docs/preferences-reference.md +7 -0
  195. package/src/resources/extensions/gsd/gsd-db.ts +2 -2
  196. package/src/resources/extensions/gsd/model-router.ts +171 -8
  197. package/src/resources/extensions/gsd/phase-anchor.ts +71 -0
  198. package/src/resources/extensions/gsd/preferences-types.ts +9 -0
  199. package/src/resources/extensions/gsd/preferences-validation.ts +38 -0
  200. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  201. package/src/resources/extensions/gsd/prompts/rethink.md +7 -0
  202. package/src/resources/extensions/gsd/prompts/triage-captures.md +6 -1
  203. package/src/resources/extensions/gsd/rethink.ts +5 -2
  204. package/src/resources/extensions/gsd/state.ts +1 -1
  205. package/src/resources/extensions/gsd/status-guards.ts +4 -3
  206. package/src/resources/extensions/gsd/tests/context-masker.test.ts +122 -0
  207. package/src/resources/extensions/gsd/tests/model-router.test.ts +87 -1
  208. package/src/resources/extensions/gsd/tests/phase-anchor.test.ts +83 -0
  209. package/src/resources/extensions/gsd/tests/status-guards.test.ts +4 -0
  210. package/src/resources/extensions/gsd/tests/stop-backtrack.test.ts +216 -0
  211. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +1 -1
  212. package/src/resources/extensions/gsd/triage-resolution.ts +144 -1
  213. package/src/resources/extensions/gsd/triage-ui.ts +12 -3
  214. package/src/resources/skills/btw/SKILL.md +42 -0
  215. package/dist/web/standalone/.next/static/chunks/app/page-62be3b5fa91e4c8f.js +0 -1
  216. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +0 -1
  217. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +0 -1
  218. /package/dist/web/standalone/.next/static/{DGvT_c5Vb7Wu3X-fEOVUU → JVkoVYumy0cDhOQISEYdG}/_buildManifest.js +0 -0
  219. /package/dist/web/standalone/.next/static/{DGvT_c5Vb7Wu3X-fEOVUU → JVkoVYumy0cDhOQISEYdG}/_ssgManifest.js +0 -0
@@ -487,11 +487,47 @@ export async function runDispatch(ic, preData, loopState) {
487
487
  }
488
488
  // ─── runGuards ────────────────────────────────────────────────────────────────
489
489
  /**
490
- * Phase 2: Guards — budget ceiling, context window, secrets re-check.
490
+ * Phase 2: Guards — stop directives, budget ceiling, context window, secrets re-check.
491
491
  * Returns break to exit the loop, or next to proceed to dispatch.
492
492
  */
493
493
  export async function runGuards(ic, mid) {
494
494
  const { ctx, pi, s, deps, prefs } = ic;
495
+ // ── Stop/Backtrack directive guard (#3487) ──
496
+ // Check for unexecuted stop or backtrack captures BEFORE dispatching any unit.
497
+ // This ensures user "halt" directives are honored immediately.
498
+ try {
499
+ const { loadStopCaptures, markCaptureExecuted } = await import("../captures.js");
500
+ const stopCaptures = loadStopCaptures(s.basePath);
501
+ if (stopCaptures.length > 0) {
502
+ const first = stopCaptures[0];
503
+ const isBacktrack = first.classification === "backtrack";
504
+ const label = isBacktrack
505
+ ? `Backtrack directive: ${first.text}`
506
+ : `Stop directive: ${first.text}`;
507
+ ctx.ui.notify(label, "warning");
508
+ deps.sendDesktopNotification("GSD", label, "warning", "stop-directive", basename(s.originalBasePath || s.basePath));
509
+ // Mark all stop/backtrack captures as executed so they don't re-fire
510
+ for (const cap of stopCaptures) {
511
+ markCaptureExecuted(s.basePath, cap.id);
512
+ }
513
+ // For backtrack captures, write the backtrack trigger before pausing
514
+ if (isBacktrack) {
515
+ try {
516
+ const { executeBacktrack } = await import("../triage-resolution.js");
517
+ executeBacktrack(s.basePath, mid, first);
518
+ }
519
+ catch (e) {
520
+ debugLog("guards", { phase: "backtrack-execution-error", error: String(e) });
521
+ }
522
+ }
523
+ await deps.pauseAuto(ctx, pi);
524
+ debugLog("autoLoop", { phase: "exit", reason: isBacktrack ? "user-backtrack" : "user-stop" });
525
+ return { action: "break", reason: isBacktrack ? "user-backtrack" : "user-stop" };
526
+ }
527
+ }
528
+ catch (e) {
529
+ debugLog("guards", { phase: "stop-guard-error", error: String(e) });
530
+ }
495
531
  // Budget ceiling guard
496
532
  const budgetCeiling = prefs?.budget_ceiling;
497
533
  if (budgetCeiling !== undefined && budgetCeiling > 0) {
@@ -843,6 +879,23 @@ export async function runUnitPhase(ic, iterData, loopState, sidecarItem) {
843
879
  s.unitDispatchCount.delete(`${unitType}/${unitId}`);
844
880
  s.unitRecoveryCount.delete(`${unitType}/${unitId}`);
845
881
  }
882
+ // Write phase handoff anchor after successful research/planning completion
883
+ const anchorPhases = new Set(["research-milestone", "research-slice", "plan-milestone", "plan-slice"]);
884
+ if (artifactVerified && mid && anchorPhases.has(unitType)) {
885
+ try {
886
+ const { writePhaseAnchor } = await import("../phase-anchor.js");
887
+ writePhaseAnchor(s.basePath, mid, {
888
+ phase: unitType,
889
+ milestoneId: mid,
890
+ generatedAt: new Date().toISOString(),
891
+ intent: `Completed ${unitType} for ${unitId}`,
892
+ decisions: [],
893
+ blockers: [],
894
+ nextSteps: [],
895
+ });
896
+ }
897
+ catch { /* non-fatal — anchor is advisory */ }
898
+ }
846
899
  deps.emitJournalEvent({ ts: new Date().toISOString(), flowId: ic.flowId, seq: ic.nextSeq(), eventType: "unit-end", data: { unitType, unitId, status: unitResult.status, artifactVerified, ...(unitResult.errorContext ? { errorContext: unitResult.errorContext } : {}) }, causedBy: { flowId: ic.flowId, seq: unitStartSeq } });
847
900
  return { action: "next", data: { unitStartedAt: s.currentUnit?.startedAt } };
848
901
  }
@@ -4,7 +4,7 @@
4
4
  * and fallback chains.
5
5
  */
6
6
  import { resolveModelWithFallbacksForUnit, resolveDynamicRoutingConfig } from "./preferences.js";
7
- import { classifyUnitComplexity, tierLabel } from "./complexity-classifier.js";
7
+ import { classifyUnitComplexity, tierLabel, extractTaskMetadata } from "./complexity-classifier.js";
8
8
  import { resolveModelForComplexity, escalateTier } from "./model-router.js";
9
9
  import { getLedger, getProjectTotals } from "./metrics.js";
10
10
  import { unitPhaseLabel } from "./auto-dashboard.js";
@@ -68,14 +68,19 @@ export async function selectAndApplyModel(ctx, pi, unitType, unitId, basePath, p
68
68
  }
69
69
  }
70
70
  }
71
- const routingResult = resolveModelForComplexity(classification, modelConfig, routingConfig, availableModelIds);
71
+ // Extract task metadata for capability scoring
72
+ const taskMeta = unitType === "execute-task"
73
+ ? extractTaskMetadata(unitId, basePath)
74
+ : undefined;
75
+ const routingResult = resolveModelForComplexity(classification, modelConfig, routingConfig, availableModelIds, unitType, taskMeta);
72
76
  if (routingResult.wasDowngraded) {
73
77
  effectiveModelConfig = {
74
78
  primary: routingResult.modelId,
75
79
  fallbacks: routingResult.fallbacks,
76
80
  };
77
81
  if (verbose) {
78
- ctx.ui.notify(`Dynamic routing [${tierLabel(classification.tier)}]: ${routingResult.modelId} (${classification.reason})`, "info");
82
+ const method = routingResult.selectionMethod === "capability-scored" ? "capability-scored" : "tier-only";
83
+ ctx.ui.notify(`Dynamic routing [${tierLabel(classification.tier)}]: ${routingResult.modelId} (${method} — ${classification.reason})`, "info");
79
84
  }
80
85
  }
81
86
  routingTierLabel = ` [${tierLabel(classification.tier)}]`;
@@ -26,7 +26,7 @@ import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getA
26
26
  import { renderPlanCheckboxes } from "./markdown-renderer.js";
27
27
  import { consumeSignal } from "./session-status-io.js";
28
28
  import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, resolveHookArtifactPath, } from "./post-unit-hooks.js";
29
- import { hasPendingCaptures, loadPendingCaptures } from "./captures.js";
29
+ import { hasPendingCaptures, loadPendingCaptures, revertExecutorResolvedCaptures } from "./captures.js";
30
30
  import { debugLog } from "./debug-logger.js";
31
31
  import { runSafely } from "./auto-utils.js";
32
32
  /** Enqueue a sidecar item (hook, triage, or quick-task) for the main loop to
@@ -478,6 +478,45 @@ export async function postUnitPostVerification(pctx) {
478
478
  }
479
479
  }
480
480
  }
481
+ // ── Fast-path stop detection (#3487) ──
482
+ // Before waiting for triage, check if any PENDING captures contain explicit
483
+ // stop/halt language. If so, pause immediately — don't wait for triage.
484
+ if (s.currentUnit && s.currentUnit.type !== "triage-captures") {
485
+ try {
486
+ const pending = loadPendingCaptures(s.basePath);
487
+ // Match only when the capture text starts with a stop/halt directive word,
488
+ // or the entire text is short and dominated by such a word. This avoids
489
+ // false positives on captures like "add a pause button" or "stop the timer
490
+ // from re-rendering" — those are feature descriptions, not halt directives.
491
+ const STOP_PATTERN = /^(stop|halt|abort|don'?t continue|pause|cease)\b/i;
492
+ const stopCapture = pending.find(c => STOP_PATTERN.test(c.text.trim()));
493
+ if (stopCapture) {
494
+ ctx.ui.notify(`Stop directive detected in pending capture ${stopCapture.id}: "${stopCapture.text}" — pausing auto-mode.`, "warning");
495
+ debugLog("postUnit", { phase: "fast-stop", captureId: stopCapture.id });
496
+ await pauseAuto(ctx, pi);
497
+ return "stopped";
498
+ }
499
+ }
500
+ catch (e) {
501
+ debugLog("postUnit", { phase: "fast-stop-error", error: String(e) });
502
+ }
503
+ }
504
+ // ── Capture protection: revert executor-silenced captures (#3487) ──
505
+ // Non-triage agents can write **Status:** resolved to CAPTURES.md, bypassing
506
+ // the triage pipeline. Revert those to pending before the triage check.
507
+ if (s.currentUnit &&
508
+ s.currentUnit.type !== "triage-captures") {
509
+ try {
510
+ const reverted = revertExecutorResolvedCaptures(s.basePath);
511
+ if (reverted > 0) {
512
+ debugLog("postUnit", { phase: "capture-protection", reverted });
513
+ ctx.ui.notify(`Reverted ${reverted} capture${reverted === 1 ? "" : "s"} silenced by executor — re-queuing for triage.`, "warning");
514
+ }
515
+ }
516
+ catch (e) {
517
+ debugLog("postUnit", { phase: "capture-protection-error", error: String(e) });
518
+ }
519
+ }
481
520
  // ── Triage check ──
482
521
  if (!s.stepMode &&
483
522
  s.currentUnit &&
@@ -17,6 +17,7 @@ import { existsSync } from "node:fs";
17
17
  import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
18
18
  import { getPendingGates } from "./gsd-db.js";
19
19
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
20
+ import { readPhaseAnchor, formatAnchorForPrompt } from "./phase-anchor.js";
20
21
  // ─── Preamble Cap ─────────────────────────────────────────────────────────────
21
22
  const MAX_PREAMBLE_CHARS = 30_000;
22
23
  function capPreamble(preamble) {
@@ -797,6 +798,10 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
797
798
  const researchPath = resolveMilestoneFile(base, mid, "RESEARCH");
798
799
  const researchRel = relMilestoneFile(base, mid, "RESEARCH");
799
800
  const inlined = [];
801
+ // Inject phase handoff anchor from research phase (if available)
802
+ const researchAnchor = readPhaseAnchor(base, mid, "research-milestone");
803
+ if (researchAnchor)
804
+ inlined.push(formatAnchorForPrompt(researchAnchor));
800
805
  inlined.push(await inlineFile(contextPath, contextRel, "Milestone Context"));
801
806
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Milestone Research");
802
807
  if (researchInline)
@@ -919,6 +924,10 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
919
924
  const researchPath = resolveSliceFile(base, mid, sid, "RESEARCH");
920
925
  const researchRel = relSliceFile(base, mid, sid, "RESEARCH");
921
926
  const inlined = [];
927
+ // Inject phase handoff anchor from research phase (if available)
928
+ const researchSliceAnchor = readPhaseAnchor(base, mid, "research-slice");
929
+ if (researchSliceAnchor)
930
+ inlined.push(formatAnchorForPrompt(researchSliceAnchor));
922
931
  inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
923
932
  const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
924
933
  if (researchInline)
@@ -974,6 +983,8 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
974
983
  ? level
975
984
  : { level: level };
976
985
  const inlineLevel = opts.level ?? resolveInlineLevel();
986
+ // Inject phase handoff anchor from planning phase (if available)
987
+ const planAnchor = readPhaseAnchor(base, mid, "plan-slice");
977
988
  const priorSummaries = opts.carryForwardPaths ?? await getPriorTaskSummaryPaths(mid, sid, tid, base);
978
989
  const priorLines = priorSummaries.length > 0
979
990
  ? priorSummaries.map(p => `- \`${p}\``).join("\n")
@@ -1042,9 +1053,11 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
1042
1053
  const runtimeContext = runtimeContent
1043
1054
  ? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
1044
1055
  : "";
1056
+ const phaseAnchorSection = planAnchor ? formatAnchorForPrompt(planAnchor) : "";
1045
1057
  return loadPrompt("execute-task", {
1046
1058
  overridesSection,
1047
1059
  runtimeContext,
1060
+ phaseAnchorSection,
1048
1061
  workingDirectory: base,
1049
1062
  milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
1050
1063
  planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
@@ -832,6 +832,76 @@ export function registerDbTools(pi) {
832
832
  };
833
833
  pi.registerTool(sliceCompleteTool);
834
834
  registerAlias(pi, sliceCompleteTool, "gsd_complete_slice", "gsd_slice_complete");
835
+ // ─── gsd_skip_slice (#3477 / #3487) ───────────────────────────────────
836
+ const skipSliceExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
837
+ const dbAvailable = await ensureDbOpen();
838
+ if (!dbAvailable) {
839
+ return {
840
+ content: [{ type: "text", text: "Error: GSD database is not available. Cannot skip slice." }],
841
+ details: { operation: "skip_slice", error: "db_unavailable" },
842
+ };
843
+ }
844
+ try {
845
+ const { getSlice, updateSliceStatus } = await import("../gsd-db.js");
846
+ const { invalidateStateCache } = await import("../state.js");
847
+ const slice = getSlice(params.milestoneId, params.sliceId);
848
+ if (!slice) {
849
+ return {
850
+ content: [{ type: "text", text: `Error: Slice ${params.sliceId} not found in milestone ${params.milestoneId}` }],
851
+ details: { operation: "skip_slice", error: "slice_not_found" },
852
+ };
853
+ }
854
+ if (slice.status === "complete" || slice.status === "done") {
855
+ return {
856
+ content: [{ type: "text", text: `Error: Slice ${params.sliceId} is already complete — cannot skip.` }],
857
+ details: { operation: "skip_slice", error: "already_complete" },
858
+ };
859
+ }
860
+ if (slice.status === "skipped") {
861
+ return {
862
+ content: [{ type: "text", text: `Slice ${params.sliceId} is already skipped.` }],
863
+ details: { operation: "skip_slice", sliceId: params.sliceId, milestoneId: params.milestoneId },
864
+ };
865
+ }
866
+ updateSliceStatus(params.milestoneId, params.sliceId, "skipped");
867
+ invalidateStateCache();
868
+ return {
869
+ content: [{ type: "text", text: `Skipped slice ${params.sliceId} (${params.milestoneId}). Reason: ${params.reason ?? "User-directed skip"}. Auto-mode will advance past this slice.` }],
870
+ details: {
871
+ operation: "skip_slice",
872
+ sliceId: params.sliceId,
873
+ milestoneId: params.milestoneId,
874
+ reason: params.reason,
875
+ },
876
+ };
877
+ }
878
+ catch (err) {
879
+ const msg = err instanceof Error ? err.message : String(err);
880
+ logError("tool", `skip_slice tool failed: ${msg}`, { tool: "gsd_skip_slice", error: String(err) });
881
+ return {
882
+ content: [{ type: "text", text: `Error skipping slice: ${msg}` }],
883
+ details: { operation: "skip_slice", error: msg },
884
+ };
885
+ }
886
+ };
887
+ pi.registerTool({
888
+ name: "gsd_skip_slice",
889
+ label: "Skip Slice",
890
+ description: "Mark a slice as skipped so auto-mode advances past it without executing. " +
891
+ "The slice data is preserved for reference. The state machine treats skipped slices like completed ones for dependency satisfaction.",
892
+ promptSnippet: "Skip a GSD slice (mark as skipped, auto-mode will advance past it)",
893
+ promptGuidelines: [
894
+ "Use gsd_skip_slice when a slice should be bypassed — descoped, superseded, or no longer relevant.",
895
+ "Cannot skip a slice that is already complete.",
896
+ "Skipped slices satisfy downstream dependencies just like completed slices.",
897
+ ],
898
+ parameters: Type.Object({
899
+ sliceId: Type.String({ description: "Slice ID (e.g. S02)" }),
900
+ milestoneId: Type.String({ description: "Milestone ID (e.g. M003)" }),
901
+ reason: Type.Optional(Type.String({ description: "Reason for skipping this slice" })),
902
+ }),
903
+ execute: skipSliceExecute,
904
+ });
835
905
  // ─── gsd_complete_milestone ────────────────────────────────────────────
836
906
  const milestoneCompleteExecute = async (_toolCallId, params, _signal, _onUpdate, _ctx) => {
837
907
  const dbAvailable = await ensureDbOpen();
@@ -244,16 +244,62 @@ export function registerHooks(pi) {
244
244
  await syncServiceTierStatus(ctx);
245
245
  });
246
246
  pi.on("before_provider_request", async (event) => {
247
+ const payload = event.payload;
248
+ if (!payload || typeof payload !== "object")
249
+ return;
250
+ // ── Observation Masking ─────────────────────────────────────────────
251
+ // Replace old tool results with placeholders to reduce context bloat.
252
+ // Only active during auto-mode when context_management.observation_masking is enabled.
253
+ if (isAutoActive()) {
254
+ try {
255
+ const { loadEffectiveGSDPreferences } = await import("../preferences.js");
256
+ const prefs = loadEffectiveGSDPreferences();
257
+ const cmConfig = prefs?.preferences.context_management;
258
+ // Observation masking: replace old tool results with placeholders
259
+ if (cmConfig?.observation_masking !== false) {
260
+ const keepTurns = cmConfig?.observation_mask_turns ?? 8;
261
+ const { createObservationMask } = await import("../context-masker.js");
262
+ const mask = createObservationMask(keepTurns);
263
+ const messages = payload.messages;
264
+ if (Array.isArray(messages)) {
265
+ payload.messages = mask(messages);
266
+ }
267
+ }
268
+ // Tool result truncation: cap individual tool result content length.
269
+ // In pi-ai format, toolResult messages have role: "toolResult" and content: TextContent[].
270
+ // Creates new objects to avoid mutating shared conversation state.
271
+ const maxChars = cmConfig?.tool_result_max_chars ?? 800;
272
+ const msgs = payload.messages;
273
+ if (Array.isArray(msgs)) {
274
+ payload.messages = msgs.map((msg) => {
275
+ // Match toolResult messages (role: "toolResult", content is array of content blocks)
276
+ if (msg?.role === "toolResult" && Array.isArray(msg.content)) {
277
+ const blocks = msg.content;
278
+ const totalLen = blocks.reduce((sum, b) => sum + (typeof b.text === "string" ? b.text.length : 0), 0);
279
+ if (totalLen > maxChars) {
280
+ const truncated = blocks.map(b => {
281
+ if (typeof b.text === "string" && b.text.length > maxChars) {
282
+ return { ...b, text: b.text.slice(0, maxChars) + "\n…[truncated]" };
283
+ }
284
+ return b;
285
+ });
286
+ return { ...msg, content: truncated };
287
+ }
288
+ }
289
+ return msg;
290
+ });
291
+ }
292
+ }
293
+ catch { /* non-fatal */ }
294
+ }
295
+ // ── Service Tier ────────────────────────────────────────────────────
247
296
  const modelId = event.model?.id;
248
297
  if (!modelId)
249
- return;
298
+ return payload;
250
299
  const { getEffectiveServiceTier, supportsServiceTier } = await import("../service-tier.js");
251
300
  const tier = getEffectiveServiceTier();
252
301
  if (!tier || !supportsServiceTier(modelId))
253
- return;
254
- const payload = event.payload;
255
- if (!payload || typeof payload !== "object")
256
- return;
302
+ return payload;
257
303
  payload.service_tier = tier;
258
304
  return payload;
259
305
  });
@@ -14,7 +14,7 @@ import { gsdRoot } from "./paths.js";
14
14
  // ─── Constants ────────────────────────────────────────────────────────────────
15
15
  const CAPTURES_FILENAME = "CAPTURES.md";
16
16
  const VALID_CLASSIFICATIONS = [
17
- "quick-task", "inject", "defer", "replan", "note",
17
+ "quick-task", "inject", "defer", "replan", "note", "stop", "backtrack",
18
18
  ];
19
19
  // ─── Path Resolution ──────────────────────────────────────────────────────────
20
20
  /**
@@ -216,6 +216,59 @@ export function loadActionableCaptures(basePath, currentMilestoneId) {
216
216
  !c.resolvedInMilestone ||
217
217
  c.resolvedInMilestone === currentMilestoneId));
218
218
  }
219
+ /**
220
+ * Load unexecuted stop captures — user directives to halt auto-mode.
221
+ * These are checked in the pre-dispatch guard pipeline (runGuards) to
222
+ * pause auto-mode before the next unit is dispatched.
223
+ */
224
+ export function loadStopCaptures(basePath) {
225
+ return loadAllCaptures(basePath).filter(c => c.status === "resolved" && !c.executed &&
226
+ (c.classification === "stop" || c.classification === "backtrack"));
227
+ }
228
+ /**
229
+ * Load unexecuted backtrack captures specifically — captures directing
230
+ * auto-mode to abandon current milestone and return to a previous one.
231
+ */
232
+ export function loadBacktrackCaptures(basePath) {
233
+ return loadAllCaptures(basePath).filter(c => c.status === "resolved" && !c.executed && c.classification === "backtrack");
234
+ }
235
+ /**
236
+ * Revert captures that were silenced by non-triage agents.
237
+ *
238
+ * When an execute-task or other non-triage agent writes `**Status:** resolved`
239
+ * to CAPTURES.md, it bypasses the triage pipeline entirely. This function
240
+ * detects such captures (resolved but missing the Classification field that
241
+ * triage always writes) and reverts them to pending so the triage sidecar
242
+ * picks them up properly.
243
+ *
244
+ * Returns the number of captures reverted.
245
+ */
246
+ export function revertExecutorResolvedCaptures(basePath) {
247
+ const filePath = resolveCapturesPath(basePath);
248
+ if (!existsSync(filePath))
249
+ return 0;
250
+ let content = readFileSync(filePath, "utf-8");
251
+ let reverted = 0;
252
+ const all = loadAllCaptures(basePath);
253
+ for (const capture of all) {
254
+ // A properly triaged capture has both resolved status AND a classification.
255
+ // An executor-silenced capture has resolved status but NO classification.
256
+ if (capture.status === "resolved" && !capture.classification) {
257
+ const sectionRegex = new RegExp(`(### ${escapeRegex(capture.id)}\\n(?:(?!### ).)*?)(?=### |$)`, "s");
258
+ const match = sectionRegex.exec(content);
259
+ if (match) {
260
+ let section = match[1];
261
+ section = section.replace(/\*\*Status:\*\*\s*resolved/i, "**Status:** pending");
262
+ content = content.replace(sectionRegex, section);
263
+ reverted++;
264
+ }
265
+ }
266
+ }
267
+ if (reverted > 0) {
268
+ writeFileSync(filePath, content, "utf-8");
269
+ }
270
+ return reverted;
271
+ }
219
272
  /**
220
273
  * Retroactively stamp a capture with a milestone ID.
221
274
  *
@@ -149,7 +149,7 @@ function analyzePlanComplexity(unitId, basePath) {
149
149
  /**
150
150
  * Extract task metadata from the task plan file on disk.
151
151
  */
152
- function extractTaskMetadata(unitId, basePath) {
152
+ export function extractTaskMetadata(unitId, basePath) {
153
153
  const meta = {};
154
154
  const { milestone: mid, slice: sid, task: tid } = parseUnitId(unitId);
155
155
  if (!mid || !sid || !tid)
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Observation masking for GSD auto-mode sessions.
3
+ *
4
+ * Replaces tool result content older than N turns with a placeholder.
5
+ * Reduces context bloat between compactions with zero LLM overhead.
6
+ * Preserves message ordering, roles, and all assistant/user messages.
7
+ *
8
+ * Operates on the pi-ai Message[] format (post-convertToLlm, pre-provider):
9
+ * - toolResult messages: { role: "toolResult", content: TextContent[] }
10
+ * - bash results are already converted to: { role: "user", content: [{type:"text",text:"..."}] }
11
+ * and start with "Ran `" from bashExecutionToText.
12
+ */
13
+ const MASK_PLACEHOLDER = "[result masked — within summarized history]";
14
+ const MASK_CONTENT_BLOCK = [{ type: "text", text: MASK_PLACEHOLDER }];
15
+ function findTurnBoundary(messages, keepRecentTurns) {
16
+ let turnsSeen = 0;
17
+ for (let i = messages.length - 1; i >= 0; i--) {
18
+ const m = messages[i];
19
+ // In the LLM payload, genuine user turns have role "user".
20
+ // Tool results have role "toolResult" and are excluded by this check.
21
+ if (m.role === "user") {
22
+ // Skip bash-result user messages (converted from bashExecution) — these aren't real user turns
23
+ if (isBashResultUserMessage(m))
24
+ continue;
25
+ turnsSeen++;
26
+ if (turnsSeen >= keepRecentTurns)
27
+ return i;
28
+ }
29
+ }
30
+ return 0;
31
+ }
32
+ /**
33
+ * Detect user messages that originated from bashExecution.
34
+ * After convertToLlm, these are {role: "user", content: [{type:"text", text:"Ran `cmd`\n..."}]}.
35
+ * The bashExecutionToText format always starts with "Ran `".
36
+ */
37
+ function isBashResultUserMessage(m) {
38
+ if (m.role !== "user" || !Array.isArray(m.content))
39
+ return false;
40
+ const first = m.content[0];
41
+ return first && typeof first === "object" && "text" in first &&
42
+ typeof first.text === "string" && first.text.startsWith("Ran `");
43
+ }
44
+ function isMaskableMessage(m) {
45
+ // Tool result messages (role: "toolResult" in pi-ai format)
46
+ if (m.role === "toolResult")
47
+ return true;
48
+ // Bash-result user messages (converted from bashExecution by convertToLlm)
49
+ if (isBashResultUserMessage(m))
50
+ return true;
51
+ return false;
52
+ }
53
+ export function createObservationMask(keepRecentTurns = 8) {
54
+ return (messages) => {
55
+ const boundary = findTurnBoundary(messages, keepRecentTurns);
56
+ if (boundary === 0)
57
+ return messages;
58
+ return messages.map((m, i) => {
59
+ if (i >= boundary)
60
+ return m;
61
+ if (isMaskableMessage(m)) {
62
+ // Content may be string or array of content blocks — always replace with array
63
+ return { ...m, content: MASK_CONTENT_BLOCK };
64
+ }
65
+ return m;
66
+ });
67
+ };
68
+ }
@@ -189,6 +189,13 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
189
189
  - `budget_pressure`: boolean — downgrade model tier when budget is under pressure. Default: `true`.
190
190
  - `cross_provider`: boolean — allow routing across different providers. Default: `true`.
191
191
  - `hooks`: boolean — enable routing hooks. Default: `true`.
192
+ - `capability_routing`: boolean — enable capability-profile scoring for model selection within a tier. Requires `enabled: true`. Default: `false`.
193
+
194
+ - `context_management`: configures context hygiene for auto-mode sessions. Keys:
195
+ - `observation_masking`: boolean — mask old tool results to reduce context bloat. Default: `true`.
196
+ - `observation_mask_turns`: number — keep this many recent turns verbatim (1-50). Default: `8`.
197
+ - `compaction_threshold_percent`: number — trigger compaction at this % of context window (0.5-0.95). Lower values fire compaction earlier, reducing drift. Default: `0.70`.
198
+ - `tool_result_max_chars`: number — max chars per tool result in GSD sessions (200-10000). Default: `800`.
192
199
 
193
200
  - `auto_visualize`: boolean — show a visualizer hint after each milestone completion in auto-mode. Default: `false`.
194
201
 
@@ -1374,11 +1374,11 @@ export function getActiveSliceFromDb(milestoneId) {
1374
1374
  // Uses json_each() to expand the JSON depends array and checks each dep is complete.
1375
1375
  const row = currentDb.prepare(`SELECT s.* FROM slices s
1376
1376
  WHERE s.milestone_id = :mid
1377
- AND s.status NOT IN ('complete', 'done')
1377
+ AND s.status NOT IN ('complete', 'done', 'skipped')
1378
1378
  AND NOT EXISTS (
1379
1379
  SELECT 1 FROM json_each(s.depends) AS dep
1380
1380
  WHERE dep.value NOT IN (
1381
- SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done')
1381
+ SELECT id FROM slices WHERE milestone_id = :mid AND status IN ('complete', 'done', 'skipped')
1382
1382
  )
1383
1383
  )
1384
1384
  ORDER BY s.sequence, s.id