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
|
@@ -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 {
|
|
@@ -14,6 +14,7 @@ import { normalizeStringArray } from "../shared/mod.js";
|
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
KNOWN_PREFERENCE_KEYS,
|
|
17
|
+
KNOWN_UNIT_TYPES,
|
|
17
18
|
SKILL_ACTIONS,
|
|
18
19
|
type WorkflowMode,
|
|
19
20
|
type GSDPreferences,
|
|
@@ -239,11 +240,7 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
239
240
|
if (preferences.post_unit_hooks && Array.isArray(preferences.post_unit_hooks)) {
|
|
240
241
|
const validHooks: PostUnitHookConfig[] = [];
|
|
241
242
|
const seenNames = new Set<string>();
|
|
242
|
-
const knownUnitTypes = new Set(
|
|
243
|
-
"research-milestone", "plan-milestone", "research-slice", "plan-slice",
|
|
244
|
-
"execute-task", "complete-slice", "replan-slice", "reassess-roadmap",
|
|
245
|
-
"run-uat", "complete-milestone",
|
|
246
|
-
]);
|
|
243
|
+
const knownUnitTypes = new Set<string>(KNOWN_UNIT_TYPES);
|
|
247
244
|
for (const hook of preferences.post_unit_hooks) {
|
|
248
245
|
if (!hook || typeof hook !== "object") {
|
|
249
246
|
errors.push("post_unit_hooks entry must be an object");
|
|
@@ -305,11 +302,7 @@ export function validatePreferences(preferences: GSDPreferences): {
|
|
|
305
302
|
if (preferences.pre_dispatch_hooks && Array.isArray(preferences.pre_dispatch_hooks)) {
|
|
306
303
|
const validPreHooks: PreDispatchHookConfig[] = [];
|
|
307
304
|
const seenPreNames = new Set<string>();
|
|
308
|
-
const knownUnitTypes = new Set(
|
|
309
|
-
"research-milestone", "plan-milestone", "research-slice", "plan-slice",
|
|
310
|
-
"execute-task", "complete-slice", "replan-slice", "reassess-roadmap",
|
|
311
|
-
"run-uat", "complete-milestone",
|
|
312
|
-
]);
|
|
305
|
+
const knownUnitTypes = new Set<string>(KNOWN_UNIT_TYPES);
|
|
313
306
|
const validActions = new Set(["modify", "skip", "replace"]);
|
|
314
307
|
for (const hook of preferences.pre_dispatch_hooks) {
|
|
315
308
|
if (!hook || typeof hook !== "object") {
|
|
@@ -17,11 +17,8 @@ If a `GSD Skill Preferences` block is present in system context, use it to decid
|
|
|
17
17
|
## UAT Instructions
|
|
18
18
|
|
|
19
19
|
**UAT file:** `{{uatPath}}`
|
|
20
|
-
**UAT type:** `{{uatType}}`
|
|
21
20
|
**Result file to write:** `{{uatResultPath}}`
|
|
22
21
|
|
|
23
|
-
### If UAT type is `artifact-driven`
|
|
24
|
-
|
|
25
22
|
You are the test runner. Execute every check defined in `{{uatPath}}` directly:
|
|
26
23
|
|
|
27
24
|
- Run shell commands with `bash`
|
|
@@ -46,7 +43,7 @@ Write `{{uatResultPath}}` with:
|
|
|
46
43
|
```markdown
|
|
47
44
|
---
|
|
48
45
|
sliceId: {{sliceId}}
|
|
49
|
-
uatType:
|
|
46
|
+
uatType: artifact-driven
|
|
50
47
|
verdict: PASS | FAIL | PARTIAL
|
|
51
48
|
date: <ISO 8601 timestamp>
|
|
52
49
|
---
|
|
@@ -68,44 +65,6 @@ date: <ISO 8601 timestamp>
|
|
|
68
65
|
<any additional context, errors encountered, or follow-up items>
|
|
69
66
|
```
|
|
70
67
|
|
|
71
|
-
### If UAT type is NOT `artifact-driven` (type is `{{uatType}}`)
|
|
72
|
-
|
|
73
|
-
This UAT type requires human execution or live-runtime observation that you cannot perform mechanically. Your role is to surface it clearly for review.
|
|
74
|
-
|
|
75
|
-
Write `{{uatResultPath}}` with:
|
|
76
|
-
|
|
77
|
-
```markdown
|
|
78
|
-
---
|
|
79
|
-
sliceId: {{sliceId}}
|
|
80
|
-
uatType: {{uatType}}
|
|
81
|
-
verdict: surfaced-for-human-review
|
|
82
|
-
date: <ISO 8601 timestamp>
|
|
83
|
-
---
|
|
84
|
-
|
|
85
|
-
# UAT Result — {{sliceId}}
|
|
86
|
-
|
|
87
|
-
## UAT Type
|
|
88
|
-
|
|
89
|
-
`{{uatType}}` — requires human execution or live-runtime verification.
|
|
90
|
-
|
|
91
|
-
## Status
|
|
92
|
-
|
|
93
|
-
Surfaced for human review. Auto-mode will pause after this unit so the UAT can be performed manually.
|
|
94
|
-
|
|
95
|
-
## UAT File
|
|
96
|
-
|
|
97
|
-
See `{{uatPath}}` for the full UAT specification and acceptance criteria.
|
|
98
|
-
|
|
99
|
-
## Instructions for Human Reviewer
|
|
100
|
-
|
|
101
|
-
Review `{{uatPath}}`, perform the described UAT steps, then update this file with:
|
|
102
|
-
- The actual verdict (PASS / FAIL / PARTIAL)
|
|
103
|
-
- Results for each check
|
|
104
|
-
- Date completed
|
|
105
|
-
|
|
106
|
-
Once updated, run `/gsd auto` to resume auto-mode.
|
|
107
|
-
```
|
|
108
|
-
|
|
109
68
|
---
|
|
110
69
|
|
|
111
70
|
**You MUST write `{{uatResultPath}}` before finishing.**
|
|
@@ -14,8 +14,7 @@ import { existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { loadPrompt } from "./prompt-loader.js";
|
|
16
16
|
import { gsdRoot } from "./paths.js";
|
|
17
|
-
import {
|
|
18
|
-
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
17
|
+
import { createGitService, runGit } from "./git-service.js";
|
|
19
18
|
|
|
20
19
|
// ─── Quick Task Helpers ───────────────────────────────────────────────────────
|
|
21
20
|
|
|
@@ -103,10 +102,9 @@ export async function handleQuick(
|
|
|
103
102
|
const date = new Date().toISOString().split("T")[0];
|
|
104
103
|
|
|
105
104
|
// Create git branch for the quick task (unless isolation: none)
|
|
106
|
-
const
|
|
107
|
-
const git = new GitServiceImpl(basePath, gitPrefs);
|
|
105
|
+
const git = createGitService(basePath);
|
|
108
106
|
const branchName = `gsd/quick/${taskNum}-${slug}`;
|
|
109
|
-
const skipBranch =
|
|
107
|
+
const skipBranch = git.prefs.isolation === "none";
|
|
110
108
|
|
|
111
109
|
let branchCreated = false;
|
|
112
110
|
if (!skipBranch) {
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
// (a)–(j) extractUatType classification (17 assertions from T01)
|
|
7
7
|
// (k) run-uat prompt template loading and content integrity (8 assertions)
|
|
8
8
|
// (l) dispatch precondition assertions via resolveSliceFile (4 assertions)
|
|
9
|
-
// (m)
|
|
9
|
+
// (m) non-artifact UAT skip: human-experience UATs are not dispatched (1 assertion)
|
|
10
|
+
// (n) stale replay guard: existing UAT-RESULT never re-dispatches (1 assertion)
|
|
10
11
|
|
|
11
12
|
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
13
|
import { join, dirname } from 'node:path';
|
|
@@ -254,8 +255,8 @@ async function main(): Promise<void> {
|
|
|
254
255
|
'prompt contains artifact-driven execution language (artifact/execute/run)',
|
|
255
256
|
);
|
|
256
257
|
assertTrue(
|
|
257
|
-
|
|
258
|
-
'prompt
|
|
258
|
+
!/surfaced for human review/i.test(promptResult ?? ''),
|
|
259
|
+
'prompt does not contain "surfaced for human review" (non-artifact UATs are skipped, not dispatched)',
|
|
259
260
|
);
|
|
260
261
|
|
|
261
262
|
// ─── (l) dispatch precondition assertions via resolveSliceFile ────────────
|
|
@@ -310,8 +311,56 @@ async function main(): Promise<void> {
|
|
|
310
311
|
}
|
|
311
312
|
}
|
|
312
313
|
|
|
313
|
-
// ─── (m)
|
|
314
|
-
console.log('\n── (m)
|
|
314
|
+
// ─── (m) non-artifact UATs are skipped (not dispatched) ─────────────────
|
|
315
|
+
console.log('\n── (m) non-artifact UAT skip');
|
|
316
|
+
|
|
317
|
+
{
|
|
318
|
+
const base = createFixtureBase();
|
|
319
|
+
try {
|
|
320
|
+
const roadmapDir = join(base, '.gsd', 'milestones', 'M001');
|
|
321
|
+
mkdirSync(roadmapDir, { recursive: true });
|
|
322
|
+
writeFileSync(
|
|
323
|
+
join(roadmapDir, 'M001-ROADMAP.md'),
|
|
324
|
+
[
|
|
325
|
+
'# M001: Test roadmap',
|
|
326
|
+
'',
|
|
327
|
+
'## Slices',
|
|
328
|
+
'',
|
|
329
|
+
'- [x] **S01: First slice** `risk:low` `depends:[]`',
|
|
330
|
+
'- [ ] **S02: Next slice** `risk:low` `depends:[S01]`',
|
|
331
|
+
'',
|
|
332
|
+
'## Boundary Map',
|
|
333
|
+
'',
|
|
334
|
+
].join('\n'),
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
// human-experience UAT — should not dispatch
|
|
338
|
+
writeSliceFile(base, 'M001', 'S01', 'UAT', makeUatContent('human-experience'));
|
|
339
|
+
|
|
340
|
+
const state = {
|
|
341
|
+
activeMilestone: { id: 'M001', title: 'Test roadmap' },
|
|
342
|
+
activeSlice: { id: 'S02', title: 'Next slice' },
|
|
343
|
+
activeTask: null,
|
|
344
|
+
phase: 'planning',
|
|
345
|
+
recentDecisions: [],
|
|
346
|
+
blockers: [],
|
|
347
|
+
nextAction: 'Plan S02',
|
|
348
|
+
registry: [],
|
|
349
|
+
} as const;
|
|
350
|
+
|
|
351
|
+
const result = await checkNeedsRunUat(base, 'M001', state as any, { uat_dispatch: true } as any);
|
|
352
|
+
assertEq(
|
|
353
|
+
result,
|
|
354
|
+
null,
|
|
355
|
+
'human-experience UAT is skipped — auto-mode only dispatches artifact-driven UATs',
|
|
356
|
+
);
|
|
357
|
+
} finally {
|
|
358
|
+
cleanup(base);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ─── (n) existing UAT-RESULT never re-dispatches ──────────────────────
|
|
363
|
+
console.log('\n── (n) stale replay guard');
|
|
315
364
|
|
|
316
365
|
{
|
|
317
366
|
const base = createFixtureBase();
|
|
@@ -334,7 +383,7 @@ async function main(): Promise<void> {
|
|
|
334
383
|
);
|
|
335
384
|
|
|
336
385
|
writeSliceFile(base, 'M001', 'S01', 'UAT', makeUatContent('artifact-driven'));
|
|
337
|
-
writeSliceFile(base, 'M001', 'S01', 'UAT-RESULT', '---\nverdict:
|
|
386
|
+
writeSliceFile(base, 'M001', 'S01', 'UAT-RESULT', '---\nverdict: FAIL\n---\n');
|
|
338
387
|
|
|
339
388
|
const state = {
|
|
340
389
|
activeMilestone: { id: 'M001', title: 'Test roadmap' },
|
|
@@ -351,7 +400,7 @@ async function main(): Promise<void> {
|
|
|
351
400
|
assertEq(
|
|
352
401
|
result,
|
|
353
402
|
null,
|
|
354
|
-
'existing UAT-RESULT with
|
|
403
|
+
'existing UAT-RESULT with FAIL verdict does not re-dispatch; verdict gate owns blocking',
|
|
355
404
|
);
|
|
356
405
|
} finally {
|
|
357
406
|
cleanup(base);
|