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.
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +3 -3
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +2 -2
- package/dist/resources/extensions/gsd/git-service.js +3 -4
- package/dist/resources/extensions/gsd/markdown-renderer.js +4 -5
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/state.js +18 -13
- package/dist/resources/extensions/gsd/tools/complete-milestone.js +3 -4
- package/dist/resources/extensions/gsd/tools/complete-slice.js +3 -4
- package/dist/resources/extensions/gsd/tools/complete-task.js +3 -4
- package/dist/resources/extensions/gsd/tools/plan-milestone.js +14 -4
- package/dist/resources/extensions/gsd/tools/plan-slice.js +14 -4
- package/dist/resources/extensions/gsd/tools/plan-task.js +14 -4
- package/dist/resources/extensions/gsd/tools/reassess-roadmap.js +7 -6
- package/dist/resources/extensions/gsd/tools/reopen-slice.js +3 -4
- package/dist/resources/extensions/gsd/tools/reopen-task.js +4 -5
- package/dist/resources/extensions/gsd/tools/replan-slice.js +6 -5
- package/dist/resources/extensions/shared/rtk.js +3 -5
- package/dist/rtk.js +1 -3
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +19 -19
- package/dist/web/standalone/.next/build-manifest.json +3 -3
- package/dist/web/standalone/.next/prerender-manifest.json +3 -3
- package/dist/web/standalone/.next/required-server-files.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page.js +3 -3
- package/dist/web/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.html +2 -2
- package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found/page.js +2 -2
- package/dist/web/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/boot/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/browse-directories/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/captures/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/cleanup/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/dev-mode/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/doctor/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/experimental/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/experimental/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/export-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/files/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/forensics/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/git/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/history/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/hooks/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/inspect/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/knowledge/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/live-state/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/onboarding/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/preferences/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/recovery/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/remote-questions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/remote-questions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/browser/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/command/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/events/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/session/events/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/session/manage/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/settings-data/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/shutdown/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/skill-health/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/steer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/switch-root/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/input/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/input/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/resize/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +2 -2
- package/dist/web/standalone/.next/server/app/api/terminal/sessions/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +4 -4
- package/dist/web/standalone/.next/server/app/api/terminal/stream/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/terminal/upload/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/undo/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/update/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
- package/dist/web/standalone/.next/server/app/api/visualizer/route_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/page.js +2 -2
- package/dist/web/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +19 -19
- package/dist/web/standalone/.next/server/chunks/2229.js +1 -1
- package/dist/web/standalone/.next/server/chunks/7471.js +3 -3
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware.js +2 -2
- package/dist/web/standalone/.next/server/next-font-manifest.js +1 -1
- package/dist/web/standalone/.next/server/next-font-manifest.json +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +2 -2
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/app/_not-found/{page-f2a7482d42a5614b.js → page-2f24283c162b6ab3.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/{layout-a16c7a7ecdf0c2cf.js → layout-9ecfd95f343793f0.js} +1 -1
- package/dist/web/standalone/.next/static/chunks/app/page-fbecd1237e2d6d1f.js +1 -0
- package/dist/web/standalone/.next/static/chunks/main-app-d3d4c336195465f9.js +1 -0
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-ab5a8926e07ec673.js +1 -0
- package/dist/web/standalone/node_modules/node-pty/build/Makefile +2 -2
- package/dist/web/standalone/node_modules/node-pty/build/Release/pty.node +0 -0
- package/dist/web/standalone/node_modules/node-pty/build/pty.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_except.target.mk +14 -14
- package/dist/web/standalone/node_modules/node-pty/node-addon-api/node_addon_api_maybe.target.mk +14 -14
- package/dist/web/standalone/server.js +1 -1
- package/package.json +1 -1
- package/scripts/ensure-workspace-builds.cjs +8 -36
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +3 -3
- package/src/resources/extensions/gsd/docs/preferences-reference.md +2 -2
- package/src/resources/extensions/gsd/git-service.ts +3 -4
- package/src/resources/extensions/gsd/markdown-renderer.ts +4 -5
- package/src/resources/extensions/gsd/preferences-types.ts +1 -1
- package/src/resources/extensions/gsd/state.ts +19 -13
- package/src/resources/extensions/gsd/tests/git-service.test.ts +3 -36
- package/src/resources/extensions/gsd/tests/preferences.test.ts +1 -1
- package/src/resources/extensions/gsd/tools/complete-milestone.ts +3 -4
- package/src/resources/extensions/gsd/tools/complete-slice.ts +3 -4
- package/src/resources/extensions/gsd/tools/complete-task.ts +3 -4
- package/src/resources/extensions/gsd/tools/plan-milestone.ts +16 -4
- package/src/resources/extensions/gsd/tools/plan-slice.ts +16 -4
- package/src/resources/extensions/gsd/tools/plan-task.ts +16 -4
- package/src/resources/extensions/gsd/tools/reassess-roadmap.ts +7 -6
- package/src/resources/extensions/gsd/tools/reopen-slice.ts +3 -4
- package/src/resources/extensions/gsd/tools/reopen-task.ts +4 -5
- package/src/resources/extensions/gsd/tools/replan-slice.ts +7 -5
- package/src/resources/extensions/shared/rtk.ts +3 -12
- package/dist/resources/extensions/gsd/status-guards.js +0 -12
- package/dist/resources/extensions/gsd/validation.js +0 -21
- package/dist/web/standalone/.next/static/chunks/app/page-b950e4e384cc62b3.js +0 -1
- package/dist/web/standalone/.next/static/chunks/main-app-fdab67f7802d7832.js +0 -1
- package/dist/web/standalone/.next/static/chunks/next/dist/client/components/builtin/global-error-459824ffb8c323dd.js +0 -1
- package/src/resources/extensions/gsd/status-guards.ts +0 -13
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +0 -30
- package/src/resources/extensions/gsd/tests/validation.test.ts +0 -72
- package/src/resources/extensions/gsd/validation.ts +0 -23
- /package/dist/web/standalone/.next/static/{KTe1kB5nPLQFIIFz2OcmI → vlgS2rkXjxeKhgXhdp4lh}/_buildManifest.js +0 -0
- /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 =
|
|
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 =
|
|
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 =
|
|
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 (
|
|
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");
|
|
@@ -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 (
|
|
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 =>
|
|
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 && !
|
|
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 =>
|
|
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 =>
|
|
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 =>
|
|
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 =>
|
|
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 (
|
|
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 (
|
|
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 =>
|
|
696
|
+
done: tasks.filter(t => isStatusDone(t.status)).length,
|
|
691
697
|
total: tasks.length,
|
|
692
698
|
};
|
|
693
699
|
|
|
694
|
-
const activeTaskRow = tasks.find(t => !
|
|
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 =>
|
|
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:
|
|
638
|
+
// ─── createSnapshot: prefs enabled ─────────────────────────────────────
|
|
639
639
|
|
|
640
|
-
test('createSnapshot: enabled
|
|
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,
|
|
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 (
|
|
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 =>
|
|
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 =>
|
|
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 &&
|
|
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 &&
|
|
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 =>
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 &&
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
67
|
-
guardError = `cannot reopen slice
|
|
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 (
|
|
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 (
|
|
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 (
|
|
78
|
-
guardError = `cannot reopen task
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
127
|
+
if (task.status === "complete" || task.status === "done") {
|
|
126
128
|
completedTaskIds.add(task.id);
|
|
127
129
|
}
|
|
128
130
|
}
|