gsd-pi 2.7.1 → 2.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -5
- package/dist/loader.js +0 -0
- package/dist/modes/interactive/theme/dark.json +85 -0
- package/dist/modes/interactive/theme/light.json +84 -0
- package/dist/modes/interactive/theme/theme-schema.json +335 -0
- package/dist/modes/interactive/theme/theme.d.ts +78 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -0
- package/dist/modes/interactive/theme/theme.js +949 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -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/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 +30 -20
- package/node_modules/@gsd/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts +2 -0
- package/node_modules/@gsd/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/index.js +3 -0
- 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 +1 -1
- package/node_modules/@gsd/pi-coding-agent/dist/modes/interactive/interactive-mode.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/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 +33 -20
- package/node_modules/@gsd/pi-coding-agent/src/index.ts +3 -0
- package/node_modules/@gsd/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
- package/node_modules/cliui/CHANGELOG.md +121 -0
- package/node_modules/color-convert/CHANGELOG.md +54 -0
- package/node_modules/esprima/ChangeLog +235 -0
- package/node_modules/mz/HISTORY.md +66 -0
- package/node_modules/proper-lockfile/CHANGELOG.md +108 -0
- package/node_modules/source-map/CHANGELOG.md +301 -0
- package/node_modules/thenify/History.md +11 -0
- package/node_modules/thenify-all/History.md +11 -0
- package/node_modules/y18n/CHANGELOG.md +100 -0
- package/node_modules/yargs/CHANGELOG.md +88 -0
- package/node_modules/yargs-parser/CHANGELOG.md +263 -0
- package/package.json +10 -2
- 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/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 +30 -20
- package/packages/pi-coding-agent/dist/core/tools/bash.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +2 -0
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +3 -0
- 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 +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.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/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 +33 -20
- package/packages/pi-coding-agent/src/index.ts +3 -0
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
- package/src/resources/extensions/browser-tools/capture.ts +165 -0
- package/src/resources/extensions/browser-tools/evaluate-helpers.ts +184 -0
- package/src/resources/extensions/browser-tools/index.ts +47 -4985
- package/src/resources/extensions/browser-tools/lifecycle.ts +270 -0
- package/src/resources/extensions/browser-tools/package.json +5 -1
- package/src/resources/extensions/browser-tools/refs.ts +264 -0
- package/src/resources/extensions/browser-tools/settle.ts +197 -0
- package/src/resources/extensions/browser-tools/state.ts +408 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +652 -0
- package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +614 -0
- package/src/resources/extensions/browser-tools/tools/assertions.ts +342 -0
- package/src/resources/extensions/browser-tools/tools/forms.ts +801 -0
- package/src/resources/extensions/browser-tools/tools/inspection.ts +492 -0
- package/src/resources/extensions/browser-tools/tools/intent.ts +614 -0
- package/src/resources/extensions/browser-tools/tools/interaction.ts +865 -0
- package/src/resources/extensions/browser-tools/tools/navigation.ts +232 -0
- package/src/resources/extensions/browser-tools/tools/pages.ts +303 -0
- package/src/resources/extensions/browser-tools/tools/refs.ts +541 -0
- package/src/resources/extensions/browser-tools/tools/screenshot.ts +83 -0
- package/src/resources/extensions/browser-tools/tools/session.ts +400 -0
- package/src/resources/extensions/browser-tools/tools/wait.ts +247 -0
- package/src/resources/extensions/browser-tools/utils.ts +660 -0
- package/src/resources/extensions/gsd/auto.ts +74 -13
- package/src/resources/extensions/gsd/dispatch-guard.ts +65 -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 +65 -17
- package/src/resources/extensions/gsd/gitignore.ts +1 -0
- package/src/resources/extensions/gsd/guided-flow.ts +39 -2
- 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-slice.md +2 -2
- package/src/resources/extensions/gsd/prompts/discuss.md +25 -0
- 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 +64 -39
- package/src/resources/extensions/gsd/tests/roadmap-slices.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/write-gate.test.ts +122 -0
- package/src/resources/extensions/shared/interview-ui.ts +1 -1
- 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
|
@@ -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";
|
|
@@ -1254,7 +1255,14 @@ async function dispatchNextUnit(
|
|
|
1254
1255
|
}
|
|
1255
1256
|
}
|
|
1256
1257
|
|
|
1257
|
-
|
|
1258
|
+
const priorSliceBlocker = getPriorSliceCompletionBlocker(basePath, getMainBranch(basePath), unitType, unitId);
|
|
1259
|
+
if (priorSliceBlocker) {
|
|
1260
|
+
await stopAuto(ctx, pi);
|
|
1261
|
+
ctx.ui.notify(priorSliceBlocker, "error");
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
const observabilityIssues = await collectObservabilityWarnings(ctx, unitType, unitId);
|
|
1258
1266
|
|
|
1259
1267
|
// Idempotency: skip units already completed in a prior session.
|
|
1260
1268
|
const idempotencyKey = `${unitType}/${unitId}`;
|
|
@@ -1394,6 +1402,13 @@ async function dispatchNextUnit(
|
|
|
1394
1402
|
}
|
|
1395
1403
|
}
|
|
1396
1404
|
|
|
1405
|
+
// Inject observability repair instructions so the agent fixes gaps before
|
|
1406
|
+
// proceeding with the unit (see #174).
|
|
1407
|
+
const repairBlock = buildObservabilityRepairBlock(observabilityIssues);
|
|
1408
|
+
if (repairBlock) {
|
|
1409
|
+
finalPrompt = `${finalPrompt}${repairBlock}`;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1397
1412
|
// Switch model if preferences specify one for this unit type
|
|
1398
1413
|
// Try primary model, then fallbacks in order if setting fails
|
|
1399
1414
|
const modelConfig = resolveModelWithFallbacksForUnit(unitType);
|
|
@@ -2357,17 +2372,17 @@ function ensurePreconditions(
|
|
|
2357
2372
|
|
|
2358
2373
|
// ─── Diagnostics ──────────────────────────────────────────────────────────────
|
|
2359
2374
|
|
|
2360
|
-
async function
|
|
2375
|
+
async function collectObservabilityWarnings(
|
|
2361
2376
|
ctx: ExtensionContext,
|
|
2362
2377
|
unitType: string,
|
|
2363
2378
|
unitId: string,
|
|
2364
|
-
): Promise<
|
|
2379
|
+
): Promise<import("./observability-validator.ts").ValidationIssue[]> {
|
|
2365
2380
|
const parts = unitId.split("/");
|
|
2366
2381
|
const mid = parts[0];
|
|
2367
2382
|
const sid = parts[1];
|
|
2368
2383
|
const tid = parts[2];
|
|
2369
2384
|
|
|
2370
|
-
if (!mid || !sid) return;
|
|
2385
|
+
if (!mid || !sid) return [];
|
|
2371
2386
|
|
|
2372
2387
|
let issues = [] as Awaited<ReturnType<typeof validatePlanBoundary>>;
|
|
2373
2388
|
|
|
@@ -2379,12 +2394,38 @@ async function emitObservabilityWarnings(
|
|
|
2379
2394
|
issues = await validateCompleteBoundary(basePath, mid, sid);
|
|
2380
2395
|
}
|
|
2381
2396
|
|
|
2382
|
-
if (issues.length
|
|
2397
|
+
if (issues.length > 0) {
|
|
2398
|
+
ctx.ui.notify(
|
|
2399
|
+
`Observability check (${unitType}) found ${issues.length} warning${issues.length === 1 ? "" : "s"}:\n${formatValidationIssues(issues)}`,
|
|
2400
|
+
"warning",
|
|
2401
|
+
);
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
return issues;
|
|
2405
|
+
}
|
|
2383
2406
|
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2407
|
+
function buildObservabilityRepairBlock(issues: import("./observability-validator.ts").ValidationIssue[]): string {
|
|
2408
|
+
if (issues.length === 0) return "";
|
|
2409
|
+
const items = issues.map(issue => {
|
|
2410
|
+
const fileName = issue.file.split("/").pop() || issue.file;
|
|
2411
|
+
let line = `- **${fileName}**: ${issue.message}`;
|
|
2412
|
+
if (issue.suggestion) line += ` → ${issue.suggestion}`;
|
|
2413
|
+
return line;
|
|
2414
|
+
});
|
|
2415
|
+
return [
|
|
2416
|
+
"",
|
|
2417
|
+
"---",
|
|
2418
|
+
"",
|
|
2419
|
+
"## Pre-flight: Observability gaps to fix FIRST",
|
|
2420
|
+
"",
|
|
2421
|
+
"The following issues were detected in plan/summary files for this unit.",
|
|
2422
|
+
"**Read each flagged file, apply the fix described, then proceed with the unit.**",
|
|
2423
|
+
"",
|
|
2424
|
+
...items,
|
|
2425
|
+
"",
|
|
2426
|
+
"---",
|
|
2427
|
+
"",
|
|
2428
|
+
].join("\n");
|
|
2388
2429
|
}
|
|
2389
2430
|
|
|
2390
2431
|
async function recoverTimedOutUnit(
|
|
@@ -2736,14 +2777,34 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
|
|
|
2736
2777
|
}
|
|
2737
2778
|
|
|
2738
2779
|
/**
|
|
2739
|
-
* Check whether the expected artifact for a unit
|
|
2740
|
-
* Returns true if
|
|
2780
|
+
* Check whether the expected artifact(s) for a unit exist on disk.
|
|
2781
|
+
* Returns true if all required artifacts exist, or if the unit type has no
|
|
2741
2782
|
* single verifiable artifact (e.g., replan-slice).
|
|
2783
|
+
*
|
|
2784
|
+
* complete-slice requires both SUMMARY and UAT files — verifying only
|
|
2785
|
+
* the summary allowed the unit to be marked complete when the LLM
|
|
2786
|
+
* skipped writing the UAT file (see #176).
|
|
2742
2787
|
*/
|
|
2743
2788
|
function verifyExpectedArtifact(unitType: string, unitId: string, base: string): boolean {
|
|
2744
2789
|
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
2745
2790
|
if (!absPath) return true;
|
|
2746
|
-
|
|
2791
|
+
if (!existsSync(absPath)) return false;
|
|
2792
|
+
|
|
2793
|
+
// complete-slice must also produce a UAT file
|
|
2794
|
+
if (unitType === "complete-slice") {
|
|
2795
|
+
const parts = unitId.split("/");
|
|
2796
|
+
const mid = parts[0];
|
|
2797
|
+
const sid = parts[1];
|
|
2798
|
+
if (mid && sid) {
|
|
2799
|
+
const dir = resolveSlicePath(base, mid, sid);
|
|
2800
|
+
if (dir) {
|
|
2801
|
+
const uatPath = join(dir, buildSliceFileName(sid, "UAT"));
|
|
2802
|
+
if (!existsSync(uatPath)) return false;
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
return true;
|
|
2747
2808
|
}
|
|
2748
2809
|
|
|
2749
2810
|
/**
|
|
@@ -2787,7 +2848,7 @@ function diagnoseExpectedArtifact(unitType: string, unitId: string, base: string
|
|
|
2787
2848
|
return `Task ${tid} marked [x] in ${relSliceFile(base, mid!, sid!, "PLAN")} + summary written`;
|
|
2788
2849
|
}
|
|
2789
2850
|
case "complete-slice":
|
|
2790
|
-
return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid!, "ROADMAP")} + summary written`;
|
|
2851
|
+
return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid!, "ROADMAP")} + summary + UAT written`;
|
|
2791
2852
|
case "replan-slice":
|
|
2792
2853
|
return `${relSliceFile(base, mid!, sid!, "REPLAN")} + updated ${relSliceFile(base, mid!, sid!, "PLAN")}`;
|
|
2793
2854
|
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
|
+
}
|
|
@@ -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,
|
|
@@ -27,6 +27,7 @@ export interface GitPreferences {
|
|
|
27
27
|
pre_merge_check?: boolean | string;
|
|
28
28
|
commit_type?: string;
|
|
29
29
|
main_branch?: string;
|
|
30
|
+
merge_strategy?: "squash" | "merge";
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;
|
|
@@ -55,7 +56,7 @@ export interface PreMergeCheckResult {
|
|
|
55
56
|
* GSD runtime paths that should be excluded from smart staging.
|
|
56
57
|
* These are transient/generated artifacts that should never be committed.
|
|
57
58
|
* Matches the union of SKIP_PATHS + SKIP_EXACT in worktree-manager.ts
|
|
58
|
-
* and the first
|
|
59
|
+
* and the first 7 entries in gitignore.ts BASELINE_PATTERNS.
|
|
59
60
|
*/
|
|
60
61
|
export const RUNTIME_EXCLUSION_PATHS: readonly string[] = [
|
|
61
62
|
".gsd/activity/",
|
|
@@ -63,6 +64,7 @@ export const RUNTIME_EXCLUSION_PATHS: readonly string[] = [
|
|
|
63
64
|
".gsd/worktrees/",
|
|
64
65
|
".gsd/auto.lock",
|
|
65
66
|
".gsd/metrics.json",
|
|
67
|
+
".gsd/completed-units.json",
|
|
66
68
|
".gsd/STATE.md",
|
|
67
69
|
];
|
|
68
70
|
|
|
@@ -130,16 +132,42 @@ export class GitServiceImpl {
|
|
|
130
132
|
*/
|
|
131
133
|
private smartStage(extraExclusions: readonly string[] = []): void {
|
|
132
134
|
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
|
|
136
|
+
// One-time cleanup: if runtime files are already tracked in the index
|
|
137
|
+
// (from older versions where the fallback bug staged them), untrack them
|
|
138
|
+
// in a dedicated commit. This must happen as a separate commit because
|
|
139
|
+
// the git reset HEAD step below would otherwise undo the rm --cached.
|
|
140
|
+
if (!this._runtimeFilesCleanedUp) {
|
|
141
|
+
let cleaned = false;
|
|
142
|
+
for (const exclusion of RUNTIME_EXCLUSION_PATHS) {
|
|
143
|
+
const result = this.git(["rm", "--cached", "-r", "--ignore-unmatch", exclusion], { allowFailure: true });
|
|
144
|
+
if (result && result.includes("rm '")) cleaned = true;
|
|
145
|
+
}
|
|
146
|
+
if (cleaned) {
|
|
147
|
+
this.git(["commit", "-F", "-"], { input: "chore: untrack .gsd/ runtime files from git index" });
|
|
148
|
+
}
|
|
149
|
+
this._runtimeFilesCleanedUp = true;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Stage everything, then unstage excluded paths.
|
|
153
|
+
//
|
|
154
|
+
// Previous approach used pathspec excludes (:(exclude)...) with git add -A,
|
|
155
|
+
// but that fails when .gsd/ is in .gitignore — git exits non-zero before
|
|
156
|
+
// evaluating the excludes. The catch fallback ran plain `git add -A`,
|
|
157
|
+
// staging all tracked runtime files unconditionally and defeating the
|
|
158
|
+
// exclusion list entirely.
|
|
159
|
+
//
|
|
160
|
+
// git reset HEAD silently succeeds when the path isn't staged, so no
|
|
161
|
+
// error handling is needed per-path.
|
|
162
|
+
this.git(["add", "-A"]);
|
|
163
|
+
for (const exclusion of allExclusions) {
|
|
164
|
+
this.git(["reset", "HEAD", "--", exclusion], { allowFailure: true });
|
|
140
165
|
}
|
|
141
166
|
}
|
|
142
167
|
|
|
168
|
+
/** Tracks whether runtime file cleanup has run this session. */
|
|
169
|
+
private _runtimeFilesCleanedUp = false;
|
|
170
|
+
|
|
143
171
|
/**
|
|
144
172
|
* Stage files (smart staging) and commit.
|
|
145
173
|
* Returns the commit message string on success, or null if nothing to commit.
|
|
@@ -312,6 +340,12 @@ export class GitServiceImpl {
|
|
|
312
340
|
// Exclude .gsd/ to prevent merge conflicts when both branches modify planning artifacts.
|
|
313
341
|
this.autoCommit("pre-switch", current, [".gsd/"]);
|
|
314
342
|
|
|
343
|
+
// Discard uncommitted .gsd/ changes so checkout doesn't fail.
|
|
344
|
+
// These are runtime files (metrics, completed-units, STATE) that were
|
|
345
|
+
// intentionally excluded from the commit above. If they remain dirty,
|
|
346
|
+
// git checkout refuses when the target branch has different versions.
|
|
347
|
+
this.git(["checkout", "--", ".gsd/"], { allowFailure: true });
|
|
348
|
+
|
|
315
349
|
this.git(["checkout", branch]);
|
|
316
350
|
return created;
|
|
317
351
|
}
|
|
@@ -327,6 +361,9 @@ export class GitServiceImpl {
|
|
|
327
361
|
// Exclude .gsd/ to prevent merge conflicts when both branches modify planning artifacts.
|
|
328
362
|
this.autoCommit("pre-switch", current, [".gsd/"]);
|
|
329
363
|
|
|
364
|
+
// Discard uncommitted .gsd/ changes so checkout doesn't fail.
|
|
365
|
+
this.git(["checkout", "--", ".gsd/"], { allowFailure: true });
|
|
366
|
+
|
|
330
367
|
this.git(["checkout", mainBranch]);
|
|
331
368
|
}
|
|
332
369
|
|
|
@@ -487,25 +524,36 @@ export class GitServiceImpl {
|
|
|
487
524
|
commitType, milestoneId, sliceId, sliceTitle, mainBranch, branch,
|
|
488
525
|
);
|
|
489
526
|
|
|
490
|
-
//
|
|
491
|
-
|
|
527
|
+
// Pull latest main before merging to avoid conflicts from remote changes
|
|
528
|
+
this.git(["pull", "--rebase", "origin", mainBranch], { allowFailure: true });
|
|
529
|
+
|
|
530
|
+
// Merge slice branch — strategy is configurable via git.merge_strategy
|
|
531
|
+
// preference. Default: "squash" (preserves existing behavior).
|
|
532
|
+
// "merge" uses --no-ff which is more resilient to conflicts from
|
|
533
|
+
// long-lived branches or frequently-changing .gsd/* artifacts.
|
|
534
|
+
const strategy = this.prefs.merge_strategy ?? "squash";
|
|
535
|
+
const mergeArgs = strategy === "merge"
|
|
536
|
+
? ["merge", "--no-ff", "-m", message, branch]
|
|
537
|
+
: ["merge", "--squash", branch];
|
|
538
|
+
|
|
492
539
|
try {
|
|
493
|
-
this.git(
|
|
540
|
+
this.git(mergeArgs);
|
|
494
541
|
} catch (mergeError) {
|
|
495
|
-
//
|
|
496
|
-
// has conflict markers and a dirty index. Reset to restore a clean state.
|
|
542
|
+
// Merge exits non-zero on conflict. Reset to restore a clean state.
|
|
497
543
|
this.git(["reset", "--hard", "HEAD"], { allowFailure: true });
|
|
498
544
|
const msg = mergeError instanceof Error ? mergeError.message : String(mergeError);
|
|
499
545
|
throw new Error(
|
|
500
|
-
|
|
546
|
+
`${strategy === "merge" ? "Merge" : "Squash-merge"} of "${branch}" into "${mainBranch}" failed with conflicts. ` +
|
|
501
547
|
`Working tree has been reset to a clean state. ` +
|
|
502
|
-
`Resolve manually: git checkout ${mainBranch} && git merge --squash ${branch}\n` +
|
|
548
|
+
`Resolve manually: git checkout ${mainBranch} && git merge ${strategy === "merge" ? "--no-ff" : "--squash"} ${branch}\n` +
|
|
503
549
|
`Original error: ${msg}`,
|
|
504
550
|
);
|
|
505
551
|
}
|
|
506
552
|
|
|
507
|
-
//
|
|
508
|
-
|
|
553
|
+
// Squash merge needs a separate commit; --no-ff merge already committed
|
|
554
|
+
if (strategy === "squash") {
|
|
555
|
+
this.git(["commit", "-F", "-"], { input: message });
|
|
556
|
+
}
|
|
509
557
|
|
|
510
558
|
// Delete the merged branch
|
|
511
559
|
this.git(["branch", "-D", branch]);
|
|
@@ -14,15 +14,16 @@ import { deriveState } from "./state.js";
|
|
|
14
14
|
import { startAuto } from "./auto.js";
|
|
15
15
|
import { readCrashLock, clearLock, formatCrashInfo } from "./crash-recovery.js";
|
|
16
16
|
import {
|
|
17
|
-
gsdRoot, milestonesDir, resolveMilestoneFile,
|
|
17
|
+
gsdRoot, milestonesDir, resolveMilestoneFile, resolveMilestonePath,
|
|
18
18
|
resolveSliceFile, resolveSlicePath, resolveGsdRootFile, relGsdRootFile,
|
|
19
19
|
relMilestoneFile, relSliceFile, relSlicePath,
|
|
20
20
|
} from "./paths.js";
|
|
21
21
|
import { join } from "node:path";
|
|
22
|
-
import { readFileSync, existsSync, mkdirSync, readdirSync } from "node:fs";
|
|
22
|
+
import { readFileSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
|
|
23
23
|
import { execSync, execFileSync } from "node:child_process";
|
|
24
24
|
import { ensureGitignore, ensurePreferences } from "./gitignore.js";
|
|
25
25
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
26
|
+
import { showConfirm } from "../shared/confirm-ui.js";
|
|
26
27
|
|
|
27
28
|
// ─── Auto-start after discuss ─────────────────────────────────────────────────
|
|
28
29
|
|
|
@@ -35,6 +36,11 @@ let pendingAutoStart: {
|
|
|
35
36
|
step?: boolean; // preserve step mode through discuss → auto transition
|
|
36
37
|
} | null = null;
|
|
37
38
|
|
|
39
|
+
/** Returns the milestoneId being discussed, or null if no discussion is active */
|
|
40
|
+
export function getDiscussionMilestoneId(): string | null {
|
|
41
|
+
return pendingAutoStart?.milestoneId ?? null;
|
|
42
|
+
}
|
|
43
|
+
|
|
38
44
|
/** Called from agent_end to check if auto-mode should start after discuss */
|
|
39
45
|
export function checkAutoStartAfterDiscuss(): boolean {
|
|
40
46
|
if (!pendingAutoStart) return false;
|
|
@@ -601,6 +607,16 @@ export async function showSmartEntry(
|
|
|
601
607
|
label: "Discuss first",
|
|
602
608
|
description: "Capture decisions on gray areas before planning.",
|
|
603
609
|
}] : []),
|
|
610
|
+
{
|
|
611
|
+
id: "skip_milestone",
|
|
612
|
+
label: "Skip — create new milestone",
|
|
613
|
+
description: "Leave this milestone on disk and start a fresh one.",
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: "discard_milestone",
|
|
617
|
+
label: "Discard this milestone",
|
|
618
|
+
description: "Delete the milestone directory and start over.",
|
|
619
|
+
},
|
|
604
620
|
];
|
|
605
621
|
|
|
606
622
|
const choice = await showNextAction(ctx as any, {
|
|
@@ -619,6 +635,27 @@ export async function showSmartEntry(
|
|
|
619
635
|
dispatchWorkflow(pi, loadPrompt("guided-discuss-milestone", {
|
|
620
636
|
milestoneId, milestoneTitle,
|
|
621
637
|
}));
|
|
638
|
+
} else if (choice === "skip_milestone") {
|
|
639
|
+
const milestoneIds = findMilestoneIds(basePath);
|
|
640
|
+
const nextId = `M${String(milestoneIds.length + 1).padStart(3, "0")}`;
|
|
641
|
+
pendingAutoStart = { ctx, pi, basePath, milestoneId: nextId, step: stepMode };
|
|
642
|
+
dispatchWorkflow(pi, buildDiscussPrompt(nextId,
|
|
643
|
+
`New milestone ${nextId}.`,
|
|
644
|
+
basePath
|
|
645
|
+
));
|
|
646
|
+
} else if (choice === "discard_milestone") {
|
|
647
|
+
const mDir = resolveMilestonePath(basePath, milestoneId);
|
|
648
|
+
if (!mDir) return;
|
|
649
|
+
const confirmed = await showConfirm(ctx as any, {
|
|
650
|
+
title: "Discard milestone?",
|
|
651
|
+
message: `This will permanently delete ${milestoneId} and all its contents.`,
|
|
652
|
+
confirmLabel: "Discard",
|
|
653
|
+
declineLabel: "Cancel",
|
|
654
|
+
});
|
|
655
|
+
if (confirmed) {
|
|
656
|
+
rmSync(mDir, { recursive: true, force: true });
|
|
657
|
+
return showSmartEntry(ctx, pi, basePath, options);
|
|
658
|
+
}
|
|
622
659
|
}
|
|
623
660
|
} else {
|
|
624
661
|
// Roadmap exists — either blocked or ready for auto
|