gsd-pi 2.31.2-dev.64d8832 → 2.31.2-dev.91f95cf
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/resources/extensions/gsd/auto-constants.ts +6 -0
- package/dist/resources/extensions/gsd/auto-dashboard.ts +20 -26
- package/dist/resources/extensions/gsd/auto-direct-dispatch.ts +1 -6
- package/dist/resources/extensions/gsd/auto-dispatch.ts +4 -8
- package/dist/resources/extensions/gsd/auto-post-unit.ts +27 -32
- package/dist/resources/extensions/gsd/auto-prompts.ts +38 -34
- package/dist/resources/extensions/gsd/auto-start.ts +4 -4
- package/dist/resources/extensions/gsd/auto.ts +11 -22
- package/dist/resources/extensions/gsd/commands-workflow-templates.ts +3 -5
- package/dist/resources/extensions/gsd/git-service.ts +9 -0
- package/dist/resources/extensions/gsd/guided-flow-queue.ts +1 -8
- package/dist/resources/extensions/gsd/preferences-types.ts +8 -0
- package/dist/resources/extensions/gsd/preferences-validation.ts +3 -10
- package/dist/resources/extensions/gsd/prompts/run-uat.md +1 -42
- package/dist/resources/extensions/gsd/quick.ts +3 -5
- package/dist/resources/extensions/gsd/tests/run-uat.test.ts +56 -7
- package/package.json +1 -1
- package/src/resources/extensions/gsd/auto-constants.ts +6 -0
- package/src/resources/extensions/gsd/auto-dashboard.ts +20 -26
- package/src/resources/extensions/gsd/auto-direct-dispatch.ts +1 -6
- package/src/resources/extensions/gsd/auto-dispatch.ts +4 -8
- package/src/resources/extensions/gsd/auto-post-unit.ts +27 -32
- package/src/resources/extensions/gsd/auto-prompts.ts +38 -34
- package/src/resources/extensions/gsd/auto-start.ts +4 -4
- package/src/resources/extensions/gsd/auto.ts +11 -22
- package/src/resources/extensions/gsd/commands-workflow-templates.ts +3 -5
- package/src/resources/extensions/gsd/git-service.ts +9 -0
- package/src/resources/extensions/gsd/guided-flow-queue.ts +1 -8
- package/src/resources/extensions/gsd/preferences-types.ts +8 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +3 -10
- package/src/resources/extensions/gsd/prompts/run-uat.md +1 -42
- package/src/resources/extensions/gsd/quick.ts +3 -5
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +56 -7
|
@@ -48,40 +48,34 @@ export interface AutoDashboardData {
|
|
|
48
48
|
|
|
49
49
|
// ─── Unit Description Helpers ─────────────────────────────────────────────────
|
|
50
50
|
|
|
51
|
+
/** Canonical verb and phase label for each known unit type. */
|
|
52
|
+
const UNIT_TYPE_INFO: Record<string, { verb: string; phaseLabel: string }> = {
|
|
53
|
+
"research-milestone": { verb: "researching", phaseLabel: "RESEARCH" },
|
|
54
|
+
"research-slice": { verb: "researching", phaseLabel: "RESEARCH" },
|
|
55
|
+
"plan-milestone": { verb: "planning", phaseLabel: "PLAN" },
|
|
56
|
+
"plan-slice": { verb: "planning", phaseLabel: "PLAN" },
|
|
57
|
+
"execute-task": { verb: "executing", phaseLabel: "EXECUTE" },
|
|
58
|
+
"complete-slice": { verb: "completing", phaseLabel: "COMPLETE" },
|
|
59
|
+
"replan-slice": { verb: "replanning", phaseLabel: "REPLAN" },
|
|
60
|
+
"rewrite-docs": { verb: "rewriting", phaseLabel: "REWRITE" },
|
|
61
|
+
"reassess-roadmap": { verb: "reassessing", phaseLabel: "REASSESS" },
|
|
62
|
+
"run-uat": { verb: "running UAT", phaseLabel: "UAT" },
|
|
63
|
+
};
|
|
64
|
+
|
|
51
65
|
export function unitVerb(unitType: string): string {
|
|
52
66
|
if (unitType.startsWith("hook/")) return `hook: ${unitType.slice(5)}`;
|
|
53
|
-
|
|
54
|
-
case "research-milestone":
|
|
55
|
-
case "research-slice": return "researching";
|
|
56
|
-
case "plan-milestone":
|
|
57
|
-
case "plan-slice": return "planning";
|
|
58
|
-
case "execute-task": return "executing";
|
|
59
|
-
case "complete-slice": return "completing";
|
|
60
|
-
case "replan-slice": return "replanning";
|
|
61
|
-
case "rewrite-docs": return "rewriting";
|
|
62
|
-
case "reassess-roadmap": return "reassessing";
|
|
63
|
-
case "run-uat": return "running UAT";
|
|
64
|
-
default: return unitType;
|
|
65
|
-
}
|
|
67
|
+
return UNIT_TYPE_INFO[unitType]?.verb ?? unitType;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
export function unitPhaseLabel(unitType: string): string {
|
|
69
71
|
if (unitType.startsWith("hook/")) return "HOOK";
|
|
70
|
-
|
|
71
|
-
case "research-milestone": return "RESEARCH";
|
|
72
|
-
case "research-slice": return "RESEARCH";
|
|
73
|
-
case "plan-milestone": return "PLAN";
|
|
74
|
-
case "plan-slice": return "PLAN";
|
|
75
|
-
case "execute-task": return "EXECUTE";
|
|
76
|
-
case "complete-slice": return "COMPLETE";
|
|
77
|
-
case "replan-slice": return "REPLAN";
|
|
78
|
-
case "rewrite-docs": return "REWRITE";
|
|
79
|
-
case "reassess-roadmap": return "REASSESS";
|
|
80
|
-
case "run-uat": return "UAT";
|
|
81
|
-
default: return unitType.toUpperCase();
|
|
82
|
-
}
|
|
72
|
+
return UNIT_TYPE_INFO[unitType]?.phaseLabel ?? unitType.toUpperCase();
|
|
83
73
|
}
|
|
84
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Describe the expected next step after the current unit completes.
|
|
77
|
+
* Unit types here mirror the keys in UNIT_TYPE_INFO above.
|
|
78
|
+
*/
|
|
85
79
|
function peekNext(unitType: string, state: GSDState): string {
|
|
86
80
|
// Show active hook info in progress display
|
|
87
81
|
const activeHookState = getActiveHook();
|
|
@@ -182,15 +182,10 @@ export async function dispatchDirectPhase(
|
|
|
182
182
|
ctx.ui.notify("Cannot dispatch run-uat: no UAT file found.", "warning");
|
|
183
183
|
return;
|
|
184
184
|
}
|
|
185
|
-
const uatContent = await loadFile(uatFile);
|
|
186
|
-
if (!uatContent) {
|
|
187
|
-
ctx.ui.notify("Cannot dispatch run-uat: UAT file is empty.", "warning");
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
185
|
const uatPath = relSliceFile(base, mid, sid, "UAT");
|
|
191
186
|
unitType = "run-uat";
|
|
192
187
|
unitId = `${mid}/${sid}`;
|
|
193
|
-
prompt = await buildRunUatPrompt(mid, sid, uatPath,
|
|
188
|
+
prompt = await buildRunUatPrompt(mid, sid, uatPath, base);
|
|
194
189
|
break;
|
|
195
190
|
}
|
|
196
191
|
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import type { GSDState } from "./types.js";
|
|
13
13
|
import type { GSDPreferences } from "./preferences.js";
|
|
14
|
-
import
|
|
15
|
-
import { loadFile, extractUatType, loadActiveOverrides, parseRoadmap } from "./files.js";
|
|
14
|
+
import { loadFile, loadActiveOverrides, parseRoadmap } from "./files.js";
|
|
16
15
|
import {
|
|
17
16
|
resolveMilestoneFile, resolveMilestonePath, resolveSliceFile, resolveTaskFile,
|
|
18
17
|
relSliceFile, buildMilestoneFileName,
|
|
@@ -39,7 +38,7 @@ import {
|
|
|
39
38
|
// ─── Types ────────────────────────────────────────────────────────────────
|
|
40
39
|
|
|
41
40
|
export type DispatchAction =
|
|
42
|
-
| { action: "dispatch"; unitType: string; unitId: string; prompt: string
|
|
41
|
+
| { action: "dispatch"; unitType: string; unitId: string; prompt: string }
|
|
43
42
|
| { action: "stop"; reason: string; level: "info" | "warning" | "error" }
|
|
44
43
|
| { action: "skip" };
|
|
45
44
|
|
|
@@ -138,17 +137,14 @@ const DISPATCH_RULES: DispatchRule[] = [
|
|
|
138
137
|
match: async ({ state, mid, basePath, prefs }) => {
|
|
139
138
|
const needsRunUat = await checkNeedsRunUat(basePath, mid, state, prefs);
|
|
140
139
|
if (!needsRunUat) return null;
|
|
141
|
-
const { sliceId
|
|
142
|
-
const uatFile = resolveSliceFile(basePath, mid, sliceId, "UAT")!;
|
|
143
|
-
const uatContent = await loadFile(uatFile);
|
|
140
|
+
const { sliceId } = needsRunUat;
|
|
144
141
|
return {
|
|
145
142
|
action: "dispatch",
|
|
146
143
|
unitType: "run-uat",
|
|
147
144
|
unitId: `${mid}/${sliceId}`,
|
|
148
145
|
prompt: await buildRunUatPrompt(
|
|
149
|
-
mid, sliceId, relSliceFile(basePath, mid, sliceId, "UAT"),
|
|
146
|
+
mid, sliceId, relSliceFile(basePath, mid, sliceId, "UAT"), basePath,
|
|
150
147
|
),
|
|
151
|
-
pauseAfterDispatch: uatType !== "artifact-driven",
|
|
152
148
|
};
|
|
153
149
|
},
|
|
154
150
|
},
|
|
@@ -60,9 +60,31 @@ import {
|
|
|
60
60
|
hideFooter,
|
|
61
61
|
} from "./auto-dashboard.js";
|
|
62
62
|
import { join } from "node:path";
|
|
63
|
+
import { STATE_REBUILD_MIN_INTERVAL_MS } from "./auto-constants.js";
|
|
63
64
|
|
|
64
|
-
/**
|
|
65
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Initialize a unit dispatch: stamp the current time, set `s.currentUnit`,
|
|
67
|
+
* and persist the initial runtime record. Returns `startedAt` for callers
|
|
68
|
+
* that need the timestamp.
|
|
69
|
+
*/
|
|
70
|
+
function dispatchUnit(
|
|
71
|
+
s: AutoSession,
|
|
72
|
+
basePath: string,
|
|
73
|
+
unitType: string,
|
|
74
|
+
unitId: string,
|
|
75
|
+
): number {
|
|
76
|
+
const startedAt = Date.now();
|
|
77
|
+
s.currentUnit = { type: unitType, id: unitId, startedAt };
|
|
78
|
+
writeUnitRuntimeRecord(basePath, unitType, unitId, startedAt, {
|
|
79
|
+
phase: "dispatched",
|
|
80
|
+
wrapupWarningSent: false,
|
|
81
|
+
timeoutAt: null,
|
|
82
|
+
lastProgressAt: startedAt,
|
|
83
|
+
progressCount: 0,
|
|
84
|
+
lastProgressKind: "dispatch",
|
|
85
|
+
});
|
|
86
|
+
return startedAt;
|
|
87
|
+
}
|
|
66
88
|
|
|
67
89
|
export interface PostUnitContext {
|
|
68
90
|
s: AutoSession;
|
|
@@ -364,19 +386,10 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
364
386
|
if (s.currentUnit && !s.stepMode) {
|
|
365
387
|
const hookUnit = checkPostUnitHooks(s.currentUnit.type, s.currentUnit.id, s.basePath);
|
|
366
388
|
if (hookUnit) {
|
|
367
|
-
const hookStartedAt = Date.now();
|
|
368
389
|
if (s.currentUnit) {
|
|
369
390
|
await closeoutUnit(ctx, s.basePath, s.currentUnit.type, s.currentUnit.id, s.currentUnit.startedAt, buildSnapshotOpts(s.currentUnit.type, s.currentUnit.id));
|
|
370
391
|
}
|
|
371
|
-
s.
|
|
372
|
-
writeUnitRuntimeRecord(s.basePath, hookUnit.unitType, hookUnit.unitId, hookStartedAt, {
|
|
373
|
-
phase: "dispatched",
|
|
374
|
-
wrapupWarningSent: false,
|
|
375
|
-
timeoutAt: null,
|
|
376
|
-
lastProgressAt: hookStartedAt,
|
|
377
|
-
progressCount: 0,
|
|
378
|
-
lastProgressKind: "dispatch",
|
|
379
|
-
});
|
|
392
|
+
dispatchUnit(s, s.basePath, hookUnit.unitType, hookUnit.unitId);
|
|
380
393
|
|
|
381
394
|
const state = await deriveState(s.basePath);
|
|
382
395
|
updateProgressWidget(ctx, hookUnit.unitType, hookUnit.unitId, state);
|
|
@@ -498,16 +511,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
498
511
|
|
|
499
512
|
const triageUnitType = "triage-captures";
|
|
500
513
|
const triageUnitId = `${mid}/${sid}/triage`;
|
|
501
|
-
|
|
502
|
-
s.currentUnit = { type: triageUnitType, id: triageUnitId, startedAt: triageStartedAt };
|
|
503
|
-
writeUnitRuntimeRecord(s.basePath, triageUnitType, triageUnitId, triageStartedAt, {
|
|
504
|
-
phase: "dispatched",
|
|
505
|
-
wrapupWarningSent: false,
|
|
506
|
-
timeoutAt: null,
|
|
507
|
-
lastProgressAt: triageStartedAt,
|
|
508
|
-
progressCount: 0,
|
|
509
|
-
lastProgressKind: "dispatch",
|
|
510
|
-
});
|
|
514
|
+
dispatchUnit(s, s.basePath, triageUnitType, triageUnitId);
|
|
511
515
|
updateProgressWidget(ctx, triageUnitType, triageUnitId, state);
|
|
512
516
|
|
|
513
517
|
const result = await s.cmdCtx!.newSession();
|
|
@@ -568,16 +572,7 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
|
|
|
568
572
|
|
|
569
573
|
const qtUnitType = "quick-task";
|
|
570
574
|
const qtUnitId = `${s.currentMilestoneId}/${capture.id}`;
|
|
571
|
-
|
|
572
|
-
s.currentUnit = { type: qtUnitType, id: qtUnitId, startedAt: qtStartedAt };
|
|
573
|
-
writeUnitRuntimeRecord(s.basePath, qtUnitType, qtUnitId, qtStartedAt, {
|
|
574
|
-
phase: "dispatched",
|
|
575
|
-
wrapupWarningSent: false,
|
|
576
|
-
timeoutAt: null,
|
|
577
|
-
lastProgressAt: qtStartedAt,
|
|
578
|
-
progressCount: 0,
|
|
579
|
-
lastProgressKind: "dispatch",
|
|
580
|
-
});
|
|
575
|
+
dispatchUnit(s, s.basePath, qtUnitType, qtUnitId);
|
|
581
576
|
const state = await deriveState(s.basePath);
|
|
582
577
|
updateProgressWidget(ctx, qtUnitType, qtUnitId, state);
|
|
583
578
|
|
|
@@ -324,6 +324,27 @@ function oneLine(text: string): string {
|
|
|
324
324
|
return text.replace(/\s+/g, " ").trim();
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
/** Build the standard inlined-context section used by all prompt builders. */
|
|
328
|
+
function buildInlinedContextSection(inlined: string[]): string {
|
|
329
|
+
return `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** Build the formatted list of available GSD source files for planners to read on demand. */
|
|
333
|
+
function buildSourceFileList(base: string, opts?: { includeProject?: boolean }): string {
|
|
334
|
+
const paths: string[] = [];
|
|
335
|
+
if (opts?.includeProject && existsSync(resolveGsdRootFile(base, "PROJECT")))
|
|
336
|
+
paths.push(`- **Project**: \`${relGsdRootFile("PROJECT")}\``);
|
|
337
|
+
if (existsSync(resolveGsdRootFile(base, "REQUIREMENTS")))
|
|
338
|
+
paths.push(`- **Requirements**: \`${relGsdRootFile("REQUIREMENTS")}\``);
|
|
339
|
+
if (existsSync(resolveGsdRootFile(base, "DECISIONS")))
|
|
340
|
+
paths.push(`- **Decisions**: \`${relGsdRootFile("DECISIONS")}\``);
|
|
341
|
+
if (paths.length === 0) {
|
|
342
|
+
const types = opts?.includeProject ? "project/requirements/decisions" : "requirements/decisions";
|
|
343
|
+
return `_No ${types} files found._`;
|
|
344
|
+
}
|
|
345
|
+
return paths.join("\n");
|
|
346
|
+
}
|
|
347
|
+
|
|
327
348
|
// ─── Section Builders ──────────────────────────────────────────────────────
|
|
328
349
|
|
|
329
350
|
export function buildResumeSection(
|
|
@@ -540,8 +561,11 @@ export async function checkNeedsRunUat(
|
|
|
540
561
|
if (resultContent) return null;
|
|
541
562
|
}
|
|
542
563
|
|
|
543
|
-
// Classify UAT type;
|
|
564
|
+
// Classify UAT type; skip non-artifact-driven types — auto-mode can only
|
|
565
|
+
// execute mechanical checks. Non-artifact UATs are tracked in the dashboard
|
|
566
|
+
// but don't block auto-mode progression.
|
|
544
567
|
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
568
|
+
if (uatType !== "artifact-driven") return null;
|
|
545
569
|
|
|
546
570
|
return { sliceId: sid, uatType };
|
|
547
571
|
}
|
|
@@ -564,7 +588,7 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
|
|
|
564
588
|
if (knowledgeInlineRM) inlined.push(knowledgeInlineRM);
|
|
565
589
|
inlined.push(inlineTemplate("research", "Research"));
|
|
566
590
|
|
|
567
|
-
const inlinedContext =
|
|
591
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
568
592
|
|
|
569
593
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
570
594
|
return loadPrompt("research-milestone", {
|
|
@@ -592,17 +616,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
592
616
|
const { inlinePriorMilestoneSummary } = await import("./files.js");
|
|
593
617
|
const priorSummaryInline = await inlinePriorMilestoneSummary(mid, base);
|
|
594
618
|
if (priorSummaryInline) inlined.push(priorSummaryInline);
|
|
595
|
-
|
|
596
|
-
const sourcePaths: string[] = [];
|
|
597
|
-
if (existsSync(resolveGsdRootFile(base, "PROJECT")))
|
|
598
|
-
sourcePaths.push(`- **Project**: \`${relGsdRootFile("PROJECT")}\``);
|
|
599
|
-
if (existsSync(resolveGsdRootFile(base, "REQUIREMENTS")))
|
|
600
|
-
sourcePaths.push(`- **Requirements**: \`${relGsdRootFile("REQUIREMENTS")}\``);
|
|
601
|
-
if (existsSync(resolveGsdRootFile(base, "DECISIONS")))
|
|
602
|
-
sourcePaths.push(`- **Decisions**: \`${relGsdRootFile("DECISIONS")}\``);
|
|
603
|
-
const sourceFilePaths = sourcePaths.length > 0
|
|
604
|
-
? sourcePaths.join("\n")
|
|
605
|
-
: "_No project/requirements/decisions files found._";
|
|
619
|
+
const sourceFilePaths = buildSourceFileList(base, { includeProject: true });
|
|
606
620
|
|
|
607
621
|
const knowledgeInlinePM = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
608
622
|
if (knowledgeInlinePM) inlined.push(knowledgeInlinePM);
|
|
@@ -618,7 +632,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
618
632
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
619
633
|
}
|
|
620
634
|
|
|
621
|
-
const inlinedContext =
|
|
635
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
622
636
|
|
|
623
637
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
624
638
|
const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS"));
|
|
@@ -667,7 +681,7 @@ export async function buildResearchSlicePrompt(
|
|
|
667
681
|
const overridesInline = formatOverridesSection(activeOverrides);
|
|
668
682
|
if (overridesInline) inlined.unshift(overridesInline);
|
|
669
683
|
|
|
670
|
-
const inlinedContext =
|
|
684
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
671
685
|
|
|
672
686
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
673
687
|
return loadPrompt("research-slice", {
|
|
@@ -697,15 +711,7 @@ export async function buildPlanSlicePrompt(
|
|
|
697
711
|
inlined.push(await inlineFile(roadmapPath, roadmapRel, "Milestone Roadmap"));
|
|
698
712
|
const researchInline = await inlineFileOptional(researchPath, researchRel, "Slice Research");
|
|
699
713
|
if (researchInline) inlined.push(researchInline);
|
|
700
|
-
|
|
701
|
-
const sliceSourcePaths: string[] = [];
|
|
702
|
-
if (existsSync(resolveGsdRootFile(base, "REQUIREMENTS")))
|
|
703
|
-
sliceSourcePaths.push(`- **Requirements**: \`${relGsdRootFile("REQUIREMENTS")}\``);
|
|
704
|
-
if (existsSync(resolveGsdRootFile(base, "DECISIONS")))
|
|
705
|
-
sliceSourcePaths.push(`- **Decisions**: \`${relGsdRootFile("DECISIONS")}\``);
|
|
706
|
-
const sliceSourceFilePaths = sliceSourcePaths.length > 0
|
|
707
|
-
? sliceSourcePaths.join("\n")
|
|
708
|
-
: "_No requirements/decisions files found._";
|
|
714
|
+
const sliceSourceFilePaths = buildSourceFileList(base);
|
|
709
715
|
|
|
710
716
|
const knowledgeInlinePS = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
711
717
|
if (knowledgeInlinePS) inlined.push(knowledgeInlinePS);
|
|
@@ -719,7 +725,7 @@ export async function buildPlanSlicePrompt(
|
|
|
719
725
|
const planOverridesInline = formatOverridesSection(planActiveOverrides);
|
|
720
726
|
if (planOverridesInline) inlined.unshift(planOverridesInline);
|
|
721
727
|
|
|
722
|
-
const inlinedContext =
|
|
728
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
723
729
|
|
|
724
730
|
// Build executor context constraints from the budget engine
|
|
725
731
|
const executorContextConstraints = formatExecutorConstraints();
|
|
@@ -894,7 +900,7 @@ export async function buildCompleteSlicePrompt(
|
|
|
894
900
|
const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
|
|
895
901
|
if (completeOverridesInline) inlined.unshift(completeOverridesInline);
|
|
896
902
|
|
|
897
|
-
const inlinedContext =
|
|
903
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
898
904
|
|
|
899
905
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
900
906
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
@@ -953,7 +959,7 @@ export async function buildCompleteMilestonePrompt(
|
|
|
953
959
|
if (contextInline) inlined.push(contextInline);
|
|
954
960
|
inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
|
|
955
961
|
|
|
956
|
-
const inlinedContext =
|
|
962
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
957
963
|
|
|
958
964
|
const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
|
|
959
965
|
|
|
@@ -1024,7 +1030,7 @@ export async function buildValidateMilestonePrompt(
|
|
|
1024
1030
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
1025
1031
|
if (contextInline) inlined.push(contextInline);
|
|
1026
1032
|
|
|
1027
|
-
const inlinedContext =
|
|
1033
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
1028
1034
|
|
|
1029
1035
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
1030
1036
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
@@ -1078,7 +1084,7 @@ export async function buildReplanSlicePrompt(
|
|
|
1078
1084
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
1079
1085
|
if (replanOverridesInline) inlined.unshift(replanOverridesInline);
|
|
1080
1086
|
|
|
1081
|
-
const inlinedContext =
|
|
1087
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
1082
1088
|
|
|
1083
1089
|
const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
|
|
1084
1090
|
|
|
@@ -1111,7 +1117,7 @@ export async function buildReplanSlicePrompt(
|
|
|
1111
1117
|
}
|
|
1112
1118
|
|
|
1113
1119
|
export async function buildRunUatPrompt(
|
|
1114
|
-
mid: string, sliceId: string, uatPath: string,
|
|
1120
|
+
mid: string, sliceId: string, uatPath: string, base: string,
|
|
1115
1121
|
): Promise<string> {
|
|
1116
1122
|
const inlined: string[] = [];
|
|
1117
1123
|
inlined.push(await inlineFile(resolveSliceFile(base, mid, sliceId, "UAT"), uatPath, `${sliceId} UAT`));
|
|
@@ -1126,10 +1132,9 @@ export async function buildRunUatPrompt(
|
|
|
1126
1132
|
const projectInline = await inlineProjectFromDb(base);
|
|
1127
1133
|
if (projectInline) inlined.push(projectInline);
|
|
1128
1134
|
|
|
1129
|
-
const inlinedContext =
|
|
1135
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
1130
1136
|
|
|
1131
1137
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
|
|
1132
|
-
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
1133
1138
|
|
|
1134
1139
|
return loadPrompt("run-uat", {
|
|
1135
1140
|
workingDirectory: base,
|
|
@@ -1137,7 +1142,6 @@ export async function buildRunUatPrompt(
|
|
|
1137
1142
|
sliceId,
|
|
1138
1143
|
uatPath,
|
|
1139
1144
|
uatResultPath,
|
|
1140
|
-
uatType,
|
|
1141
1145
|
inlinedContext,
|
|
1142
1146
|
});
|
|
1143
1147
|
}
|
|
@@ -1165,7 +1169,7 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1165
1169
|
const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
1166
1170
|
if (knowledgeInlineRA) inlined.push(knowledgeInlineRA);
|
|
1167
1171
|
|
|
1168
|
-
const inlinedContext =
|
|
1172
|
+
const inlinedContext = buildInlinedContextSection(inlined);
|
|
1169
1173
|
|
|
1170
1174
|
const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
|
|
1171
1175
|
|
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
import { selfHealRuntimeRecords } from "./auto-recovery.js";
|
|
39
39
|
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
40
40
|
import { nativeIsRepo, nativeInit } from "./native-git-bridge.js";
|
|
41
|
-
import {
|
|
41
|
+
import { createGitService } from "./git-service.js";
|
|
42
42
|
import {
|
|
43
43
|
captureIntegrationBranch,
|
|
44
44
|
detectWorktreeName,
|
|
@@ -129,7 +129,7 @@ export async function bootstrapAutoSession(
|
|
|
129
129
|
}
|
|
130
130
|
|
|
131
131
|
// Initialize GitServiceImpl
|
|
132
|
-
s.gitService =
|
|
132
|
+
s.gitService = createGitService(s.basePath);
|
|
133
133
|
|
|
134
134
|
// Check for crash from previous session (use both old and new lock data)
|
|
135
135
|
const crashLock = readCrashLock(base);
|
|
@@ -330,12 +330,12 @@ export async function bootstrapAutoSession(
|
|
|
330
330
|
if (existingWtPath) {
|
|
331
331
|
const wtPath = enterAutoWorktree(base, s.currentMilestoneId);
|
|
332
332
|
s.basePath = wtPath;
|
|
333
|
-
s.gitService =
|
|
333
|
+
s.gitService = createGitService(s.basePath);
|
|
334
334
|
ctx.ui.notify(`Entered auto-worktree at ${wtPath}`, "info");
|
|
335
335
|
} else {
|
|
336
336
|
const wtPath = createAutoWorktree(base, s.currentMilestoneId);
|
|
337
337
|
s.basePath = wtPath;
|
|
338
|
-
s.gitService =
|
|
338
|
+
s.gitService = createGitService(s.basePath);
|
|
339
339
|
ctx.ui.notify(`Created auto-worktree at ${wtPath}`, "info");
|
|
340
340
|
}
|
|
341
341
|
registerSigtermHandler(s.originalBasePath);
|
|
@@ -118,7 +118,7 @@ import {
|
|
|
118
118
|
parseSliceBranch,
|
|
119
119
|
setActiveMilestoneId,
|
|
120
120
|
} from "./worktree.js";
|
|
121
|
-
import {
|
|
121
|
+
import { createGitService, type TaskCommitContext } from "./git-service.js";
|
|
122
122
|
import { getPriorSliceCompletionBlocker } from "./dispatch-guard.js";
|
|
123
123
|
import { formatGitError } from "./git-self-heal.js";
|
|
124
124
|
import {
|
|
@@ -204,8 +204,7 @@ import type { CompletedUnit, CurrentUnit, UnitRouting, StartModel, PendingVerifi
|
|
|
204
204
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
205
|
const s = new AutoSession();
|
|
206
206
|
|
|
207
|
-
|
|
208
|
-
const STATE_REBUILD_MIN_INTERVAL_MS = 30_000;
|
|
207
|
+
import { STATE_REBUILD_MIN_INTERVAL_MS } from "./auto-constants.js";
|
|
209
208
|
|
|
210
209
|
export function shouldUseWorktreeIsolation(): boolean {
|
|
211
210
|
const prefs = loadEffectiveGSDPreferences()?.preferences?.git;
|
|
@@ -462,7 +461,7 @@ export async function stopAuto(ctx?: ExtensionContext, pi?: ExtensionAPI, reason
|
|
|
462
461
|
try { autoCommitCurrentBranch(s.basePath, "stop", s.currentMilestoneId); } catch (e) { debugLog("stop-auto-commit-failed", { error: e instanceof Error ? e.message : String(e) }); }
|
|
463
462
|
teardownAutoWorktree(s.originalBasePath, s.currentMilestoneId, { preserveBranch: true });
|
|
464
463
|
s.basePath = s.originalBasePath;
|
|
465
|
-
s.gitService =
|
|
464
|
+
s.gitService = createGitService(s.basePath);
|
|
466
465
|
ctx?.ui.notify("Exited auto-worktree (branch preserved for resume).", "info");
|
|
467
466
|
} catch (err) {
|
|
468
467
|
ctx?.ui.notify(
|
|
@@ -626,12 +625,12 @@ export async function startAuto(
|
|
|
626
625
|
if (existingWtPath) {
|
|
627
626
|
const wtPath = enterAutoWorktree(s.originalBasePath, s.currentMilestoneId);
|
|
628
627
|
s.basePath = wtPath;
|
|
629
|
-
s.gitService =
|
|
628
|
+
s.gitService = createGitService(s.basePath);
|
|
630
629
|
ctx.ui.notify(`Re-entered auto-worktree at ${wtPath}`, "info");
|
|
631
630
|
} else {
|
|
632
631
|
const wtPath = createAutoWorktree(s.originalBasePath, s.currentMilestoneId);
|
|
633
632
|
s.basePath = wtPath;
|
|
634
|
-
s.gitService =
|
|
633
|
+
s.gitService = createGitService(s.basePath);
|
|
635
634
|
ctx.ui.notify(`Recreated auto-worktree at ${wtPath}`, "info");
|
|
636
635
|
}
|
|
637
636
|
} catch (err) {
|
|
@@ -1124,7 +1123,7 @@ async function dispatchNextUnit(
|
|
|
1124
1123
|
}
|
|
1125
1124
|
|
|
1126
1125
|
s.basePath = s.originalBasePath;
|
|
1127
|
-
s.gitService =
|
|
1126
|
+
s.gitService = createGitService(s.basePath);
|
|
1128
1127
|
invalidateAllCaches();
|
|
1129
1128
|
|
|
1130
1129
|
state = await deriveState(s.basePath);
|
|
@@ -1136,7 +1135,7 @@ async function dispatchNextUnit(
|
|
|
1136
1135
|
try {
|
|
1137
1136
|
const wtPath = createAutoWorktree(s.basePath, mid);
|
|
1138
1137
|
s.basePath = wtPath;
|
|
1139
|
-
s.gitService =
|
|
1138
|
+
s.gitService = createGitService(s.basePath);
|
|
1140
1139
|
ctx.ui.notify(`Created auto-worktree for ${mid} at ${wtPath}`, "info");
|
|
1141
1140
|
} catch (err) {
|
|
1142
1141
|
ctx.ui.notify(
|
|
@@ -1176,7 +1175,7 @@ async function dispatchNextUnit(
|
|
|
1176
1175
|
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1177
1176
|
const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
|
|
1178
1177
|
s.basePath = s.originalBasePath;
|
|
1179
|
-
s.gitService =
|
|
1178
|
+
s.gitService = createGitService(s.basePath);
|
|
1180
1179
|
ctx.ui.notify(
|
|
1181
1180
|
`Milestone ${ s.currentMilestoneId } merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1182
1181
|
"info",
|
|
@@ -1201,7 +1200,7 @@ async function dispatchNextUnit(
|
|
|
1201
1200
|
if (roadmapPath) {
|
|
1202
1201
|
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1203
1202
|
const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
|
|
1204
|
-
s.gitService =
|
|
1203
|
+
s.gitService = createGitService(s.basePath);
|
|
1205
1204
|
ctx.ui.notify(
|
|
1206
1205
|
`Milestone ${ s.currentMilestoneId } merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1207
1206
|
"info",
|
|
@@ -1279,7 +1278,7 @@ async function dispatchNextUnit(
|
|
|
1279
1278
|
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1280
1279
|
const mergeResult = mergeMilestoneToMain(s.originalBasePath, s.currentMilestoneId, roadmapContent);
|
|
1281
1280
|
s.basePath = s.originalBasePath;
|
|
1282
|
-
s.gitService =
|
|
1281
|
+
s.gitService = createGitService(s.basePath);
|
|
1283
1282
|
ctx.ui.notify(
|
|
1284
1283
|
`Milestone ${ s.currentMilestoneId } merged to main.${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1285
1284
|
"info",
|
|
@@ -1303,7 +1302,7 @@ async function dispatchNextUnit(
|
|
|
1303
1302
|
if (roadmapPath) {
|
|
1304
1303
|
const roadmapContent = readFileSync(roadmapPath, "utf-8");
|
|
1305
1304
|
const mergeResult = mergeMilestoneToMain(s.basePath, s.currentMilestoneId, roadmapContent);
|
|
1306
|
-
s.gitService =
|
|
1305
|
+
s.gitService = createGitService(s.basePath);
|
|
1307
1306
|
ctx.ui.notify(
|
|
1308
1307
|
`Milestone ${ s.currentMilestoneId } merged (branch mode).${mergeResult.pushed ? " Pushed to remote." : ""}`,
|
|
1309
1308
|
"info",
|
|
@@ -1457,7 +1456,6 @@ async function dispatchNextUnit(
|
|
|
1457
1456
|
unitType = dispatchResult.unitType;
|
|
1458
1457
|
unitId = dispatchResult.unitId;
|
|
1459
1458
|
prompt = dispatchResult.prompt;
|
|
1460
|
-
let pauseAfterUatDispatch = dispatchResult.pauseAfterDispatch ?? false;
|
|
1461
1459
|
|
|
1462
1460
|
// ── Pre-dispatch hooks ──
|
|
1463
1461
|
const preDispatchResult = runPreDispatchHooks(unitType, unitId, prompt, s.basePath);
|
|
@@ -1712,13 +1710,6 @@ async function dispatchNextUnit(
|
|
|
1712
1710
|
{ triggerTurn: true },
|
|
1713
1711
|
);
|
|
1714
1712
|
|
|
1715
|
-
if (pauseAfterUatDispatch) {
|
|
1716
|
-
ctx.ui.notify(
|
|
1717
|
-
"UAT requires human execution. Auto-mode will pause after this unit writes the result file.",
|
|
1718
|
-
"info",
|
|
1719
|
-
);
|
|
1720
|
-
await pauseAuto(ctx, pi);
|
|
1721
|
-
}
|
|
1722
1713
|
} finally {
|
|
1723
1714
|
s.dispatching = false;
|
|
1724
1715
|
}
|
|
@@ -1874,8 +1865,6 @@ export async function dispatchHookUnit(
|
|
|
1874
1865
|
ctx.ui.setStatus("gsd-auto", s.stepMode ? "next" : "auto");
|
|
1875
1866
|
ctx.ui.notify(`Running post-unit hook: ${hookName}`, "info");
|
|
1876
1867
|
|
|
1877
|
-
console.log(`[dispatchHookUnit] Sending prompt of length ${hookPrompt.length}`);
|
|
1878
|
-
console.log(`[dispatchHookUnit] Prompt preview: ${hookPrompt.substring(0, 200)}...`);
|
|
1879
1868
|
pi.sendMessage(
|
|
1880
1869
|
{ customType: "gsd-auto", content: hookPrompt, display: true },
|
|
1881
1870
|
{ triggerTurn: true },
|
|
@@ -19,8 +19,7 @@ import {
|
|
|
19
19
|
} from "./workflow-templates.js";
|
|
20
20
|
import { loadPrompt } from "./prompt-loader.js";
|
|
21
21
|
import { gsdRoot } from "./paths.js";
|
|
22
|
-
import {
|
|
23
|
-
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
22
|
+
import { createGitService, runGit } from "./git-service.js";
|
|
24
23
|
import { isAutoActive, isAutoPaused } from "./auto.js";
|
|
25
24
|
|
|
26
25
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
@@ -423,9 +422,8 @@ export async function handleStart(
|
|
|
423
422
|
|
|
424
423
|
// ─── Create git branch (unless isolation: none) ─────────────────────────
|
|
425
424
|
|
|
426
|
-
const
|
|
427
|
-
const
|
|
428
|
-
const skipBranch = gitPrefs.isolation === "none";
|
|
425
|
+
const git = createGitService(basePath);
|
|
426
|
+
const skipBranch = git.prefs.isolation === "none";
|
|
429
427
|
const slug = slugify(description || templateId);
|
|
430
428
|
const branchName = `gsd/${templateId}/${slug}`;
|
|
431
429
|
let branchCreated = false;
|
|
@@ -13,6 +13,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
13
13
|
import { join } from "node:path";
|
|
14
14
|
import { gsdRoot } from "./paths.js";
|
|
15
15
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
16
|
+
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
16
17
|
|
|
17
18
|
import {
|
|
18
19
|
detectWorktreeName,
|
|
@@ -541,6 +542,14 @@ export class GitServiceImpl {
|
|
|
541
542
|
|
|
542
543
|
}
|
|
543
544
|
|
|
545
|
+
// ─── Factory ───────────────────────────────────────────────────────────────
|
|
546
|
+
|
|
547
|
+
/** Create a GitServiceImpl with the current effective git preferences. */
|
|
548
|
+
export function createGitService(basePath: string): GitServiceImpl {
|
|
549
|
+
const gitPrefs = loadEffectiveGSDPreferences()?.preferences?.git ?? {};
|
|
550
|
+
return new GitServiceImpl(basePath, gitPrefs);
|
|
551
|
+
}
|
|
552
|
+
|
|
544
553
|
// ─── Commit Type Inference ─────────────────────────────────────────────────
|
|
545
554
|
|
|
546
555
|
/**
|
|
@@ -23,13 +23,6 @@ import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
|
23
23
|
import { loadQueueOrder, sortByQueueOrder, saveQueueOrder } from "./queue-order.js";
|
|
24
24
|
import { findMilestoneIds, nextMilestoneId } from "./milestone-ids.js";
|
|
25
25
|
|
|
26
|
-
// ─── Commit Instruction Helper (local copy — avoids circular dep) ───────────
|
|
27
|
-
|
|
28
|
-
/** Build commit instruction for queue prompts. .gsd/ is managed externally and always gitignored. */
|
|
29
|
-
function buildDocsCommitInstruction(_message: string): string {
|
|
30
|
-
return "Do not commit planning artifacts — .gsd/ is managed externally.";
|
|
31
|
-
}
|
|
32
|
-
|
|
33
26
|
// ─── Queue Entry Point ──────────────────────────────────────────────────────
|
|
34
27
|
|
|
35
28
|
/**
|
|
@@ -207,7 +200,7 @@ export async function showQueueAdd(
|
|
|
207
200
|
preamble,
|
|
208
201
|
existingMilestonesContext: existingContext,
|
|
209
202
|
inlinedTemplates: queueInlinedTemplates,
|
|
210
|
-
commitInstruction:
|
|
203
|
+
commitInstruction: "Do not commit planning artifacts — .gsd/ is managed externally.",
|
|
211
204
|
});
|
|
212
205
|
|
|
213
206
|
pi.sendMessage(
|
|
@@ -86,6 +86,14 @@ export const KNOWN_PREFERENCE_KEYS = new Set<string>([
|
|
|
86
86
|
"context_selection",
|
|
87
87
|
]);
|
|
88
88
|
|
|
89
|
+
/** Canonical list of all dispatch unit types. */
|
|
90
|
+
export const KNOWN_UNIT_TYPES = [
|
|
91
|
+
"research-milestone", "plan-milestone", "research-slice", "plan-slice",
|
|
92
|
+
"execute-task", "complete-slice", "replan-slice", "reassess-roadmap",
|
|
93
|
+
"run-uat", "complete-milestone",
|
|
94
|
+
] as const;
|
|
95
|
+
export type UnitType = (typeof KNOWN_UNIT_TYPES)[number];
|
|
96
|
+
|
|
89
97
|
export const SKILL_ACTIONS = new Set(["use", "prefer", "avoid"]);
|
|
90
98
|
|
|
91
99
|
export interface GSDSkillRule {
|