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