gsd-pi 2.82.0-dev.3709f22a5 → 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 +2 -2
- package/dist/resources/.managed-resources-content-hash +1 -1
- package/dist/resources/extensions/claude-code-cli/stream-adapter.js +1 -1
- package/dist/resources/extensions/gsd/auto/loop.js +14 -1
- package/dist/resources/extensions/gsd/auto/phases.js +53 -29
- 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-direct-dispatch.js +1 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +13 -19
- package/dist/resources/extensions/gsd/auto-post-unit.js +13 -6
- package/dist/resources/extensions/gsd/auto-recovery.js +40 -13
- package/dist/resources/extensions/gsd/auto-start.js +3 -3
- package/dist/resources/extensions/gsd/auto-verification.js +17 -4
- package/dist/resources/extensions/gsd/auto-worktree.js +65 -9
- package/dist/resources/extensions/gsd/auto.js +14 -8
- package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +6 -1
- package/dist/resources/extensions/gsd/bootstrap/db-tools.js +1 -1
- package/dist/resources/extensions/gsd/bootstrap/write-gate.js +1 -1
- 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-prefs-wizard.js +7 -2
- package/dist/resources/extensions/gsd/commands-verdict.js +139 -0
- package/dist/resources/extensions/gsd/crash-recovery.js +16 -4
- package/dist/resources/extensions/gsd/db/milestone-leases.js +24 -0
- package/dist/resources/extensions/gsd/forensics.js +3 -3
- package/dist/resources/extensions/gsd/git-service.js +6 -2
- package/dist/resources/extensions/gsd/gsd-db.js +20 -6
- package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -3
- package/dist/resources/extensions/gsd/guided-flow.js +8 -5
- package/dist/resources/extensions/gsd/markdown-renderer.js +10 -8
- package/dist/resources/extensions/gsd/paths.js +4 -0
- package/dist/resources/extensions/gsd/queue-reorder-ui.js +30 -13
- package/dist/resources/extensions/gsd/state.js +3 -3
- package/dist/resources/extensions/gsd/status-guards.js +7 -0
- 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/gsd/workflow-mcp.js +17 -1
- package/dist/resources/extensions/gsd/worktree-manager.js +1 -1
- 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 +11 -11
- 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/api/browse-directories/route.js +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 +11 -11
- 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-ai/dist/providers/google-gemini-cli.d.ts.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.js +5 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.js.map +1 -1
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts +2 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.d.ts.map +1 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js +41 -0
- package/packages/pi-ai/dist/providers/google-gemini-cli.test.js.map +1 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.test.ts +49 -0
- package/packages/pi-ai/src/providers/google-gemini-cli.ts +7 -0
- package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js +44 -3
- package/packages/pi-coding-agent/dist/core/chat-controller-ordering.test.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js +1 -1
- package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js +71 -97
- package/packages/pi-coding-agent/dist/modes/interactive/controllers/chat-controller.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-ordering.test.js +25 -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 +24 -10
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/chat-controller-ordering.test.ts +53 -3
- package/packages/pi-coding-agent/src/core/sdk.ts +1 -1
- package/packages/pi-coding-agent/src/modes/interactive/controllers/chat-controller.ts +75 -102
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-ordering.test.ts +30 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +29 -10
- package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts +2 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.d.ts.map +1 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js +103 -0
- package/packages/pi-tui/dist/__tests__/terminal.test.js.map +1 -0
- package/packages/pi-tui/dist/terminal.d.ts +2 -0
- package/packages/pi-tui/dist/terminal.d.ts.map +1 -1
- package/packages/pi-tui/dist/terminal.js +12 -0
- package/packages/pi-tui/dist/terminal.js.map +1 -1
- package/packages/pi-tui/src/__tests__/terminal.test.ts +121 -0
- package/packages/pi-tui/src/terminal.ts +11 -0
- package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
- package/src/resources/extensions/claude-code-cli/stream-adapter.ts +1 -1
- package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +9 -0
- package/src/resources/extensions/gsd/auto/loop.ts +14 -1
- package/src/resources/extensions/gsd/auto/phases.ts +60 -36
- 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-direct-dispatch.ts +1 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +13 -19
- package/src/resources/extensions/gsd/auto-post-unit.ts +14 -6
- package/src/resources/extensions/gsd/auto-recovery.ts +45 -11
- package/src/resources/extensions/gsd/auto-start.ts +2 -3
- package/src/resources/extensions/gsd/auto-verification.ts +22 -2
- package/src/resources/extensions/gsd/auto-worktree.ts +74 -9
- package/src/resources/extensions/gsd/auto.ts +13 -8
- package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +9 -1
- package/src/resources/extensions/gsd/bootstrap/db-tools.ts +1 -1
- package/src/resources/extensions/gsd/bootstrap/write-gate.ts +1 -1
- 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-prefs-wizard.ts +8 -3
- package/src/resources/extensions/gsd/commands-verdict.ts +202 -0
- package/src/resources/extensions/gsd/crash-recovery.ts +16 -2
- package/src/resources/extensions/gsd/db/milestone-leases.ts +26 -0
- package/src/resources/extensions/gsd/forensics.ts +3 -3
- package/src/resources/extensions/gsd/git-service.ts +6 -3
- package/src/resources/extensions/gsd/gsd-db.ts +18 -6
- package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -3
- package/src/resources/extensions/gsd/guided-flow.ts +8 -5
- package/src/resources/extensions/gsd/markdown-renderer.ts +10 -8
- package/src/resources/extensions/gsd/paths.ts +5 -0
- package/src/resources/extensions/gsd/queue-reorder-ui.ts +31 -13
- package/src/resources/extensions/gsd/state.ts +3 -3
- package/src/resources/extensions/gsd/status-guards.ts +8 -0
- 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-loop.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/auto-paused-ui-cleanup.test.ts +139 -1
- package/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts +6 -5
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +76 -5
- package/src/resources/extensions/gsd/tests/checkout-branch-stash-guard.test.ts +87 -0
- package/src/resources/extensions/gsd/tests/commands-verdict.test.ts +378 -0
- package/src/resources/extensions/gsd/tests/crash-recovery-via-db.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +2 -0
- package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +27 -0
- package/src/resources/extensions/gsd/tests/gsd-db.test.ts +11 -0
- package/src/resources/extensions/gsd/tests/gsdroot-worktree-detection.test.ts +5 -2
- package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +9 -0
- package/src/resources/extensions/gsd/tests/journal-integration.test.ts +46 -0
- package/src/resources/extensions/gsd/tests/merge-db-cycle.test.ts +179 -0
- package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +2 -1
- 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/prefs-wizard-coverage.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/provider-errors.test.ts +18 -1
- package/src/resources/extensions/gsd/tests/quality-gates.test.ts +6 -0
- package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +54 -0
- package/src/resources/extensions/gsd/tests/remediation-completion-guard.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/run-uat-replay-cap.test.ts +2 -3
- package/src/resources/extensions/gsd/tests/status-guards.test.ts +13 -1
- package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +17 -0
- package/src/resources/extensions/gsd/tests/validate-milestone-stuck-guard.test.ts +29 -2
- package/src/resources/extensions/gsd/tests/workflow-kernel.test.ts +7 -0
- package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +18 -0
- package/src/resources/extensions/gsd/tools/plan-slice.ts +3 -4
- package/src/resources/extensions/gsd/workflow-mcp.ts +18 -1
- package/src/resources/extensions/gsd/worktree-manager.ts +1 -1
- 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/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_buildManifest.js +0 -0
- /package/dist/web/standalone/.next/static/{kkGf3_VaPFkiDNV_D7Dtl → 78uanrILNOKG-Jpi4itAE}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
import { loadFile } from "./files.js";
|
|
4
|
+
import { resolveMilestoneFile } from "./paths.js";
|
|
5
|
+
import { deriveState } from "./state.js";
|
|
6
|
+
import { executeValidateMilestone } from "./tools/workflow-tool-executors.js";
|
|
7
|
+
import {
|
|
8
|
+
VALIDATION_VERDICTS,
|
|
9
|
+
extractVerdict,
|
|
10
|
+
isValidMilestoneVerdict,
|
|
11
|
+
type ValidationVerdict,
|
|
12
|
+
} from "./verdict-parser.js";
|
|
13
|
+
|
|
14
|
+
const USAGE =
|
|
15
|
+
'Usage: /gsd verdict <pass|needs-attention|needs-remediation> [--milestone Mxxx] [--rationale "..."]';
|
|
16
|
+
|
|
17
|
+
interface ParsedArgs {
|
|
18
|
+
verdict?: ValidationVerdict;
|
|
19
|
+
milestoneId?: string;
|
|
20
|
+
rationale?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ParsedValidation {
|
|
24
|
+
verdict: string | undefined;
|
|
25
|
+
remediationRound: number;
|
|
26
|
+
successCriteriaChecklist: string;
|
|
27
|
+
sliceDeliveryAudit: string;
|
|
28
|
+
crossSliceIntegration: string;
|
|
29
|
+
requirementCoverage: string;
|
|
30
|
+
verificationClasses?: string;
|
|
31
|
+
verdictRationale: string;
|
|
32
|
+
remediationPlan?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function tokenize(raw: string): string[] {
|
|
36
|
+
const tokens: string[] = [];
|
|
37
|
+
const re = /"([^"]*)"|(\S+)/g;
|
|
38
|
+
let match: RegExpExecArray | null;
|
|
39
|
+
while ((match = re.exec(raw)) !== null) {
|
|
40
|
+
tokens.push(match[1] ?? match[2]);
|
|
41
|
+
}
|
|
42
|
+
return tokens;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseArgs(raw: string): ParsedArgs | { error: string } {
|
|
46
|
+
const tokens = tokenize(raw);
|
|
47
|
+
const out: ParsedArgs = {};
|
|
48
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
49
|
+
const t = tokens[i];
|
|
50
|
+
if (t === "--milestone") {
|
|
51
|
+
const next = tokens[++i];
|
|
52
|
+
if (!next) return { error: "--milestone requires a milestone ID" };
|
|
53
|
+
out.milestoneId = next;
|
|
54
|
+
} else if (t === "--rationale") {
|
|
55
|
+
const next = tokens[++i];
|
|
56
|
+
if (next == null) return { error: "--rationale requires a value" };
|
|
57
|
+
out.rationale = next;
|
|
58
|
+
} else if (!out.verdict) {
|
|
59
|
+
if (!isValidMilestoneVerdict(t)) {
|
|
60
|
+
return {
|
|
61
|
+
error: `Invalid verdict "${t}". Must be one of: ${VALIDATION_VERDICTS.join(", ")}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
out.verdict = t;
|
|
65
|
+
} else {
|
|
66
|
+
return { error: `Unexpected argument: ${t}` };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function extractRemediationRound(content: string): number {
|
|
73
|
+
const fm = content.match(/^---\n([\s\S]*?)\n---/);
|
|
74
|
+
if (!fm) return 0;
|
|
75
|
+
const m = fm[1].match(/^remediation_round:\s*(\d+)/im);
|
|
76
|
+
return m ? Number.parseInt(m[1], 10) : 0;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function extractSection(content: string, heading: string): string | undefined {
|
|
80
|
+
const escaped = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
81
|
+
// Match section bodies bounded by the next "## " heading or end-of-string.
|
|
82
|
+
// Leading "\n" prefix lets a single pattern handle first-line headings too.
|
|
83
|
+
// No /m flag — we want `$` to mean end-of-string, not end-of-line.
|
|
84
|
+
const re = new RegExp(`\\n## ${escaped}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`);
|
|
85
|
+
const m = ("\n" + content).match(re);
|
|
86
|
+
if (!m) return undefined;
|
|
87
|
+
return m[1].replace(/\s+$/, "");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function parseValidationFile(content: string): ParsedValidation {
|
|
91
|
+
return {
|
|
92
|
+
verdict: extractVerdict(content),
|
|
93
|
+
remediationRound: extractRemediationRound(content),
|
|
94
|
+
successCriteriaChecklist: extractSection(content, "Success Criteria Checklist") ?? "",
|
|
95
|
+
sliceDeliveryAudit: extractSection(content, "Slice Delivery Audit") ?? "",
|
|
96
|
+
crossSliceIntegration: extractSection(content, "Cross-Slice Integration") ?? "",
|
|
97
|
+
requirementCoverage: extractSection(content, "Requirement Coverage") ?? "",
|
|
98
|
+
verificationClasses: extractSection(content, "Verification Class Compliance"),
|
|
99
|
+
verdictRationale: extractSection(content, "Verdict Rationale") ?? "",
|
|
100
|
+
remediationPlan: extractSection(content, "Remediation Plan"),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function handleVerdict(
|
|
105
|
+
rawArgs: string,
|
|
106
|
+
ctx: ExtensionCommandContext,
|
|
107
|
+
basePath: string,
|
|
108
|
+
): Promise<void> {
|
|
109
|
+
if (!rawArgs.trim()) {
|
|
110
|
+
ctx.ui.notify(USAGE, "warning");
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const parsed = parseArgs(rawArgs);
|
|
115
|
+
if ("error" in parsed) {
|
|
116
|
+
ctx.ui.notify(`${parsed.error}\n${USAGE}`, "warning");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!parsed.verdict) {
|
|
120
|
+
ctx.ui.notify(USAGE, "warning");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let milestoneId = parsed.milestoneId;
|
|
125
|
+
if (!milestoneId) {
|
|
126
|
+
const state = await deriveState(basePath);
|
|
127
|
+
if (!state.activeMilestone) {
|
|
128
|
+
ctx.ui.notify(
|
|
129
|
+
"No active milestone — pass --milestone Mxxx to target a specific milestone.",
|
|
130
|
+
"warning",
|
|
131
|
+
);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
milestoneId = state.activeMilestone.id;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const validationPath = resolveMilestoneFile(basePath, milestoneId, "VALIDATION");
|
|
138
|
+
if (!validationPath) {
|
|
139
|
+
ctx.ui.notify(
|
|
140
|
+
`No VALIDATION file found for ${milestoneId}. Run gsd_validate_milestone first to produce one.`,
|
|
141
|
+
"warning",
|
|
142
|
+
);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const existing = await loadFile(validationPath);
|
|
146
|
+
if (!existing) {
|
|
147
|
+
ctx.ui.notify(
|
|
148
|
+
`Could not read VALIDATION file for ${milestoneId} (${validationPath}).`,
|
|
149
|
+
"warning",
|
|
150
|
+
);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const current = parseValidationFile(existing);
|
|
155
|
+
|
|
156
|
+
if (parsed.verdict !== "pass" && !parsed.rationale) {
|
|
157
|
+
ctx.ui.notify(
|
|
158
|
+
`--rationale is required when overriding to ${parsed.verdict}.`,
|
|
159
|
+
"warning",
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const verdictRationale =
|
|
165
|
+
parsed.rationale ?? "Manually overridden via /gsd verdict";
|
|
166
|
+
|
|
167
|
+
const result = await executeValidateMilestone(
|
|
168
|
+
{
|
|
169
|
+
milestoneId,
|
|
170
|
+
verdict: parsed.verdict,
|
|
171
|
+
remediationRound: current.remediationRound,
|
|
172
|
+
successCriteriaChecklist: current.successCriteriaChecklist,
|
|
173
|
+
sliceDeliveryAudit: current.sliceDeliveryAudit,
|
|
174
|
+
crossSliceIntegration: current.crossSliceIntegration,
|
|
175
|
+
requirementCoverage: current.requirementCoverage,
|
|
176
|
+
verificationClasses: current.verificationClasses,
|
|
177
|
+
verdictRationale,
|
|
178
|
+
remediationPlan: current.remediationPlan,
|
|
179
|
+
},
|
|
180
|
+
basePath,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (result.isError) {
|
|
184
|
+
const msg =
|
|
185
|
+
result.content[0]?.type === "text" ? result.content[0].text : "Unknown error";
|
|
186
|
+
ctx.ui.notify(msg, "error");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const prevVerdict = current.verdict ?? "unknown";
|
|
191
|
+
ctx.ui.notify(
|
|
192
|
+
`Milestone ${milestoneId} verdict: ${prevVerdict} -> ${parsed.verdict}`,
|
|
193
|
+
"success",
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
if (parsed.verdict === "needs-remediation") {
|
|
197
|
+
ctx.ui.notify(
|
|
198
|
+
"Follow up with gsd_reassess_roadmap to add remediation slices, then re-run /gsd auto.",
|
|
199
|
+
"info",
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -31,8 +31,10 @@ import {
|
|
|
31
31
|
findStaleWorkerForProject,
|
|
32
32
|
getAllAutoWorkers,
|
|
33
33
|
markWorkerCrashed,
|
|
34
|
+
markWorkerStopping,
|
|
34
35
|
type AutoWorkerRow,
|
|
35
36
|
} from "./db/auto-workers.js";
|
|
37
|
+
import { forceReleaseLeasesForWorker } from "./db/milestone-leases.js";
|
|
36
38
|
import { markLatestActiveForWorkerCanceled, type DispatchStatus } from "./db/unit-dispatches.js";
|
|
37
39
|
import { getRuntimeKv, setRuntimeKv, deleteRuntimeKv } from "./db/runtime-kv.js";
|
|
38
40
|
import { _getAdapter, isDbAvailable } from "./gsd-db.js";
|
|
@@ -219,9 +221,21 @@ export function clearLock(basePath: string): void {
|
|
|
219
221
|
if (!isDbAvailable()) return;
|
|
220
222
|
try {
|
|
221
223
|
const projectRoot = normalizeRealPath(basePath);
|
|
224
|
+
const staleWorker = findStaleWorkerForProject(projectRoot);
|
|
225
|
+
if (staleWorker) {
|
|
226
|
+
markWorkerCrashed(staleWorker.worker_id);
|
|
227
|
+
forceReleaseLeasesForWorker(staleWorker.worker_id);
|
|
228
|
+
deleteRuntimeKv("worker", staleWorker.worker_id, SESSION_FILE_KV_KEY);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
222
231
|
const worker = findActiveWorkerForCurrentProcess(projectRoot);
|
|
223
|
-
if (
|
|
224
|
-
|
|
232
|
+
if (worker) deleteRuntimeKv("worker", worker.worker_id, SESSION_FILE_KV_KEY);
|
|
233
|
+
|
|
234
|
+
const stale = findStaleWorkerForProject(projectRoot);
|
|
235
|
+
if (stale) {
|
|
236
|
+
markWorkerStopping(stale.worker_id);
|
|
237
|
+
deleteRuntimeKv("worker", stale.worker_id, SESSION_FILE_KV_KEY);
|
|
238
|
+
}
|
|
225
239
|
} catch {
|
|
226
240
|
// Best-effort.
|
|
227
241
|
}
|
|
@@ -255,6 +255,32 @@ export function releaseMilestoneLease(
|
|
|
255
255
|
});
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Force-release all held leases for a worker.
|
|
260
|
+
*
|
|
261
|
+
* Used by crash recovery once PID liveness has confirmed the worker is dead.
|
|
262
|
+
* No fencing token is required because this path is cleanup-only for a
|
|
263
|
+
* non-running process.
|
|
264
|
+
*/
|
|
265
|
+
export function forceReleaseLeasesForWorker(workerId: string): number {
|
|
266
|
+
if (!isDbAvailable()) return 0;
|
|
267
|
+
const db = _getAdapter()!;
|
|
268
|
+
let changes = 0;
|
|
269
|
+
transaction(() => {
|
|
270
|
+
const result = db.prepare(
|
|
271
|
+
`UPDATE milestone_leases
|
|
272
|
+
SET status = 'released'
|
|
273
|
+
WHERE worker_id = :worker_id
|
|
274
|
+
AND status = 'held'`,
|
|
275
|
+
).run({ ":worker_id": workerId });
|
|
276
|
+
changes =
|
|
277
|
+
typeof (result as { changes?: unknown }).changes === "number"
|
|
278
|
+
? (result as { changes: number }).changes
|
|
279
|
+
: 0;
|
|
280
|
+
});
|
|
281
|
+
return changes;
|
|
282
|
+
}
|
|
283
|
+
|
|
258
284
|
/**
|
|
259
285
|
* Read current lease row for diagnostics. Returns null if no row exists.
|
|
260
286
|
*/
|
|
@@ -837,7 +837,7 @@ export function detectWorktreeOrphans(
|
|
|
837
837
|
type: "worktree-unmerged-exit",
|
|
838
838
|
severity: "warning",
|
|
839
839
|
summary: `${summary.exitsWithUnmergedWork} auto-exit(s) left milestone work unmerged`,
|
|
840
|
-
details: `Exit reasons: ${reasonBreakdown || "(none)"} · Producer-side signal for
|
|
840
|
+
details: `Exit reasons: ${reasonBreakdown || "(none)"} · Producer-side signal for orphaned worktrees. Inspect .gsd/journal/*.jsonl with eventType:"auto-exit" for per-exit detail.`,
|
|
841
841
|
});
|
|
842
842
|
}
|
|
843
843
|
}
|
|
@@ -1056,7 +1056,7 @@ function saveForensicReport(basePath: string, report: ForensicReport, problemDes
|
|
|
1056
1056
|
.map(([r, n]) => `${r}=${n}`).join(", ");
|
|
1057
1057
|
sections.push(` - Exit reasons: ${breakdown}`);
|
|
1058
1058
|
}
|
|
1059
|
-
sections.push(`- Canonical-root redirects
|
|
1059
|
+
sections.push(`- Canonical-root redirects: ${t.canonicalRedirects}`);
|
|
1060
1060
|
// #4765 slice-cadence counters
|
|
1061
1061
|
if (t.slicesMerged + t.sliceMergeConflicts + t.milestoneResquashes > 0) {
|
|
1062
1062
|
sections.push(`- Slices merged: ${t.slicesMerged} · Slice merge conflicts: ${t.sliceMergeConflicts}`);
|
|
@@ -1220,7 +1220,7 @@ function formatReportForPrompt(report: ForensicReport): string {
|
|
|
1220
1220
|
if (hasSignal) {
|
|
1221
1221
|
sections.push("### Worktree Telemetry");
|
|
1222
1222
|
sections.push(`- Created: ${t.worktreesCreated} · Merged: ${t.worktreesMerged} · Conflicts: ${t.mergeConflicts}`);
|
|
1223
|
-
sections.push(`- Orphans: ${t.orphansDetected} · Unmerged exits: ${t.exitsWithUnmergedWork} · Redirects
|
|
1223
|
+
sections.push(`- Orphans: ${t.orphansDetected} · Unmerged exits: ${t.exitsWithUnmergedWork} · Redirects: ${t.canonicalRedirects}`);
|
|
1224
1224
|
if (t.orphansDetected > 0) {
|
|
1225
1225
|
const breakdown = Object.entries(t.orphansByReason)
|
|
1226
1226
|
.map(([r, n]) => `${r}=${n}`).join(", ");
|
|
@@ -388,6 +388,9 @@ export function readIntegrationBranch(basePath: string, milestoneId: string): st
|
|
|
388
388
|
}
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
/** Re-export for backward compatibility — canonical definitions in branch-patterns.ts */
|
|
392
|
+
export { QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
|
|
393
|
+
|
|
391
394
|
/**
|
|
392
395
|
* Persist the integration branch for a milestone.
|
|
393
396
|
*
|
|
@@ -398,14 +401,14 @@ export function readIntegrationBranch(basePath: string, milestoneId: string): st
|
|
|
398
401
|
*
|
|
399
402
|
* The file is committed immediately so the metadata is persisted in git.
|
|
400
403
|
*/
|
|
401
|
-
/** Re-export for backward compatibility — canonical definitions in branch-patterns.ts */
|
|
402
|
-
export { QUICK_BRANCH_RE, WORKFLOW_BRANCH_RE } from "./branch-patterns.js";
|
|
403
|
-
|
|
404
404
|
export function writeIntegrationBranch(
|
|
405
405
|
basePath: string,
|
|
406
406
|
milestoneId: string,
|
|
407
407
|
branch: string,
|
|
408
408
|
): void {
|
|
409
|
+
// Never persist milestone branches as integration targets.
|
|
410
|
+
// They are ephemeral execution branches and can cause self-diff corruption.
|
|
411
|
+
if (branch.startsWith("milestone/")) return;
|
|
409
412
|
// Don't record slice branches as the integration target
|
|
410
413
|
if (SLICE_BRANCH_RE.test(branch)) return;
|
|
411
414
|
// Don't record quick-task branches — they are ephemeral and merge back
|
|
@@ -111,13 +111,14 @@ const providerLoader = createSqliteProviderLoader({
|
|
|
111
111
|
|
|
112
112
|
export const SCHEMA_VERSION = 28;
|
|
113
113
|
|
|
114
|
-
function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
115
|
-
|
|
114
|
+
function initSchema(db: DbAdapter, fileBacked: boolean, dbPath: string | null): void {
|
|
115
|
+
const conservativeFilePragmas = fileBacked && _isLikelyWslDrvFsPathForTest(dbPath);
|
|
116
|
+
if (fileBacked) db.exec(conservativeFilePragmas ? "PRAGMA journal_mode=DELETE" : "PRAGMA journal_mode=WAL");
|
|
116
117
|
if (fileBacked) db.exec("PRAGMA busy_timeout = 5000");
|
|
117
|
-
if (fileBacked) db.exec("PRAGMA synchronous = NORMAL");
|
|
118
|
+
if (fileBacked) db.exec(conservativeFilePragmas ? "PRAGMA synchronous = FULL" : "PRAGMA synchronous = NORMAL");
|
|
118
119
|
if (fileBacked) db.exec("PRAGMA auto_vacuum = INCREMENTAL");
|
|
119
120
|
if (fileBacked) db.exec("PRAGMA cache_size = -8000"); // 8 MB page cache
|
|
120
|
-
if (fileBacked && process.platform !== "darwin") db.exec("PRAGMA mmap_size = 67108864"); // 64 MB mmap
|
|
121
|
+
if (fileBacked && !conservativeFilePragmas && process.platform !== "darwin") db.exec("PRAGMA mmap_size = 67108864"); // 64 MB mmap
|
|
121
122
|
db.exec("PRAGMA temp_store = MEMORY");
|
|
122
123
|
db.exec("PRAGMA foreign_keys = ON");
|
|
123
124
|
|
|
@@ -156,6 +157,17 @@ function initSchema(db: DbAdapter, fileBacked: boolean): void {
|
|
|
156
157
|
migrateSchema(db);
|
|
157
158
|
}
|
|
158
159
|
|
|
160
|
+
export function _isLikelyWslDrvFsPathForTest(dbPath: string | null): boolean {
|
|
161
|
+
if (!dbPath || process.platform !== "linux") return false;
|
|
162
|
+
const drvFsPathPattern = /^\/mnt\/[a-z](?:\/|$)/i;
|
|
163
|
+
if (drvFsPathPattern.test(dbPath)) return true;
|
|
164
|
+
try {
|
|
165
|
+
return drvFsPathPattern.test(realpathSync(dbPath));
|
|
166
|
+
} catch {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
159
171
|
/**
|
|
160
172
|
* Create the FTS5 virtual table for memories plus the triggers that keep it
|
|
161
173
|
* in sync with the base table. FTS5 may be unavailable on stripped-down
|
|
@@ -605,14 +617,14 @@ export function openDatabase(path: string): boolean {
|
|
|
605
617
|
const adapter = createDbAdapter(rawDb);
|
|
606
618
|
const fileBacked = path !== ":memory:";
|
|
607
619
|
try {
|
|
608
|
-
initSchema(adapter, fileBacked);
|
|
620
|
+
initSchema(adapter, fileBacked, path);
|
|
609
621
|
} catch (err) {
|
|
610
622
|
// Corrupt freelist: DDL fails with "malformed" but VACUUM can rebuild.
|
|
611
623
|
// Attempt VACUUM recovery before giving up (see #2519).
|
|
612
624
|
if (fileBacked && err instanceof Error && err.message?.includes("malformed")) {
|
|
613
625
|
try {
|
|
614
626
|
adapter.exec("VACUUM");
|
|
615
|
-
initSchema(adapter, fileBacked);
|
|
627
|
+
initSchema(adapter, fileBacked, path);
|
|
616
628
|
process.stderr.write("gsd-db: recovered corrupt database via VACUUM\n");
|
|
617
629
|
} catch (retryErr) {
|
|
618
630
|
_dbOpenState.recordError("vacuum-recovery", retryErr);
|
|
@@ -23,6 +23,7 @@ import { nativeAddPaths, nativeCommit } from "./native-git-bridge.js";
|
|
|
23
23
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
24
24
|
import { loadQueueOrder, sortByQueueOrder, saveQueueOrder } from "./queue-order.js";
|
|
25
25
|
import { findMilestoneIds, nextMilestoneId } from "./milestone-ids.js";
|
|
26
|
+
import { isFutureMilestoneStatus } from "./status-guards.js";
|
|
26
27
|
|
|
27
28
|
// ─── Queue Entry Point ──────────────────────────────────────────────────────
|
|
28
29
|
|
|
@@ -63,7 +64,7 @@ export async function showQueue(
|
|
|
63
64
|
|
|
64
65
|
// ── Count pending milestones ────────────────────────────────────────
|
|
65
66
|
const pendingMilestones = state.registry.filter(
|
|
66
|
-
m => m.status
|
|
67
|
+
m => isFutureMilestoneStatus(m.status) || m.status === "active",
|
|
67
68
|
);
|
|
68
69
|
const completeCount = state.registry.filter(m => m.status === "complete").length;
|
|
69
70
|
const parkedCount = state.registry.filter(m => m.status === "parked").length;
|
|
@@ -182,7 +183,7 @@ export async function showQueueAdd(
|
|
|
182
183
|
? `Currently executing: ${state.activeMilestone.id} — ${state.activeMilestone.title} (phase: ${state.phase}).`
|
|
183
184
|
: "No milestone currently active.";
|
|
184
185
|
|
|
185
|
-
const pendingCount = state.registry.filter(m => m.status
|
|
186
|
+
const pendingCount = state.registry.filter(m => isFutureMilestoneStatus(m.status)).length;
|
|
186
187
|
const completeCount = state.registry.filter(m => m.status === "complete").length;
|
|
187
188
|
|
|
188
189
|
const preamble = [
|
|
@@ -284,7 +285,7 @@ export async function buildExistingMilestonesContext(
|
|
|
284
285
|
|
|
285
286
|
// For active/pending/parked milestones, include the roadmap if it exists
|
|
286
287
|
// (shows what's planned but not yet built)
|
|
287
|
-
if (status === "active" || status
|
|
288
|
+
if (status === "active" || isFutureMilestoneStatus(status) || status === "parked") {
|
|
288
289
|
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
289
290
|
if (roadmapFile) {
|
|
290
291
|
const content = await loadFile(roadmapFile);
|
|
@@ -48,6 +48,7 @@ import { getIsolationMode, loadEffectiveGSDPreferences } from "./preferences.js"
|
|
|
48
48
|
import { resolveUokFlags } from "./uok/flags.js";
|
|
49
49
|
import { ensurePlanV2Graph, isMissingFinalizedContextResult } from "./uok/plan-v2.js";
|
|
50
50
|
import { detectProjectState, hasGsdBootstrapArtifacts } from "./detection.js";
|
|
51
|
+
import { isFutureMilestoneStatus } from "./status-guards.js";
|
|
51
52
|
import { showProjectInit, offerMigration } from "./init-wizard.js";
|
|
52
53
|
import { validateDirectory } from "./validate-directory.js";
|
|
53
54
|
import { showConfirm } from "../shared/tui.js";
|
|
@@ -1042,6 +1043,7 @@ async function dispatchWorkflow(
|
|
|
1042
1043
|
? ctx.modelRegistry.getProviderAuthMode(ctx.model.provider)
|
|
1043
1044
|
: undefined,
|
|
1044
1045
|
baseUrl: result.appliedModel?.baseUrl ?? ctx.model?.baseUrl,
|
|
1046
|
+
activeTools: typeof pi.getActiveTools === "function" ? pi.getActiveTools() : [],
|
|
1045
1047
|
},
|
|
1046
1048
|
);
|
|
1047
1049
|
if (compatibilityError) {
|
|
@@ -1471,7 +1473,7 @@ export async function showDiscuss(
|
|
|
1471
1473
|
// No active milestone (or corrupted milestone with undefined id) —
|
|
1472
1474
|
// check for pending milestones to discuss instead
|
|
1473
1475
|
if (!state.activeMilestone?.id) {
|
|
1474
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1476
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1475
1477
|
if (pendingMilestones.length === 0) {
|
|
1476
1478
|
ctx.ui.notify("No active milestone. Run /gsd to create one first.", "warning");
|
|
1477
1479
|
return;
|
|
@@ -1583,7 +1585,7 @@ export async function showDiscuss(
|
|
|
1583
1585
|
|
|
1584
1586
|
if (pendingSlices.length === 0) {
|
|
1585
1587
|
// All slices complete — but queued milestones may still need discussion (#3150)
|
|
1586
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1588
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1587
1589
|
if (pendingMilestones.length > 0) {
|
|
1588
1590
|
await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
|
|
1589
1591
|
return;
|
|
@@ -1607,7 +1609,7 @@ export async function showDiscuss(
|
|
|
1607
1609
|
// If all pending slices are discussed, check for queued milestones before exiting (#3150)
|
|
1608
1610
|
const allDiscussed = pendingSlices.every(s => discussedMap.get(s.id));
|
|
1609
1611
|
if (allDiscussed) {
|
|
1610
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1612
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1611
1613
|
if (pendingMilestones.length > 0) {
|
|
1612
1614
|
await showDiscussQueuedMilestone(ctx, pi, basePath, pendingMilestones);
|
|
1613
1615
|
return;
|
|
@@ -1643,7 +1645,7 @@ export async function showDiscuss(
|
|
|
1643
1645
|
});
|
|
1644
1646
|
|
|
1645
1647
|
// Offer access to queued milestones when any exist
|
|
1646
|
-
const pendingMilestones = state.registry.filter(m => m.status
|
|
1648
|
+
const pendingMilestones = state.registry.filter(m => isFutureMilestoneStatus(m.status));
|
|
1647
1649
|
if (pendingMilestones.length > 0) {
|
|
1648
1650
|
actions.push({
|
|
1649
1651
|
id: "discuss_queued_milestone",
|
|
@@ -1718,10 +1720,11 @@ async function showDiscussQueuedMilestone(
|
|
|
1718
1720
|
const hasContext = !!resolveMilestoneFile(basePath, m.id, "CONTEXT");
|
|
1719
1721
|
const hasDraft = !hasContext && !!resolveMilestoneFile(basePath, m.id, "CONTEXT-DRAFT");
|
|
1720
1722
|
const contextStatus = hasContext ? "context ✓" : hasDraft ? "draft context" : "no context yet";
|
|
1723
|
+
const statusLabel = m.status === "planned" ? "planned" : "queued";
|
|
1721
1724
|
return {
|
|
1722
1725
|
id: m.id,
|
|
1723
1726
|
label: `${m.id}: ${m.title}`,
|
|
1724
|
-
description: `[
|
|
1727
|
+
description: `[${statusLabel}] · ${contextStatus}`,
|
|
1725
1728
|
recommended: i === 0,
|
|
1726
1729
|
};
|
|
1727
1730
|
});
|
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
resolveMilestoneFile,
|
|
33
33
|
resolveSliceFile,
|
|
34
34
|
resolveSlicePath,
|
|
35
|
-
|
|
35
|
+
gsdProjectionRoot,
|
|
36
36
|
gsdRoot,
|
|
37
37
|
buildTaskFileName,
|
|
38
38
|
buildSliceFileName,
|
|
@@ -48,7 +48,11 @@ import { clearPathCache } from "./paths.js";
|
|
|
48
48
|
* E.g. "/project/.gsd/milestones/M001/M001-ROADMAP.md" → "milestones/M001/M001-ROADMAP.md"
|
|
49
49
|
*/
|
|
50
50
|
function toArtifactPath(absPath: string, basePath: string): string {
|
|
51
|
-
const
|
|
51
|
+
const projectionRoot = gsdProjectionRoot(basePath);
|
|
52
|
+
const projectionRel = relative(projectionRoot, absPath);
|
|
53
|
+
const root = projectionRel && !projectionRel.startsWith("..") && !projectionRel.startsWith("/")
|
|
54
|
+
? projectionRoot
|
|
55
|
+
: gsdRoot(basePath);
|
|
52
56
|
const rel = relative(root, absPath);
|
|
53
57
|
// Normalize to forward slashes for consistent DB keys
|
|
54
58
|
return rel.replace(/\\/g, "/");
|
|
@@ -374,10 +378,9 @@ export async function renderPlanFromDb(
|
|
|
374
378
|
throw new Error(`no tasks found for ${milestoneId}/${sliceId}`);
|
|
375
379
|
}
|
|
376
380
|
|
|
377
|
-
const slicePath =
|
|
378
|
-
|
|
379
|
-
const absPath =
|
|
380
|
-
?? join(slicePath, `${sliceId}-PLAN.md`);
|
|
381
|
+
const slicePath = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId);
|
|
382
|
+
mkdirSync(slicePath, { recursive: true });
|
|
383
|
+
const absPath = join(slicePath, `${sliceId}-PLAN.md`);
|
|
381
384
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
382
385
|
const sliceGates = getGateResults(milestoneId, sliceId, "slice");
|
|
383
386
|
const content = renderSlicePlanMarkdown(slice, tasks, sliceGates);
|
|
@@ -408,8 +411,7 @@ export async function renderTaskPlanFromDb(
|
|
|
408
411
|
throw new Error(`task ${milestoneId}/${sliceId}/${taskId} not found`);
|
|
409
412
|
}
|
|
410
413
|
|
|
411
|
-
const tasksDir =
|
|
412
|
-
?? join(gsdRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks");
|
|
414
|
+
const tasksDir = join(gsdProjectionRoot(basePath), "milestones", milestoneId, "slices", sliceId, "tasks");
|
|
413
415
|
mkdirSync(tasksDir, { recursive: true });
|
|
414
416
|
const absPath = join(tasksDir, buildTaskFileName(taskId, "PLAN"));
|
|
415
417
|
const artifactPath = toArtifactPath(absPath, basePath);
|
|
@@ -357,6 +357,11 @@ export function resolveGsdPathContract(
|
|
|
357
357
|
};
|
|
358
358
|
}
|
|
359
359
|
|
|
360
|
+
export function gsdProjectionRoot(basePath: string): string {
|
|
361
|
+
const contract = resolveGsdPathContract(basePath);
|
|
362
|
+
return normalizeRealPath(contract.worktreeGsd ?? contract.projectGsd);
|
|
363
|
+
}
|
|
364
|
+
|
|
360
365
|
/**
|
|
361
366
|
* Invalidate the gsdRoot cache.
|
|
362
367
|
* Use ONLY at session-reset boundaries: workspace switch, process exit, or
|
|
@@ -43,6 +43,7 @@ export async function showQueueReorder(
|
|
|
43
43
|
const items = [...pending];
|
|
44
44
|
let cursor = 0;
|
|
45
45
|
let grabbed = false;
|
|
46
|
+
let scrollOffset = 0;
|
|
46
47
|
let cachedLines: string[] | undefined;
|
|
47
48
|
let validation: DependencyValidation;
|
|
48
49
|
|
|
@@ -154,8 +155,10 @@ export async function showQueueReorder(
|
|
|
154
155
|
|
|
155
156
|
const ui = makeUI(theme, width);
|
|
156
157
|
const lines: string[] = [];
|
|
158
|
+
const queueRows: string[] = [];
|
|
157
159
|
const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
|
|
158
160
|
const add = (s: string) => truncateToWidth(s, width);
|
|
161
|
+
let cursorQueueRow = 0;
|
|
159
162
|
|
|
160
163
|
const headerText = grabbed ? " Queue Reorder — Moving Item" : " Queue Reorder";
|
|
161
164
|
push(ui.bar(), ui.blank(), ui.header(headerText), ui.blank());
|
|
@@ -188,11 +191,13 @@ export async function showQueueReorder(
|
|
|
188
191
|
const label = item.title && item.title !== item.id ? `${item.id} ${item.title}` : item.id;
|
|
189
192
|
|
|
190
193
|
if (isCursor && grabbed) {
|
|
191
|
-
|
|
194
|
+
cursorQueueRow = queueRows.length;
|
|
195
|
+
queueRows.push(add(` ${theme.fg("warning", `▸▸ ${num}. ${label}`)}`));
|
|
192
196
|
} else if (isCursor) {
|
|
193
|
-
|
|
197
|
+
cursorQueueRow = queueRows.length;
|
|
198
|
+
queueRows.push(add(` ${theme.fg("accent", `${GLYPH.cursor} ${num}. ${label}`)}`));
|
|
194
199
|
} else {
|
|
195
|
-
|
|
200
|
+
queueRows.push(add(` ${theme.fg("text", `${num}. ${label}`)}`));
|
|
196
201
|
}
|
|
197
202
|
|
|
198
203
|
// depends_on annotations
|
|
@@ -201,36 +206,37 @@ export async function showQueueReorder(
|
|
|
201
206
|
if (completedIds.has(dep)) continue;
|
|
202
207
|
const pairKey = `${item.id}:${dep}`;
|
|
203
208
|
if (violatedPairs.has(pairKey)) {
|
|
204
|
-
|
|
209
|
+
queueRows.push(add(` ${theme.fg("warning", `${GLYPH.statusWarning} depends_on: ${dep} — auto-removed on confirm`)}`));
|
|
205
210
|
} else if (redundantPairs.has(pairKey)) {
|
|
206
|
-
|
|
211
|
+
queueRows.push(add(` ${theme.fg("dim", `↳ depends_on: ${dep} (redundant)`)}`));
|
|
207
212
|
} else {
|
|
208
|
-
|
|
213
|
+
queueRows.push(add(` ${theme.fg("dim", `↳ depends_on: ${dep}`)}`));
|
|
209
214
|
}
|
|
210
215
|
}
|
|
211
216
|
|
|
212
217
|
// Missing deps
|
|
213
218
|
for (const v of validation.violations.filter(v => v.milestone === item.id && v.type === 'missing_dep')) {
|
|
214
|
-
|
|
219
|
+
queueRows.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} depends_on: ${v.dependsOn} (does not exist)`)}`));
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
222
|
|
|
218
223
|
// Removed deps feedback
|
|
224
|
+
const trailingLines: string[] = [];
|
|
219
225
|
if (removedDeps.length > 0) {
|
|
220
|
-
push(ui.blank());
|
|
226
|
+
trailingLines.push(...ui.blank());
|
|
221
227
|
for (const r of removedDeps) {
|
|
222
|
-
|
|
228
|
+
trailingLines.push(add(` ${theme.fg("success", `${GLYPH.statusDone} Removed: ${r.milestone} depends_on ${r.dep}`)}`));
|
|
223
229
|
}
|
|
224
230
|
}
|
|
225
231
|
|
|
226
232
|
// Circular warning
|
|
227
233
|
const circ = validation.violations.find(v => v.type === 'circular');
|
|
228
234
|
if (circ) {
|
|
229
|
-
push(ui.blank());
|
|
230
|
-
|
|
235
|
+
trailingLines.push(...ui.blank());
|
|
236
|
+
trailingLines.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} ${circ.message}`)}`));
|
|
231
237
|
}
|
|
232
238
|
|
|
233
|
-
push(ui.blank());
|
|
239
|
+
trailingLines.push(...ui.blank());
|
|
234
240
|
|
|
235
241
|
// Hints — context-sensitive based on grab state
|
|
236
242
|
const hints: string[] = [];
|
|
@@ -250,7 +256,19 @@ export async function showQueueReorder(
|
|
|
250
256
|
}
|
|
251
257
|
hints.push("esc");
|
|
252
258
|
|
|
253
|
-
push(ui.hints(hints), ui.bar());
|
|
259
|
+
trailingLines.push(...ui.hints(hints), ...ui.bar());
|
|
260
|
+
|
|
261
|
+
const maxOverlayRows = Math.max(10, process.stdout.rows ? Math.floor(process.stdout.rows * 0.8) : 24);
|
|
262
|
+
const availableQueueRows = Math.max(1, maxOverlayRows - lines.length - trailingLines.length);
|
|
263
|
+
const maxScroll = Math.max(0, queueRows.length - availableQueueRows);
|
|
264
|
+
if (cursorQueueRow < scrollOffset) {
|
|
265
|
+
scrollOffset = cursorQueueRow;
|
|
266
|
+
} else if (cursorQueueRow >= scrollOffset + availableQueueRows) {
|
|
267
|
+
scrollOffset = cursorQueueRow - availableQueueRows + 1;
|
|
268
|
+
}
|
|
269
|
+
scrollOffset = Math.min(Math.max(scrollOffset, 0), maxScroll);
|
|
270
|
+
|
|
271
|
+
lines.push(...queueRows.slice(scrollOffset, scrollOffset + availableQueueRows), ...trailingLines);
|
|
254
272
|
|
|
255
273
|
cachedLines = lines;
|
|
256
274
|
return lines;
|