gsd-pi 2.15.1 → 2.16.0
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 +1 -0
- package/dist/resources/extensions/async-jobs/async-bash-tool.ts +2 -1
- package/dist/resources/extensions/async-jobs/await-tool.ts +5 -3
- package/dist/resources/extensions/async-jobs/cancel-job-tool.ts +2 -1
- package/dist/resources/extensions/async-jobs/index.ts +3 -3
- package/dist/resources/extensions/gsd/auto-dashboard.ts +3 -0
- package/dist/resources/extensions/gsd/auto-dispatch.ts +31 -1
- package/dist/resources/extensions/gsd/auto-prompts.ts +85 -2
- package/dist/resources/extensions/gsd/auto-recovery.ts +47 -28
- package/dist/resources/extensions/gsd/auto-supervisor.ts +2 -7
- package/dist/resources/extensions/gsd/auto-worktree.ts +53 -106
- package/dist/resources/extensions/gsd/auto.ts +28 -12
- package/dist/resources/extensions/gsd/commands.ts +93 -27
- package/dist/resources/extensions/gsd/doctor.ts +12 -22
- package/dist/resources/extensions/gsd/files.ts +154 -2
- package/dist/resources/extensions/gsd/git-self-heal.ts +4 -4
- package/dist/resources/extensions/gsd/git-service.ts +23 -23
- package/dist/resources/extensions/gsd/gitignore.ts +2 -5
- package/dist/resources/extensions/gsd/guided-flow.ts +11 -11
- package/dist/resources/extensions/gsd/index.ts +10 -3
- package/dist/resources/extensions/gsd/native-git-bridge.ts +843 -12
- package/dist/resources/extensions/gsd/native-parser-bridge.ts +128 -1
- package/dist/resources/extensions/gsd/paths.ts +89 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +2 -0
- package/dist/resources/extensions/gsd/prompts/rewrite-docs.md +32 -0
- package/dist/resources/extensions/gsd/prompts/system.md +1 -0
- package/dist/resources/extensions/gsd/provider-error-pause.ts +1 -1
- package/dist/resources/extensions/gsd/session-forensics.ts +29 -12
- package/dist/resources/extensions/gsd/state.ts +1 -38
- package/dist/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +2 -2
- package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +1 -1
- package/dist/resources/extensions/gsd/tests/overrides.test.ts +131 -0
- package/dist/resources/extensions/gsd/tests/preferences-git.test.ts +5 -5
- package/dist/resources/extensions/gsd/tests/preferences-hooks.test.ts +7 -6
- package/dist/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +1 -1
- package/dist/resources/extensions/gsd/undo.ts +24 -18
- package/dist/resources/extensions/gsd/worktree-command.ts +2 -2
- package/dist/resources/extensions/gsd/worktree-manager.ts +81 -134
- package/package.json +3 -2
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/model-resolver.js +2 -2
- package/packages/pi-coding-agent/dist/core/model-resolver.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/custom-editor.js +8 -1
- package/packages/pi-coding-agent/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/packages/pi-coding-agent/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/types.ts +1 -1
- package/packages/pi-coding-agent/src/core/model-resolver.ts +2 -2
- package/packages/pi-coding-agent/src/modes/interactive/components/custom-editor.ts +8 -1
- package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +1 -1
- package/packages/pi-coding-agent/src/modes/rpc/rpc-mode.ts +1 -1
- package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
- package/packages/pi-tui/dist/components/editor.js +4 -2
- package/packages/pi-tui/dist/components/editor.js.map +1 -1
- package/packages/pi-tui/src/components/editor.ts +5 -3
- package/src/resources/extensions/async-jobs/async-bash-tool.ts +2 -1
- package/src/resources/extensions/async-jobs/await-tool.ts +5 -3
- package/src/resources/extensions/async-jobs/cancel-job-tool.ts +2 -1
- package/src/resources/extensions/async-jobs/index.ts +3 -3
- package/src/resources/extensions/gsd/auto-dashboard.ts +3 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +31 -1
- package/src/resources/extensions/gsd/auto-prompts.ts +85 -2
- package/src/resources/extensions/gsd/auto-recovery.ts +47 -28
- package/src/resources/extensions/gsd/auto-supervisor.ts +2 -7
- package/src/resources/extensions/gsd/auto-worktree.ts +53 -106
- package/src/resources/extensions/gsd/auto.ts +28 -12
- package/src/resources/extensions/gsd/commands.ts +93 -27
- package/src/resources/extensions/gsd/doctor.ts +12 -22
- package/src/resources/extensions/gsd/files.ts +154 -2
- package/src/resources/extensions/gsd/git-self-heal.ts +4 -4
- package/src/resources/extensions/gsd/git-service.ts +23 -23
- package/src/resources/extensions/gsd/gitignore.ts +2 -5
- package/src/resources/extensions/gsd/guided-flow.ts +11 -11
- package/src/resources/extensions/gsd/index.ts +10 -3
- package/src/resources/extensions/gsd/native-git-bridge.ts +843 -12
- package/src/resources/extensions/gsd/native-parser-bridge.ts +128 -1
- package/src/resources/extensions/gsd/paths.ts +89 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +2 -0
- package/src/resources/extensions/gsd/prompts/rewrite-docs.md +32 -0
- package/src/resources/extensions/gsd/prompts/system.md +1 -0
- package/src/resources/extensions/gsd/provider-error-pause.ts +1 -1
- package/src/resources/extensions/gsd/session-forensics.ts +29 -12
- package/src/resources/extensions/gsd/state.ts +1 -38
- package/src/resources/extensions/gsd/tests/agent-end-provider-error.test.ts +2 -2
- package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/integration-mixed-milestones.test.ts +1 -1
- package/src/resources/extensions/gsd/tests/overrides.test.ts +131 -0
- package/src/resources/extensions/gsd/tests/preferences-git.test.ts +5 -5
- package/src/resources/extensions/gsd/tests/preferences-hooks.test.ts +7 -6
- package/src/resources/extensions/gsd/tests/unique-milestone-ids.test.ts +1 -1
- package/src/resources/extensions/gsd/undo.ts +24 -18
- package/src/resources/extensions/gsd/worktree-command.ts +2 -2
- package/src/resources/extensions/gsd/worktree-manager.ts +81 -134
package/README.md
CHANGED
|
@@ -213,6 +213,7 @@ On first run, GSD launches a branded setup wizard that walks you through LLM pro
|
|
|
213
213
|
| `/gsd next` | Explicit step mode (same as bare `/gsd`) |
|
|
214
214
|
| `/gsd auto` | Autonomous mode — researches, plans, executes, commits, repeats |
|
|
215
215
|
| `/gsd stop` | Stop auto mode gracefully |
|
|
216
|
+
| `/gsd steer` | Hard-steer plan documents during execution |
|
|
216
217
|
| `/gsd discuss` | Discuss architecture and decisions (works alongside auto mode) |
|
|
217
218
|
| `/gsd status` | Progress dashboard |
|
|
218
219
|
| `/gsd queue` | Queue future milestones (safe during auto mode) |
|
|
@@ -71,7 +71,7 @@ export function createAsyncBashTool(
|
|
|
71
71
|
"Check /jobs to see all running and recent background jobs.",
|
|
72
72
|
],
|
|
73
73
|
parameters: schema,
|
|
74
|
-
async execute(_toolCallId, params) {
|
|
74
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
75
75
|
const manager = getManager();
|
|
76
76
|
const cwd = getCwd();
|
|
77
77
|
const { command, timeout, label } = params;
|
|
@@ -91,6 +91,7 @@ export function createAsyncBashTool(
|
|
|
91
91
|
"Use `await_job` to get results when ready, or `cancel_job` to stop.",
|
|
92
92
|
].join("\n"),
|
|
93
93
|
}],
|
|
94
|
+
details: undefined,
|
|
94
95
|
};
|
|
95
96
|
},
|
|
96
97
|
};
|
|
@@ -24,7 +24,7 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
|
|
|
24
24
|
description:
|
|
25
25
|
"Wait for background jobs to complete. Provide specific job IDs or omit to wait for the next job that finishes. Returns results of completed jobs.",
|
|
26
26
|
parameters: schema,
|
|
27
|
-
async execute(_toolCallId, params) {
|
|
27
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
28
28
|
const manager = getManager();
|
|
29
29
|
const { jobs: jobIds } = params;
|
|
30
30
|
|
|
@@ -43,6 +43,7 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
|
|
|
43
43
|
if (notFound.length > 0 && watched.length === 0) {
|
|
44
44
|
return {
|
|
45
45
|
content: [{ type: "text", text: `No jobs found: ${notFound.join(", ")}` }],
|
|
46
|
+
details: undefined,
|
|
46
47
|
};
|
|
47
48
|
}
|
|
48
49
|
} else {
|
|
@@ -50,6 +51,7 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
|
|
|
50
51
|
if (watched.length === 0) {
|
|
51
52
|
return {
|
|
52
53
|
content: [{ type: "text", text: "No running background jobs." }],
|
|
54
|
+
details: undefined,
|
|
53
55
|
};
|
|
54
56
|
}
|
|
55
57
|
}
|
|
@@ -59,7 +61,7 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
|
|
|
59
61
|
if (running.length === 0) {
|
|
60
62
|
const result = formatResults(watched);
|
|
61
63
|
manager.acknowledgeDeliveries(watched.map((j) => j.id));
|
|
62
|
-
return { content: [{ type: "text", text: result }] };
|
|
64
|
+
return { content: [{ type: "text", text: result }], details: undefined };
|
|
63
65
|
}
|
|
64
66
|
|
|
65
67
|
// Wait for at least one to complete
|
|
@@ -75,7 +77,7 @@ export function createAwaitTool(getManager: () => AsyncJobManager): ToolDefiniti
|
|
|
75
77
|
result += `\n\n**Still running:** ${stillRunning.map((j) => `${j.id} (${j.label})`).join(", ")}`;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
return { content: [{ type: "text", text: result }] };
|
|
80
|
+
return { content: [{ type: "text", text: result }], details: undefined };
|
|
79
81
|
},
|
|
80
82
|
};
|
|
81
83
|
}
|
|
@@ -16,7 +16,7 @@ export function createCancelJobTool(getManager: () => AsyncJobManager): ToolDefi
|
|
|
16
16
|
label: "Cancel Background Job",
|
|
17
17
|
description: "Cancel a running background job by its ID.",
|
|
18
18
|
parameters: schema,
|
|
19
|
-
async execute(_toolCallId, params) {
|
|
19
|
+
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
20
20
|
const manager = getManager();
|
|
21
21
|
const result = manager.cancel(params.job_id);
|
|
22
22
|
|
|
@@ -28,6 +28,7 @@ export function createCancelJobTool(getManager: () => AsyncJobManager): ToolDefi
|
|
|
28
28
|
|
|
29
29
|
return {
|
|
30
30
|
content: [{ type: "text", text: messages[result] ?? `Unknown result: ${result}` }],
|
|
31
|
+
details: undefined,
|
|
31
32
|
};
|
|
32
33
|
},
|
|
33
34
|
};
|
|
@@ -62,7 +62,7 @@ export default function AsyncJobs(pi: ExtensionAPI) {
|
|
|
62
62
|
"",
|
|
63
63
|
truncatedOutput,
|
|
64
64
|
].join("\n"),
|
|
65
|
-
display:
|
|
65
|
+
display: true,
|
|
66
66
|
},
|
|
67
67
|
{ deliverAs: "followUp", triggerTurn: true },
|
|
68
68
|
);
|
|
@@ -92,7 +92,7 @@ export default function AsyncJobs(pi: ExtensionAPI) {
|
|
|
92
92
|
pi.sendMessage({
|
|
93
93
|
customType: "async_jobs_list",
|
|
94
94
|
content: "No async job manager active.",
|
|
95
|
-
display:
|
|
95
|
+
display: true,
|
|
96
96
|
});
|
|
97
97
|
return;
|
|
98
98
|
}
|
|
@@ -126,7 +126,7 @@ export default function AsyncJobs(pi: ExtensionAPI) {
|
|
|
126
126
|
pi.sendMessage({
|
|
127
127
|
customType: "async_jobs_list",
|
|
128
128
|
content: lines.join("\n"),
|
|
129
|
-
display:
|
|
129
|
+
display: true,
|
|
130
130
|
});
|
|
131
131
|
},
|
|
132
132
|
});
|
|
@@ -49,6 +49,7 @@ export function unitVerb(unitType: string): string {
|
|
|
49
49
|
case "execute-task": return "executing";
|
|
50
50
|
case "complete-slice": return "completing";
|
|
51
51
|
case "replan-slice": return "replanning";
|
|
52
|
+
case "rewrite-docs": return "rewriting";
|
|
52
53
|
case "reassess-roadmap": return "reassessing";
|
|
53
54
|
case "run-uat": return "running UAT";
|
|
54
55
|
default: return unitType;
|
|
@@ -65,6 +66,7 @@ export function unitPhaseLabel(unitType: string): string {
|
|
|
65
66
|
case "execute-task": return "EXECUTE";
|
|
66
67
|
case "complete-slice": return "COMPLETE";
|
|
67
68
|
case "replan-slice": return "REPLAN";
|
|
69
|
+
case "rewrite-docs": return "REWRITE";
|
|
68
70
|
case "reassess-roadmap": return "REASSESS";
|
|
69
71
|
case "run-uat": return "UAT";
|
|
70
72
|
default: return unitType.toUpperCase();
|
|
@@ -88,6 +90,7 @@ function peekNext(unitType: string, state: GSDState): string {
|
|
|
88
90
|
case "execute-task": return `continue ${sid}`;
|
|
89
91
|
case "complete-slice": return "reassess roadmap";
|
|
90
92
|
case "replan-slice": return `re-execute ${sid}`;
|
|
93
|
+
case "rewrite-docs": return "continue execution";
|
|
91
94
|
case "reassess-roadmap": return "advance to next slice";
|
|
92
95
|
case "run-uat": return "reassess roadmap";
|
|
93
96
|
default: return "";
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import type { GSDState } from "./types.js";
|
|
13
13
|
import type { GSDPreferences } from "./preferences.js";
|
|
14
14
|
import type { UatType } from "./files.js";
|
|
15
|
-
import { loadFile, extractUatType } from "./files.js";
|
|
15
|
+
import { loadFile, extractUatType, loadActiveOverrides } from "./files.js";
|
|
16
16
|
import {
|
|
17
17
|
resolveMilestoneFile, resolveSliceFile,
|
|
18
18
|
relSliceFile,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
buildReplanSlicePrompt,
|
|
29
29
|
buildRunUatPrompt,
|
|
30
30
|
buildReassessRoadmapPrompt,
|
|
31
|
+
buildRewriteDocsPrompt,
|
|
31
32
|
checkNeedsReassessment,
|
|
32
33
|
checkNeedsRunUat,
|
|
33
34
|
} from "./auto-prompts.js";
|
|
@@ -54,9 +55,38 @@ interface DispatchRule {
|
|
|
54
55
|
match: (ctx: DispatchContext) => Promise<DispatchAction | null>;
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
// ─── Rewrite Circuit Breaker ──────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
const MAX_REWRITE_ATTEMPTS = 3;
|
|
61
|
+
let rewriteAttemptCount = 0;
|
|
62
|
+
export function resetRewriteCircuitBreaker(): void {
|
|
63
|
+
rewriteAttemptCount = 0;
|
|
64
|
+
}
|
|
65
|
+
|
|
57
66
|
// ─── Rules ────────────────────────────────────────────────────────────────
|
|
58
67
|
|
|
59
68
|
const DISPATCH_RULES: DispatchRule[] = [
|
|
69
|
+
{
|
|
70
|
+
name: "rewrite-docs (override gate)",
|
|
71
|
+
match: async ({ mid, midTitle, state, basePath }) => {
|
|
72
|
+
const pendingOverrides = await loadActiveOverrides(basePath);
|
|
73
|
+
if (pendingOverrides.length === 0) return null;
|
|
74
|
+
if (rewriteAttemptCount >= MAX_REWRITE_ATTEMPTS) {
|
|
75
|
+
const { resolveAllOverrides } = await import("./files.js");
|
|
76
|
+
await resolveAllOverrides(basePath);
|
|
77
|
+
rewriteAttemptCount = 0;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
rewriteAttemptCount++;
|
|
81
|
+
const unitId = state.activeSlice ? `${mid}/${state.activeSlice.id}` : mid;
|
|
82
|
+
return {
|
|
83
|
+
action: "dispatch",
|
|
84
|
+
unitType: "rewrite-docs",
|
|
85
|
+
unitId,
|
|
86
|
+
prompt: await buildRewriteDocsPrompt(mid, midTitle, state.activeSlice, basePath, pendingOverrides),
|
|
87
|
+
};
|
|
88
|
+
},
|
|
89
|
+
},
|
|
60
90
|
{
|
|
61
91
|
name: "summarizing → complete-slice",
|
|
62
92
|
match: async ({ state, mid, midTitle, basePath }) => {
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* utility.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { loadFile, parseContinue, parseRoadmap, parseSummary, extractUatType } from "./files.js";
|
|
10
|
-
import type { UatType } from "./files.js";
|
|
9
|
+
import { loadFile, parseContinue, parsePlan, parseRoadmap, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection } from "./files.js";
|
|
10
|
+
import type { Override, UatType } from "./files.js";
|
|
11
11
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
12
12
|
import {
|
|
13
13
|
resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
@@ -457,6 +457,9 @@ export async function buildResearchSlicePrompt(
|
|
|
457
457
|
inlined.push(inlineTemplate("research", "Research"));
|
|
458
458
|
|
|
459
459
|
const depContent = await inlineDependencySummaries(mid, sid, base);
|
|
460
|
+
const activeOverrides = await loadActiveOverrides(base);
|
|
461
|
+
const overridesInline = formatOverridesSection(activeOverrides);
|
|
462
|
+
if (overridesInline) inlined.unshift(overridesInline);
|
|
460
463
|
|
|
461
464
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
462
465
|
|
|
@@ -495,6 +498,9 @@ export async function buildPlanSlicePrompt(
|
|
|
495
498
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
496
499
|
|
|
497
500
|
const depContent = await inlineDependencySummaries(mid, sid, base);
|
|
501
|
+
const planActiveOverrides = await loadActiveOverrides(base);
|
|
502
|
+
const planOverridesInline = formatOverridesSection(planActiveOverrides);
|
|
503
|
+
if (planOverridesInline) inlined.unshift(planOverridesInline);
|
|
498
504
|
|
|
499
505
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
500
506
|
|
|
@@ -562,7 +568,11 @@ export async function buildExecuteTaskPrompt(
|
|
|
562
568
|
|
|
563
569
|
const taskSummaryPath = `${relSlicePath(base, mid, sid)}/tasks/${tid}-SUMMARY.md`;
|
|
564
570
|
|
|
571
|
+
const activeOverrides = await loadActiveOverrides(base);
|
|
572
|
+
const overridesSection = formatOverridesSection(activeOverrides);
|
|
573
|
+
|
|
565
574
|
return loadPrompt("execute-task", {
|
|
575
|
+
overridesSection,
|
|
566
576
|
workingDirectory: base,
|
|
567
577
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
|
|
568
578
|
planPath: relSliceFile(base, mid, sid, "PLAN"),
|
|
@@ -609,6 +619,9 @@ export async function buildCompleteSlicePrompt(
|
|
|
609
619
|
}
|
|
610
620
|
inlined.push(inlineTemplate("slice-summary", "Slice Summary"));
|
|
611
621
|
inlined.push(inlineTemplate("uat", "UAT"));
|
|
622
|
+
const completeActiveOverrides = await loadActiveOverrides(base);
|
|
623
|
+
const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
|
|
624
|
+
if (completeOverridesInline) inlined.unshift(completeOverridesInline);
|
|
612
625
|
|
|
613
626
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
614
627
|
|
|
@@ -712,6 +725,9 @@ export async function buildReplanSlicePrompt(
|
|
|
712
725
|
// Inline decisions
|
|
713
726
|
const decisionsInline = await inlineGsdRootFile(base, "decisions.md", "Decisions");
|
|
714
727
|
if (decisionsInline) inlined.push(decisionsInline);
|
|
728
|
+
const replanActiveOverrides = await loadActiveOverrides(base);
|
|
729
|
+
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
730
|
+
if (replanOverridesInline) inlined.unshift(replanOverridesInline);
|
|
715
731
|
|
|
716
732
|
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
|
|
717
733
|
|
|
@@ -795,3 +811,70 @@ export async function buildReassessRoadmapPrompt(
|
|
|
795
811
|
inlinedContext,
|
|
796
812
|
});
|
|
797
813
|
}
|
|
814
|
+
|
|
815
|
+
export async function buildRewriteDocsPrompt(
|
|
816
|
+
mid: string, midTitle: string,
|
|
817
|
+
activeSlice: { id: string; title: string } | null,
|
|
818
|
+
base: string,
|
|
819
|
+
overrides: Override[],
|
|
820
|
+
): Promise<string> {
|
|
821
|
+
const sid = activeSlice?.id;
|
|
822
|
+
const sTitle = activeSlice?.title ?? "";
|
|
823
|
+
const docList: string[] = [];
|
|
824
|
+
|
|
825
|
+
if (sid) {
|
|
826
|
+
const slicePlanPath = resolveSliceFile(base, mid, sid, "PLAN");
|
|
827
|
+
const slicePlanRel = relSliceFile(base, mid, sid, "PLAN");
|
|
828
|
+
if (slicePlanPath) {
|
|
829
|
+
docList.push(`- Slice plan: \`${slicePlanRel}\``);
|
|
830
|
+
const tDir = resolveTasksDir(base, mid, sid);
|
|
831
|
+
if (tDir) {
|
|
832
|
+
const planContent = await loadFile(slicePlanPath);
|
|
833
|
+
if (planContent) {
|
|
834
|
+
const plan = parsePlan(planContent);
|
|
835
|
+
for (const task of plan.tasks) {
|
|
836
|
+
if (!task.done) {
|
|
837
|
+
const taskPlanPath = resolveTaskFile(base, mid, sid, task.id, "PLAN");
|
|
838
|
+
if (taskPlanPath) {
|
|
839
|
+
const taskRelPath = `${relSlicePath(base, mid, sid)}/tasks/${task.id}-PLAN.md`;
|
|
840
|
+
docList.push(`- Task plan: \`${taskRelPath}\``);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const decisionsPath = resolveGsdRootFile(base, "DECISIONS");
|
|
850
|
+
if (existsSync(decisionsPath)) docList.push(`- Decisions: \`${relGsdRootFile("DECISIONS")}\``);
|
|
851
|
+
const requirementsPath = resolveGsdRootFile(base, "REQUIREMENTS");
|
|
852
|
+
if (existsSync(requirementsPath)) docList.push(`- Requirements: \`${relGsdRootFile("REQUIREMENTS")}\``);
|
|
853
|
+
const projectPath = resolveGsdRootFile(base, "PROJECT");
|
|
854
|
+
if (existsSync(projectPath)) docList.push(`- Project: \`${relGsdRootFile("PROJECT")}\``);
|
|
855
|
+
const contextPath = resolveMilestoneFile(base, mid, "CONTEXT");
|
|
856
|
+
const contextRel = relMilestoneFile(base, mid, "CONTEXT");
|
|
857
|
+
if (contextPath) docList.push(`- Milestone context (reference only): \`${contextRel}\``);
|
|
858
|
+
const roadmapPath = resolveMilestoneFile(base, mid, "ROADMAP");
|
|
859
|
+
const roadmapRel = relMilestoneFile(base, mid, "ROADMAP");
|
|
860
|
+
if (roadmapPath) docList.push(`- Roadmap: \`${roadmapRel}\``);
|
|
861
|
+
|
|
862
|
+
const overrideContent = overrides.map((o, i) => [
|
|
863
|
+
`### Override ${i + 1}`,
|
|
864
|
+
`**Change:** ${o.change}`,
|
|
865
|
+
`**Issued:** ${o.timestamp}`,
|
|
866
|
+
`**During:** ${o.appliedAt}`,
|
|
867
|
+
].join("\n")).join("\n\n");
|
|
868
|
+
|
|
869
|
+
const documentList = docList.length > 0 ? docList.join("\n") : "- No active plan documents found.";
|
|
870
|
+
|
|
871
|
+
return loadPrompt("rewrite-docs", {
|
|
872
|
+
milestoneId: mid,
|
|
873
|
+
milestoneTitle: midTitle,
|
|
874
|
+
sliceId: sid ?? "none",
|
|
875
|
+
sliceTitle: sTitle,
|
|
876
|
+
overrideContent,
|
|
877
|
+
documentList,
|
|
878
|
+
overridesPath: relGsdRootFile("OVERRIDES"),
|
|
879
|
+
});
|
|
880
|
+
}
|
|
@@ -11,7 +11,14 @@ import type { ExtensionContext } from "@gsd/pi-coding-agent";
|
|
|
11
11
|
import {
|
|
12
12
|
clearUnitRuntimeRecord,
|
|
13
13
|
} from "./unit-runtime.js";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
nativeConflictFiles,
|
|
16
|
+
nativeCommit,
|
|
17
|
+
nativeCheckoutTheirs,
|
|
18
|
+
nativeAddPaths,
|
|
19
|
+
nativeMergeAbort,
|
|
20
|
+
nativeResetHard,
|
|
21
|
+
} from "./native-git-bridge.js";
|
|
15
22
|
import {
|
|
16
23
|
resolveMilestonePath,
|
|
17
24
|
resolveSlicePath,
|
|
@@ -26,6 +33,7 @@ import {
|
|
|
26
33
|
buildTaskFileName,
|
|
27
34
|
resolveMilestoneFile,
|
|
28
35
|
clearPathCache,
|
|
36
|
+
resolveGsdRootFile,
|
|
29
37
|
} from "./paths.js";
|
|
30
38
|
import { parseRoadmap } from "./files.js";
|
|
31
39
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync, renameSync } from "node:fs";
|
|
@@ -78,6 +86,8 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
|
|
|
78
86
|
const dir = resolveMilestonePath(base, mid);
|
|
79
87
|
return dir ? join(dir, buildMilestoneFileName(mid, "SUMMARY")) : null;
|
|
80
88
|
}
|
|
89
|
+
case "rewrite-docs":
|
|
90
|
+
return null;
|
|
81
91
|
default:
|
|
82
92
|
return null;
|
|
83
93
|
}
|
|
@@ -93,13 +103,20 @@ export function resolveExpectedArtifactPath(unitType: string, unitId: string, ba
|
|
|
93
103
|
* skipped writing the UAT file (see #176).
|
|
94
104
|
*/
|
|
95
105
|
export function verifyExpectedArtifact(unitType: string, unitId: string, base: string): boolean {
|
|
96
|
-
// Clear stale directory listing cache so artifact checks see fresh disk state (#431)
|
|
97
|
-
clearPathCache();
|
|
98
|
-
|
|
99
106
|
// Hook units have no standard artifact — always pass. Their lifecycle
|
|
100
107
|
// is managed by the hook engine, not the artifact verification system.
|
|
101
108
|
if (unitType.startsWith("hook/")) return true;
|
|
102
109
|
|
|
110
|
+
// Clear stale directory listing cache so artifact checks see fresh disk state (#431).
|
|
111
|
+
// Moved after hook check to avoid unnecessary cache clears for hook units.
|
|
112
|
+
clearPathCache();
|
|
113
|
+
|
|
114
|
+
if (unitType === "rewrite-docs") {
|
|
115
|
+
const overridesPath = resolveGsdRootFile(base, "OVERRIDES");
|
|
116
|
+
if (!existsSync(overridesPath)) return true;
|
|
117
|
+
const content = readFileSync(overridesPath, "utf-8");
|
|
118
|
+
return !content.includes("**Scope:** active");
|
|
119
|
+
}
|
|
103
120
|
|
|
104
121
|
const absPath = resolveExpectedArtifactPath(unitType, unitId, base);
|
|
105
122
|
// Unit types with no verifiable artifact always pass (e.g. replan-slice).
|
|
@@ -206,6 +223,8 @@ export function diagnoseExpectedArtifact(unitType: string, unitId: string, base:
|
|
|
206
223
|
return `Slice ${sid} marked [x] in ${relMilestoneFile(base, mid!, "ROADMAP")} + summary + UAT written`;
|
|
207
224
|
case "replan-slice":
|
|
208
225
|
return `${relSliceFile(base, mid!, sid!, "REPLAN")} + updated ${relSliceFile(base, mid!, sid!, "PLAN")}`;
|
|
226
|
+
case "rewrite-docs":
|
|
227
|
+
return "Active overrides resolved in .gsd/OVERRIDES.md + plan documents updated";
|
|
209
228
|
case "reassess-roadmap":
|
|
210
229
|
return `${relSliceFile(base, mid!, sid!, "ASSESSMENT")} (roadmap reassessment)`;
|
|
211
230
|
case "run-uat":
|
|
@@ -284,7 +303,8 @@ export function persistCompletedKey(base: string, key: string): void {
|
|
|
284
303
|
keys = JSON.parse(readFileSync(file, "utf-8"));
|
|
285
304
|
}
|
|
286
305
|
} catch (e) { /* corrupt file — start fresh */ void e; }
|
|
287
|
-
|
|
306
|
+
const keySet = new Set(keys);
|
|
307
|
+
if (!keySet.has(key)) {
|
|
288
308
|
keys.push(key);
|
|
289
309
|
// Atomic write: tmp file + rename prevents partial writes on crash
|
|
290
310
|
const tmpFile = file + ".tmp";
|
|
@@ -298,12 +318,15 @@ export function removePersistedKey(base: string, key: string): void {
|
|
|
298
318
|
const file = completedKeysPath(base);
|
|
299
319
|
try {
|
|
300
320
|
if (existsSync(file)) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
//
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
321
|
+
const keys: string[] = JSON.parse(readFileSync(file, "utf-8"));
|
|
322
|
+
const filtered = keys.filter(k => k !== key);
|
|
323
|
+
// Only write if the key was actually present
|
|
324
|
+
if (filtered.length !== keys.length) {
|
|
325
|
+
// Atomic write: tmp file + rename prevents partial writes on crash
|
|
326
|
+
const tmpFile = file + ".tmp";
|
|
327
|
+
writeFileSync(tmpFile, JSON.stringify(filtered), "utf-8");
|
|
328
|
+
renameSync(tmpFile, file);
|
|
329
|
+
}
|
|
307
330
|
}
|
|
308
331
|
} catch (e) { /* non-fatal: removePersistedKey failure */ void e; }
|
|
309
332
|
}
|
|
@@ -335,11 +358,11 @@ export function reconcileMergeState(basePath: string, ctx: ExtensionContext): bo
|
|
|
335
358
|
const hasSquashMsg = existsSync(squashMsgPath);
|
|
336
359
|
if (!hasMergeHead && !hasSquashMsg) return false;
|
|
337
360
|
|
|
338
|
-
const
|
|
339
|
-
if (
|
|
361
|
+
const conflictedFiles = nativeConflictFiles(basePath);
|
|
362
|
+
if (conflictedFiles.length === 0) {
|
|
340
363
|
// All conflicts resolved — finalize the merge/squash commit
|
|
341
364
|
try {
|
|
342
|
-
|
|
365
|
+
nativeCommit(basePath, ""); // --no-edit equivalent: use empty message placeholder
|
|
343
366
|
const mode = hasMergeHead ? "merge" : "squash commit";
|
|
344
367
|
ctx.ui.notify(`Finalized leftover ${mode} from prior session.`, "info");
|
|
345
368
|
} catch {
|
|
@@ -347,25 +370,21 @@ export function reconcileMergeState(basePath: string, ctx: ExtensionContext): bo
|
|
|
347
370
|
}
|
|
348
371
|
} else {
|
|
349
372
|
// Still conflicted — try auto-resolving .gsd/ state file conflicts (#530)
|
|
350
|
-
const conflictedFiles = unmerged.trim().split("\n").filter(Boolean);
|
|
351
373
|
const gsdConflicts = conflictedFiles.filter(f => f.startsWith(".gsd/"));
|
|
352
374
|
const codeConflicts = conflictedFiles.filter(f => !f.startsWith(".gsd/"));
|
|
353
375
|
|
|
354
376
|
if (gsdConflicts.length > 0 && codeConflicts.length === 0) {
|
|
355
377
|
// All conflicts are in .gsd/ state files — auto-resolve by accepting theirs
|
|
356
378
|
let resolved = true;
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
resolved = false;
|
|
363
|
-
break;
|
|
364
|
-
}
|
|
379
|
+
try {
|
|
380
|
+
nativeCheckoutTheirs(basePath, gsdConflicts);
|
|
381
|
+
nativeAddPaths(basePath, gsdConflicts);
|
|
382
|
+
} catch {
|
|
383
|
+
resolved = false;
|
|
365
384
|
}
|
|
366
385
|
if (resolved) {
|
|
367
386
|
try {
|
|
368
|
-
|
|
387
|
+
nativeCommit(basePath, "chore: auto-resolve .gsd/ state file conflicts");
|
|
369
388
|
ctx.ui.notify(
|
|
370
389
|
`Auto-resolved ${gsdConflicts.length} .gsd/ state file conflict(s) from prior merge.`,
|
|
371
390
|
"info",
|
|
@@ -376,11 +395,11 @@ export function reconcileMergeState(basePath: string, ctx: ExtensionContext): bo
|
|
|
376
395
|
}
|
|
377
396
|
if (!resolved) {
|
|
378
397
|
if (hasMergeHead) {
|
|
379
|
-
|
|
398
|
+
try { nativeMergeAbort(basePath); } catch { /* best-effort */ }
|
|
380
399
|
} else if (hasSquashMsg) {
|
|
381
400
|
try { unlinkSync(squashMsgPath); } catch { /* best-effort */ }
|
|
382
401
|
}
|
|
383
|
-
|
|
402
|
+
try { nativeResetHard(basePath); } catch { /* best-effort */ }
|
|
384
403
|
ctx.ui.notify(
|
|
385
404
|
"Detected leftover merge state — auto-resolve failed, cleaned up. Re-deriving state.",
|
|
386
405
|
"warning",
|
|
@@ -389,11 +408,11 @@ export function reconcileMergeState(basePath: string, ctx: ExtensionContext): bo
|
|
|
389
408
|
} else {
|
|
390
409
|
// Code conflicts present — abort and reset
|
|
391
410
|
if (hasMergeHead) {
|
|
392
|
-
|
|
411
|
+
try { nativeMergeAbort(basePath); } catch { /* best-effort */ }
|
|
393
412
|
} else if (hasSquashMsg) {
|
|
394
413
|
try { unlinkSync(squashMsgPath); } catch { /* best-effort */ }
|
|
395
414
|
}
|
|
396
|
-
|
|
415
|
+
try { nativeResetHard(basePath); } catch { /* best-effort */ }
|
|
397
416
|
ctx.ui.notify(
|
|
398
417
|
"Detected leftover merge state with unresolved conflicts — cleaned up. Re-deriving state.",
|
|
399
418
|
"warning",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { clearLock } from "./crash-recovery.js";
|
|
8
|
-
import {
|
|
8
|
+
import { nativeHasChanges } from "./native-git-bridge.js";
|
|
9
9
|
|
|
10
10
|
// ─── SIGTERM Handling ─────────────────────────────────────────────────────────
|
|
11
11
|
|
|
@@ -47,12 +47,7 @@ export function deregisterSigtermHandler(handler: (() => void) | null): void {
|
|
|
47
47
|
*/
|
|
48
48
|
export function detectWorkingTreeActivity(cwd: string): boolean {
|
|
49
49
|
try {
|
|
50
|
-
|
|
51
|
-
cwd,
|
|
52
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
53
|
-
timeout: 5000,
|
|
54
|
-
});
|
|
55
|
-
return out.toString().trim().length > 0;
|
|
50
|
+
return nativeHasChanges(cwd);
|
|
56
51
|
} catch {
|
|
57
52
|
return false;
|
|
58
53
|
}
|