gsd-pi 2.29.0-dev.2ccf3fb → 2.29.0-dev.4c155ee
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/headless.js +4 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +31 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +32 -3
- package/dist/resources/extensions/gsd/auto-post-unit.ts +39 -10
- package/dist/resources/extensions/gsd/auto-prompts.ts +40 -17
- package/dist/resources/extensions/gsd/auto-recovery.ts +2 -1
- package/dist/resources/extensions/gsd/auto-start.ts +18 -32
- package/dist/resources/extensions/gsd/auto-worktree.ts +21 -182
- package/dist/resources/extensions/gsd/auto.ts +2 -9
- package/dist/resources/extensions/gsd/captures.ts +4 -10
- package/dist/resources/extensions/gsd/commands-handlers.ts +2 -1
- package/dist/resources/extensions/gsd/commands.ts +2 -1
- package/dist/resources/extensions/gsd/detection.ts +2 -1
- package/dist/resources/extensions/gsd/doctor-checks.ts +49 -1
- package/dist/resources/extensions/gsd/doctor-types.ts +3 -1
- package/dist/resources/extensions/gsd/forensics.ts +2 -2
- package/dist/resources/extensions/gsd/git-service.ts +3 -2
- package/dist/resources/extensions/gsd/gitignore.ts +9 -63
- package/dist/resources/extensions/gsd/gsd-db.ts +1 -165
- package/dist/resources/extensions/gsd/guided-flow.ts +8 -5
- package/dist/resources/extensions/gsd/index.ts +3 -3
- package/dist/resources/extensions/gsd/md-importer.ts +3 -2
- package/dist/resources/extensions/gsd/mechanical-completion.ts +430 -0
- package/dist/resources/extensions/gsd/migrate/command.ts +3 -2
- package/dist/resources/extensions/gsd/migrate/writer.ts +2 -1
- package/dist/resources/extensions/gsd/migrate-external.ts +123 -0
- package/dist/resources/extensions/gsd/paths.ts +24 -2
- package/dist/resources/extensions/gsd/post-unit-hooks.ts +6 -5
- package/dist/resources/extensions/gsd/preferences-models.ts +7 -1
- package/dist/resources/extensions/gsd/preferences-validation.ts +2 -1
- package/dist/resources/extensions/gsd/preferences.ts +10 -5
- package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -1
- package/dist/resources/extensions/gsd/repo-identity.ts +148 -0
- package/dist/resources/extensions/gsd/resource-version.ts +99 -0
- package/dist/resources/extensions/gsd/session-forensics.ts +4 -3
- package/dist/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
- package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
- package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +5 -18
- package/dist/resources/extensions/gsd/tests/git-service.test.ts +10 -37
- package/dist/resources/extensions/gsd/tests/knowledge.test.ts +4 -4
- package/dist/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
- package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
- package/dist/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
- package/dist/resources/extensions/gsd/triage-resolution.ts +2 -1
- package/dist/resources/extensions/gsd/types.ts +2 -0
- package/dist/resources/extensions/gsd/worktree-command.ts +1 -11
- package/dist/resources/extensions/gsd/worktree-manager.ts +3 -2
- package/dist/resources/extensions/gsd/worktree.ts +42 -5
- package/dist/resources/skills/react-best-practices/SKILL.md +1 -1
- package/package.json +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/lsp/client.js +3 -0
- package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
- package/packages/pi-coding-agent/src/core/lsp/client.ts +3 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +31 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +32 -3
- package/src/resources/extensions/gsd/auto-post-unit.ts +39 -10
- package/src/resources/extensions/gsd/auto-prompts.ts +40 -17
- package/src/resources/extensions/gsd/auto-recovery.ts +2 -1
- package/src/resources/extensions/gsd/auto-start.ts +18 -32
- package/src/resources/extensions/gsd/auto-worktree.ts +21 -182
- package/src/resources/extensions/gsd/auto.ts +2 -9
- package/src/resources/extensions/gsd/captures.ts +4 -10
- package/src/resources/extensions/gsd/commands-handlers.ts +2 -1
- package/src/resources/extensions/gsd/commands.ts +2 -1
- package/src/resources/extensions/gsd/detection.ts +2 -1
- package/src/resources/extensions/gsd/doctor-checks.ts +49 -1
- package/src/resources/extensions/gsd/doctor-types.ts +3 -1
- package/src/resources/extensions/gsd/forensics.ts +2 -2
- package/src/resources/extensions/gsd/git-service.ts +3 -2
- package/src/resources/extensions/gsd/gitignore.ts +9 -63
- package/src/resources/extensions/gsd/gsd-db.ts +1 -165
- package/src/resources/extensions/gsd/guided-flow.ts +8 -5
- package/src/resources/extensions/gsd/index.ts +3 -3
- package/src/resources/extensions/gsd/md-importer.ts +3 -2
- package/src/resources/extensions/gsd/mechanical-completion.ts +430 -0
- package/src/resources/extensions/gsd/migrate/command.ts +3 -2
- package/src/resources/extensions/gsd/migrate/writer.ts +2 -1
- package/src/resources/extensions/gsd/migrate-external.ts +123 -0
- package/src/resources/extensions/gsd/paths.ts +24 -2
- package/src/resources/extensions/gsd/post-unit-hooks.ts +6 -5
- package/src/resources/extensions/gsd/preferences-models.ts +7 -1
- package/src/resources/extensions/gsd/preferences-validation.ts +2 -1
- package/src/resources/extensions/gsd/preferences.ts +10 -5
- package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
- package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -1
- package/src/resources/extensions/gsd/repo-identity.ts +148 -0
- package/src/resources/extensions/gsd/resource-version.ts +99 -0
- package/src/resources/extensions/gsd/session-forensics.ts +4 -3
- package/src/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
- package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
- package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +5 -18
- package/src/resources/extensions/gsd/tests/git-service.test.ts +10 -37
- package/src/resources/extensions/gsd/tests/knowledge.test.ts +4 -4
- package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
- package/src/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
- package/src/resources/extensions/gsd/triage-resolution.ts +2 -1
- package/src/resources/extensions/gsd/types.ts +2 -0
- package/src/resources/extensions/gsd/worktree-command.ts +1 -11
- package/src/resources/extensions/gsd/worktree-manager.ts +3 -2
- package/src/resources/extensions/gsd/worktree.ts +42 -5
- package/src/resources/skills/react-best-practices/SKILL.md +1 -1
- package/dist/resources/extensions/gsd/auto-worktree-sync.ts +0 -199
- package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
- package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -199
- package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
- package/src/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
package/dist/headless.js
CHANGED
|
@@ -128,6 +128,10 @@ async function runHeadlessOnce(options, restartCount) {
|
|
|
128
128
|
let interrupted = false;
|
|
129
129
|
const startTime = Date.now();
|
|
130
130
|
const isNewMilestone = options.command === 'new-milestone';
|
|
131
|
+
// new-milestone involves codebase investigation + artifact writing — needs more time
|
|
132
|
+
if (isNewMilestone && options.timeout === 300_000) {
|
|
133
|
+
options.timeout = 600_000; // 10 minutes
|
|
134
|
+
}
|
|
131
135
|
// Supervised mode cannot share stdin with --context -
|
|
132
136
|
if (options.supervised && options.context === '-') {
|
|
133
137
|
process.stderr.write('[headless] Error: --supervised cannot be used with --context - (both require stdin)\n');
|
|
@@ -11,6 +11,7 @@ import type { GSDState } from "./types.js";
|
|
|
11
11
|
import { getCurrentBranch } from "./worktree.js";
|
|
12
12
|
import { getActiveHook } from "./post-unit-hooks.js";
|
|
13
13
|
import { getLedger, getProjectTotals, formatCost, formatTokenCount, formatTierSavings } from "./metrics.js";
|
|
14
|
+
import { getHealthTrend, getConsecutiveErrorUnits } from "./doctor-proactive.js";
|
|
14
15
|
import {
|
|
15
16
|
resolveMilestoneFile,
|
|
16
17
|
resolveSliceFile,
|
|
@@ -649,6 +650,31 @@ export function updateProgressWidget(
|
|
|
649
650
|
* Build a compact string-array representation of the progress widget.
|
|
650
651
|
* Used as a fallback when the factory-based widget cannot render (RPC mode).
|
|
651
652
|
*/
|
|
653
|
+
// ─── Model Health Indicator ───────────────────────────────────────────────────
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* Compute a traffic-light health indicator from observable signals.
|
|
657
|
+
* 🟢 progressing well — no errors, trend stable/improving
|
|
658
|
+
* 🟡 struggling — some errors or degrading trend
|
|
659
|
+
* 🔴 stuck — consecutive errors, likely needs attention
|
|
660
|
+
*/
|
|
661
|
+
export function getModelHealthIndicator(): { emoji: string; label: string } {
|
|
662
|
+
const trend = getHealthTrend();
|
|
663
|
+
const consecutiveErrors = getConsecutiveErrorUnits();
|
|
664
|
+
|
|
665
|
+
if (consecutiveErrors >= 3) {
|
|
666
|
+
return { emoji: "🔴", label: "stuck" };
|
|
667
|
+
}
|
|
668
|
+
if (consecutiveErrors >= 1 || trend === "degrading") {
|
|
669
|
+
return { emoji: "🟡", label: "struggling" };
|
|
670
|
+
}
|
|
671
|
+
if (trend === "improving") {
|
|
672
|
+
return { emoji: "🟢", label: "progressing well" };
|
|
673
|
+
}
|
|
674
|
+
// stable or unknown
|
|
675
|
+
return { emoji: "🟢", label: "progressing" };
|
|
676
|
+
}
|
|
677
|
+
|
|
652
678
|
function buildProgressTextLines(
|
|
653
679
|
verb: string,
|
|
654
680
|
phaseLabel: string,
|
|
@@ -697,6 +723,11 @@ function buildProgressTextLines(
|
|
|
697
723
|
}
|
|
698
724
|
|
|
699
725
|
if (next) lines.push(` Next: ${next}`);
|
|
726
|
+
|
|
727
|
+
// Model health indicator
|
|
728
|
+
const health = getModelHealthIndicator();
|
|
729
|
+
lines.push(` Health: ${health.emoji} ${health.label}`);
|
|
730
|
+
|
|
700
731
|
lines.push(` ${widgetPwd}`);
|
|
701
732
|
|
|
702
733
|
return lines;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import type { GSDState } from "./types.js";
|
|
13
13
|
import type { GSDPreferences } from "./preferences.js";
|
|
14
14
|
import type { UatType } from "./files.js";
|
|
15
|
-
import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
|
|
15
|
+
import { loadFile, extractUatType, loadActiveOverrides, parseRoadmap } from "./files.js";
|
|
16
16
|
import {
|
|
17
17
|
resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile,
|
|
18
18
|
relSliceFile, buildMilestoneFileName,
|
|
@@ -123,11 +123,40 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
123
123
|
};
|
|
124
124
|
},
|
|
125
125
|
},
|
|
126
|
+
{
|
|
127
|
+
name: "uat-verdict-gate (non-PASS blocks progression)",
|
|
128
|
+
match: async ({ mid, basePath, prefs }) => {
|
|
129
|
+
// Only applies when UAT dispatch is enabled
|
|
130
|
+
if (!prefs?.uat_dispatch) return null;
|
|
131
|
+
|
|
132
|
+
const roadmapFile = resolveMilestoneFile(basePath, mid, "ROADMAP");
|
|
133
|
+
const roadmapContent = roadmapFile ? await loadFile(roadmapFile) : null;
|
|
134
|
+
if (!roadmapContent) return null;
|
|
135
|
+
|
|
136
|
+
const roadmap = parseRoadmap(roadmapContent);
|
|
137
|
+
for (const slice of roadmap.slices.filter(s => s.done)) {
|
|
138
|
+
const resultFile = resolveSliceFile(basePath, mid, slice.id, "UAT-RESULT");
|
|
139
|
+
if (!resultFile) continue;
|
|
140
|
+
const content = await loadFile(resultFile);
|
|
141
|
+
if (!content) continue;
|
|
142
|
+
const verdictMatch = content.match(/verdict:\s*([\w-]+)/i);
|
|
143
|
+
const verdict = verdictMatch?.[1]?.toLowerCase();
|
|
144
|
+
if (verdict && verdict !== "pass" && verdict !== "passed") {
|
|
145
|
+
return {
|
|
146
|
+
action: "stop" as const,
|
|
147
|
+
reason: `UAT verdict for ${slice.id} is "${verdict}" — blocking progression until resolved.\nReview the UAT result and update the verdict to PASS, or re-run /gsd auto after fixing.`,
|
|
148
|
+
level: "warning" as const,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
},
|
|
154
|
+
},
|
|
126
155
|
{
|
|
127
156
|
name: "reassess-roadmap (post-completion)",
|
|
128
157
|
match: async ({ state, mid, midTitle, basePath, prefs }) => {
|
|
129
|
-
//
|
|
130
|
-
if (prefs?.phases?.
|
|
158
|
+
// Reassess is opt-in: only fire when explicitly enabled
|
|
159
|
+
if (!prefs?.phases?.reassess_after_slice) return null;
|
|
131
160
|
const needsReassess = await checkNeedsReassessment(basePath, mid, state);
|
|
132
161
|
if (!needsReassess) return null;
|
|
133
162
|
return {
|
|
@@ -37,7 +37,6 @@ import { resolveAutoSupervisorConfig, loadEffectiveGSDPreferences } from "./pref
|
|
|
37
37
|
import { runGSDDoctor, rebuildState, summarizeDoctorIssues } from "./doctor.js";
|
|
38
38
|
import { COMPLETION_TRANSITION_CODES } from "./doctor-types.js";
|
|
39
39
|
import { recordHealthSnapshot, checkHealEscalation } from "./doctor-proactive.js";
|
|
40
|
-
import { syncStateToProjectRoot } from "./auto-worktree-sync.js";
|
|
41
40
|
import { resetRewriteCircuitBreaker } from "./auto-dispatch.js";
|
|
42
41
|
import { isDbAvailable } from "./gsd-db.js";
|
|
43
42
|
import { consumeSignal } from "./session-status-io.js";
|
|
@@ -213,15 +212,6 @@ export async function postUnitPreVerification(pctx: PostUnitContext): Promise<"d
|
|
|
213
212
|
// Non-fatal
|
|
214
213
|
}
|
|
215
214
|
|
|
216
|
-
// Sync worktree state back to project root
|
|
217
|
-
if (s.originalBasePath && s.originalBasePath !== s.basePath) {
|
|
218
|
-
try {
|
|
219
|
-
syncStateToProjectRoot(s.basePath, s.originalBasePath, s.currentMilestoneId);
|
|
220
|
-
} catch {
|
|
221
|
-
// Non-fatal
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
215
|
// Rewrite-docs completion
|
|
226
216
|
if (s.currentUnit.type === "rewrite-docs") {
|
|
227
217
|
try {
|
|
@@ -331,6 +321,45 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
331
321
|
}
|
|
332
322
|
}
|
|
333
323
|
|
|
324
|
+
// ── Mechanical completion (ADR-003) ──
|
|
325
|
+
// After task execution, attempt mechanical slice and milestone completion
|
|
326
|
+
// instead of dispatching LLM sessions for complete-slice / validate-milestone.
|
|
327
|
+
if (s.currentUnit?.type === "execute-task" && !s.stepMode) {
|
|
328
|
+
try {
|
|
329
|
+
const [mid, sid] = s.currentUnit.id.split("/");
|
|
330
|
+
if (mid && sid) {
|
|
331
|
+
const state = await deriveState(s.basePath);
|
|
332
|
+
if (state.phase === "summarizing" && state.activeSlice?.id === sid) {
|
|
333
|
+
const { mechanicalSliceCompletion } = await import("./mechanical-completion.js");
|
|
334
|
+
const ok = await mechanicalSliceCompletion(s.basePath, mid, sid);
|
|
335
|
+
if (ok) {
|
|
336
|
+
invalidateAllCaches();
|
|
337
|
+
autoCommitCurrentBranch(s.basePath, "mechanical-completion", `${mid}/${sid}`);
|
|
338
|
+
ctx.ui.notify(`Mechanical completion: ${sid} summary + roadmap updated.`, "info");
|
|
339
|
+
|
|
340
|
+
// Re-derive state — check if milestone is now ready for validation
|
|
341
|
+
invalidateAllCaches();
|
|
342
|
+
const postSliceState = await deriveState(s.basePath);
|
|
343
|
+
if (postSliceState.phase === "validating-milestone" || postSliceState.phase === "completing-milestone") {
|
|
344
|
+
const { aggregateMilestoneVerification, generateMilestoneSummary } = await import("./mechanical-completion.js");
|
|
345
|
+
const validation = await aggregateMilestoneVerification(s.basePath, mid);
|
|
346
|
+
if (validation.verdict !== "failed") {
|
|
347
|
+
await generateMilestoneSummary(s.basePath, mid);
|
|
348
|
+
invalidateAllCaches();
|
|
349
|
+
autoCommitCurrentBranch(s.basePath, "mechanical-milestone-completion", mid);
|
|
350
|
+
ctx.ui.notify(`Mechanical completion: ${mid} validation + summary written.`, "info");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
// If !ok, summarizing phase persists → dispatch rule fires as LLM fallback
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
} catch (err) {
|
|
358
|
+
process.stderr.write(`gsd-mechanical: completion failed: ${(err as Error).message}\n`);
|
|
359
|
+
// Non-fatal — fall through to normal dispatch
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
334
363
|
// ── Post-unit hooks ──
|
|
335
364
|
if (s.currentUnit && !s.stepMode) {
|
|
336
365
|
const hookUnit = checkPostUnitHooks(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
@@ -530,11 +530,21 @@ export async function checkNeedsRunUat(
|
|
|
530
530
|
const uatContent = await loadFile(uatFile);
|
|
531
531
|
if (!uatContent) return null;
|
|
532
532
|
|
|
533
|
-
// If UAT result already exists, skip (idempotent)
|
|
533
|
+
// If UAT result already exists with a PASS verdict, skip (idempotent).
|
|
534
|
+
// Non-PASS verdicts (FAIL, surfaced-for-human-review) should block slice
|
|
535
|
+
// progression — return the slice for re-evaluation (#1231).
|
|
534
536
|
const uatResultFile = resolveSliceFile(base, mid, sid, "UAT-RESULT");
|
|
535
537
|
if (uatResultFile) {
|
|
536
|
-
const
|
|
537
|
-
if (
|
|
538
|
+
const resultContent = await loadFile(uatResultFile);
|
|
539
|
+
if (resultContent) {
|
|
540
|
+
const verdictMatch = resultContent.match(/verdict:\s*([\w-]+)/i);
|
|
541
|
+
const verdict = verdictMatch?.[1]?.toLowerCase();
|
|
542
|
+
if (verdict === "pass" || verdict === "passed") return null; // PASS — skip
|
|
543
|
+
// Non-PASS verdict exists — don't re-run UAT, but don't advance either.
|
|
544
|
+
// Return null here since the UAT already ran; the dispatch table's
|
|
545
|
+
// complete-slice rule should check the verdict before advancing.
|
|
546
|
+
// For now, returning the slice signals it still needs attention.
|
|
547
|
+
}
|
|
538
548
|
}
|
|
539
549
|
|
|
540
550
|
// Classify UAT type; unknown type → treat as human-experience (human review)
|
|
@@ -589,14 +599,18 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
589
599
|
const { inlinePriorMilestoneSummary } = await import("./files.js");
|
|
590
600
|
const priorSummaryInline = await inlinePriorMilestoneSummary(mid, base);
|
|
591
601
|
if (priorSummaryInline) inlined.push(priorSummaryInline);
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
602
|
+
// Build source file paths for the planner to read on demand (reduces inlining)
|
|
603
|
+
const sourcePaths: string[] = [];
|
|
604
|
+
if (existsSync(resolveGsdRootFile(base, "PROJECT")))
|
|
605
|
+
sourcePaths.push(`- **Project**: \`${relGsdRootFile("PROJECT")}\``);
|
|
606
|
+
if (existsSync(resolveGsdRootFile(base, "REQUIREMENTS")))
|
|
607
|
+
sourcePaths.push(`- **Requirements**: \`${relGsdRootFile("REQUIREMENTS")}\``);
|
|
608
|
+
if (existsSync(resolveGsdRootFile(base, "DECISIONS")))
|
|
609
|
+
sourcePaths.push(`- **Decisions**: \`${relGsdRootFile("DECISIONS")}\``);
|
|
610
|
+
const sourceFilePaths = sourcePaths.length > 0
|
|
611
|
+
? sourcePaths.join("\n")
|
|
612
|
+
: "_No project/requirements/decisions files found._";
|
|
613
|
+
|
|
600
614
|
const knowledgeInlinePM = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
601
615
|
if (knowledgeInlinePM) inlined.push(knowledgeInlinePM);
|
|
602
616
|
inlined.push(inlineTemplate("roadmap", "Roadmap"));
|
|
@@ -615,6 +629,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
615
629
|
|
|
616
630
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
617
631
|
const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS"));
|
|
632
|
+
const researchOutputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
618
633
|
return loadPrompt("plan-milestone", {
|
|
619
634
|
workingDirectory: base,
|
|
620
635
|
milestoneId: mid, milestoneTitle: midTitle,
|
|
@@ -624,6 +639,9 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
624
639
|
outputPath: join(base, outputRelPath),
|
|
625
640
|
secretsOutputPath,
|
|
626
641
|
inlinedContext,
|
|
642
|
+
sourceFilePaths,
|
|
643
|
+
researchOutputPath: join(base, researchOutputRelPath),
|
|
644
|
+
...buildSkillDiscoveryVars(),
|
|
627
645
|
});
|
|
628
646
|
}
|
|
629
647
|
|
|
@@ -686,12 +704,16 @@ export async function buildPlanSlicePrompt(
|
|
|
686
704
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
687
705
|
const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
|
|
688
706
|
if (researchInline) inlined.push(researchInline);
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
707
|
+
// Build source file paths for the planner to read on demand (reduces inlining)
|
|
708
|
+
const sliceSourcePaths: string[] = [];
|
|
709
|
+
if (existsSync(resolveGsdRootFile(base, "REQUIREMENTS")))
|
|
710
|
+
sliceSourcePaths.push(`- **Requirements**: \`${relGsdRootFile("REQUIREMENTS")}\``);
|
|
711
|
+
if (existsSync(resolveGsdRootFile(base, "DECISIONS")))
|
|
712
|
+
sliceSourcePaths.push(`- **Decisions**: \`${relGsdRootFile("DECISIONS")}\``);
|
|
713
|
+
const sliceSourceFilePaths = sliceSourcePaths.length > 0
|
|
714
|
+
? sliceSourcePaths.join("\n")
|
|
715
|
+
: "_No requirements/decisions files found._";
|
|
716
|
+
|
|
695
717
|
const knowledgeInlinePS = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
696
718
|
if (knowledgeInlinePS) inlined.push(knowledgeInlinePS);
|
|
697
719
|
inlined.push(inlineTemplate("plan", "Slice Plan"));
|
|
@@ -726,6 +748,7 @@ export async function buildPlanSlicePrompt(
|
|
|
726
748
|
dependencySummaries: depContent,
|
|
727
749
|
executorContextConstraints,
|
|
728
750
|
commitInstruction,
|
|
751
|
+
sourceFilePaths: sliceSourceFilePaths,
|
|
729
752
|
});
|
|
730
753
|
}
|
|
731
754
|
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
resolveMilestoneFile,
|
|
36
36
|
clearPathCache,
|
|
37
37
|
resolveGsdRootFile,
|
|
38
|
+
gsdRoot,
|
|
38
39
|
} from "./paths.js";
|
|
39
40
|
import { isValidationTerminal } from "./state.js";
|
|
40
41
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "node:fs";
|
|
@@ -361,7 +362,7 @@ function isStringArray(data: unknown): data is string[] {
|
|
|
361
362
|
|
|
362
363
|
/** Path to the persisted completed-unit keys file. */
|
|
363
364
|
export function completedKeysPath(base: string): string {
|
|
364
|
-
return join(base, "
|
|
365
|
+
return join(gsdRoot(base), "completed-units.json");
|
|
365
366
|
}
|
|
366
367
|
|
|
367
368
|
/** Write a completed unit key to disk (read-modify-write append to set). */
|
|
@@ -16,6 +16,8 @@ import type {
|
|
|
16
16
|
import { deriveState } from "./state.js";
|
|
17
17
|
import { loadFile, getManifestStatus } from "./files.js";
|
|
18
18
|
import { loadEffectiveGSDPreferences, resolveSkillDiscoveryMode, getIsolationMode } from "./preferences.js";
|
|
19
|
+
import { isInsideWorktree, ensureGsdSymlink } from "./repo-identity.js";
|
|
20
|
+
import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
|
|
19
21
|
import { sendDesktopNotification } from "./notifications.js";
|
|
20
22
|
import { sendRemoteNotification } from "../remote-questions/notify.js";
|
|
21
23
|
import {
|
|
@@ -48,7 +50,7 @@ import {
|
|
|
48
50
|
getAutoWorktreePath,
|
|
49
51
|
isInAutoWorktree,
|
|
50
52
|
} from "./auto-worktree.js";
|
|
51
|
-
import { readResourceVersion } from "./
|
|
53
|
+
import { readResourceVersion } from "./resource-version.js";
|
|
52
54
|
import { initMetrics, getLedger } from "./metrics.js";
|
|
53
55
|
import { initRoutingHistory } from "./routing-history.js";
|
|
54
56
|
import { restoreHookState, resetHookState, clearPersistedHookState } from "./post-unit-hooks.js";
|
|
@@ -61,7 +63,6 @@ import { debugLog, enableDebug, isDebugEnabled, getDebugLogPath } from "./debug-
|
|
|
61
63
|
import type { AutoSession } from "./auto/session.js";
|
|
62
64
|
import { existsSync, mkdirSync, readdirSync, statSync, unlinkSync } from "node:fs";
|
|
63
65
|
import { join } from "node:path";
|
|
64
|
-
import { sep as pathSep } from "node:path";
|
|
65
66
|
|
|
66
67
|
export interface BootstrapDeps {
|
|
67
68
|
shouldUseWorktreeIsolation: () => boolean;
|
|
@@ -113,8 +114,17 @@ export async function bootstrapAutoSession(
|
|
|
113
114
|
ensureGitignore(base, { commitDocs, manageGitignore });
|
|
114
115
|
if (manageGitignore !== false) untrackRuntimeFiles(base);
|
|
115
116
|
|
|
117
|
+
// Migrate legacy in-project .gsd/ to external state directory
|
|
118
|
+
recoverFailedMigration(base);
|
|
119
|
+
const migration = migrateToExternalState(base);
|
|
120
|
+
if (migration.error) {
|
|
121
|
+
ctx.ui.notify(`External state migration warning: ${migration.error}`, "warning");
|
|
122
|
+
}
|
|
123
|
+
// Ensure symlink exists (handles fresh projects and post-migration)
|
|
124
|
+
ensureGsdSymlink(base);
|
|
125
|
+
|
|
116
126
|
// Bootstrap .gsd/ if it doesn't exist
|
|
117
|
-
const gsdDir =
|
|
127
|
+
const gsdDir = gsdRoot(base);
|
|
118
128
|
if (!existsSync(gsdDir)) {
|
|
119
129
|
mkdirSync(join(gsdDir, "milestones"), { recursive: true });
|
|
120
130
|
if (commitDocs !== false) {
|
|
@@ -204,18 +214,6 @@ export async function bootstrapAutoSession(
|
|
|
204
214
|
|
|
205
215
|
let state = await deriveState(base);
|
|
206
216
|
|
|
207
|
-
// Stale worktree state recovery (#654)
|
|
208
|
-
if (
|
|
209
|
-
state.activeMilestone &&
|
|
210
|
-
shouldUseWorktreeIsolation() &&
|
|
211
|
-
!detectWorktreeName(base)
|
|
212
|
-
) {
|
|
213
|
-
const wtPath = getAutoWorktreePath(base, state.activeMilestone.id);
|
|
214
|
-
if (wtPath) {
|
|
215
|
-
state = await deriveState(wtPath);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
217
|
// Milestone branch recovery (#601)
|
|
220
218
|
let hasSurvivorBranch = false;
|
|
221
219
|
if (
|
|
@@ -223,7 +221,7 @@ export async function bootstrapAutoSession(
|
|
|
223
221
|
(state.phase === "pre-planning" || state.phase === "needs-discussion") &&
|
|
224
222
|
shouldUseWorktreeIsolation() &&
|
|
225
223
|
!detectWorktreeName(base) &&
|
|
226
|
-
!base
|
|
224
|
+
!isInsideWorktree(base)
|
|
227
225
|
) {
|
|
228
226
|
const milestoneBranch = `milestone/${state.activeMilestone.id}`;
|
|
229
227
|
const { nativeBranchExists } = await import("./native-git-bridge.js");
|
|
@@ -333,14 +331,7 @@ export async function bootstrapAutoSession(
|
|
|
333
331
|
// ── Auto-worktree setup ──
|
|
334
332
|
s.originalBasePath = base;
|
|
335
333
|
|
|
336
|
-
|
|
337
|
-
const marker = `${pathSep}.gsd${pathSep}worktrees${pathSep}`;
|
|
338
|
-
if (p.includes(marker)) return true;
|
|
339
|
-
const worktreesSuffix = `${pathSep}.gsd${pathSep}worktrees`;
|
|
340
|
-
return p.endsWith(worktreesSuffix);
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
if (s.currentMilestoneId && shouldUseWorktreeIsolation() && !detectWorktreeName(base) && !isUnderGsdWorktrees(base)) {
|
|
334
|
+
if (s.currentMilestoneId && shouldUseWorktreeIsolation() && !detectWorktreeName(base) && !isInsideWorktree(base)) {
|
|
344
335
|
try {
|
|
345
336
|
const existingWtPath = getAutoWorktreePath(base, s.currentMilestoneId);
|
|
346
337
|
if (existingWtPath) {
|
|
@@ -355,11 +346,6 @@ export async function bootstrapAutoSession(
|
|
|
355
346
|
ctx.ui.notify(`Created auto-worktree at ${wtPath}`, "info");
|
|
356
347
|
}
|
|
357
348
|
registerSigtermHandler(s.originalBasePath);
|
|
358
|
-
|
|
359
|
-
// Load completed keys from BOTH locations
|
|
360
|
-
if (s.basePath !== s.originalBasePath) {
|
|
361
|
-
loadPersistedKeys(s.basePath, s.completedKeySet);
|
|
362
|
-
}
|
|
363
349
|
} catch (err) {
|
|
364
350
|
ctx.ui.notify(
|
|
365
351
|
`Auto-worktree setup failed: ${err instanceof Error ? err.message : String(err)}. Continuing in project root.`,
|
|
@@ -369,8 +355,8 @@ export async function bootstrapAutoSession(
|
|
|
369
355
|
}
|
|
370
356
|
|
|
371
357
|
// ── DB lifecycle ──
|
|
372
|
-
const gsdDbPath = join(s.basePath, "
|
|
373
|
-
const gsdDirPath =
|
|
358
|
+
const gsdDbPath = join(gsdRoot(s.basePath), "gsd.db");
|
|
359
|
+
const gsdDirPath = gsdRoot(s.basePath);
|
|
374
360
|
if (existsSync(gsdDirPath) && !existsSync(gsdDbPath)) {
|
|
375
361
|
const hasDecisions = existsSync(join(gsdDirPath, "DECISIONS.md"));
|
|
376
362
|
const hasRequirements = existsSync(join(gsdDirPath, "REQUIREMENTS.md"));
|
|
@@ -476,7 +462,7 @@ export async function bootstrapAutoSession(
|
|
|
476
462
|
|
|
477
463
|
// Pre-flight: validate milestone queue
|
|
478
464
|
try {
|
|
479
|
-
const msDir = join(base, "
|
|
465
|
+
const msDir = join(gsdRoot(base), "milestones");
|
|
480
466
|
if (existsSync(msDir)) {
|
|
481
467
|
const milestoneIds = readdirSync(msDir, { withFileTypes: true })
|
|
482
468
|
.filter(d => d.isDirectory() && /^M\d{3}/.test(d.name))
|