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
@@ -9,7 +9,6 @@
9
9
  // parseRoadmap(), parsePlan(), parseSummary() in files.ts.
10
10
 
11
11
  import { readFileSync, existsSync, mkdirSync } from "node:fs";
12
- import { isClosedStatus } from "./status-guards.js";
13
12
  import { join, relative } from "node:path";
14
13
  import { createRequire } from "node:module";
15
14
  import {
@@ -338,7 +337,7 @@ function renderSlicePlanMarkdown(slice: SliceRow, tasks: TaskRow[], gates: GateR
338
337
  lines.push("## Tasks");
339
338
  lines.push("");
340
339
  for (const task of tasks) {
341
- const done = isClosedStatus(task.status) ? "x" : " ";
340
+ const done = task.status === "done" || task.status === "complete" ? "x" : " ";
342
341
  const estimate = task.estimate.trim() ? ` \`est:${task.estimate.trim()}\`` : "";
343
342
  lines.push(`- [${done}] **${task.id}: ${task.title || task.id}**${estimate}`);
344
343
  if (task.description.trim()) {
@@ -574,7 +573,7 @@ export async function renderPlanCheckboxes(
574
573
  // Apply checkbox patches for each task
575
574
  let updated = content;
576
575
  for (const task of tasks) {
577
- const isDone = isClosedStatus(task.status);
576
+ const isDone = task.status === "done" || task.status === "complete";
578
577
  const tid = task.id;
579
578
 
580
579
  if (isDone) {
@@ -858,7 +857,7 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
858
857
  const parsed = parsePlan(content);
859
858
 
860
859
  for (const task of tasks) {
861
- const isDoneInDb = isClosedStatus(task.status);
860
+ const isDoneInDb = task.status === "done" || task.status === "complete";
862
861
  const planTask = parsed.tasks.find((t: { id: string }) => t.id === task.id);
863
862
  if (!planTask) continue;
864
863
 
@@ -881,7 +880,7 @@ export function detectStaleRenders(basePath: string): StaleEntry[] {
881
880
 
882
881
  // Check missing task summary files
883
882
  for (const task of tasks) {
884
- if (isClosedStatus(task.status) && task.full_summary_md) {
883
+ if ((task.status === "done" || task.status === "complete") && task.full_summary_md) {
885
884
  const slicePath = resolveSlicePath(basePath, milestone.id, slice.id);
886
885
  if (slicePath) {
887
886
  const tasksDir = join(slicePath, "tasks");
@@ -33,7 +33,7 @@ export const MODE_DEFAULTS: Record<WorkflowMode, Partial<GSDPreferences>> = {
33
33
  git: {
34
34
  auto_push: true,
35
35
  push_branches: false,
36
- pre_merge_check: "auto",
36
+ pre_merge_check: false,
37
37
  merge_strategy: "squash",
38
38
  isolation: "none",
39
39
  },
@@ -36,7 +36,6 @@ import {
36
36
 
37
37
  import { findMilestoneIds } from './milestone-ids.js';
38
38
  import { loadQueueOrder, sortByQueueOrder } from './queue-order.js';
39
- import { isClosedStatus } from './status-guards.js';
40
39
  import { nativeBatchParseGsdFiles, type BatchParsedFile } from './native-parser-bridge.js';
41
40
 
42
41
  import { join, resolve } from 'path';
@@ -273,6 +272,13 @@ function extractContextTitle(content: string | null, fallback: string): string {
273
272
 
274
273
  // ─── DB-backed State Derivation ────────────────────────────────────────────
275
274
 
275
+ /**
276
+ * Helper: check if a DB status counts as "done" (handles K002 ambiguity).
277
+ */
278
+ function isStatusDone(status: string): boolean {
279
+ return status === 'complete' || status === 'done';
280
+ }
281
+
276
282
  /**
277
283
  * Derive GSD state from the milestones/slices/tasks DB tables.
278
284
  * Flag files (PARKED, VALIDATION, CONTINUE, REPLAN, REPLAN-TRIGGER, CONTEXT-DRAFT)
@@ -362,7 +368,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
362
368
  continue;
363
369
  }
364
370
 
365
- if (isClosedStatus(m.status)) {
371
+ if (isStatusDone(m.status)) {
366
372
  completeMilestoneIds.add(m.id);
367
373
  continue;
368
374
  }
@@ -376,7 +382,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
376
382
 
377
383
  // Check roadmap: all slices done means milestone is complete
378
384
  const slices = getMilestoneSlices(m.id);
379
- if (slices.length > 0 && slices.every(s => isClosedStatus(s.status))) {
385
+ if (slices.length > 0 && slices.every(s => isStatusDone(s.status))) {
380
386
  // All slices done but no summary — still counts as complete for dep resolution
381
387
  // if a summary file exists
382
388
  // Note: without summary file, the milestone is in validating/completing state, not complete
@@ -398,7 +404,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
398
404
 
399
405
  // Ghost milestone check: no slices in DB AND no substantive files on disk
400
406
  const slices = getMilestoneSlices(m.id);
401
- if (slices.length === 0 && !isClosedStatus(m.status)) {
407
+ if (slices.length === 0 && !isStatusDone(m.status)) {
402
408
  // Check disk for ghost detection
403
409
  if (isGhostMilestone(basePath, m.id)) continue;
404
410
  }
@@ -421,7 +427,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
421
427
  }
422
428
 
423
429
  // Not complete — determine if it should be active
424
- const allSlicesDone = slices.length > 0 && slices.every(s => isClosedStatus(s.status));
430
+ const allSlicesDone = slices.length > 0 && slices.every(s => isStatusDone(s.status));
425
431
 
426
432
  // Get title — prefer DB, fall back to context file extraction
427
433
  let title = stripMilestonePrefix(m.title) || m.id;
@@ -576,7 +582,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
576
582
  // Guard: [].every() === true (vacuous truth). Without the length check,
577
583
  // an empty slice array causes a premature phase transition to
578
584
  // validating-milestone. See: https://github.com/gsd-build/gsd-2/issues/2667
579
- const allSlicesDone = activeMilestoneSlices.length > 0 && activeMilestoneSlices.every(s => isClosedStatus(s.status));
585
+ const allSlicesDone = activeMilestoneSlices.length > 0 && activeMilestoneSlices.every(s => isStatusDone(s.status));
580
586
  if (allSlicesDone) {
581
587
  const validationFile = resolveMilestoneFile(basePath, activeMilestone.id, "VALIDATION");
582
588
  const validationContent = validationFile ? await loadFile(validationFile) : null;
@@ -609,19 +615,19 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
609
615
 
610
616
  // ── Find active slice (first incomplete with deps satisfied) ─────────
611
617
  const sliceProgress = {
612
- done: activeMilestoneSlices.filter(s => isClosedStatus(s.status)).length,
618
+ done: activeMilestoneSlices.filter(s => isStatusDone(s.status)).length,
613
619
  total: activeMilestoneSlices.length,
614
620
  };
615
621
 
616
622
  const doneSliceIds = new Set(
617
- activeMilestoneSlices.filter(s => isClosedStatus(s.status)).map(s => s.id)
623
+ activeMilestoneSlices.filter(s => isStatusDone(s.status)).map(s => s.id)
618
624
  );
619
625
 
620
626
  let activeSlice: ActiveRef | null = null;
621
627
  let activeSliceRow: SliceRow | null = null;
622
628
 
623
629
  for (const s of activeMilestoneSlices) {
624
- if (isClosedStatus(s.status)) continue;
630
+ if (isStatusDone(s.status)) continue;
625
631
  if (s.depends.every(dep => doneSliceIds.has(dep))) {
626
632
  activeSlice = { id: s.id, title: s.title };
627
633
  activeSliceRow = s;
@@ -664,7 +670,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
664
670
  // causing the dispatcher to re-dispatch the same completed task forever.
665
671
  let reconciled = false;
666
672
  for (const t of tasks) {
667
- if (isClosedStatus(t.status)) continue;
673
+ if (isStatusDone(t.status)) continue;
668
674
  const summaryPath = resolveTaskFile(basePath, activeMilestone.id, activeSlice.id, t.id, "SUMMARY");
669
675
  if (summaryPath && existsSync(summaryPath)) {
670
676
  try {
@@ -687,11 +693,11 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
687
693
  }
688
694
 
689
695
  const taskProgress = {
690
- done: tasks.filter(t => isClosedStatus(t.status)).length,
696
+ done: tasks.filter(t => isStatusDone(t.status)).length,
691
697
  total: tasks.length,
692
698
  };
693
699
 
694
- const activeTaskRow = tasks.find(t => !isClosedStatus(t.status));
700
+ const activeTaskRow = tasks.find(t => !isStatusDone(t.status));
695
701
 
696
702
  if (!activeTaskRow && tasks.length > 0) {
697
703
  // All tasks done but slice not marked complete → summarizing
@@ -752,7 +758,7 @@ export async function deriveStateFromDb(basePath: string): Promise<GSDState> {
752
758
  }
753
759
 
754
760
  // ── Blocker detection: check completed tasks for blocker_discovered ──
755
- const completedTasks = tasks.filter(t => isClosedStatus(t.status));
761
+ const completedTasks = tasks.filter(t => isStatusDone(t.status));
756
762
  let blockerTaskId: string | null = null;
757
763
  for (const ct of completedTasks) {
758
764
  if (ct.blocker_discovered) {
@@ -635,11 +635,11 @@ describe('git-service', async () => {
635
635
  // S05: Enhanced features — snapshots, pre-merge checks
636
636
  // ═══════════════════════════════════════════════════════════════════════
637
637
 
638
- // ─── createSnapshot: default (enabled) ─────────────────────────────────
638
+ // ─── createSnapshot: prefs enabled ─────────────────────────────────────
639
639
 
640
- test('createSnapshot: enabled by default when prefs omitted', () => {
640
+ test('createSnapshot: enabled', () => {
641
641
  const repo = initBranchTestRepo();
642
- const svc = new GitServiceImpl(repo);
642
+ const svc = new GitServiceImpl(repo, { snapshots: true });
643
643
 
644
644
  // Create a branch with a commit
645
645
  run("git checkout -b gsd/M001/S01", repo);
@@ -675,39 +675,6 @@ describe('git-service', async () => {
675
675
  rmSync(repo, { recursive: true, force: true });
676
676
  });
677
677
 
678
- // ─── runPreMergeCheck: default (auto-detect) ──────────────────────────
679
-
680
- test('runPreMergeCheck: auto-detects when prefs omitted', () => {
681
- const repo = initBranchTestRepo();
682
- createFile(repo, "package.json", JSON.stringify({
683
- name: "test-default",
684
- scripts: { test: 'node -e "process.exit(0)"' },
685
- }));
686
- run("git add -A", repo);
687
- run('git commit -m "add package.json"', repo);
688
-
689
- // No pre_merge_check pref set — should auto-detect and run
690
- const svc = new GitServiceImpl(repo);
691
- const result: PreMergeCheckResult = svc.runPreMergeCheck();
692
-
693
- assert.deepStrictEqual(result.passed, true, "runPreMergeCheck auto-detects and passes when prefs omitted");
694
- assert.ok(!result.skipped, "runPreMergeCheck is not skipped when prefs omitted and package.json exists");
695
-
696
- rmSync(repo, { recursive: true, force: true });
697
- });
698
-
699
- test('runPreMergeCheck: gracefully skips when prefs omitted and no package.json', () => {
700
- const repo = initBranchTestRepo();
701
- // No package.json — auto-detect should skip gracefully
702
- const svc = new GitServiceImpl(repo);
703
- const result: PreMergeCheckResult = svc.runPreMergeCheck();
704
-
705
- assert.deepStrictEqual(result.passed, true, "runPreMergeCheck passes when no package.json (skip)");
706
- assert.deepStrictEqual(result.skipped, true, "runPreMergeCheck skips when no test runner detected");
707
-
708
- rmSync(repo, { recursive: true, force: true });
709
- });
710
-
711
678
  // ─── runPreMergeCheck: pass ────────────────────────────────────────────
712
679
 
713
680
  test('runPreMergeCheck: pass', () => {
@@ -59,7 +59,7 @@ test("solo mode applies correct defaults", () => {
59
59
  const result = applyModeDefaults("solo", { mode: "solo" });
60
60
  assert.equal(result.git?.auto_push, true);
61
61
  assert.equal(result.git?.push_branches, false);
62
- assert.equal(result.git?.pre_merge_check, "auto");
62
+ assert.equal(result.git?.pre_merge_check, false);
63
63
  assert.equal(result.git?.merge_strategy, "squash");
64
64
  assert.equal(result.git?.isolation, "none");
65
65
  assert.equal(result.unique_milestone_ids, false);
@@ -17,7 +17,6 @@ import {
17
17
  updateMilestoneStatus,
18
18
  } from "../gsd-db.js";
19
19
  import { resolveMilestonePath, clearPathCache } from "../paths.js";
20
- import { isClosedStatus } from "../status-guards.js";
21
20
  import { saveFile, clearParseCache } from "../files.js";
22
21
  import { invalidateStateCache } from "../state.js";
23
22
  import { renderAllProjections } from "../workflow-projections.js";
@@ -135,7 +134,7 @@ export async function handleCompleteMilestone(
135
134
  guardError = `milestone not found: ${params.milestoneId}`;
136
135
  return;
137
136
  }
138
- if (isClosedStatus(milestone.status)) {
137
+ if (milestone.status === "complete" || milestone.status === "done") {
139
138
  guardError = `milestone ${params.milestoneId} is already complete`;
140
139
  return;
141
140
  }
@@ -147,7 +146,7 @@ export async function handleCompleteMilestone(
147
146
  return;
148
147
  }
149
148
 
150
- const incompleteSlices = slices.filter(s => !isClosedStatus(s.status));
149
+ const incompleteSlices = slices.filter(s => s.status !== "complete" && s.status !== "done");
151
150
  if (incompleteSlices.length > 0) {
152
151
  const incompleteIds = incompleteSlices.map(s => `${s.id} (status: ${s.status})`).join(", ");
153
152
  guardError = `incomplete slices: ${incompleteIds}`;
@@ -157,7 +156,7 @@ export async function handleCompleteMilestone(
157
156
  // Deep check: verify all tasks in all slices are complete
158
157
  for (const slice of slices) {
159
158
  const tasks = getSliceTasks(params.milestoneId, slice.id);
160
- const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
159
+ const incompleteTasks = tasks.filter(t => t.status !== "complete" && t.status !== "done");
161
160
  if (incompleteTasks.length > 0) {
162
161
  const ids = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
163
162
  guardError = `slice ${slice.id} has incomplete tasks: ${ids}`;
@@ -11,7 +11,6 @@ import { join } from "node:path";
11
11
  import { mkdirSync } from "node:fs";
12
12
 
13
13
  import type { CompleteSliceParams } from "../types.js";
14
- import { isClosedStatus } from "../status-guards.js";
15
14
  import {
16
15
  transaction,
17
16
  insertMilestone,
@@ -226,13 +225,13 @@ export async function handleCompleteSlice(
226
225
  // Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
227
226
  // Only block if they exist and are closed.
228
227
  const milestone = getMilestone(params.milestoneId);
229
- if (milestone && isClosedStatus(milestone.status)) {
228
+ if (milestone && (milestone.status === "complete" || milestone.status === "done")) {
230
229
  guardError = `cannot complete slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
231
230
  return;
232
231
  }
233
232
 
234
233
  const slice = getSlice(params.milestoneId, params.sliceId);
235
- if (slice && isClosedStatus(slice.status)) {
234
+ if (slice && (slice.status === "complete" || slice.status === "done")) {
236
235
  guardError = `slice ${params.sliceId} is already complete — use gsd_slice_reopen first if you need to redo it`;
237
236
  return;
238
237
  }
@@ -244,7 +243,7 @@ export async function handleCompleteSlice(
244
243
  return;
245
244
  }
246
245
 
247
- const incompleteTasks = tasks.filter(t => !isClosedStatus(t.status));
246
+ const incompleteTasks = tasks.filter(t => t.status !== "complete" && t.status !== "done");
248
247
  if (incompleteTasks.length > 0) {
249
248
  const incompleteIds = incompleteTasks.map(t => `${t.id} (status: ${t.status})`).join(", ");
250
249
  guardError = `incomplete tasks: ${incompleteIds}`;
@@ -11,7 +11,6 @@ import { join } from "node:path";
11
11
  import { mkdirSync, existsSync } from "node:fs";
12
12
 
13
13
  import type { CompleteTaskParams } from "../types.js";
14
- import { isClosedStatus } from "../status-guards.js";
15
14
  import {
16
15
  transaction,
17
16
  insertMilestone,
@@ -160,19 +159,19 @@ export async function handleCompleteTask(
160
159
  // Milestone/slice not existing is OK — insertMilestone/insertSlice below will auto-create.
161
160
  // Only block if they exist and are closed.
162
161
  const milestone = getMilestone(params.milestoneId);
163
- if (milestone && isClosedStatus(milestone.status)) {
162
+ if (milestone && (milestone.status === "complete" || milestone.status === "done")) {
164
163
  guardError = `cannot complete task in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
165
164
  return;
166
165
  }
167
166
 
168
167
  const slice = getSlice(params.milestoneId, params.sliceId);
169
- if (slice && isClosedStatus(slice.status)) {
168
+ if (slice && (slice.status === "complete" || slice.status === "done")) {
170
169
  guardError = `cannot complete task in a closed slice: ${params.sliceId} (status: ${slice.status})`;
171
170
  return;
172
171
  }
173
172
 
174
173
  const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
175
- if (existingTask && isClosedStatus(existingTask.status)) {
174
+ if (existingTask && (existingTask.status === "complete" || existingTask.status === "done")) {
176
175
  guardError = `task ${params.taskId} is already complete — use gsd_task_reopen first if you need to redo it`;
177
176
  return;
178
177
  }
@@ -1,6 +1,4 @@
1
1
  import { clearParseCache } from "../files.js";
2
- import { isClosedStatus } from "../status-guards.js";
3
- import { isNonEmptyString, validateStringArray } from "../validation.js";
4
2
  import {
5
3
  transaction,
6
4
  getMilestone,
@@ -56,6 +54,20 @@ export interface PlanMilestoneResult {
56
54
  roadmapPath: string;
57
55
  }
58
56
 
57
+ function isNonEmptyString(value: unknown): value is string {
58
+ return typeof value === "string" && value.trim().length > 0;
59
+ }
60
+
61
+ function validateStringArray(value: unknown, field: string): string[] {
62
+ if (!Array.isArray(value)) {
63
+ throw new Error(`${field} must be an array`);
64
+ }
65
+ if (value.some((item) => !isNonEmptyString(item))) {
66
+ throw new Error(`${field} must contain only non-empty strings`);
67
+ }
68
+ return value;
69
+ }
70
+
59
71
  function validateRiskEntries(value: unknown): Array<{ risk: string; whyItMatters: string }> {
60
72
  if (!Array.isArray(value)) {
61
73
  throw new Error("keyRisks must be an array");
@@ -184,7 +196,7 @@ export async function handlePlanMilestone(
184
196
  try {
185
197
  transaction(() => {
186
198
  const existingMilestone = getMilestone(params.milestoneId);
187
- if (existingMilestone && isClosedStatus(existingMilestone.status)) {
199
+ if (existingMilestone && (existingMilestone.status === "complete" || existingMilestone.status === "done")) {
188
200
  guardError = `cannot re-plan milestone ${params.milestoneId}: it is already complete`;
189
201
  return;
190
202
  }
@@ -197,7 +209,7 @@ export async function handlePlanMilestone(
197
209
  guardError = `depends_on references unknown milestone: ${depId}`;
198
210
  return;
199
211
  }
200
- if (!isClosedStatus(dep.status)) {
212
+ if (dep.status !== "complete" && dep.status !== "done") {
201
213
  guardError = `depends_on milestone ${depId} is not yet complete (status: ${dep.status})`;
202
214
  return;
203
215
  }
@@ -1,6 +1,4 @@
1
1
  import { clearParseCache } from "../files.js";
2
- import { isClosedStatus } from "../status-guards.js";
3
- import { isNonEmptyString, validateStringArray } from "../validation.js";
4
2
  import {
5
3
  transaction,
6
4
  getMilestone,
@@ -52,6 +50,20 @@ export interface PlanSliceResult {
52
50
  taskPlanPaths: string[];
53
51
  }
54
52
 
53
+ function isNonEmptyString(value: unknown): value is string {
54
+ return typeof value === "string" && value.trim().length > 0;
55
+ }
56
+
57
+ function validateStringArray(value: unknown, field: string): string[] {
58
+ if (!Array.isArray(value)) {
59
+ throw new Error(`${field} must be an array`);
60
+ }
61
+ if (value.some((item) => !isNonEmptyString(item))) {
62
+ throw new Error(`${field} must contain only non-empty strings`);
63
+ }
64
+ return value;
65
+ }
66
+
55
67
  function validateTasks(value: unknown): PlanSliceTaskInput[] {
56
68
  if (!Array.isArray(value) || value.length === 0) {
57
69
  throw new Error("tasks must be a non-empty array");
@@ -145,7 +157,7 @@ export async function handlePlanSlice(
145
157
  guardError = `milestone not found: ${params.milestoneId}`;
146
158
  return;
147
159
  }
148
- if (isClosedStatus(parentMilestone.status)) {
160
+ if (parentMilestone.status === "complete" || parentMilestone.status === "done") {
149
161
  guardError = `cannot plan slice in a closed milestone: ${params.milestoneId} (status: ${parentMilestone.status})`;
150
162
  return;
151
163
  }
@@ -155,7 +167,7 @@ export async function handlePlanSlice(
155
167
  guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
156
168
  return;
157
169
  }
158
- if (isClosedStatus(parentSlice.status)) {
170
+ if (parentSlice.status === "complete" || parentSlice.status === "done") {
159
171
  guardError = `cannot re-plan slice ${params.sliceId}: it is already complete — use gsd_slice_reopen first`;
160
172
  return;
161
173
  }
@@ -1,6 +1,4 @@
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";
@@ -34,6 +32,20 @@ export interface PlanTaskResult {
34
32
  taskPlanPath: string;
35
33
  }
36
34
 
35
+ function isNonEmptyString(value: unknown): value is string {
36
+ return typeof value === "string" && value.trim().length > 0;
37
+ }
38
+
39
+ function validateStringArray(value: unknown, field: string): string[] {
40
+ if (!Array.isArray(value)) {
41
+ throw new Error(`${field} must be an array`);
42
+ }
43
+ if (value.some((item) => !isNonEmptyString(item))) {
44
+ throw new Error(`${field} must contain only non-empty strings`);
45
+ }
46
+ return value;
47
+ }
48
+
37
49
  function validateParams(params: PlanTaskParams): PlanTaskParams {
38
50
  if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required");
39
51
  if (!isNonEmptyString(params?.sliceId)) throw new Error("sliceId is required");
@@ -77,13 +89,13 @@ export async function handlePlanTask(
77
89
  guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
78
90
  return;
79
91
  }
80
- if (isClosedStatus(parentSlice.status)) {
92
+ if (parentSlice.status === "complete" || parentSlice.status === "done") {
81
93
  guardError = `cannot plan task in a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
82
94
  return;
83
95
  }
84
96
 
85
97
  const existingTask = getTask(params.milestoneId, params.sliceId, params.taskId);
86
- if (existingTask && isClosedStatus(existingTask.status)) {
98
+ if (existingTask && (existingTask.status === "complete" || existingTask.status === "done")) {
87
99
  guardError = `cannot re-plan task ${params.taskId}: it is already complete — use gsd_task_reopen first`;
88
100
  return;
89
101
  }
@@ -1,7 +1,4 @@
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 {
6
3
  transaction,
7
4
  getMilestone,
@@ -17,6 +14,7 @@ import { renderRoadmapFromDb, renderAssessmentFromDb } from "../markdown-rendere
17
14
  import { renderAllProjections } from "../workflow-projections.js";
18
15
  import { writeManifest } from "../workflow-manifest.js";
19
16
  import { appendEvent } from "../workflow-events.js";
17
+ import { join } from "node:path";
20
18
 
21
19
  export interface SliceChangeInput {
22
20
  sliceId: string;
@@ -49,6 +47,9 @@ export interface ReassessRoadmapResult {
49
47
  roadmapPath: string;
50
48
  }
51
49
 
50
+ function isNonEmptyString(value: unknown): value is string {
51
+ return typeof value === "string" && value.trim().length > 0;
52
+ }
52
53
 
53
54
  function validateParams(params: ReassessRoadmapParams): ReassessRoadmapParams {
54
55
  if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required");
@@ -124,7 +125,7 @@ export async function handleReassessRoadmap(
124
125
  guardError = `milestone not found: ${params.milestoneId}`;
125
126
  return;
126
127
  }
127
- if (isClosedStatus(milestone.status)) {
128
+ if (milestone.status === "complete" || milestone.status === "done") {
128
129
  guardError = `cannot reassess a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
129
130
  return;
130
131
  }
@@ -135,7 +136,7 @@ export async function handleReassessRoadmap(
135
136
  guardError = `completedSliceId not found: ${params.milestoneId}/${params.completedSliceId}`;
136
137
  return;
137
138
  }
138
- if (!isClosedStatus(completedSlice.status)) {
139
+ if (completedSlice.status !== "complete" && completedSlice.status !== "done") {
139
140
  guardError = `completedSliceId ${params.completedSliceId} is not complete (status: ${completedSlice.status}) — reassess can only be called after a slice finishes`;
140
141
  return;
141
142
  }
@@ -144,7 +145,7 @@ export async function handleReassessRoadmap(
144
145
  const existingSlices = getMilestoneSlices(params.milestoneId);
145
146
  const completedSliceIds = new Set<string>();
146
147
  for (const slice of existingSlices) {
147
- if (isClosedStatus(slice.status)) {
148
+ if (slice.status === "complete" || slice.status === "done") {
148
149
  completedSliceIds.add(slice.id);
149
150
  }
150
151
  }
@@ -20,7 +20,6 @@ import {
20
20
  transaction,
21
21
  } from "../gsd-db.js";
22
22
  import { invalidateStateCache } from "../state.js";
23
- import { isClosedStatus } from "../status-guards.js";
24
23
  import { renderAllProjections } from "../workflow-projections.js";
25
24
  import { writeManifest } from "../workflow-manifest.js";
26
25
  import { appendEvent } from "../workflow-events.js";
@@ -63,8 +62,8 @@ export async function handleReopenSlice(
63
62
  guardError = `milestone not found: ${params.milestoneId}`;
64
63
  return;
65
64
  }
66
- if (isClosedStatus(milestone.status)) {
67
- guardError = `cannot reopen slice in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
65
+ if (milestone.status === "complete" || milestone.status === "done") {
66
+ guardError = `cannot reopen slice inside a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
68
67
  return;
69
68
  }
70
69
 
@@ -73,7 +72,7 @@ export async function handleReopenSlice(
73
72
  guardError = `slice not found: ${params.milestoneId}/${params.sliceId}`;
74
73
  return;
75
74
  }
76
- if (!isClosedStatus(slice.status)) {
75
+ if (slice.status !== "complete" && slice.status !== "done") {
77
76
  guardError = `slice ${params.sliceId} is not complete (status: ${slice.status}) — nothing to reopen`;
78
77
  return;
79
78
  }
@@ -18,7 +18,6 @@ import {
18
18
  transaction,
19
19
  } from "../gsd-db.js";
20
20
  import { invalidateStateCache } from "../state.js";
21
- import { isClosedStatus } from "../status-guards.js";
22
21
  import { renderAllProjections } from "../workflow-projections.js";
23
22
  import { writeManifest } from "../workflow-manifest.js";
24
23
  import { appendEvent } from "../workflow-events.js";
@@ -64,7 +63,7 @@ export async function handleReopenTask(
64
63
  guardError = `milestone not found: ${params.milestoneId}`;
65
64
  return;
66
65
  }
67
- if (isClosedStatus(milestone.status)) {
66
+ if (milestone.status === "complete" || milestone.status === "done") {
68
67
  guardError = `cannot reopen task in a closed milestone: ${params.milestoneId} (status: ${milestone.status})`;
69
68
  return;
70
69
  }
@@ -74,8 +73,8 @@ export async function handleReopenTask(
74
73
  guardError = `slice not found: ${params.milestoneId}/${params.sliceId}`;
75
74
  return;
76
75
  }
77
- if (isClosedStatus(slice.status)) {
78
- guardError = `cannot reopen task in a closed slice: ${params.sliceId} (status: ${slice.status}) — use gsd_slice_reopen first`;
76
+ if (slice.status === "complete" || slice.status === "done") {
77
+ guardError = `cannot reopen task inside a closed slice: ${params.sliceId} (status: ${slice.status}) — use gsd_slice_reopen first`;
79
78
  return;
80
79
  }
81
80
 
@@ -84,7 +83,7 @@ export async function handleReopenTask(
84
83
  guardError = `task not found: ${params.milestoneId}/${params.sliceId}/${params.taskId}`;
85
84
  return;
86
85
  }
87
- if (!isClosedStatus(task.status)) {
86
+ if (task.status !== "complete" && task.status !== "done") {
88
87
  guardError = `task ${params.taskId} is not complete (status: ${task.status}) — nothing to reopen`;
89
88
  return;
90
89
  }
@@ -10,8 +10,6 @@ import {
10
10
  deleteTask,
11
11
  } from "../gsd-db.js";
12
12
  import { invalidateStateCache } from "../state.js";
13
- import { isClosedStatus } from "../status-guards.js";
14
- import { isNonEmptyString } from "../validation.js";
15
13
  import { renderPlanFromDb, renderReplanFromDb } from "../markdown-renderer.js";
16
14
  import { renderAllProjections } from "../workflow-projections.js";
17
15
  import { writeManifest } from "../workflow-manifest.js";
@@ -50,6 +48,10 @@ export interface ReplanSliceResult {
50
48
  planPath: string;
51
49
  }
52
50
 
51
+ function isNonEmptyString(value: unknown): value is string {
52
+ return typeof value === "string" && value.trim().length > 0;
53
+ }
54
+
53
55
  function validateParams(params: ReplanSliceParams): ReplanSliceParams {
54
56
  if (!isNonEmptyString(params?.milestoneId)) throw new Error("milestoneId is required");
55
57
  if (!isNonEmptyString(params?.sliceId)) throw new Error("sliceId is required");
@@ -102,7 +104,7 @@ export async function handleReplanSlice(
102
104
  guardError = `missing parent slice: ${params.milestoneId}/${params.sliceId}`;
103
105
  return;
104
106
  }
105
- if (isClosedStatus(parentSlice.status)) {
107
+ if (parentSlice.status === "complete" || parentSlice.status === "done") {
106
108
  guardError = `cannot replan a closed slice: ${params.sliceId} (status: ${parentSlice.status})`;
107
109
  return;
108
110
  }
@@ -113,7 +115,7 @@ export async function handleReplanSlice(
113
115
  guardError = `blockerTaskId not found: ${params.milestoneId}/${params.sliceId}/${params.blockerTaskId}`;
114
116
  return;
115
117
  }
116
- if (!isClosedStatus(blockerTask.status)) {
118
+ if (blockerTask.status !== "complete" && blockerTask.status !== "done") {
117
119
  guardError = `blockerTaskId ${params.blockerTaskId} is not complete (status: ${blockerTask.status}) — the blocker task must be finished before a replan is triggered`;
118
120
  return;
119
121
  }
@@ -122,7 +124,7 @@ export async function handleReplanSlice(
122
124
  const existingTasks = getSliceTasks(params.milestoneId, params.sliceId);
123
125
  const completedTaskIds = new Set<string>();
124
126
  for (const task of existingTasks) {
125
- if (isClosedStatus(task.status)) {
127
+ if (task.status === "complete" || task.status === "done") {
126
128
  completedTaskIds.add(task.id);
127
129
  }
128
130
  }