gsd-pi 2.82.0-dev.3a3c6509d → 2.82.0-dev.4285182e8
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/README.md +1 -1
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +14 -1
- package/dist/resources/extensions/gsd/auto/session.js +4 -0
- package/dist/resources/extensions/gsd/auto/workflow-kernel.js +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-post-unit.js +12 -5
- package/dist/resources/extensions/gsd/auto.js +14 -7
- package/dist/resources/extensions/gsd/commands/catalog.js +7 -1
- package/dist/resources/extensions/gsd/commands/handlers/core.js +1 -0
- package/dist/resources/extensions/gsd/commands/handlers/ops.js +5 -0
- package/dist/resources/extensions/gsd/commands-verdict.js +139 -0
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -8
- package/dist/resources/extensions/gsd/paths.js +4 -0
- package/dist/resources/extensions/gsd/state.js +2 -2
- package/dist/resources/extensions/gsd/templates/plan.md +1 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/dist/resources/extensions/gsd/tools/plan-slice.js +3 -5
- package/dist/resources/extensions/ttsr/ttsr-manager.js +3 -1
- package/dist/tsconfig.extensions.tsbuildinfo +1 -1
- package/dist/web/standalone/.next/BUILD_ID +1 -1
- package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
- 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/react-loadable-manifest.json +3 -3
- package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
- 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.html +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
- package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- 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/index.html +1 -1
- package/dist/web/standalone/.next/server/app/index.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
- 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 +1 -1
- package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
- package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
- package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
- package/dist/web/standalone/.next/server/pages/404.html +1 -1
- package/dist/web/standalone/.next/server/pages/500.html +1 -1
- package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
- package/dist/web/standalone/.next/static/chunks/8359.65b24fac92188a6b.js +10 -0
- package/dist/web/standalone/.next/static/chunks/9441.ff70bb53f6835771.js +1 -0
- package/dist/web/standalone/.next/static/chunks/{webpack-9a4db269f9ed63ad.js → webpack-855d616060cb6e59.js} +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +13 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +2 -0
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +5 -2
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +16 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +6 -2
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/session.ts +4 -0
- package/src/resources/extensions/gsd/auto/workflow-kernel.ts +5 -1
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-post-unit.ts +13 -5
- package/src/resources/extensions/gsd/auto.ts +13 -7
- package/src/resources/extensions/gsd/commands/catalog.ts +7 -1
- package/src/resources/extensions/gsd/commands/handlers/core.ts +1 -0
- package/src/resources/extensions/gsd/commands/handlers/ops.ts +5 -0
- package/src/resources/extensions/gsd/commands-verdict.ts +202 -0
- package/src/resources/extensions/gsd/markdown-renderer.ts +10 -8
- package/src/resources/extensions/gsd/paths.ts +5 -0
- package/src/resources/extensions/gsd/state.ts +2 -2
- package/src/resources/extensions/gsd/templates/plan.md +1 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +6 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +110 -0
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/plan-slice.test.ts +26 -1
- package/src/resources/extensions/gsd/tests/post-unit-state-rebuild.test.ts +84 -0
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -4
- package/src/resources/extensions/ttsr/ttsr-manager.ts +5 -1
- package/dist/web/standalone/.next/static/chunks/8359.7eb3bb8f8ecf4c01.js +0 -10
- package/dist/web/standalone/.next/static/chunks/9441.1081da1125d1764f.js +0 -1
- /package/dist/web/standalone/.next/static/{O6femb9LLl3nlgsDaYwS- → 78uanrILNOKG-Jpi4itAE}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{O6femb9LLl3nlgsDaYwS- → 78uanrILNOKG-Jpi4itAE}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -350,7 +350,7 @@ This is what makes GSD different. Run it, walk away, come back to built software
|
|
|
350
350
|
|
|
351
351
|
Auto mode is a state machine driven by the GSD database at the project root. It derives the next unit of work from authoritative SQLite state, creates a fresh agent session, injects a focused prompt with all relevant context pre-inlined, and lets the LLM execute. When the LLM finishes, auto mode persists the result to the database, refreshes markdown projections such as `STATE.md`, and dispatches the next unit.
|
|
352
352
|
|
|
353
|
-
The database is authoritative for milestones, slices, tasks, requirements, summaries, and completion status. Durable decisions and project knowledge are stored in the `memories` table: decisions are `architecture` memories, and KNOWLEDGE patterns/lessons are `pattern`/`gotcha` memories. Markdown under `.gsd/` is a rendered projection for review, prompts, and git-friendly history; it is not a runtime fallback unless you explicitly run a recovery/import command. In worktree mode,
|
|
353
|
+
The database is authoritative for milestones, slices, tasks, requirements, summaries, and completion status. Durable decisions and project knowledge are stored in the `memories` table: decisions are `architecture` memories, and KNOWLEDGE patterns/lessons are `pattern`/`gotcha` memories. Markdown under `.gsd/` is a rendered projection for review, prompts, and git-friendly history; it is not a runtime fallback unless you explicitly run a recovery/import command. In worktree mode, artifact/projection writes are rendered under the active worktree-local `.gsd/`, while the project-root DB remains authoritative runtime state.
|
|
354
354
|
|
|
355
355
|
`KNOWLEDGE.md` is hybrid: rules remain file-canonical, while patterns and lessons are stored in the `memories` table and rendered back into `KNOWLEDGE.md` on the next session-start projection. Existing pattern and lesson rows are backfilled into memories before projection, so newly captured patterns and lessons may appear in memory-backed prompt context before the file view refreshes.
|
|
356
356
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
8102192ede112252
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Main auto-mode execution loop.
|
|
1
3
|
/**
|
|
2
4
|
* auto/loop.ts — Main auto-mode execution loop.
|
|
3
5
|
*
|
|
@@ -789,11 +791,18 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
789
791
|
unitId: iterData.unitId,
|
|
790
792
|
});
|
|
791
793
|
const finalizeReason = finalizeResult.action === "break" ? finalizeResult.reason : undefined;
|
|
794
|
+
const finalizeStatus = finalizeReason === "step-wizard"
|
|
795
|
+
? "completed"
|
|
796
|
+
: finalizeResult.action === "next"
|
|
797
|
+
? "completed"
|
|
798
|
+
: finalizeResult.action === "continue"
|
|
799
|
+
? "retry"
|
|
800
|
+
: "stopped";
|
|
792
801
|
journalReporter.emit("post-unit-finalize-end", {
|
|
793
802
|
iteration,
|
|
794
803
|
unitType: iterData.unitType,
|
|
795
804
|
unitId: iterData.unitId,
|
|
796
|
-
status:
|
|
805
|
+
status: finalizeStatus,
|
|
797
806
|
action: finalizeResult.action,
|
|
798
807
|
...(finalizeReason ? { reason: finalizeReason } : {}),
|
|
799
808
|
});
|
|
@@ -837,6 +846,10 @@ export async function autoLoop(ctx, pi, s, deps, options) {
|
|
|
837
846
|
}) || dispatchSettled;
|
|
838
847
|
completeIteration();
|
|
839
848
|
finishTurn("completed");
|
|
849
|
+
if (finalizeDecision.action === "complete-and-break") {
|
|
850
|
+
s.preserveStepSurfaceAfterLoopExit = true;
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
840
853
|
}
|
|
841
854
|
catch (loopErr) {
|
|
842
855
|
// ── Blanket catch: absorb unexpected exceptions, apply graduated recovery ──
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Project/App: GSD-2
|
|
2
|
+
// File Purpose: Mutable auto-mode session state container.
|
|
1
3
|
/**
|
|
2
4
|
* AutoSession — encapsulates all mutable auto-mode state into a single instance.
|
|
3
5
|
*
|
|
@@ -26,6 +28,7 @@ export class AutoSession {
|
|
|
26
28
|
active = false;
|
|
27
29
|
paused = false;
|
|
28
30
|
completionStopInProgress = false;
|
|
31
|
+
preserveStepSurfaceAfterLoopExit = false;
|
|
29
32
|
stepMode = false;
|
|
30
33
|
verbose = false;
|
|
31
34
|
activeEngineId = null;
|
|
@@ -210,6 +213,7 @@ export class AutoSession {
|
|
|
210
213
|
this.active = false;
|
|
211
214
|
this.paused = false;
|
|
212
215
|
this.completionStopInProgress = false;
|
|
216
|
+
this.preserveStepSurfaceAfterLoopExit = false;
|
|
213
217
|
this.stepMode = false;
|
|
214
218
|
this.verbose = false;
|
|
215
219
|
this.activeEngineId = null;
|
|
@@ -66,6 +66,9 @@ export function decideEngineDispatch(input) {
|
|
|
66
66
|
export function decideFinalizeResult(input) {
|
|
67
67
|
if (input.action === "break") {
|
|
68
68
|
const reason = input.reason ?? "unknown";
|
|
69
|
+
if (reason === "step-wizard") {
|
|
70
|
+
return { action: "complete-and-break" };
|
|
71
|
+
}
|
|
69
72
|
return {
|
|
70
73
|
action: "stop",
|
|
71
74
|
failureClass: reason === "git-closeout-failure" ? "git" : "closeout",
|
|
@@ -1108,7 +1108,7 @@ export const DISPATCH_RULES = [
|
|
|
1108
1108
|
if (verdict !== "pass") {
|
|
1109
1109
|
return {
|
|
1110
1110
|
action: "stop",
|
|
1111
|
-
reason: `Cannot complete milestone ${mid}: VALIDATION verdict is "${verdict}". Address the validation findings and re-run validation, or
|
|
1111
|
+
reason: `Cannot complete milestone ${mid}: VALIDATION verdict is "${verdict}". Address the validation findings and re-run validation, or run \`/gsd verdict pass --rationale "..."\` to override.`,
|
|
1112
1112
|
level: "warning",
|
|
1113
1113
|
};
|
|
1114
1114
|
}
|
|
@@ -28,7 +28,7 @@ import { regenerateIfMissing } from "./workflow-projections.js";
|
|
|
28
28
|
import { WorktreeStateProjection } from "./worktree-state-projection.js";
|
|
29
29
|
import { createWorkspace, scopeMilestone } from "./workspace.js";
|
|
30
30
|
import { normalizeWorktreePathForCompare } from "./worktree-root.js";
|
|
31
|
-
import { isDbAvailable, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter, getVerificationEvidence } from "./gsd-db.js";
|
|
31
|
+
import { isDbAvailable, getDbPath, refreshOpenDatabaseFromDisk, getTask, getSlice, getMilestone, updateTaskStatus, _getAdapter, getVerificationEvidence } from "./gsd-db.js";
|
|
32
32
|
import { renderPlanCheckboxes } from "./markdown-renderer.js";
|
|
33
33
|
import { consumeSignal } from "./session-status-io.js";
|
|
34
34
|
import { checkPostUnitHooks, isRetryPending, consumeRetryTrigger, persistHookState, resolveHookArtifactPath, } from "./post-unit-hooks.js";
|
|
@@ -294,14 +294,14 @@ export function detectRogueFileWrites(unitType, unitId, basePath) {
|
|
|
294
294
|
* looping indefinitely (#2007).
|
|
295
295
|
*/
|
|
296
296
|
export const MAX_ARTIFACT_VERIFICATION_RETRIES = 3;
|
|
297
|
-
export const STEP_COMPLETE_FALLBACK_MESSAGE = "Step complete. Run /clear, then /gsd to continue (or /gsd auto to run continuously).";
|
|
297
|
+
export const STEP_COMPLETE_FALLBACK_MESSAGE = "Step complete. Run /clear if you want a clean view, then /gsd next to continue one step (or /gsd auto to run continuously).";
|
|
298
298
|
export function buildStepCompleteMessage(nextState) {
|
|
299
299
|
if (nextState.phase === "complete") {
|
|
300
300
|
return "Step complete — milestone finished. Run /gsd status to review, or start the next milestone.";
|
|
301
301
|
}
|
|
302
302
|
const next = describeNextUnit(nextState);
|
|
303
303
|
return `Step complete. Next: ${next.label}\n`
|
|
304
|
-
+ `Run /clear, then /gsd to continue (or /gsd auto to run continuously).`;
|
|
304
|
+
+ `Run /clear if you want a clean view, then /gsd next to continue one step (or /gsd auto to run continuously).`;
|
|
305
305
|
}
|
|
306
306
|
/**
|
|
307
307
|
* Decide whether step mode should stop at the step wizard after a unit finishes.
|
|
@@ -553,6 +553,13 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
553
553
|
if (!opts?.skipSettleDelay) {
|
|
554
554
|
await new Promise(r => setTimeout(r, 100));
|
|
555
555
|
}
|
|
556
|
+
const dbPath = getDbPath();
|
|
557
|
+
if (isDbAvailable() && dbPath && dbPath !== ":memory:") {
|
|
558
|
+
const refreshed = refreshOpenDatabaseFromDisk();
|
|
559
|
+
if (!refreshed) {
|
|
560
|
+
logWarning("db", "post-unit database refresh failed; derived state may be stale");
|
|
561
|
+
}
|
|
562
|
+
}
|
|
556
563
|
// Turn-level git action (commit | snapshot | status-only)
|
|
557
564
|
if (s.currentUnit) {
|
|
558
565
|
const unit = s.currentUnit;
|
|
@@ -1458,8 +1465,8 @@ export async function postUnitPostVerification(pctx) {
|
|
|
1458
1465
|
}
|
|
1459
1466
|
}
|
|
1460
1467
|
// Step mode → show wizard instead of dispatch.
|
|
1461
|
-
// Without this notify(), /gsd
|
|
1462
|
-
//
|
|
1468
|
+
// Without this notify(), /gsd next finishes a unit and silently exits the
|
|
1469
|
+
// loop, leaving the user with no next-step command.
|
|
1463
1470
|
if (s.stepMode) {
|
|
1464
1471
|
let phaseAfterUnit = null;
|
|
1465
1472
|
try {
|
|
@@ -689,6 +689,8 @@ export async function rerootCommandSession(cmdCtx, workspaceRoot) {
|
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
691
|
export async function cleanupAfterLoopExit(ctx) {
|
|
692
|
+
const preserveStepSurface = s.preserveStepSurfaceAfterLoopExit;
|
|
693
|
+
const preservePausedSurface = s.paused;
|
|
692
694
|
s.currentUnit = null;
|
|
693
695
|
s.active = false;
|
|
694
696
|
deactivateGSD();
|
|
@@ -712,19 +714,24 @@ export async function cleanupAfterLoopExit(ctx) {
|
|
|
712
714
|
// A transient provider-error pause intentionally leaves the paused badge
|
|
713
715
|
// visible so the user still has a resumable auto-mode signal on screen.
|
|
714
716
|
if (!s.paused) {
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
717
|
+
if (preserveStepSurface) {
|
|
718
|
+
s.preserveStepSurfaceAfterLoopExit = false;
|
|
719
|
+
}
|
|
720
|
+
else {
|
|
721
|
+
ctx.ui.setStatus("gsd-auto", undefined);
|
|
722
|
+
ctx.ui.setWidget("gsd-progress", undefined);
|
|
723
|
+
if (s.completionStopInProgress) {
|
|
724
|
+
s.completionStopInProgress = false;
|
|
725
|
+
}
|
|
726
|
+
initHealthWidget(ctx);
|
|
719
727
|
}
|
|
720
|
-
initHealthWidget(ctx);
|
|
721
728
|
}
|
|
722
729
|
// ADR-016 phase 3 (#5693): the stop-path basePath restore + chdir routes
|
|
723
730
|
// through `Lifecycle.restoreToProjectRoot()`, the sole owner of both
|
|
724
731
|
// `s.basePath` mutation and the paired `process.chdir` for auto-loop
|
|
725
732
|
// transitions. The verb assigns `s.basePath` before any throwable work, so
|
|
726
733
|
// a thrown error still leaves basePath restored.
|
|
727
|
-
if (s.originalBasePath) {
|
|
734
|
+
if (s.originalBasePath && !preserveStepSurface && !preservePausedSurface) {
|
|
728
735
|
try {
|
|
729
736
|
buildLifecycle().restoreToProjectRoot();
|
|
730
737
|
}
|
|
@@ -732,7 +739,7 @@ export async function cleanupAfterLoopExit(ctx) {
|
|
|
732
739
|
logWarning("engine", `restore project root failed: ${err instanceof Error ? err.message : String(err)}`, { file: "auto.ts" });
|
|
733
740
|
}
|
|
734
741
|
}
|
|
735
|
-
if (s.originalBasePath && s.cmdCtx) {
|
|
742
|
+
if (s.originalBasePath && s.cmdCtx && !preserveStepSurface && !preservePausedSurface) {
|
|
736
743
|
const result = await rerootCommandSession(s.cmdCtx, s.originalBasePath);
|
|
737
744
|
if (result.status === "cancelled") {
|
|
738
745
|
logWarning("engine", "post-loop session re-root was cancelled", { file: "auto.ts", basePath: s.originalBasePath });
|
|
@@ -3,7 +3,7 @@ import { join, resolve } from "node:path";
|
|
|
3
3
|
import { loadRegistry } from "../workflow-templates.js";
|
|
4
4
|
import { gsdHome } from "../gsd-home.js";
|
|
5
5
|
import { VISUAL_BRIEF_MODES } from "../../visual-brief/prompts.js";
|
|
6
|
-
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|brief|queue|quick|discuss|capture|triage|dispatch|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|new-project|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|fast|mcp|rethink|workflow|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|language|worktree|eval-review";
|
|
6
|
+
export const GSD_COMMAND_DESCRIPTION = "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|brief|queue|quick|discuss|capture|triage|dispatch|verdict|history|undo|undo-task|reset-slice|rate|skip|export|cleanup|model|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|debug|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|new-project|parallel|cmux|park|unpark|init|setup|onboarding|inspect|extensions|update|fast|mcp|rethink|workflow|codebase|notifications|ship|do|session-report|backlog|pr-branch|add-tests|scan|language|worktree|eval-review";
|
|
7
7
|
export const TOP_LEVEL_SUBCOMMANDS = [
|
|
8
8
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
9
9
|
{ cmd: "next", desc: "Explicit step mode (same as /gsd)" },
|
|
@@ -21,6 +21,7 @@ export const TOP_LEVEL_SUBCOMMANDS = [
|
|
|
21
21
|
{ cmd: "changelog", desc: "Show categorized release notes" },
|
|
22
22
|
{ cmd: "triage", desc: "Manually trigger triage of pending captures" },
|
|
23
23
|
{ cmd: "dispatch", desc: "Dispatch a specific phase directly" },
|
|
24
|
+
{ cmd: "verdict", desc: "Override the recorded milestone validation verdict (pass|needs-attention|needs-remediation)" },
|
|
24
25
|
{ cmd: "history", desc: "View execution history" },
|
|
25
26
|
{ cmd: "undo", desc: "Revert last completed unit" },
|
|
26
27
|
{ cmd: "undo-task", desc: "Reset a specific task's completion state (DB + markdown)" },
|
|
@@ -235,6 +236,11 @@ const NESTED_COMPLETIONS = {
|
|
|
235
236
|
{ cmd: "uat", desc: "Run user acceptance testing" },
|
|
236
237
|
{ cmd: "replan", desc: "Replan the current slice" },
|
|
237
238
|
],
|
|
239
|
+
verdict: [
|
|
240
|
+
{ cmd: "pass", desc: "Override the milestone validation verdict to pass" },
|
|
241
|
+
{ cmd: "needs-attention", desc: "Override the verdict to needs-attention (requires --rationale)" },
|
|
242
|
+
{ cmd: "needs-remediation", desc: "Override the verdict to needs-remediation (requires --rationale)" },
|
|
243
|
+
],
|
|
238
244
|
rate: [
|
|
239
245
|
{ cmd: "over", desc: "Model was overqualified for this task" },
|
|
240
246
|
{ cmd: "ok", desc: "Model was appropriate for this task" },
|
|
@@ -65,6 +65,7 @@ export function showHelp(ctx, args = "") {
|
|
|
65
65
|
" /gsd new-project Bootstrap a new project (use --deep for staged project-level discovery)",
|
|
66
66
|
" /gsd quick Execute a quick task without full planning overhead",
|
|
67
67
|
" /gsd dispatch Dispatch a specific phase directly [research|plan|execute|complete|uat|replan]",
|
|
68
|
+
" /gsd verdict <v> Override milestone validation verdict [pass|needs-attention|needs-remediation] [--milestone Mxxx] [--rationale \"...\"]",
|
|
68
69
|
" /gsd parallel Parallel milestone orchestration [start|status|stop|pause|resume|merge|watch]",
|
|
69
70
|
" /gsd workflow Custom workflow lifecycle [new|run|list|validate|pause|resume]",
|
|
70
71
|
"",
|
|
@@ -183,6 +183,11 @@ Examples:
|
|
|
183
183
|
await dispatchDirectPhase(ctx, pi, phase, projectRoot());
|
|
184
184
|
return true;
|
|
185
185
|
}
|
|
186
|
+
if (trimmed === "verdict" || trimmed.startsWith("verdict ")) {
|
|
187
|
+
const { handleVerdict } = await import("../../commands-verdict.js");
|
|
188
|
+
await handleVerdict(trimmed.replace(/^verdict\s*/, "").trim(), ctx, projectRoot());
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
186
191
|
if (trimmed === "notifications" || trimmed.startsWith("notifications ")) {
|
|
187
192
|
const { handleNotificationsCommand } = await import("./notifications-handler.js");
|
|
188
193
|
await handleNotificationsCommand(trimmed.replace(/^notifications\s*/, "").trim(), ctx, pi);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { loadFile } from "./files.js";
|
|
2
|
+
import { resolveMilestoneFile } from "./paths.js";
|
|
3
|
+
import { deriveState } from "./state.js";
|
|
4
|
+
import { executeValidateMilestone } from "./tools/workflow-tool-executors.js";
|
|
5
|
+
import { VALIDATION_VERDICTS, extractVerdict, isValidMilestoneVerdict, } from "./verdict-parser.js";
|
|
6
|
+
const USAGE = 'Usage: /gsd verdict <pass|needs-attention|needs-remediation> [--milestone Mxxx] [--rationale "..."]';
|
|
7
|
+
function tokenize(raw) {
|
|
8
|
+
const tokens = [];
|
|
9
|
+
const re = /"([^"]*)"|(\S+)/g;
|
|
10
|
+
let match;
|
|
11
|
+
while ((match = re.exec(raw)) !== null) {
|
|
12
|
+
tokens.push(match[1] ?? match[2]);
|
|
13
|
+
}
|
|
14
|
+
return tokens;
|
|
15
|
+
}
|
|
16
|
+
function parseArgs(raw) {
|
|
17
|
+
const tokens = tokenize(raw);
|
|
18
|
+
const out = {};
|
|
19
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
20
|
+
const t = tokens[i];
|
|
21
|
+
if (t === "--milestone") {
|
|
22
|
+
const next = tokens[++i];
|
|
23
|
+
if (!next)
|
|
24
|
+
return { error: "--milestone requires a milestone ID" };
|
|
25
|
+
out.milestoneId = next;
|
|
26
|
+
}
|
|
27
|
+
else if (t === "--rationale") {
|
|
28
|
+
const next = tokens[++i];
|
|
29
|
+
if (next == null)
|
|
30
|
+
return { error: "--rationale requires a value" };
|
|
31
|
+
out.rationale = next;
|
|
32
|
+
}
|
|
33
|
+
else if (!out.verdict) {
|
|
34
|
+
if (!isValidMilestoneVerdict(t)) {
|
|
35
|
+
return {
|
|
36
|
+
error: `Invalid verdict "${t}". Must be one of: ${VALIDATION_VERDICTS.join(", ")}`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
out.verdict = t;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
return { error: `Unexpected argument: ${t}` };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function extractRemediationRound(content) {
|
|
48
|
+
const fm = content.match(/^---\n([\s\S]*?)\n---/);
|
|
49
|
+
if (!fm)
|
|
50
|
+
return 0;
|
|
51
|
+
const m = fm[1].match(/^remediation_round:\s*(\d+)/im);
|
|
52
|
+
return m ? Number.parseInt(m[1], 10) : 0;
|
|
53
|
+
}
|
|
54
|
+
function extractSection(content, heading) {
|
|
55
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
56
|
+
// Match section bodies bounded by the next "## " heading or end-of-string.
|
|
57
|
+
// Leading "\n" prefix lets a single pattern handle first-line headings too.
|
|
58
|
+
// No /m flag — we want `$` to mean end-of-string, not end-of-line.
|
|
59
|
+
const re = new RegExp(`\\n## ${escaped}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`);
|
|
60
|
+
const m = ("\n" + content).match(re);
|
|
61
|
+
if (!m)
|
|
62
|
+
return undefined;
|
|
63
|
+
return m[1].replace(/\s+$/, "");
|
|
64
|
+
}
|
|
65
|
+
export function parseValidationFile(content) {
|
|
66
|
+
return {
|
|
67
|
+
verdict: extractVerdict(content),
|
|
68
|
+
remediationRound: extractRemediationRound(content),
|
|
69
|
+
successCriteriaChecklist: extractSection(content, "Success Criteria Checklist") ?? "",
|
|
70
|
+
sliceDeliveryAudit: extractSection(content, "Slice Delivery Audit") ?? "",
|
|
71
|
+
crossSliceIntegration: extractSection(content, "Cross-Slice Integration") ?? "",
|
|
72
|
+
requirementCoverage: extractSection(content, "Requirement Coverage") ?? "",
|
|
73
|
+
verificationClasses: extractSection(content, "Verification Class Compliance"),
|
|
74
|
+
verdictRationale: extractSection(content, "Verdict Rationale") ?? "",
|
|
75
|
+
remediationPlan: extractSection(content, "Remediation Plan"),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
export async function handleVerdict(rawArgs, ctx, basePath) {
|
|
79
|
+
if (!rawArgs.trim()) {
|
|
80
|
+
ctx.ui.notify(USAGE, "warning");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const parsed = parseArgs(rawArgs);
|
|
84
|
+
if ("error" in parsed) {
|
|
85
|
+
ctx.ui.notify(`${parsed.error}\n${USAGE}`, "warning");
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!parsed.verdict) {
|
|
89
|
+
ctx.ui.notify(USAGE, "warning");
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
let milestoneId = parsed.milestoneId;
|
|
93
|
+
if (!milestoneId) {
|
|
94
|
+
const state = await deriveState(basePath);
|
|
95
|
+
if (!state.activeMilestone) {
|
|
96
|
+
ctx.ui.notify("No active milestone — pass --milestone Mxxx to target a specific milestone.", "warning");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
milestoneId = state.activeMilestone.id;
|
|
100
|
+
}
|
|
101
|
+
const validationPath = resolveMilestoneFile(basePath, milestoneId, "VALIDATION");
|
|
102
|
+
if (!validationPath) {
|
|
103
|
+
ctx.ui.notify(`No VALIDATION file found for ${milestoneId}. Run gsd_validate_milestone first to produce one.`, "warning");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const existing = await loadFile(validationPath);
|
|
107
|
+
if (!existing) {
|
|
108
|
+
ctx.ui.notify(`Could not read VALIDATION file for ${milestoneId} (${validationPath}).`, "warning");
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
const current = parseValidationFile(existing);
|
|
112
|
+
if (parsed.verdict !== "pass" && !parsed.rationale) {
|
|
113
|
+
ctx.ui.notify(`--rationale is required when overriding to ${parsed.verdict}.`, "warning");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const verdictRationale = parsed.rationale ?? "Manually overridden via /gsd verdict";
|
|
117
|
+
const result = await executeValidateMilestone({
|
|
118
|
+
milestoneId,
|
|
119
|
+
verdict: parsed.verdict,
|
|
120
|
+
remediationRound: current.remediationRound,
|
|
121
|
+
successCriteriaChecklist: current.successCriteriaChecklist,
|
|
122
|
+
sliceDeliveryAudit: current.sliceDeliveryAudit,
|
|
123
|
+
crossSliceIntegration: current.crossSliceIntegration,
|
|
124
|
+
requirementCoverage: current.requirementCoverage,
|
|
125
|
+
verificationClasses: current.verificationClasses,
|
|
126
|
+
verdictRationale,
|
|
127
|
+
remediationPlan: current.remediationPlan,
|
|
128
|
+
}, basePath);
|
|
129
|
+
if (result.isError) {
|
|
130
|
+
const msg = result.content[0]?.type === "text" ? result.content[0].text : "Unknown error";
|
|
131
|
+
ctx.ui.notify(msg, "error");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const prevVerdict = current.verdict ?? "unknown";
|
|
135
|
+
ctx.ui.notify(`Milestone ${milestoneId} verdict: ${prevVerdict} -> ${parsed.verdict}`, "success");
|
|
136
|
+
if (parsed.verdict === "needs-remediation") {
|
|
137
|
+
ctx.ui.notify("Follow up with gsd_reassess_roadmap to add remediation slices, then re-run /gsd auto.", "info");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -14,7 +14,7 @@ import { isClosedStatus } from "./status-guards.js";
|
|
|
14
14
|
import { join, relative } from "node:path";
|
|
15
15
|
import { createRequire } from "node:module";
|
|
16
16
|
import { getAllMilestones, getMilestone, getMilestoneSlices, getSliceTasks, getTask, getSlice, getArtifact, insertArtifact, getGateResults, } from "./gsd-db.js";
|
|
17
|
-
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
17
|
+
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, gsdProjectionRoot, gsdRoot, buildTaskFileName, buildSliceFileName, } from "./paths.js";
|
|
18
18
|
import { saveFile, clearParseCache } from "./files.js";
|
|
19
19
|
import { invalidateStateCache } from "./state.js";
|
|
20
20
|
import { clearPathCache } from "./paths.js";
|
|
@@ -24,7 +24,11 @@ import { clearPathCache } from "./paths.js";
|
|
|
24
24
|
* E.g. "/project/.gsd/milestones/M001/M001-ROADMAP.md" → "milestones/M001/M001-ROADMAP.md"
|
|
25
25
|
*/
|
|
26
26
|
function toArtifactPath(absPath, basePath) {
|
|
27
|
-
const
|
|
27
|
+
const projectionRoot = gsdProjectionRoot(basePath);
|
|
28
|
+
const projectionRel = relative(projectionRoot, absPath);
|
|
29
|
+
const root = projectionRel && !projectionRel.startsWith("..") && !projectionRel.startsWith("/")
|
|
30
|
+
? projectionRoot
|
|
31
|
+
: gsdRoot(basePath);
|
|
28
32
|
const rel = relative(root, absPath);
|
|
29
33
|
// Normalize to forward slashes for consistent DB keys
|
|
30
34
|
return rel.replace(/\\/g, "/");
|
|
@@ -305,10 +309,9 @@ export async function renderPlanFromDb(basePath, milestoneId, sliceId) {
|
|
|
305
309
|
if (tasks.length === 0) {
|
|
306
310
|
throw new Error(`no tasks found for ${milestoneId}/${sliceId}`);
|
|
307
311
|
}
|
|
308
|
-
const slicePath =
|
|
309
|
-
|
|
310
|
-
const absPath =
|
|
311
|
-
?? join(slicePath, `${sliceId}-PLAN.md`);
|
|
312
|
+
const slicePath = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId);
|
|
313
|
+
mkdirSync(slicePath, { recursive: true });
|
|
314
|
+
const absPath = join(slicePath, `${sliceId}-PLAN.md`);
|
|
312
315
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
313
316
|
const sliceGates = getGateResults(milestoneId, sliceId, "slice");
|
|
314
317
|
const content = renderSlicePlanMarkdown(slice, tasks, sliceGates);
|
|
@@ -329,8 +332,7 @@ export async function renderTaskPlanFromDb(basePath, milestoneId, sliceId, taskI
|
|
|
329
332
|
if (!task) {
|
|
330
333
|
throw new Error(`task ${milestoneId}/${sliceId}/${taskId} not found`);
|
|
331
334
|
}
|
|
332
|
-
const tasksDir =
|
|
333
|
-
?? join(gsdRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks");
|
|
335
|
+
const tasksDir = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks");
|
|
334
336
|
mkdirSync(tasksDir, { recursive: true });
|
|
335
337
|
const absPath = join(tasksDir, buildTaskFileName(taskId, "PLAN"));
|
|
336
338
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
@@ -320,6 +320,10 @@ export function resolveGsdPathContract(workRoot, originalProjectRoot) {
|
|
|
320
320
|
isWorktree,
|
|
321
321
|
};
|
|
322
322
|
}
|
|
323
|
+
export function gsdProjectionRoot(basePath) {
|
|
324
|
+
const contract = resolveGsdPathContract(basePath);
|
|
325
|
+
return normalizeRealPath(contract.worktreeGsd ?? contract.projectGsd);
|
|
326
|
+
}
|
|
323
327
|
/**
|
|
324
328
|
* Invalidate the gsdRoot cache.
|
|
325
329
|
* Use ONLY at session-reset boundaries: workspace switch, process exit, or
|
|
@@ -460,7 +460,7 @@ async function handleAllSlicesDone(basePath, activeMilestone, registry, requirem
|
|
|
460
460
|
recentDecisions: [],
|
|
461
461
|
blockers: [
|
|
462
462
|
`Milestone ${activeMilestone.id} validation verdict is needs-remediation but all slices are complete. ` +
|
|
463
|
-
`Add remediation slices via gsd_reassess_roadmap or
|
|
463
|
+
`Add remediation slices via gsd_reassess_roadmap, or run \`/gsd verdict pass --rationale "..."\` to override.`,
|
|
464
464
|
],
|
|
465
465
|
nextAction: `Resolve ${activeMilestone.id} remediation before proceeding.`,
|
|
466
466
|
registry, requirements,
|
|
@@ -1132,7 +1132,7 @@ export async function _deriveStateImpl(basePath, opts) {
|
|
|
1132
1132
|
recentDecisions: [],
|
|
1133
1133
|
blockers: [
|
|
1134
1134
|
`Milestone ${activeMilestone.id} validation verdict is needs-remediation but all slices are complete. ` +
|
|
1135
|
-
`Add remediation slices via gsd_reassess_roadmap or
|
|
1135
|
+
`Add remediation slices via gsd_reassess_roadmap, or run \`/gsd verdict pass --rationale "..."\` to override.`,
|
|
1136
1136
|
],
|
|
1137
1137
|
nextAction: `Resolve ${activeMilestone.id} remediation before proceeding.`,
|
|
1138
1138
|
registry,
|
|
@@ -132,6 +132,7 @@
|
|
|
132
132
|
Verify field rules:
|
|
133
133
|
- MUST be a mechanically executable command: `npm test`, `grep -q "pattern" file`, `test -f path`
|
|
134
134
|
- MUST NOT use shell pipes, redirects, semicolons, backticks, command substitution, or output trimming
|
|
135
|
+
- MUST NOT use inline `node -e` assertions for verification; put assertions in a real test file and run it with `node --test` or a package test script
|
|
135
136
|
- For content/document tasks: verify file existence, section count, YAML validity, or word count
|
|
136
137
|
NOT exact phrasing, specific formulas, or "zero TBD" aspirational criteria
|
|
137
138
|
- If no command can verify the output, write: "Manual review — file exists and is non-empty"
|
|
@@ -57,6 +57,12 @@ skills_used:
|
|
|
57
57
|
- {{howToVerifyThisTaskIsActuallyDone}}
|
|
58
58
|
- {{commandToRun_OR_behaviorToCheck}}
|
|
59
59
|
|
|
60
|
+
## Verify Rules
|
|
61
|
+
|
|
62
|
+
- Use a real executable check, not prose.
|
|
63
|
+
- If the check needs file-content assertions, write a `node:test` file and run it with `node --test` or a package test script.
|
|
64
|
+
- Do not use inline `node -e` assertions for verification.
|
|
65
|
+
|
|
60
66
|
## Observability Impact
|
|
61
67
|
|
|
62
68
|
<!-- OMIT THIS SECTION ENTIRELY for simple tasks that don't touch runtime boundaries,
|
|
@@ -12,7 +12,7 @@ import { appendEvent } from "../workflow-events.js";
|
|
|
12
12
|
import { logWarning } from "../workflow-logger.js";
|
|
13
13
|
import { validatePlanningPathScope } from "../planning-path-scope.js";
|
|
14
14
|
import { checkFilePathConsistency, checkTaskOrdering } from "../pre-execution-checks.js";
|
|
15
|
-
import { buildTaskFileName,
|
|
15
|
+
import { buildTaskFileName, gsdProjectionRoot } from "../paths.js";
|
|
16
16
|
function validateTasks(value) {
|
|
17
17
|
if (!Array.isArray(value) || value.length === 0) {
|
|
18
18
|
throw new Error("tasks must be a non-empty array");
|
|
@@ -241,14 +241,12 @@ export async function handlePlanSlice(rawParams, basePath) {
|
|
|
241
241
|
return { error: guardError };
|
|
242
242
|
}
|
|
243
243
|
try {
|
|
244
|
-
const tasksDir =
|
|
244
|
+
const tasksDir = join(gsdProjectionRoot(basePath), "milestones", params.milestoneId, "slices", params.sliceId, "tasks");
|
|
245
245
|
for (const taskId of omittedTaskIds) {
|
|
246
|
-
if (!tasksDir)
|
|
247
|
-
continue;
|
|
248
246
|
const taskPlanPath = join(tasksDir, buildTaskFileName(taskId, "PLAN"));
|
|
249
247
|
if (existsSync(taskPlanPath))
|
|
250
248
|
rmSync(taskPlanPath, { force: true });
|
|
251
|
-
const artifactPath = relative(
|
|
249
|
+
const artifactPath = relative(gsdProjectionRoot(basePath), taskPlanPath).replace(/\\/g, "/");
|
|
252
250
|
deleteArtifactByPath(artifactPath);
|
|
253
251
|
}
|
|
254
252
|
const renderResult = await renderPlanFromDb(basePath, params.milestoneId, params.sliceId);
|
|
@@ -43,6 +43,7 @@ const MAX_BUFFER_BYTES = 512 * 1024;
|
|
|
43
43
|
* Prevents CPU spinning when deltas arrive faster than regex evaluation (#468).
|
|
44
44
|
*/
|
|
45
45
|
const JS_FALLBACK_CHECK_INTERVAL_MS = 50;
|
|
46
|
+
const JS_FALLBACK_THROTTLE_MIN_BUFFER_BYTES = 4 * 1024;
|
|
46
47
|
const DEFAULT_SCOPE = {
|
|
47
48
|
allowText: true,
|
|
48
49
|
allowThinking: false,
|
|
@@ -308,7 +309,8 @@ export class TtsrManager {
|
|
|
308
309
|
// streams — regex on a growing buffer is O(rules × buffer_size) (#468).
|
|
309
310
|
const now = Date.now();
|
|
310
311
|
const lastCheck = this.#lastJsCheckAt.get(bufferKey) ?? 0;
|
|
311
|
-
if (
|
|
312
|
+
if (nextBuffer.length >= JS_FALLBACK_THROTTLE_MIN_BUFFER_BYTES &&
|
|
313
|
+
now - lastCheck < JS_FALLBACK_CHECK_INTERVAL_MS) {
|
|
312
314
|
stopTimer({ bufferSize: nextBuffer.length, throttled: true });
|
|
313
315
|
return [];
|
|
314
316
|
}
|