gsd-pi 2.52.0-dev.585e355 → 2.52.0

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 (188) hide show
  1. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -3
  2. package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  3. package/dist/resources/extensions/gsd/git-service.js +3 -4
  4. package/dist/resources/extensions/gsd/markdown-renderer.js +4 -5
  5. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  6. package/dist/resources/extensions/gsd/state.js +18 -13
  7. package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -4
  8. package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -4
  9. package/dist/resources/extensions/gsd/tools/complete-task.js +3 -4
  10. package/dist/resources/extensions/gsd/tools/plan-milestone.js +14 -4
  11. package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -4
  12. package/dist/resources/extensions/gsd/tools/plan-task.js +14 -4
  13. package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +7 -6
  14. package/dist/resources/extensions/gsd/tools/reopen-slice.js +3 -4
  15. package/dist/resources/extensions/gsd/tools/reopen-task.js +4 -5
  16. package/dist/resources/extensions/gsd/tools/replan-slice.js +6 -5
  17. package/dist/resources/extensions/shared/rtk.js +3 -5
  18. package/dist/rtk.js +1 -3
  19. package/dist/web/standalone/.next/BUILD_ID +1 -1
  20. package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
  21. package/dist/web/standalone/.next/build-manifest.json +3 -3
  22. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  23. package/dist/web/standalone/.next/required-server-files.json +3 -3
  24. package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
  25. package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  26. package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
  27. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  28. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  29. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  30. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  31. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  32. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  33. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  34. package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
  35. package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  36. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  37. package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
  38. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  39. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  40. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  41. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  42. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  43. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  44. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  45. package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
  46. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  47. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
  48. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  49. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
  50. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
  51. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
  52. package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
  53. package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
  54. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  55. package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
  56. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  57. package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
  58. package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
  59. package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
  60. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  61. package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
  62. package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
  63. package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
  64. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  65. package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
  66. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  67. package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
  68. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  69. package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
  70. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  71. package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  73. package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
  74. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  75. package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
  76. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  77. package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
  78. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  79. package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
  80. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  81. package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
  82. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  83. package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
  84. package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
  85. package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
  86. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  87. package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
  88. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  89. package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
  90. package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
  91. package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
  92. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  93. package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
  94. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  95. package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
  96. package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
  97. package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
  98. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  99. package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
  100. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  101. package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
  102. package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
  103. package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
  104. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  105. package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
  106. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  107. package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
  108. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  109. package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
  110. package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
  111. package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
  112. package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
  113. package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
  114. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
  115. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
  116. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
  117. package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
  118. package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
  119. package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
  120. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  121. package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
  122. package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
  123. package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
  124. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  125. package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
  126. package/dist/web/standalone/.next/server/app/index.html +1 -1
  127. package/dist/web/standalone/.next/server/app/index.rsc +4 -4
  128. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  129. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  130. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  131. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  132. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  133. package/dist/web/standalone/.next/server/app/page.js +2 -2
  134. package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  135. package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
  136. package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
  137. package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
  138. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  139. package/dist/web/standalone/.next/server/middleware.js +2 -2
  140. package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
  141. package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
  142. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  143. package/dist/web/standalone/.next/server/pages/500.html +2 -2
  144. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  145. package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
  146. package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
  147. package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +1 -0
  148. package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
  149. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
  150. package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
  151. package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
  152. package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
  153. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
  154. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
  155. package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
  156. package/dist/web/standalone/server.js +1 -1
  157. package/package.json +1 -1
  158. package/scripts/ensure-workspace-builds.cjs +8 -36
  159. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +3 -3
  160. package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
  161. package/src/resources/extensions/gsd/git-service.ts +3 -4
  162. package/src/resources/extensions/gsd/markdown-renderer.ts +4 -5
  163. package/src/resources/extensions/gsd/preferences-types.ts +1 -1
  164. package/src/resources/extensions/gsd/state.ts +19 -13
  165. package/src/resources/extensions/gsd/tests/git-service.test.ts +3 -36
  166. package/src/resources/extensions/gsd/tests/preferences.test.ts +1 -1
  167. package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -4
  168. package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -4
  169. package/src/resources/extensions/gsd/tools/complete-task.ts +3 -4
  170. package/src/resources/extensions/gsd/tools/plan-milestone.ts +16 -4
  171. package/src/resources/extensions/gsd/tools/plan-slice.ts +16 -4
  172. package/src/resources/extensions/gsd/tools/plan-task.ts +16 -4
  173. package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +7 -6
  174. package/src/resources/extensions/gsd/tools/reopen-slice.ts +3 -4
  175. package/src/resources/extensions/gsd/tools/reopen-task.ts +4 -5
  176. package/src/resources/extensions/gsd/tools/replan-slice.ts +7 -5
  177. package/src/resources/extensions/shared/rtk.ts +3 -12
  178. package/dist/resources/extensions/gsd/status-guards.js +0 -12
  179. package/dist/resources/extensions/gsd/validation.js +0 -21
  180. package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +0 -1
  181. package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
  182. package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
  183. package/src/resources/extensions/gsd/status-guards.ts +0 -13
  184. package/src/resources/extensions/gsd/tests/status-guards.test.ts +0 -30
  185. package/src/resources/extensions/gsd/tests/validation.test.ts +0 -72
  186. package/src/resources/extensions/gsd/validation.ts +0 -23
  187. /package/dist/web/standalone/.next/static/{KTe1kB5nPLQFIIFz2OcmI → vlgS2rkXjxeKhgXhdp4lh}/_buildManifest.js +0 -0
  188. /package/dist/web/standalone/.next/static/{KTe1kB5nPLQFIIFz2OcmI → vlgS2rkXjxeKhgXhdp4lh}/_ssgManifest.js +0 -0
