gsd-pi 2.8.0 → 2.8.2
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/loader.js +5 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/config.js +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/config.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js +117 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/bash-executor.js +2 -2
- package/node_modules/@gsd/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js +97 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/blob-store.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js +112 -3
- package/node_modules/@gsd/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js +32 -22
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.js +13 -2
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.d.ts.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.js +57 -0
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -0
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts +3 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js +4 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js +7 -5
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.d.ts +7 -0
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.js +11 -0
- package/node_modules/@gsd/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/src/config.ts +5 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/artifact-manager.ts +125 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/bash-executor.ts +2 -2
- package/node_modules/@gsd/pi-coding-agent/src/core/blob-store.ts +106 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/session-manager.ts +119 -3
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/bash.ts +35 -22
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/path-utils.test.ts +66 -0
- package/node_modules/@gsd/pi-coding-agent/src/core/tools/path-utils.ts +14 -2
- package/node_modules/@gsd/pi-coding-agent/src/index.ts +4 -1
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +6 -4
- package/node_modules/@gsd/pi-coding-agent/src/utils/shell.ts +11 -0
- package/package.json +6 -1
- package/packages/pi-coding-agent/dist/config.d.ts +2 -0
- package/packages/pi-coding-agent/dist/config.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/config.js +4 -0
- package/packages/pi-coding-agent/dist/config.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts +52 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.js +117 -0
- package/packages/pi-coding-agent/dist/core/artifact-manager.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/bash-executor.js +2 -2
- package/packages/pi-coding-agent/dist/core/bash-executor.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts +31 -0
- package/packages/pi-coding-agent/dist/core/blob-store.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/blob-store.js +97 -0
- package/packages/pi-coding-agent/dist/core/blob-store.js.map +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/session-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/session-manager.js +112 -3
- package/packages/pi-coding-agent/dist/core/session-manager.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts +4 -0
- package/packages/pi-coding-agent/dist/core/tools/bash.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/bash.js +32 -22
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.js +13 -2
- package/packages/pi-coding-agent/dist/core/tools/path-utils.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.d.ts +2 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.d.ts.map +1 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js +57 -0
- package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -0
- package/packages/pi-coding-agent/dist/index.d.ts +3 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +4 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- 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 +7 -5
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.d.ts +7 -0
- package/packages/pi-coding-agent/dist/utils/shell.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/utils/shell.js +11 -0
- package/packages/pi-coding-agent/dist/utils/shell.js.map +1 -1
- package/packages/pi-coding-agent/src/config.ts +5 -0
- package/packages/pi-coding-agent/src/core/artifact-manager.ts +125 -0
- package/packages/pi-coding-agent/src/core/bash-executor.ts +2 -2
- package/packages/pi-coding-agent/src/core/blob-store.ts +106 -0
- package/packages/pi-coding-agent/src/core/session-manager.ts +119 -3
- package/packages/pi-coding-agent/src/core/tools/bash.ts +35 -22
- package/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +66 -0
- package/packages/pi-coding-agent/src/core/tools/path-utils.ts +14 -2
- package/packages/pi-coding-agent/src/index.ts +4 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +6 -4
- package/packages/pi-coding-agent/src/utils/shell.ts +11 -0
- package/src/resources/extensions/bg-shell/index.ts +2 -1
- package/src/resources/extensions/browser-tools/lifecycle.ts +6 -1
- package/src/resources/extensions/gsd/auto.ts +92 -49
- package/src/resources/extensions/gsd/dispatch-guard.ts +65 -0
- package/src/resources/extensions/gsd/docs/preferences-reference.md +76 -0
- package/src/resources/extensions/gsd/exit-command.ts +18 -0
- package/src/resources/extensions/gsd/files.ts +9 -40
- package/src/resources/extensions/gsd/git-service.ts +62 -17
- package/src/resources/extensions/gsd/gitignore.ts +28 -0
- package/src/resources/extensions/gsd/guided-flow.ts +49 -11
- package/src/resources/extensions/gsd/index.ts +111 -16
- package/src/resources/extensions/gsd/preferences.ts +8 -0
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +2 -2
- package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/discuss.md +27 -2
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +3 -3
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +3 -3
- package/src/resources/extensions/gsd/prompts/replan-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +4 -4
- package/src/resources/extensions/gsd/roadmap-slices.ts +50 -0
- package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +102 -0
- package/src/resources/extensions/gsd/tests/exit-command.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/git-service.test.ts +116 -39
- package/src/resources/extensions/gsd/tests/reassess-prompt.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/replan-slice.test.ts +2 -1
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +2 -4
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
- package/src/resources/extensions/ttsr/index.ts +163 -0
- package/src/resources/extensions/ttsr/rule-loader.ts +121 -0
- package/src/resources/extensions/ttsr/ttsr-interrupt.md +6 -0
- package/src/resources/extensions/ttsr/ttsr-manager.ts +344 -0
|
@@ -48,14 +48,14 @@ import {
|
|
|
48
48
|
validateCompleteBoundary,
|
|
49
49
|
formatValidationIssues,
|
|
50
50
|
} from "./observability-validator.js";
|
|
51
|
-
import { ensureGitignore } from "./gitignore.js";
|
|
51
|
+
import { ensureGitignore, untrackRuntimeFiles } from "./gitignore.js";
|
|
52
52
|
import { runGSDDoctor, rebuildState } from "./doctor.js";
|
|
53
53
|
import { snapshotSkills, clearSkillSnapshot } from "./skill-discovery.js";
|
|
54
54
|
import {
|
|
55
55
|
initMetrics, resetMetrics, snapshotUnitMetrics, getLedger,
|
|
56
56
|
getProjectTotals, formatCost, formatTokenCount,
|
|
57
57
|
} from "./metrics.js";
|
|
58
|
-
import { join } from "node:path";
|
|
58
|
+
import { dirname, join } from "node:path";
|
|
59
59
|
import { readdirSync, readFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
60
60
|
import { execSync, execFileSync } from "node:child_process";
|
|
61
61
|
import {
|
|
@@ -68,6 +68,7 @@ import {
|
|
|
68
68
|
mergeSliceToMain,
|
|
69
69
|
} from "./worktree.ts";
|
|
70
70
|
import { GitServiceImpl } from "./git-service.ts";
|
|
71
|
+
import { getPriorSliceCompletionBlocker } from "./dispatch-guard.ts";
|
|
71
72
|
import type { GitPreferences } from "./git-service.ts";
|
|
72
73
|
import { truncateToWidth, visibleWidth } from "@gsd/pi-tui";
|
|
73
74
|
import { makeUI, GLYPH, INDENT } from "../shared/ui.js";
|
|
@@ -380,6 +381,7 @@ export async function startAuto(
|
|
|
380
381
|
|
|
381
382
|
// Ensure .gitignore has baseline patterns
|
|
382
383
|
ensureGitignore(base);
|
|
384
|
+
untrackRuntimeFiles(base);
|
|
383
385
|
|
|
384
386
|
// Bootstrap .gsd/ if it doesn't exist
|
|
385
387
|
const gsdDir = join(base, ".gsd");
|
|
@@ -1254,7 +1256,14 @@ async function dispatchNextUnit(
|
|
|
1254
1256
|
}
|
|
1255
1257
|
}
|
|
1256
1258
|
|
|
1257
|
-
|
|
1259
|
+
const priorSliceBlocker = getPriorSliceCompletionBlocker(basePath, getMainBranch(basePath), unitType, unitId);
|
|
1260
|
+
if (priorSliceBlocker) {
|
|
1261
|
+
await stopAuto(ctx, pi);
|
|
1262
|
+
ctx.ui.notify(priorSliceBlocker, "error");
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
const observabilityIssues = await collectObservabilityWarnings(ctx, unitType, unitId);
|
|
1258
1267
|
|
|
1259
1268
|
// Idempotency: skip units already completed in a prior session.
|
|
1260
1269
|
const idempotencyKey = `${unitType}/${unitId}`;
|
|
@@ -1394,6 +1403,13 @@ async function dispatchNextUnit(
|
|
|
1394
1403
|
}
|
|
1395
1404
|
}
|
|
1396
1405
|
|
|
1406
|
+
// Inject observability repair instructions so the agent fixes gaps before
|
|
1407
|
+
// proceeding with the unit (see #174).
|
|
1408
|
+
const repairBlock = buildObservabilityRepairBlock(observabilityIssues);
|
|
1409
|
+
if (repairBlock) {
|
|
1410
|
+
finalPrompt = `${finalPrompt}${repairBlock}`;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1397
1413
|
// Switch model if preferences specify one for this unit type
|
|
1398
1414
|
// Try primary model, then fallbacks in order if setting fails
|
|
1399
1415
|
const modelConfig = resolveModelWithFallbacksForUnit(unitType);
|
|
@@ -1681,13 +1697,11 @@ async function buildResearchMilestonePrompt(mid: string, midTitle: string, base:
|
|
|
1681
1697
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
1682
1698
|
|
|
1683
1699
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
1684
|
-
const outputAbsPath = resolveMilestoneFile(base, mid, "RESEARCH") ?? join(base, outputRelPath);
|
|
1685
1700
|
return loadPrompt("research-milestone", {
|
|
1686
1701
|
milestoneId: mid, milestoneTitle: midTitle,
|
|
1687
1702
|
milestonePath: relMilestonePath(base, mid),
|
|
1688
1703
|
contextPath: contextRel,
|
|
1689
1704
|
outputPath: outputRelPath,
|
|
1690
|
-
outputAbsPath,
|
|
1691
1705
|
inlinedContext,
|
|
1692
1706
|
...buildSkillDiscoveryVars(),
|
|
1693
1707
|
});
|
|
@@ -1715,7 +1729,6 @@ async function buildPlanMilestonePrompt(mid: string, midTitle: string, base: str
|
|
|
1715
1729
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
1716
1730
|
|
|
1717
1731
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
1718
|
-
const outputAbsPath = resolveMilestoneFile(base, mid, "ROADMAP") ?? join(base, outputRelPath);
|
|
1719
1732
|
const secretsOutputPath = relMilestoneFile(base, mid, "SECRETS");
|
|
1720
1733
|
return loadPrompt("plan-milestone", {
|
|
1721
1734
|
milestoneId: mid, milestoneTitle: midTitle,
|
|
@@ -1723,7 +1736,6 @@ async function buildPlanMilestonePrompt(mid: string, midTitle: string, base: str
|
|
|
1723
1736
|
contextPath: contextRel,
|
|
1724
1737
|
researchPath: researchRel,
|
|
1725
1738
|
outputPath: outputRelPath,
|
|
1726
|
-
outputAbsPath,
|
|
1727
1739
|
secretsOutputPath,
|
|
1728
1740
|
inlinedContext,
|
|
1729
1741
|
});
|
|
@@ -1755,7 +1767,6 @@ async function buildResearchSlicePrompt(
|
|
|
1755
1767
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
1756
1768
|
|
|
1757
1769
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
1758
|
-
const outputAbsPath = resolveSliceFile(base, mid, sid, "RESEARCH") ?? join(base, outputRelPath);
|
|
1759
1770
|
return loadPrompt("research-slice", {
|
|
1760
1771
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
|
|
1761
1772
|
slicePath: relSlicePath(base, mid, sid),
|
|
@@ -1763,7 +1774,6 @@ async function buildResearchSlicePrompt(
|
|
|
1763
1774
|
contextPath: contextRel,
|
|
1764
1775
|
milestoneResearchPath: milestoneResearchRel,
|
|
1765
1776
|
outputPath: outputRelPath,
|
|
1766
|
-
outputAbsPath,
|
|
1767
1777
|
inlinedContext,
|
|
1768
1778
|
dependencySummaries: depContent,
|
|
1769
1779
|
...buildSkillDiscoveryVars(),
|
|
@@ -1792,16 +1802,12 @@ async function buildPlanSlicePrompt(
|
|
|
1792
1802
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
1793
1803
|
|
|
1794
1804
|
const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
|
|
1795
|
-
const outputAbsPath = resolveSliceFile(base, mid, sid, "PLAN") ?? join(base, outputRelPath);
|
|
1796
|
-
const sliceAbsPath = resolveSlicePath(base, mid, sid) ?? join(base, relSlicePath(base, mid, sid));
|
|
1797
1805
|
return loadPrompt("plan-slice", {
|
|
1798
1806
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
|
|
1799
1807
|
slicePath: relSlicePath(base, mid, sid),
|
|
1800
|
-
sliceAbsPath,
|
|
1801
1808
|
roadmapPath: roadmapRel,
|
|
1802
1809
|
researchPath: researchRel,
|
|
1803
1810
|
outputPath: outputRelPath,
|
|
1804
|
-
outputAbsPath,
|
|
1805
1811
|
inlinedContext,
|
|
1806
1812
|
dependencySummaries: depContent,
|
|
1807
1813
|
});
|
|
@@ -1852,8 +1858,7 @@ async function buildExecuteTaskPrompt(
|
|
|
1852
1858
|
|
|
1853
1859
|
const carryForwardSection = await buildCarryForwardSection(priorSummaries, base);
|
|
1854
1860
|
|
|
1855
|
-
const
|
|
1856
|
-
const taskSummaryAbsPath = join(sliceDirAbs, "tasks", `${tid}-SUMMARY.md`);
|
|
1861
|
+
const taskSummaryPath = `${relSlicePath(base, mid, sid)}/tasks/${tid}-SUMMARY.md`;
|
|
1857
1862
|
|
|
1858
1863
|
return loadPrompt("execute-task", {
|
|
1859
1864
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
|
|
@@ -1865,7 +1870,7 @@ async function buildExecuteTaskPrompt(
|
|
|
1865
1870
|
carryForwardSection,
|
|
1866
1871
|
resumeSection,
|
|
1867
1872
|
priorTaskLines: priorLines,
|
|
1868
|
-
|
|
1873
|
+
taskSummaryPath,
|
|
1869
1874
|
});
|
|
1870
1875
|
}
|
|
1871
1876
|
|
|
@@ -1901,17 +1906,17 @@ async function buildCompleteSlicePrompt(
|
|
|
1901
1906
|
|
|
1902
1907
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
1903
1908
|
|
|
1904
|
-
const
|
|
1905
|
-
const
|
|
1906
|
-
const
|
|
1909
|
+
const sliceRel = relSlicePath(base, mid, sid);
|
|
1910
|
+
const sliceSummaryPath = `${sliceRel}/${sid}-SUMMARY.md`;
|
|
1911
|
+
const sliceUatPath = `${sliceRel}/${sid}-UAT.md`;
|
|
1907
1912
|
|
|
1908
1913
|
return loadPrompt("complete-slice", {
|
|
1909
1914
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle,
|
|
1910
|
-
slicePath:
|
|
1915
|
+
slicePath: sliceRel,
|
|
1911
1916
|
roadmapPath: roadmapRel,
|
|
1912
1917
|
inlinedContext,
|
|
1913
|
-
|
|
1914
|
-
|
|
1918
|
+
sliceSummaryPath,
|
|
1919
|
+
sliceUatPath,
|
|
1915
1920
|
});
|
|
1916
1921
|
}
|
|
1917
1922
|
|
|
@@ -1950,15 +1955,14 @@ async function buildCompleteMilestonePrompt(
|
|
|
1950
1955
|
|
|
1951
1956
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
1952
1957
|
|
|
1953
|
-
const
|
|
1954
|
-
const milestoneSummaryAbsPath = join(milestoneDirAbs, `${mid}-SUMMARY.md`);
|
|
1958
|
+
const milestoneSummaryPath = `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`;
|
|
1955
1959
|
|
|
1956
1960
|
return loadPrompt("complete-milestone", {
|
|
1957
1961
|
milestoneId: mid,
|
|
1958
1962
|
milestoneTitle: midTitle,
|
|
1959
1963
|
roadmapPath: roadmapRel,
|
|
1960
1964
|
inlinedContext,
|
|
1961
|
-
|
|
1965
|
+
milestoneSummaryPath,
|
|
1962
1966
|
});
|
|
1963
1967
|
}
|
|
1964
1968
|
|
|
@@ -2001,8 +2005,7 @@ async function buildReplanSlicePrompt(
|
|
|
2001
2005
|
|
|
2002
2006
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
2003
2007
|
|
|
2004
|
-
const
|
|
2005
|
-
const replanAbsPath = join(sliceDirAbs, `${sid}-REPLAN.md`);
|
|
2008
|
+
const replanPath = `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`;
|
|
2006
2009
|
|
|
2007
2010
|
return loadPrompt("replan-slice", {
|
|
2008
2011
|
milestoneId: mid,
|
|
@@ -2012,7 +2015,7 @@ async function buildReplanSlicePrompt(
|
|
|
2012
2015
|
planPath: slicePlanRel,
|
|
2013
2016
|
blockerTaskId,
|
|
2014
2017
|
inlinedContext,
|
|
2015
|
-
|
|
2018
|
+
replanPath,
|
|
2016
2019
|
});
|
|
2017
2020
|
}
|
|
2018
2021
|
|
|
@@ -2130,8 +2133,6 @@ async function buildRunUatPrompt(
|
|
|
2130
2133
|
|
|
2131
2134
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
2132
2135
|
|
|
2133
|
-
const sliceDirAbs = resolveSlicePath(base, mid, sliceId) ?? join(base, relSlicePath(base, mid, sliceId));
|
|
2134
|
-
const uatResultAbsPath = join(sliceDirAbs, `${sliceId}-UAT-RESULT.md`);
|
|
2135
2136
|
const uatResultPath = relSliceFile(base, mid, sliceId, "UAT-RESULT");
|
|
2136
2137
|
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
2137
2138
|
|
|
@@ -2139,7 +2140,6 @@ async function buildRunUatPrompt(
|
|
|
2139
2140
|
milestoneId: mid,
|
|
2140
2141
|
sliceId,
|
|
2141
2142
|
uatPath,
|
|
2142
|
-
uatResultAbsPath,
|
|
2143
2143
|
uatResultPath,
|
|
2144
2144
|
uatType,
|
|
2145
2145
|
inlinedContext,
|
|
@@ -2166,9 +2166,7 @@ async function buildReassessRoadmapPrompt(
|
|
|
2166
2166
|
|
|
2167
2167
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
2168
2168
|
|
|
2169
|
-
const
|
|
2170
|
-
const sliceDirAbs = resolveSlicePath(base, mid, completedSliceId) ?? join(base, relSlicePath(base, mid, completedSliceId));
|
|
2171
|
-
const assessmentAbsPath = join(sliceDirAbs, `${completedSliceId}-ASSESSMENT.md`);
|
|
2169
|
+
const assessmentPath = relSliceFile(base, mid, completedSliceId, "ASSESSMENT");
|
|
2172
2170
|
|
|
2173
2171
|
return loadPrompt("reassess-roadmap", {
|
|
2174
2172
|
milestoneId: mid,
|
|
@@ -2176,8 +2174,7 @@ async function buildReassessRoadmapPrompt(
|
|
|
2176
2174
|
completedSliceId,
|
|
2177
2175
|
roadmapPath: roadmapRel,
|
|
2178
2176
|
completedSliceSummaryPath: summaryRel,
|
|
2179
|
-
assessmentPath
|
|
2180
|
-
assessmentAbsPath,
|
|
2177
|
+
assessmentPath,
|
|
2181
2178
|
inlinedContext,
|
|
2182
2179
|
});
|
|
2183
2180
|
}
|
|
@@ -2357,17 +2354,17 @@ function ensurePreconditions(
|
|
|
2357
2354
|
|
|
2358
2355
|
// ─── Diagnostics ──────────────────────────────────────────────────────────────
|
|
2359
2356
|
|
|
2360
|
-
async function
|
|
2357
|
+
async function collectObservabilityWarnings(
|
|
2361
2358
|
ctx: ExtensionContext,
|
|
2362
2359
|
unitType: string,
|
|
2363
2360
|
unitId: string,
|
|
2364
|
-
): Promise<
|
|
2361
|
+
): Promise<import("./observability-validator.ts").ValidationIssue[]> {
|
|
2365
2362
|
const parts = unitId.split("/");
|
|
2366
2363
|
const mid = parts[0];
|
|
2367
2364
|
const sid = parts[1];
|
|
2368
2365
|
const tid = parts[2];
|
|
2369
2366
|
|
|
2370
|
-
if (!mid || !sid) return;
|
|
2367
|
+
if (!mid || !sid) return [];
|
|
2371
2368
|
|
|
2372
2369
|
let issues = [] as Awaited<ReturnType<typeof validatePlanBoundary>>;
|
|
2373
2370
|
|
|
@@ -2379,12 +2376,38 @@ async function emitObservabilityWarnings(
|
|
|
2379
2376
|
issues = await validateCompleteBoundary(basePath, mid, sid);
|
|
2380
2377
|
}
|
|
2381
2378
|
|
|
2382
|
-
if (issues.length
|
|
2379
|
+
if (issues.length > 0) {
|
|
2380
|
+
ctx.ui.notify(
|
|
2381
|
+
`Observability check (${unitType}) found ${issues.length} warning${issues.length === 1 ? "" : "s"}:\n${formatValidationIssues(issues)}`,
|
|
2382
|
+
"warning",
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2383
2385
|
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2386
|
+
return issues;
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
function buildObservabilityRepairBlock(issues: import("./observability-validator.ts").ValidationIssue[]): string {
|
|
2390
|
+
if (issues.length === 0) return "";
|
|
2391
|
+
const items = issues.map(issue => {
|
|
2392
|
+
const fileName = issue.file.split("/").pop() || issue.file;
|
|
2393
|
+
let line = `- **${fileName}**: ${issue.message}`;
|
|
2394
|
+
if (issue.suggestion) line += ` → ${issue.suggestion}`;
|
|
2395
|
+
return line;
|
|
2396
|
+
});
|
|
2397
|
+
return [
|
|
2398
|
+
"",
|
|
2399
|
+
"---",
|
|
2400
|
+
"",
|
|
2401
|
+
"## Pre-flight: Observability gaps to fix FIRST",
|
|
2402
|
+
"",
|
|
2403
|
+
"The following issues were detected in plan/summary files for this unit.",
|
|
2404
|
+
"**Read each flagged file, apply the fix described, then proceed with the unit.**",
|
|
2405
|
+
"",
|
|
2406
|
+
...items,
|
|
2407
|
+
"",
|
|
2408
|
+
"---",
|
|
2409
|
+
"",
|
|
2410
|
+
].join("\n");
|
|
2388
2411
|
}
|
|
2389
2412
|
|
|
2390
2413
|
async function recoverTimedOutUnit(
|
|
@@ -2736,14 +2759,34 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
|
|
|
2736
2759
|
}
|
|
2737
2760
|
|
|
2738
2761
|
/**
|
|
2739
|
-
* Check whether the expected artifact for a unit
|
|
2740
|
-
* Returns true if
|
|
2762
|
+
* Check whether the expected artifact(s) for a unit exist on disk.
|
|
2763
|
+
* Returns true if all required artifacts exist, or if the unit type has no
|
|
2741
2764
|
* single verifiable artifact (e.g., replan-slice).
|
|
2765
|
+
*
|
|
2766
|
+
* complete-slice requires both SUMMARY and UAT files — verifying only
|
|
2767
|
+
* the summary allowed the unit to be marked complete when the LLM
|
|
2768
|
+
* skipped writing the UAT file (see #176).
|
|
2742
2769
|
*/
|
|
2743
2770
|
function verifyExpectedArtifact(unitType: string, unitId: string, base: string): boolean {
|
|
2744
2771
|
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
2745
2772
|
if (!absPath) return true;
|
|
2746
|
-
|
|
2773
|
+
if (!existsSync(absPath)) return false;
|
|
2774
|
+
|
|
2775
|
+
// complete-slice must also produce a UAT file
|
|
2776
|
+
if (unitType === "complete-slice") {
|
|
2777
|
+
const parts = unitId.split("/");
|
|
2778
|
+
const mid = parts[0];
|
|
2779
|
+
const sid = parts[1];
|
|
2780
|
+
if (mid && sid) {
|
|
2781
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
2782
|
+
if (dir) {
|
|
2783
|
+
const uatPath = join(dir, buildSliceFileName(sid, "UAT"));
|
|
2784
|
+
if (!existsSync(uatPath)) return false;
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2789
|
+
return true;
|
|
2747
2790
|
}
|
|
2748
2791
|
|
|
2749
2792
|
/**
|
|
@@ -2753,7 +2796,7 @@ function verifyExpectedArtifact(unitType: string, unitId: string, base: string):
|
|
|
2753
2796
|
export function writeBlockerPlaceholder(unitType: string, unitId: string, base: string, reason: string): string | null {
|
|
2754
2797
|
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
2755
2798
|
if (!absPath) return null;
|
|
2756
|
-
const dir =
|
|
2799
|
+
const dir = dirname(absPath);
|
|
2757
2800
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
2758
2801
|
const content = [
|
|
2759
2802
|
`# BLOCKER — auto-mode recovery failed`,
|
|
@@ -2787,7 +2830,7 @@ function diagnoseExpectedArtifact(unitType: string, unitId: string, base: string
|
|
|
2787
2830
|
return `Task ${tid} marked [x] in ${relSliceFile(base, mid!, sid!, "PLAN")} + summary written`;
|
|
2788
2831
|
}
|
|
2789
2832
|
case "complete-slice":
|
|
2790
|
-
return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid!, "ROADMAP")} + summary written`;
|
|
2833
|
+
return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid!, "ROADMAP")} + summary + UAT written`;
|
|
2791
2834
|
case "replan-slice":
|
|
2792
2835
|
return `${relSliceFile(base, mid!, sid!, "REPLAN")} + updated ${relSliceFile(base, mid!, sid!, "PLAN")}`;
|
|
2793
2836
|
case "reassess-roadmap":
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { relMilestoneFile } from "./paths.js";
|
|
3
|
+
import { parseRoadmapSlices } from "./roadmap-slices.ts";
|
|
4
|
+
|
|
5
|
+
const SLICE_DISPATCH_TYPES = new Set([
|
|
6
|
+
"research-slice",
|
|
7
|
+
"plan-slice",
|
|
8
|
+
"replan-slice",
|
|
9
|
+
"execute-task",
|
|
10
|
+
"complete-slice",
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
function readTrackedFileFromBranch(base: string, branch: string, relPath: string): string | null {
|
|
14
|
+
try {
|
|
15
|
+
return execSync(`git show ${branch}:${relPath}`, {
|
|
16
|
+
cwd: base,
|
|
17
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
18
|
+
encoding: "utf-8",
|
|
19
|
+
}).trim();
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function milestoneIdFromNumber(num: number): string {
|
|
26
|
+
return `M${String(num).padStart(3, "0")}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function getPriorSliceCompletionBlocker(base: string, mainBranch: string, unitType: string, unitId: string): string | null {
|
|
30
|
+
if (!SLICE_DISPATCH_TYPES.has(unitType)) return null;
|
|
31
|
+
|
|
32
|
+
const [targetMid, targetSid] = unitId.split("/");
|
|
33
|
+
if (!targetMid || !targetSid) return null;
|
|
34
|
+
|
|
35
|
+
const targetMidNumber = Number.parseInt(targetMid.slice(1), 10);
|
|
36
|
+
if (!Number.isFinite(targetMidNumber)) return null;
|
|
37
|
+
|
|
38
|
+
for (let milestoneNumber = 1; milestoneNumber <= targetMidNumber; milestoneNumber += 1) {
|
|
39
|
+
const mid = milestoneIdFromNumber(milestoneNumber);
|
|
40
|
+
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
41
|
+
if (!roadmapRel) continue;
|
|
42
|
+
|
|
43
|
+
const roadmapContent = readTrackedFileFromBranch(base, mainBranch, roadmapRel);
|
|
44
|
+
if (!roadmapContent) continue;
|
|
45
|
+
|
|
46
|
+
const slices = parseRoadmapSlices(roadmapContent);
|
|
47
|
+
if (mid !== targetMid) {
|
|
48
|
+
const incomplete = slices.find(slice => !slice.done);
|
|
49
|
+
if (incomplete) {
|
|
50
|
+
return `Cannot dispatch ${unitType} ${unitId}: earlier slice ${mid}/${incomplete.id} is not complete on ${mainBranch}.`;
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const targetIndex = slices.findIndex(slice => slice.id === targetSid);
|
|
56
|
+
if (targetIndex === -1) return null;
|
|
57
|
+
|
|
58
|
+
const incomplete = slices.slice(0, targetIndex).find(slice => !slice.done);
|
|
59
|
+
if (incomplete) {
|
|
60
|
+
return `Cannot dispatch ${unitType} ${unitId}: earlier slice ${targetMid}/${incomplete.id} is not complete on ${mainBranch}.`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
@@ -13,6 +13,61 @@ Full documentation for `~/.gsd/preferences.md` (global) and `.gsd/preferences.md
|
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
+
## Semantics
|
|
17
|
+
|
|
18
|
+
### Empty Arrays vs Omitted Fields
|
|
19
|
+
|
|
20
|
+
**Empty arrays (`[]`) are equivalent to omitting the field entirely.** During validation, GSD deletes empty arrays from the preferences object (see `validatePreferences()` in `preferences.ts`):
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
for (const key of ["always_use_skills", "prefer_skills", "avoid_skills", "custom_instructions"] as const) {
|
|
24
|
+
if (validated[key] && validated[key]!.length === 0) {
|
|
25
|
+
delete validated[key];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
These are functionally identical:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
# Explicit empty arrays — will be normalized away
|
|
34
|
+
prefer_skills: []
|
|
35
|
+
avoid_skills: []
|
|
36
|
+
skill_rules: []
|
|
37
|
+
|
|
38
|
+
# Omitted entirely — same result
|
|
39
|
+
# (just don't write these fields)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Recommendation:** Omit fields you don't need. Empty arrays add noise with no effect.
|
|
43
|
+
|
|
44
|
+
### Global vs Project Preferences
|
|
45
|
+
|
|
46
|
+
Preferences are loaded from two locations and merged:
|
|
47
|
+
|
|
48
|
+
1. **Global:** `~/.gsd/preferences.md` — applies to all projects
|
|
49
|
+
2. **Project:** `.gsd/preferences.md` — applies to the current project only
|
|
50
|
+
|
|
51
|
+
**Merge behavior** (see `mergePreferences()` in `preferences.ts`):
|
|
52
|
+
- **Scalar fields** (`skill_discovery`, `budget_ceiling`, etc.): Project wins if defined, otherwise global. Uses nullish coalescing (`??`).
|
|
53
|
+
- **Array fields** (`always_use_skills`, `prefer_skills`, etc.): Concatenated via `mergeStringLists()` (global first, then project).
|
|
54
|
+
- **Object fields** (`models`, `git`, `auto_supervisor`): Shallow merge via spread operator `{ ...base, ...override }`.
|
|
55
|
+
|
|
56
|
+
For `models`, project settings override global at the phase level. If global has `planning: opus` and project has `planning: sonnet`, the project wins. But if project omits `research`, global's `research` setting is preserved.
|
|
57
|
+
|
|
58
|
+
### Skill Discovery vs Skill Preferences
|
|
59
|
+
|
|
60
|
+
These are **separate concerns**:
|
|
61
|
+
|
|
62
|
+
| Field | What it controls | Code reference |
|
|
63
|
+
|-------|-----------------|----------------|
|
|
64
|
+
| `skill_discovery` | **Whether** GSD looks for relevant skills during research | `resolveSkillDiscoveryMode()` in `preferences.ts` |
|
|
65
|
+
| `always_use_skills`, `prefer_skills`, `avoid_skills` | **Which** skills to use when they're found relevant | `renderPreferencesForSystemPrompt()` in `preferences.ts` |
|
|
66
|
+
|
|
67
|
+
Setting `prefer_skills: []` does **not** disable skill discovery — it just means you have no preference overrides. Use `skill_discovery: off` to disable discovery entirely.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
16
71
|
## Field Guide
|
|
17
72
|
|
|
18
73
|
- `version`: schema version. Start at `1`.
|
|
@@ -60,6 +115,27 @@ Full documentation for `~/.gsd/preferences.md` (global) and `.gsd/preferences.md
|
|
|
60
115
|
- Use `skill_rules` for situational routing, not broad personality preferences.
|
|
61
116
|
- Prefer skill names for stable built-in skills.
|
|
62
117
|
- Prefer absolute paths for local personal skills.
|
|
118
|
+
- **Omit fields you don't need** — empty arrays add noise with no effect.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## Minimal Example
|
|
123
|
+
|
|
124
|
+
The cleanest preferences file only specifies what you actually want:
|
|
125
|
+
|
|
126
|
+
```yaml
|
|
127
|
+
---
|
|
128
|
+
version: 1
|
|
129
|
+
always_use_skills:
|
|
130
|
+
- debug-like-expert
|
|
131
|
+
skill_discovery: suggest
|
|
132
|
+
models:
|
|
133
|
+
planning: claude-opus-4-6
|
|
134
|
+
execution: claude-sonnet-4-6
|
|
135
|
+
---
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Everything else uses defaults. No `prefer_skills: []`, no `avoid_skills: []`, no `auto_supervisor: {}` — those are just noise.
|
|
63
139
|
|
|
64
140
|
---
|
|
65
141
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
|
+
|
|
3
|
+
type StopAutoFn = (ctx: ExtensionCommandContext, pi: ExtensionAPI) => Promise<void>;
|
|
4
|
+
|
|
5
|
+
export function registerExitCommand(
|
|
6
|
+
pi: ExtensionAPI,
|
|
7
|
+
deps: { stopAuto?: StopAutoFn } = {},
|
|
8
|
+
): void {
|
|
9
|
+
pi.registerCommand("exit", {
|
|
10
|
+
description: "Exit GSD gracefully",
|
|
11
|
+
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
12
|
+
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
13
|
+
const stopAuto = deps.stopAuto ?? (await import("./auto.js")).stopAuto;
|
|
14
|
+
await stopAuto(ctx, pi);
|
|
15
|
+
ctx.shutdown();
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
// GSD Extension
|
|
1
|
+
// GSD Extension - File Parsing and I/O
|
|
2
2
|
// Parsers for roadmap, plan, summary, and continue files.
|
|
3
3
|
// Used by state derivation and the status widget.
|
|
4
|
-
// Pure functions, zero Pi dependencies
|
|
4
|
+
// Pure functions, zero Pi dependencies - uses only Node built-ins.
|
|
5
5
|
|
|
6
6
|
import { promises as fs, readdirSync } from 'node:fs';
|
|
7
7
|
import { dirname, resolve } from 'node:path';
|
|
8
8
|
import { milestonesDir, resolveMilestoneFile, relMilestoneFile } from './paths.js';
|
|
9
9
|
|
|
10
10
|
import type {
|
|
11
|
-
Roadmap,
|
|
11
|
+
Roadmap, BoundaryMapEntry,
|
|
12
12
|
SlicePlan, TaskPlanEntry,
|
|
13
13
|
Summary, SummaryFrontmatter, SummaryRequires, FileModified,
|
|
14
14
|
Continue, ContinueFrontmatter, ContinueStatus,
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
} from './types.ts';
|
|
19
19
|
|
|
20
20
|
import { checkExistingEnvKeys } from '../get-secrets-from-user.ts';
|
|
21
|
+
import { parseRoadmapSlices } from './roadmap-slices.ts';
|
|
21
22
|
|
|
22
23
|
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
23
24
|
|
|
@@ -199,40 +200,8 @@ export function parseRoadmap(content: string): Roadmap {
|
|
|
199
200
|
})();
|
|
200
201
|
const successCriteria = scSection ? parseBullets(scSection) : [];
|
|
201
202
|
|
|
202
|
-
// Slices
|
|
203
|
-
const
|
|
204
|
-
const slices: RoadmapSliceEntry[] = [];
|
|
205
|
-
|
|
206
|
-
if (slicesSection) {
|
|
207
|
-
const checkboxItems = slicesSection.split('\n');
|
|
208
|
-
let currentSlice: RoadmapSliceEntry | null = null;
|
|
209
|
-
|
|
210
|
-
for (const line of checkboxItems) {
|
|
211
|
-
const cbMatch = line.match(/^-\s+\[([ xX])\]\s+\*\*(\w+):\s+(.+?)\*\*\s*(.*)/);
|
|
212
|
-
if (cbMatch) {
|
|
213
|
-
if (currentSlice) slices.push(currentSlice);
|
|
214
|
-
|
|
215
|
-
const done = cbMatch[1].toLowerCase() === 'x';
|
|
216
|
-
const id = cbMatch[2];
|
|
217
|
-
const sliceTitle = cbMatch[3];
|
|
218
|
-
const rest = cbMatch[4];
|
|
219
|
-
|
|
220
|
-
const riskMatch = rest.match(/`risk:(\w+)`/);
|
|
221
|
-
const risk = (riskMatch ? riskMatch[1] : 'low') as RiskLevel;
|
|
222
|
-
|
|
223
|
-
const depsMatch = rest.match(/`depends:\[([^\]]*)\]`/);
|
|
224
|
-
const depends = depsMatch && depsMatch[1].trim()
|
|
225
|
-
? depsMatch[1].split(',').map(s => s.trim())
|
|
226
|
-
: [];
|
|
227
|
-
|
|
228
|
-
currentSlice = { id, title: sliceTitle, risk, depends, done, demo: '' };
|
|
229
|
-
} else if (currentSlice && line.trim().startsWith('>')) {
|
|
230
|
-
const demoText = line.trim().replace(/^>\s*/, '').replace(/^After this:\s*/i, '');
|
|
231
|
-
currentSlice.demo = demoText;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
if (currentSlice) slices.push(currentSlice);
|
|
235
|
-
}
|
|
203
|
+
// Slices
|
|
204
|
+
const slices = parseRoadmapSlices(content);
|
|
236
205
|
|
|
237
206
|
// Boundary map
|
|
238
207
|
const boundaryMap: BoundaryMapEntry[] = [];
|
|
@@ -657,7 +626,7 @@ export function parseTaskPlanMustHaves(content: string): Array<{ text: string; c
|
|
|
657
626
|
checked: cbMatch[1].toLowerCase() === 'x',
|
|
658
627
|
};
|
|
659
628
|
}
|
|
660
|
-
// No checkbox
|
|
629
|
+
// No checkbox - treat as unchecked with full line as text
|
|
661
630
|
return { text: line.trim(), checked: false };
|
|
662
631
|
});
|
|
663
632
|
}
|
|
@@ -732,7 +701,7 @@ export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' |
|
|
|
732
701
|
/**
|
|
733
702
|
* Extract the UAT type from a UAT file's raw content.
|
|
734
703
|
*
|
|
735
|
-
* UAT files have no YAML frontmatter
|
|
704
|
+
* UAT files have no YAML frontmatter - pass raw file content directly.
|
|
736
705
|
* Classification is leading-keyword-only: e.g. `mixed (artifact-driven + live-runtime)` → `'mixed'`.
|
|
737
706
|
*
|
|
738
707
|
* Returns `undefined` when:
|
|
@@ -811,7 +780,7 @@ export async function inlinePriorMilestoneSummary(mid: string, base: string): Pr
|
|
|
811
780
|
* with the current environment (.env + process.env).
|
|
812
781
|
*
|
|
813
782
|
* Returns `null` when no manifest file exists (path resolution failure or
|
|
814
|
-
* file not on disk)
|
|
783
|
+
* file not on disk) - callers can distinguish "no manifest" from "empty manifest".
|
|
815
784
|
*/
|
|
816
785
|
export async function getManifestStatus(
|
|
817
786
|
base: string, milestoneId: string,
|