@@ -337,7 +337,7 @@ async function configureGit(ctx, prefs) {
337
337
  const gitBooleanFields = [
338
338
  { key: "auto_push", label: "Auto-push commits after committing", defaultVal: false },
339
339
  { key: "push_branches", label: "Push milestone branches to remote", defaultVal: false },
340
- { key: "snapshots", label: "Create WIP snapshot commits during long tasks", defaultVal: true },
340
+ { key: "snapshots", label: "Create WIP snapshot commits during long tasks", defaultVal: false },
341
341
  ];
342
342
  for (const field of gitBooleanFields) {
343
343
  const current = git[field.key];
@@ -361,7 +361,7 @@ async function configureGit(ctx, prefs) {
361
361
  }
362
362
  // pre_merge_check
363
363
  const currentPreMerge = git.pre_merge_check !== undefined ? String(git.pre_merge_check) : "";
364
- const preMergeChoice = await ctx.ui.select(`Pre-merge check${currentPreMerge ? ` (current: ${currentPreMerge})` : " (default: auto)"}:`, ["true", "false", "auto", "(keep current)"]);
364
+ const preMergeChoice = await ctx.ui.select(`Pre-merge check${currentPreMerge ? ` (current: ${currentPreMerge})` : " (default: false)"}:`, ["true", "false", "auto", "(keep current)"]);
365
365
  if (preMergeChoice && preMergeChoice !== "(keep current)") {
366
366
  if (preMergeChoice === "auto") {
367
367
  git.pre_merge_check = "auto";
@@ -487,7 +487,7 @@ export async function configureMode(ctx, prefs) {
487
487
  if (modeStr && modeStr !== "(keep current)") {
488
488
  if (modeStr.startsWith("solo")) {
489
489
  prefs.mode = "solo";
490
- ctx.ui.notify("Mode: solo — defaults: auto_push=true, push_branches=false, pre_merge_check=auto, merge_strategy=squash, isolation=worktree, unique_milestone_ids=false", "info");
490
+ ctx.ui.notify("Mode: solo — defaults: auto_push=true, push_branches=false, pre_merge_check=false, merge_strategy=squash, isolation=worktree, unique_milestone_ids=false", "info");
491
491
  }
492
492
  else if (modeStr.startsWith("team")) {
493
493
  prefs.mode = "team";
@@ -126,8 +126,8 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
126
126
  - `auto_push`: boolean — automatically push commits to the remote after committing. Default: `false`.
127
127
  - `push_branches`: boolean — push the milestone branch to the remote after commits. Default: `false`.
128
128
  - `remote`: string — git remote name to push to. Default: `"origin"`.
129
- - `snapshots`: boolean — create snapshot commits (WIP saves) during long-running tasks. Default: `true`.
130
- - `pre_merge_check`: boolean or `"auto"` — run pre-merge checks before merging a worktree back to the integration branch. `true` always runs, `false` never runs, `"auto"` runs when CI is detected. Default: `"auto"`.
129
+ - `snapshots`: boolean — create snapshot commits (WIP saves) during long-running tasks. Default: `false`.
130
+ - `pre_merge_check`: boolean or `"auto"` — run pre-merge checks before merging a worktree back to the integration branch. `true` always runs, `false` never runs, `"auto"` runs when CI is detected. Default: `false`.
131
131
  - `commit_type`: string — override the conventional commit type prefix. Must be one of: `feat`, `fix`, `refactor`, `docs`, `test`, `chore`, `perf`, `ci`, `build`, `style`. Default: inferred from diff content.
132
132
  - `main_branch`: string — the primary branch name for new git repos (e.g., `"main"`, `"master"`, `"trunk"`). Also used by `getMainBranch()` as the preferred branch when auto-detection is ambiguous. Default: `"main"`.
133
133
  - `merge_strategy`: `"squash"` or `"merge"` — controls how worktree branches are merged back. `"squash"` combines all commits into one; `"merge"` preserves individual commits. Default: `"squash"`.
@@ -446,12 +446,11 @@ export class GitServiceImpl {
446
446
  }
447
447
  /**
448
448
  * Create a snapshot ref for the given label (typically a slice branch name).
449
- * Enabled by default; opt out with prefs.snapshots === false.
450
- * Ref path: refs/gsd/snapshots/<label>/<timestamp>
449
+ * Gated on prefs.snapshots === true. Ref path: refs/gsd/snapshots/<label>/<timestamp>
451
450
  * The ref points at HEAD, capturing the current commit before destructive operations.
452
451
  */
453
452
  createSnapshot(label) {
454
- if (this.prefs.snapshots === false)
453
+ if (this.prefs.snapshots !== true)
455
454
  return;
456
455
  const now = new Date();
457
456
  const ts = now.getFullYear().toString()
@@ -471,7 +470,7 @@ export class GitServiceImpl {
471
470
  * Stub: to be implemented in T03.
472
471
  */
473
472
  runPreMergeCheck() {
474
- if (this.prefs.pre_merge_check === false) {
473
+ if (this.prefs.pre_merge_check === false || this.prefs.pre_merge_check === undefined) {
475
474
  return { passed: true, skipped: true };
476
475
  }
477
476
  // Determine command: explicit string or auto-detect from package.json
@@ -8,7 +8,6 @@
8
8
  // Critical invariant: rendered markdown must round-trip through
9
9
  // parseRoadmap(), parsePlan(), parseSummary() in files.ts.
10
10
  import { readFileSync, existsSync, mkdirSync } from "node:fs";
11
- import { isClosedStatus } from "./status-guards.js";
12
11
  import { join, relative } from "node:path";
13
12
  import { createRequire } from "node:module";
14
13
  import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getTask, getSlice, getArtifact, insertArtifact, getGateResults, } from "./gsd-db.js";
@@ -263,7 +262,7 @@ function renderSlicePlanMarkdown(slice, tasks, gates = []) {
263
262
  lines.push("## Tasks");
264
263
  lines.push("");
265
264
  for (const task of tasks) {
266
- const done = isClosedStatus(task.status) ? "x" : " ";
265
+ const done = task.status === "done" || task.status === "complete" ? "x" : " ";
267
266
  const estimate = task.estimate.trim() ? ` \`est:${task.estimate.trim()}\`` : "";
268
267
  lines.push(`- [${done}] **${task.id}: ${task.title || task.id}**${estimate}`);
269
268
  if (task.description.trim()) {
@@ -436,7 +435,7 @@ export async function renderPlanCheckboxes(basePath, milestoneId, sliceId) {
436
435
  // Apply checkbox patches for each task
437
436
  let updated = content;
438
437
  for (const task of tasks) {
439
- const isDone = isClosedStatus(task.status);
438
+ const isDone = task.status === "done" || task.status === "complete";
440
439
  const tid = task.id;
441
440
  if (isDone) {
442
441
  // Set [x]
@@ -661,7 +660,7 @@ export function detectStaleRenders(basePath) {
661
660
  const content = readFileSync(planPath, "utf-8");
662
661
  const parsed = parsePlan(content);
663
662
  for (const task of tasks) {
664
- const isDoneInDb = isClosedStatus(task.status);
663
+ const isDoneInDb = task.status === "done" || task.status === "complete";
665
664
  const planTask = parsed.tasks.find((t) => t.id === task.id);
666
665
  if (!planTask)
667
666
  continue;
@@ -685,7 +684,7 @@ export function detectStaleRenders(basePath) {
685
684
  }
686
685
  // Check missing task summary files
687
686
  for (const task of tasks) {
688
- if (isClosedStatus(task.status) && task.full_summary_md) {
687
+ if ((task.status === "done" || task.status === "complete") && task.full_summary_md) {
689
688
  const slicePath = resolveSlicePath(basePath, milestone.id, slice.id);
690
689
  if (slicePath) {
691
690
  const tasksDir = join(slicePath, "tasks");
@@ -11,7 +11,7 @@ export const MODE_DEFAULTS = {
11
11
  git: {
12
12
  auto_push: true,
13
13
  push_branches: false,
14
- pre_merge_check: "auto",
14
+ pre_merge_check: false,
15
15
  merge_strategy: "squash",
16
16
  isolation: "none",
17
17
  },
@@ -6,7 +6,6 @@ import { parseSummary, loadFile, parseRequirementCounts, parseContextDependsOn,
6
6
  import { resolveMilestoneFile, resolveSlicePath, resolveSliceFile, resolveTaskFile, resolveTasksDir, resolveGsdRootFile, gsdRoot, } from './paths.js';
7
7
  import { findMilestoneIds } from './milestone-ids.js';
8
8
  import { loadQueueOrder, sortByQueueOrder } from './queue-order.js';
9
- import { isClosedStatus } from './status-guards.js';
10
9
  import { nativeBatchParseGsdFiles } from './native-parser-bridge.js';
11
10
  import { join, resolve } from 'path';
12
11
  import { existsSync, readdirSync } from 'node:fs';
@@ -204,6 +203,12 @@ function extractContextTitle(content, fallback) {
204
203
  return stripMilestonePrefix(h1.slice(2).trim()) || fallback;
205
204
  }
206
205
  // ─── DB-backed State Derivation ────────────────────────────────────────────
206
+ /**
207
+ * Helper: check if a DB status counts as "done" (handles K002 ambiguity).
208
+ */
209
+ function isStatusDone(status) {
210
+ return status === 'complete' || status === 'done';
211
+ }
207
212
  /**
208
213
  * Derive GSD state from the milestones/slices/tasks DB tables.
209
214
  * Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
@@ -287,7 +292,7 @@ export async function deriveStateFromDb(basePath) {
287
292
  parkedMilestoneIds.add(m.id);
288
293
  continue;
289
294
  }
290
- if (isClosedStatus(m.status)) {
295
+ if (isStatusDone(m.status)) {
291
296
  completeMilestoneIds.add(m.id);
292
297
  continue;
293
298
  }
@@ -299,7 +304,7 @@ export async function deriveStateFromDb(basePath) {
299
304
  }
300
305
  // Check roadmap: all slices done means milestone is complete
301
306
  const slices = getMilestoneSlices(m.id);
302
- if (slices.length > 0 && slices.every(s => isClosedStatus(s.status))) {
307
+ if (slices.length > 0 && slices.every(s => isStatusDone(s.status))) {
303
308
  // All slices done but no summary — still counts as complete for dep resolution
304
309
  // if a summary file exists
305
310
  // Note: without summary file, the milestone is in validating/completing state, not complete
@@ -318,7 +323,7 @@ export async function deriveStateFromDb(basePath) {
318
323
  }
319
324
  // Ghost milestone check: no slices in DB AND no substantive files on disk
320
325
  const slices = getMilestoneSlices(m.id);
321
- if (slices.length === 0 && !isClosedStatus(m.status)) {
326
+ if (slices.length === 0 && !isStatusDone(m.status)) {
322
327
  // Check disk for ghost detection
323
328
  if (isGhostMilestone(basePath, m.id))
324
329
  continue;
@@ -339,7 +344,7 @@ export async function deriveStateFromDb(basePath) {
339
344
  continue;
340
345
  }
341
346
  // Not complete — determine if it should be active
342
- const allSlicesDone = slices.length > 0 && slices.every(s => isClosedStatus(s.status));
347
+ const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
343
348
  // Get title — prefer DB, fall back to context file extraction
344
349
  let title = stripMilestonePrefix(m.title) || m.id;
345
350
  if (title === m.id) {
@@ -479,7 +484,7 @@ export async function deriveStateFromDb(basePath) {
479
484
  // Guard: [].every() === true (vacuous truth). Without the length check,
480
485
  // an empty slice array causes a premature phase transition to
481
486
  // validating-milestone. See: https://github.com/gsd-build/gsd-2/issues/2667
482
- const allSlicesDone = activeMilestoneSlices.length > 0 && activeMilestoneSlices.every(s => isClosedStatus(s.status));
487
+ const allSlicesDone = activeMilestoneSlices.length > 0 && activeMilestoneSlices.every(s => isStatusDone(s.status));
483
488
  if (allSlicesDone) {
484
489
  const validationFile = resolveMilestoneFile(basePath, activeMilestone.id, "VALIDATION");
485
490
  const validationContent = validationFile ? await loadFile(validationFile) : null;
@@ -509,14 +514,14 @@ export async function deriveStateFromDb(basePath) {
509
514
  }
510
515
  // ── Find active slice (first incomplete with deps satisfied) ─────────
511
516
  const sliceProgress = {
512
- done: activeMilestoneSlices.filter(s => isClosedStatus(s.status)).length,
517
+ done: activeMilestoneSlices.filter(s => isStatusDone(s.status)).length,
513
518
  total: activeMilestoneSlices.length,
514
519
  };
515
- const doneSliceIds = new Set(activeMilestoneSlices.filter(s => isClosedStatus(s.status)).map(s => s.id));
520
+ const doneSliceIds = new Set(activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id));
516
521
  let activeSlice = null;
517
522
  let activeSliceRow = null;
518
523
  for (const s of activeMilestoneSlices) {
519
- if (isClosedStatus(s.status))
524
+ if (isStatusDone(s.status))
520
525
  continue;
521
526
  if (s.depends.every(dep => doneSliceIds.has(dep))) {
522
527
  activeSlice = { id: s.id, title: s.title };
@@ -556,7 +561,7 @@ export async function deriveStateFromDb(basePath) {
556
561
  // causing the dispatcher to re-dispatch the same completed task forever.
557
562
  let reconciled = false;
558
563
  for (const t of tasks) {
559
- if (isClosedStatus(t.status))
564
+ if (isStatusDone(t.status))
560
565
  continue;
561
566
  const summaryPath = resolveTaskFile(basePath, activeMilestone.id, activeSlice.id, t.id, "SUMMARY");
562
567
  if (summaryPath && existsSync(summaryPath)) {
@@ -576,10 +581,10 @@ export async function deriveStateFromDb(basePath) {
576
581
  tasks = getSliceTasks(activeMilestone.id, activeSlice.id);
577
582
  }
578
583
  const taskProgress = {
579
- done: tasks.filter(t => isClosedStatus(t.status)).length,
584
+ done: tasks.filter(t => isStatusDone(t.status)).length,
580
585
  total: tasks.length,
581
586
  };
582
- const activeTaskRow = tasks.find(t => !isClosedStatus(t.status));
587
+ const activeTaskRow = tasks.find(t => !isStatusDone(t.status));
583
588
  if (!activeTaskRow && tasks.length > 0) {
584
589
  // All tasks done but slice not marked complete → summarizing
585
590
  return {
@@ -634,7 +639,7 @@ export async function deriveStateFromDb(basePath) {
634
639
  };
635
640
  }
636
641
  // ── Blocker detection: check completed tasks for blocker_discovered ──
637
- const completedTasks = tasks.filter(t => isClosedStatus(t.status));
642
+ const completedTasks = tasks.filter(t => isStatusDone(t.status));
638
643
  let blockerTaskId = null;
639
644
  for (const ct of completedTasks) {
640
645
  if (ct.blocker_discovered) {
@@ -9,7 +9,6 @@ import { join } from "node:path";
9
9
  import { mkdirSync } from "node:fs";
10
10
  import { transaction, getMilestone, getMilestoneSlices, getSliceTasks, updateMilestoneStatus, } from "../gsd-db.js";
11
11
  import { resolveMilestonePath, clearPathCache } from "../paths.js";
12
- import { isClosedStatus } from "../status-guards.js";
13
12
  import { saveFile, clearParseCache } from "../files.js";
14
13
  import { invalidateStateCache } from "../state.js";
15
14
  import { renderAllProjections } from "../workflow-projections.js";
@@ -90,7 +89,7 @@ export async function handleCompleteMilestone(params, basePath) {
90
89
  guardError = `milestone not found: ${params.milestoneId}`;
91
90
  return;
92
91
  }
93
- if (isClosedStatus(milestone.status)) {
92
+ if (milestone.status === "complete" || milestone.status === "done") {
94
93
  guardError = `milestone ${params.milestoneId} is already complete`;
95
94
  return;
96
95
  }
@@ -100,7 +99,7 @@ export async function handleCompleteMilestone(params, basePath) {
100
99
  guardError = `no slices found for milestone ${params.milestoneId}`;
101
100
  return;
102
101
  }
103
- const incompleteSlices = slices.filter(s => !isClosedStatus(s.status));
102
+ const incompleteSlices = slices.filter(s => s.status !== "complete" && s.status !== "done");
104
103
  if (incompleteSlices.length > 0) {
105
104
  const incompleteIds = incompleteSlices.map(s => `${s.id} (status: ${s.status})`).join(", ");
106
105
  guardError = `incomplete slices: ${incompleteIds}`;
@@ -109,7 +108,7 @@ export async function handleCompleteMilestone(params, basePath) {
109
108
  // Deep check: verify all tasks in all slices are complete
110
109
  for (const slice of slices) {
111
110
  const tasks = getSliceTasks(params.milestoneId, slice.id);
112
- const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
111
+ const incompleteTasks = tasks.filter(t => t.status !== "complete" && t.status !== "done");
113
112
  if (incompleteTasks.length > 0) {
114
113
  const ids = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
115
114
  guardError = `slice ${slice.id} has incomplete tasks: ${ids}`;
@@ -8,7 +8,6 @@
8
8
  */
9
9
  import { join } from "node:path";
10
10
  import { mkdirSync } from "node:fs";
11
- import { isClosedStatus } from "../status-guards.js";
12
11
  import { transaction, insertMilestone, insertSlice, getSlice, getSliceTasks, getMilestone, updateSliceStatus, setSliceSummaryMd, } from "../gsd-db.js";
13
12
  import { resolveSlicePath, clearPathCache } from "../paths.js";
14
13
  import { checkOwnership, sliceUnitKey } from "../unit-ownership.js";
@@ -180,12 +179,12 @@ export async function handleCompleteSlice(params, basePath) {
180
179
  // Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
181
180
  // Only block if they exist and are closed.
182
181
  const milestone = getMilestone(params.milestoneId);
183
- if (milestone && isClosedStatus(milestone.status)) {
182
+ if (milestone && (milestone.status === "complete" || milestone.status === "done")) {
184
183
  guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
185
184
  return;
186
185
  }
187
186
  const slice = getSlice(params.milestoneId, params.sliceId);
188
- if (slice && isClosedStatus(slice.status)) {
187
+ if (slice && (slice.status === "complete" || slice.status === "done")) {
189
188
  guardError = `slice ${params.sliceId} is already complete — use gsd_slice_reopen first if you need to redo it`;
190
189
  return;
191
190
  }
@@ -195,7 +194,7 @@ export async function handleCompleteSlice(params, basePath) {
195
194
  guardError = `no tasks found for slice ${params.sliceId} in milestone ${params.milestoneId}`;
196
195
  return;
197
196
  }
198
- const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
197
+ const incompleteTasks = tasks.filter(t => t.status !== "complete" && t.status !== "done");
199
198
  if (incompleteTasks.length > 0) {
200
199
  const incompleteIds = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
201
200
  guardError = `incomplete tasks: ${incompleteIds}`;
@@ -8,7 +8,6 @@
8
8
  */
9
9
  import { join } from "node:path";
10
10
  import { mkdirSync } from "node:fs";
11
- import { isClosedStatus } from "../status-guards.js";
12
11
  import { transaction, insertMilestone, insertSlice, insertTask, insertVerificationEvidence, getMilestone, getSlice, getTask, updateTaskStatus, setTaskSummaryMd, deleteVerificationEvidence, } from "../gsd-db.js";
13
12
  import { resolveSliceFile, resolveTasksDir, clearPathCache } from "../paths.js";
14
13
  import { checkOwnership, taskUnitKey } from "../unit-ownership.js";
@@ -123,17 +122,17 @@ export async function handleCompleteTask(params, basePath) {
123
122
  // Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
124
123
  // Only block if they exist and are closed.
125
124
  const milestone = getMilestone(params.milestoneId);
126
- if (milestone && isClosedStatus(milestone.status)) {
125
+ if (milestone && (milestone.status === "complete" || milestone.status === "done")) {
127
126
  guardError = `cannot complete task in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
128
127
  return;
129
128
  }
130
129
  const slice = getSlice(params.milestoneId, params.sliceId);
131
- if (slice && isClosedStatus(slice.status)) {
130
+ if (slice && (slice.status === "complete" || slice.status === "done")) {
132
131
  guardError = `cannot complete task in a closed slice: ${params.sliceId} (status: ${slice.status})`;
133
132
  return;
134
133
  }
135
134
  const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
136
- if (existingTask && isClosedStatus(existingTask.status)) {
135
+ if (existingTask && (existingTask.status === "complete" || existingTask.status === "done")) {
137
136
  guardError = `task ${params.taskId} is already complete — use gsd_task_reopen first if you need to redo it`;
138
137
  return;
139
138
  }
@@ -1,12 +1,22 @@
1
1
  import { clearParseCache } from "../files.js";
2
- import { isClosedStatus } from "../status-guards.js";
3
- import { isNonEmptyString, validateStringArray } from "../validation.js";
4
2
  import { transaction, getMilestone, insertMilestone, insertSlice, upsertMilestonePlanning, upsertSlicePlanning, } from "../gsd-db.js";
5
3
  import { invalidateStateCache } from "../state.js";
6
4
  import { renderRoadmapFromDb } from "../markdown-renderer.js";
7
5
  import { renderAllProjections } from "../workflow-projections.js";
8
6
  import { writeManifest } from "../workflow-manifest.js";
9
7
  import { appendEvent } from "../workflow-events.js";
8
+ function isNonEmptyString(value) {
9
+ return typeof value === "string" && value.trim().length > 0;
10
+ }
11
+ function validateStringArray(value, field) {
12
+ if (!Array.isArray(value)) {
13
+ throw new Error(`${field} must be an array`);
14
+ }
15
+ if (value.some((item) => !isNonEmptyString(item))) {
16
+ throw new Error(`${field} must contain only non-empty strings`);
17
+ }
18
+ return value;
19
+ }
10
20
  function validateRiskEntries(value) {
11
21
  if (!Array.isArray(value)) {
12
22
  throw new Error("keyRisks must be an array");
@@ -142,7 +152,7 @@ export async function handlePlanMilestone(rawParams, basePath) {
142
152
  try {
143
153
  transaction(() => {
144
154
  const existingMilestone = getMilestone(params.milestoneId);
145
- if (existingMilestone && isClosedStatus(existingMilestone.status)) {
155
+ if (existingMilestone && (existingMilestone.status === "complete" || existingMilestone.status === "done")) {
146
156
  guardError = `cannot re-plan milestone ${params.milestoneId}: it is already complete`;
147
157
  return;
148
158
  }
@@ -154,7 +164,7 @@ export async function handlePlanMilestone(rawParams, basePath) {
154
164
  guardError = `depends_on references unknown milestone: ${depId}`;
155
165
  return;
156
166
  }
157
- if (!isClosedStatus(dep.status)) {
167
+ if (dep.status !== "complete" && dep.status !== "done") {
158
168
  guardError = `depends_on milestone ${depId} is not yet complete (status: ${dep.status})`;
159
169
  return;
160
170
  }
@@ -1,12 +1,22 @@
1
1
  import { clearParseCache } from "../files.js";
2
- import { isClosedStatus } from "../status-guards.js";
3
- import { isNonEmptyString } from "../validation.js";
4
2
  import { transaction, getMilestone, getSlice, insertTask, upsertSlicePlanning, upsertTaskPlanning, insertGateRow, } from "../gsd-db.js";
5
3
  import { invalidateStateCache } from "../state.js";
6
4
  import { renderPlanFromDb } from "../markdown-renderer.js";
7
5
  import { renderAllProjections } from "../workflow-projections.js";
8
6
  import { writeManifest } from "../workflow-manifest.js";
9
7
  import { appendEvent } from "../workflow-events.js";
8
+ function isNonEmptyString(value) {
9
+ return typeof value === "string" && value.trim().length > 0;
10
+ }
11
+ function validateStringArray(value, field) {
12
+ if (!Array.isArray(value)) {
13
+ throw new Error(`${field} must be an array`);
14
+ }
15
+ if (value.some((item) => !isNonEmptyString(item))) {
16
+ throw new Error(`${field} must contain only non-empty strings`);
17
+ }
18
+ return value;
19
+ }
10
20
  function validateTasks(value) {
11
21
  if (!Array.isArray(value) || value.length === 0) {
12
22
  throw new Error("tasks must be a non-empty array");
@@ -103,7 +113,7 @@ export async function handlePlanSlice(rawParams, basePath) {
103
113
  guardError = `milestone not found: ${params.milestoneId}`;
104
114
  return;
105
115
  }
106
- if (isClosedStatus(parentMilestone.status)) {
116
+ if (parentMilestone.status === "complete" || parentMilestone.status === "done") {
107
117
  guardError = `cannot plan slice in a closed milestone: ${params.milestoneId} (status: ${parentMilestone.status})`;
108
118
  return;
109
119
  }
@@ -112,7 +122,7 @@ export async function handlePlanSlice(rawParams, basePath) {
112
122
  guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
113
123
  return;
114
124
  }
115
- if (isClosedStatus(parentSlice.status)) {
125
+ if (parentSlice.status === "complete" || parentSlice.status === "done") {
116
126
  guardError = `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first`;
117
127
  return;
118
128
  }
@@ -1,12 +1,22 @@
1
1
  import { clearParseCache } from "../files.js";
2
- import { isClosedStatus } from "../status-guards.js";
3
- import { isNonEmptyString, validateStringArray } from "../validation.js";
4
2
  import { transaction, getSlice, getTask, insertTask, upsertTaskPlanning } from "../gsd-db.js";
5
3
  import { invalidateStateCache } from "../state.js";
6
4
  import { renderTaskPlanFromDb } from "../markdown-renderer.js";
7
5
  import { renderAllProjections } from "../workflow-projections.js";
8
6
  import { writeManifest } from "../workflow-manifest.js";
9
7
  import { appendEvent } from "../workflow-events.js";
8
+ function isNonEmptyString(value) {
9
+ return typeof value === "string" && value.trim().length > 0;
10
+ }
11
+ function validateStringArray(value, field) {
12
+ if (!Array.isArray(value)) {
13
+ throw new Error(`${field} must be an array`);
14
+ }
15
+ if (value.some((item) => !isNonEmptyString(item))) {
16
+ throw new Error(`${field} must contain only non-empty strings`);
17
+ }
18
+ return value;
19
+ }
10
20
  function validateParams(params) {
11
21
  if (!isNonEmptyString(params?.milestoneId))
12
22
  throw new Error("milestoneId is required");
@@ -51,12 +61,12 @@ export async function handlePlanTask(rawParams, basePath) {
51
61
  guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
52
62
  return;
53
63
  }
54
- if (isClosedStatus(parentSlice.status)) {
64
+ if (parentSlice.status === "complete" || parentSlice.status === "done") {
55
65
  guardError = `cannot plan task in a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
56
66
  return;
57
67
  }
58
68
  const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
59
- if (existingTask && isClosedStatus(existingTask.status)) {
69
+ if (existingTask && (existingTask.status === "complete" || existingTask.status === "done")) {
60
70
  guardError = `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first`;
61
71
  return;
62
72
  }
@@ -1,13 +1,14 @@
1
- import { join } from "node:path";
2
1
  import { clearParseCache } from "../files.js";
3
- import { isClosedStatus } from "../status-guards.js";
4
- import { isNonEmptyString } from "../validation.js";
5
2
  import { transaction, getMilestone, getMilestoneSlices, getSlice, insertSlice, updateSliceFields, insertAssessment, deleteSlice, } from "../gsd-db.js";
6
3
  import { invalidateStateCache } from "../state.js";
7
4
  import { renderRoadmapFromDb, renderAssessmentFromDb } from "../markdown-renderer.js";
8
5
  import { renderAllProjections } from "../workflow-projections.js";
9
6
  import { writeManifest } from "../workflow-manifest.js";
10
7
  import { appendEvent } from "../workflow-events.js";
8
+ import { join } from "node:path";
9
+ function isNonEmptyString(value) {
10
+ return typeof value === "string" && value.trim().length > 0;
11
+ }
11
12
  function validateParams(params) {
12
13
  if (!isNonEmptyString(params?.milestoneId))
13
14
  throw new Error("milestoneId is required");
@@ -75,7 +76,7 @@ export async function handleReassessRoadmap(rawParams, basePath) {
75
76
  guardError = `milestone not found: ${params.milestoneId}`;
76
77
  return;
77
78
  }
78
- if (isClosedStatus(milestone.status)) {
79
+ if (milestone.status === "complete" || milestone.status === "done") {
79
80
  guardError = `cannot reassess a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
80
81
  return;
81
82
  }
@@ -85,7 +86,7 @@ export async function handleReassessRoadmap(rawParams, basePath) {
85
86
  guardError = `completedSliceId not found: ${params.milestoneId}/${params.completedSliceId}`;
86
87
  return;
87
88
  }
88
- if (!isClosedStatus(completedSlice.status)) {
89
+ if (completedSlice.status !== "complete" && completedSlice.status !== "done") {
89
90
  guardError = `completedSliceId ${params.completedSliceId} is not complete (status: ${completedSlice.status}) — reassess can only be called after a slice finishes`;
90
91
  return;
91
92
  }
@@ -93,7 +94,7 @@ export async function handleReassessRoadmap(rawParams, basePath) {
93
94
  const existingSlices = getMilestoneSlices(params.milestoneId);
94
95
  const completedSliceIds = new Set();
95
96
  for (const slice of existingSlices) {
96
- if (isClosedStatus(slice.status)) {
97
+ if (slice.status === "complete" || slice.status === "done") {
97
98
  completedSliceIds.add(slice.id);
98
99
  }
99
100
  }
@@ -11,7 +11,6 @@
11
11
  // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
12
12
  import { getMilestone, getSlice, getSliceTasks, updateSliceStatus, updateTaskStatus, transaction, } from "../gsd-db.js";
13
13
  import { invalidateStateCache } from "../state.js";
14
- import { isClosedStatus } from "../status-guards.js";
15
14
  import { renderAllProjections } from "../workflow-projections.js";
16
15
  import { writeManifest } from "../workflow-manifest.js";
17
16
  import { appendEvent } from "../workflow-events.js";
@@ -32,8 +31,8 @@ export async function handleReopenSlice(params, basePath) {
32
31
  guardError = `milestone not found: ${params.milestoneId}`;
33
32
  return;
34
33
  }
35
- if (isClosedStatus(milestone.status)) {
36
- guardError = `cannot reopen slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
34
+ if (milestone.status === "complete" || milestone.status === "done") {
35
+ guardError = `cannot reopen slice inside a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
37
36
  return;
38
37
  }
39
38
  const slice = getSlice(params.milestoneId, params.sliceId);
@@ -41,7 +40,7 @@ export async function handleReopenSlice(params, basePath) {
41
40
  guardError = `slice not found: ${params.milestoneId}/${params.sliceId}`;
42
41
  return;
43
42
  }
44
- if (!isClosedStatus(slice.status)) {
43
+ if (slice.status !== "complete" && slice.status !== "done") {
45
44
  guardError = `slice ${params.sliceId} is not complete (status: ${slice.status}) — nothing to reopen`;
46
45
  return;
47
46
  }
@@ -10,7 +10,6 @@
10
10
  // Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
11
11
  import { getMilestone, getSlice, getTask, updateTaskStatus, transaction, } from "../gsd-db.js";
12
12
  import { invalidateStateCache } from "../state.js";
13
- import { isClosedStatus } from "../status-guards.js";
14
13
  import { renderAllProjections } from "../workflow-projections.js";
15
14
  import { writeManifest } from "../workflow-manifest.js";
16
15
  import { appendEvent } from "../workflow-events.js";
@@ -33,7 +32,7 @@ export async function handleReopenTask(params, basePath) {
33
32
  guardError = `milestone not found: ${params.milestoneId}`;
34
33
  return;
35
34
  }
36
- if (isClosedStatus(milestone.status)) {
35
+ if (milestone.status === "complete" || milestone.status === "done") {
37
36
  guardError = `cannot reopen task in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
38
37
  return;
39
38
  }
@@ -42,8 +41,8 @@ export async function handleReopenTask(params, basePath) {
42
41
  guardError = `slice not found: ${params.milestoneId}/${params.sliceId}`;
43
42
  return;
44
43
  }
45
- if (isClosedStatus(slice.status)) {
46
- guardError = `cannot reopen task in a closed slice: ${params.sliceId} (status: ${slice.status}) — use gsd_slice_reopen first`;
44
+ if (slice.status === "complete" || slice.status === "done") {
45
+ guardError = `cannot reopen task inside a closed slice: ${params.sliceId} (status: ${slice.status}) — use gsd_slice_reopen first`;
47
46
  return;
48
47
  }
49
48
  const task = getTask(params.milestoneId, params.sliceId, params.taskId);
@@ -51,7 +50,7 @@ export async function handleReopenTask(params, basePath) {
51
50
  guardError = `task not found: ${params.milestoneId}/${params.sliceId}/${params.taskId}`;
52
51
  return;
53
52
  }
54
- if (!isClosedStatus(task.status)) {
53
+ if (task.status !== "complete" && task.status !== "done") {
55
54
  guardError = `task ${params.taskId} is not complete (status: ${task.status}) — nothing to reopen`;
56
55
  return;
57
56
  }
@@ -1,12 +1,13 @@
1
1
  import { clearParseCache } from "../files.js";
2
2
  import { transaction, getSlice, getSliceTasks, getTask, insertTask, upsertTaskPlanning, insertReplanHistory, deleteTask, } from "../gsd-db.js";
3
3
  import { invalidateStateCache } from "../state.js";
4
- import { isClosedStatus } from "../status-guards.js";
5
- import { isNonEmptyString } from "../validation.js";
6
4
  import { renderPlanFromDb, renderReplanFromDb } from "../markdown-renderer.js";
7
5
  import { renderAllProjections } from "../workflow-projections.js";
8
6
  import { writeManifest } from "../workflow-manifest.js";
9
7
  import { appendEvent } from "../workflow-events.js";
8
+ function isNonEmptyString(value) {
9
+ return typeof value === "string" && value.trim().length > 0;
10
+ }
10
11
  function validateParams(params) {
11
12
  if (!isNonEmptyString(params?.milestoneId))
12
13
  throw new Error("milestoneId is required");
@@ -58,7 +59,7 @@ export async function handleReplanSlice(rawParams, basePath) {
58
59
  guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
59
60
  return;
60
61
  }
61
- if (isClosedStatus(parentSlice.status)) {
62
+ if (parentSlice.status === "complete" || parentSlice.status === "done") {
62
63
  guardError = `cannot replan a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
63
64
  return;
64
65
  }
@@ -68,7 +69,7 @@ export async function handleReplanSlice(rawParams, basePath) {
68
69
  guardError = `blockerTaskId not found: ${params.milestoneId}/${params.sliceId}/${params.blockerTaskId}`;
69
70
  return;
70
71
  }
71
- if (!isClosedStatus(blockerTask.status)) {
72
+ if (blockerTask.status !== "complete" && blockerTask.status !== "done") {
72
73
  guardError = `blockerTaskId ${params.blockerTaskId} is not complete (status: ${blockerTask.status}) — the blocker task must be finished before a replan is triggered`;
73
74
  return;
74
75
  }
@@ -76,7 +77,7 @@ export async function handleReplanSlice(rawParams, basePath) {
76
77
  const existingTasks = getSliceTasks(params.milestoneId, params.sliceId);
77
78
  const completedTaskIds = new Set();
78
79
  for (const task of existingTasks) {
79
- if (isClosedStatus(task.status)) {
80
+ if (task.status === "complete" || task.status === "done") {
80
81
  completedTaskIds.add(task.id);
81
82
  }
82
83
  }
@@ -75,17 +75,15 @@ export function resolveRtkBinaryPath(options = {}) {
75
75
  }
76
76
  return resolveSystemRtkPath(options.pathValue ?? getPathValue(env), platform);
77
77
  }
78
- export function rewriteCommandWithRtk(command, options = {}) {
79
- const env = options.env ?? process.env;
78
+ export function rewriteCommandWithRtk(command, env = process.env) {
80
79
  if (!command.trim())
81
80
  return command;
82
81
  if (!isRtkEnabled(env))
83
82
  return command;
84
- const binaryPath = options.binaryPath ?? resolveRtkBinaryPath({ env });
83
+ const binaryPath = resolveRtkBinaryPath({ env });
85
84
  if (!binaryPath)
86
85
  return command;
87
- const run = options.spawnSyncImpl ?? spawnSync;
88
- const result = run(binaryPath, ["rewrite", command], {
86
+ const result = spawnSync(binaryPath, ["rewrite", command], {
89
87
  encoding: "utf-8",
90
88
  env: buildRtkEnv(env),
91
89
  stdio: ["ignore", "pipe", "ignore"],