gsd-pi 2.38.0-dev.63ad7e5 → 2.38.0-dev.7209774
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/resource-loader.js +34 -1
- package/dist/resources/extensions/browser-tools/index.js +3 -1
- package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-loop.js +593 -516
- package/dist/resources/extensions/gsd/auto-post-unit.js +28 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +26 -15
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/commands.js +2 -1
- package/dist/resources/extensions/gsd/doctor.js +20 -1
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/files.js +4 -0
- package/dist/resources/extensions/gsd/git-service.js +30 -12
- package/dist/resources/extensions/gsd/guided-flow.js +82 -32
- package/dist/resources/extensions/gsd/index.js +22 -19
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- package/dist/resources/extensions/gsd/paths.js +3 -0
- package/dist/resources/extensions/gsd/preferences-types.js +1 -0
- package/dist/resources/extensions/gsd/preferences-validation.js +58 -0
- package/dist/resources/extensions/gsd/preferences.js +3 -0
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/package.json +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/src/resources/extensions/browser-tools/index.ts +3 -0
- package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-loop.ts +472 -434
- package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +29 -15
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/commands.ts +2 -2
- package/src/resources/extensions/gsd/doctor.ts +22 -1
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/files.ts +3 -1
- package/src/resources/extensions/gsd/git-service.ts +44 -10
- package/src/resources/extensions/gsd/guided-flow.ts +110 -38
- package/src/resources/extensions/gsd/index.ts +21 -16
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/paths.ts +4 -0
- package/src/resources/extensions/gsd/preferences-types.ts +4 -0
- package/src/resources/extensions/gsd/preferences-validation.ts +50 -0
- package/src/resources/extensions/gsd/preferences.ts +3 -0
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
- package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/templates/runtime.md +21 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +111 -37
- package/src/resources/extensions/gsd/types.ts +8 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
- package/src/resources/extensions/mcp-client/index.ts +17 -1
|
@@ -121,11 +121,21 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
121
121
|
const summaryContent = await loadFile(summaryPath);
|
|
122
122
|
if (summaryContent) {
|
|
123
123
|
const summary = parseSummary(summaryContent);
|
|
124
|
+
// Look up GitHub issue number for commit linking
|
|
125
|
+
let ghIssueNumber: number | undefined;
|
|
126
|
+
try {
|
|
127
|
+
const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
|
|
128
|
+
ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined;
|
|
129
|
+
} catch {
|
|
130
|
+
// GitHub sync not available — skip
|
|
131
|
+
}
|
|
132
|
+
|
|
124
133
|
taskContext = {
|
|
125
134
|
taskId: `${sid}/${tid}`,
|
|
126
135
|
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
127
136
|
oneLiner: summary.oneLiner || undefined,
|
|
128
137
|
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
138
|
+
issueNumber: ghIssueNumber,
|
|
129
139
|
};
|
|
130
140
|
}
|
|
131
141
|
} catch (e) {
|
|
@@ -143,6 +153,14 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
143
153
|
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
144
154
|
}
|
|
145
155
|
|
|
156
|
+
// GitHub sync (non-blocking, opt-in)
|
|
157
|
+
try {
|
|
158
|
+
const { runGitHubSync } = await import("../github-sync/sync.js");
|
|
159
|
+
await runGitHubSync(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
160
|
+
} catch (e) {
|
|
161
|
+
debugLog("postUnit", { phase: "github-sync", error: String(e) });
|
|
162
|
+
}
|
|
163
|
+
|
|
146
164
|
// Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
|
|
147
165
|
if (!opts?.skipDoctor) try {
|
|
148
166
|
const scopeParts = s.currentUnit.id.split("/").slice(0, 2);
|
|
@@ -154,13 +172,20 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
154
172
|
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
155
173
|
}
|
|
156
174
|
|
|
157
|
-
// Proactive health tracking
|
|
158
|
-
|
|
175
|
+
// Proactive health tracking — filter to current milestone to avoid
|
|
176
|
+
// cross-milestone stale errors inflating the escalation counter
|
|
177
|
+
const currentMilestoneId = s.currentUnit.id.split("/")[0];
|
|
178
|
+
const milestoneIssues = currentMilestoneId
|
|
179
|
+
? report.issues.filter(i =>
|
|
180
|
+
i.unitId === currentMilestoneId ||
|
|
181
|
+
i.unitId.startsWith(`${currentMilestoneId}/`))
|
|
182
|
+
: report.issues;
|
|
183
|
+
const summary = summarizeDoctorIssues(milestoneIssues);
|
|
159
184
|
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
160
185
|
|
|
161
186
|
// Check if we should escalate to LLM-assisted heal
|
|
162
187
|
if (summary.errors > 0) {
|
|
163
|
-
const unresolvedErrors =
|
|
188
|
+
const unresolvedErrors = milestoneIssues
|
|
164
189
|
.filter(i => i.severity === "error" && !i.fixable)
|
|
165
190
|
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
166
191
|
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
@@ -176,6 +201,7 @@ export async function postUnitPreVerification(pctx: PostUnitContext, opts?: PreV
|
|
|
176
201
|
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
177
202
|
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
178
203
|
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
204
|
+
return "dispatched";
|
|
179
205
|
} catch (e) {
|
|
180
206
|
debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
|
|
181
207
|
}
|
|
@@ -13,16 +13,25 @@ import {
|
|
|
13
13
|
resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
14
14
|
resolveTasksDir, resolveTaskFiles, resolveTaskFile,
|
|
15
15
|
relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath,
|
|
16
|
-
resolveGsdRootFile, relGsdRootFile,
|
|
16
|
+
resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile,
|
|
17
17
|
} from "./paths.js";
|
|
18
18
|
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
19
19
|
import type { GSDState, InlineLevel } from "./types.js";
|
|
20
20
|
import type { GSDPreferences } from "./preferences.js";
|
|
21
21
|
import { join } from "node:path";
|
|
22
22
|
import { existsSync } from "node:fs";
|
|
23
|
-
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
23
|
+
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
24
24
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
25
25
|
|
|
26
|
+
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const MAX_PREAMBLE_CHARS = 30_000;
|
|
29
|
+
|
|
30
|
+
function capPreamble(preamble: string): string {
|
|
31
|
+
if (preamble.length <= MAX_PREAMBLE_CHARS) return preamble;
|
|
32
|
+
return truncateAtSectionBoundary(preamble, MAX_PREAMBLE_CHARS).content;
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
27
36
|
|
|
28
37
|
/**
|
|
@@ -157,7 +166,6 @@ export async function inlineFileSmart(
|
|
|
157
166
|
}
|
|
158
167
|
|
|
159
168
|
// For large files, truncate at section boundary
|
|
160
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
161
169
|
const truncated = truncateAtSectionBoundary(content, threshold).content;
|
|
162
170
|
return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
|
|
163
171
|
}
|
|
@@ -193,7 +201,6 @@ export async function inlineDependencySummaries(
|
|
|
193
201
|
|
|
194
202
|
const result = sections.join("\n\n");
|
|
195
203
|
if (budgetChars !== undefined && result.length > budgetChars) {
|
|
196
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
197
204
|
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
198
205
|
}
|
|
199
206
|
return result;
|
|
@@ -611,7 +618,7 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
|
|
|
611
618
|
if (knowledgeInlineRM) inlined.push(knowledgeInlineRM);
|
|
612
619
|
inlined.push(inlineTemplate("research", "Research"));
|
|
613
620
|
|
|
614
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
621
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
615
622
|
|
|
616
623
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
617
624
|
return loadPrompt("research-milestone", {
|
|
@@ -661,7 +668,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
661
668
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
662
669
|
}
|
|
663
670
|
|
|
664
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
671
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
665
672
|
|
|
666
673
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
667
674
|
const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
|
|
@@ -710,7 +717,7 @@ export async function buildResearchSlicePrompt(
|
|
|
710
717
|
const overridesInline = formatOverridesSection(activeOverrides);
|
|
711
718
|
if (overridesInline) inlined.unshift(overridesInline);
|
|
712
719
|
|
|
713
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
720
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
714
721
|
|
|
715
722
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
716
723
|
return loadPrompt("research-slice", {
|
|
@@ -758,7 +765,7 @@ export async function buildPlanSlicePrompt(
|
|
|
758
765
|
const planOverridesInline = formatOverridesSection(planActiveOverrides);
|
|
759
766
|
if (planOverridesInline) inlined.unshift(planOverridesInline);
|
|
760
767
|
|
|
761
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
768
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
762
769
|
|
|
763
770
|
// Build executor context constraints from the budget engine
|
|
764
771
|
const executorContextConstraints = formatExecutorConstraints();
|
|
@@ -881,12 +888,19 @@ export async function buildExecuteTaskPrompt(
|
|
|
881
888
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
882
889
|
let finalCarryForward = carryForwardSection;
|
|
883
890
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
884
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
885
891
|
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
886
892
|
}
|
|
887
893
|
|
|
894
|
+
// Inline RUNTIME.md if present
|
|
895
|
+
const runtimePath = resolveRuntimeFile(base);
|
|
896
|
+
const runtimeContent = existsSync(runtimePath) ? await loadFile(runtimePath) : null;
|
|
897
|
+
const runtimeContext = runtimeContent
|
|
898
|
+
? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
|
|
899
|
+
: "";
|
|
900
|
+
|
|
888
901
|
return loadPrompt("execute-task", {
|
|
889
902
|
overridesSection,
|
|
903
|
+
runtimeContext,
|
|
890
904
|
workingDirectory: base,
|
|
891
905
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
|
|
892
906
|
planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
|
|
@@ -945,7 +959,7 @@ export async function buildCompleteSlicePrompt(
|
|
|
945
959
|
const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
|
|
946
960
|
if (completeOverridesInline) inlined.unshift(completeOverridesInline);
|
|
947
961
|
|
|
948
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
962
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
949
963
|
|
|
950
964
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
951
965
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
@@ -1004,7 +1018,7 @@ export async function buildCompleteMilestonePrompt(
|
|
|
1004
1018
|
if (contextInline) inlined.push(contextInline);
|
|
1005
1019
|
inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
|
|
1006
1020
|
|
|
1007
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1021
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1008
1022
|
|
|
1009
1023
|
const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
|
|
1010
1024
|
|
|
@@ -1075,7 +1089,7 @@ export async function buildValidateMilestonePrompt(
|
|
|
1075
1089
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
1076
1090
|
if (contextInline) inlined.push(contextInline);
|
|
1077
1091
|
|
|
1078
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1092
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1079
1093
|
|
|
1080
1094
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
1081
1095
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
@@ -1129,7 +1143,7 @@ export async function buildReplanSlicePrompt(
|
|
|
1129
1143
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
1130
1144
|
if (replanOverridesInline) inlined.unshift(replanOverridesInline);
|
|
1131
1145
|
|
|
1132
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1146
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1133
1147
|
|
|
1134
1148
|
const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
|
|
1135
1149
|
|
|
@@ -1177,7 +1191,7 @@ export async function buildRunUatPrompt(
|
|
|
1177
1191
|
const projectInline = await inlineProjectFromDb(base);
|
|
1178
1192
|
if (projectInline) inlined.push(projectInline);
|
|
1179
1193
|
|
|
1180
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1194
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1181
1195
|
|
|
1182
1196
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
|
|
1183
1197
|
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
@@ -1216,7 +1230,7 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1216
1230
|
const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
1217
1231
|
if (knowledgeInlineRA) inlined.push(knowledgeInlineRA);
|
|
1218
1232
|
|
|
1219
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1233
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1220
1234
|
|
|
1221
1235
|
const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
|
|
1222
1236
|
|
|
@@ -37,13 +37,13 @@ import {
|
|
|
37
37
|
resolveGitHeadPath,
|
|
38
38
|
nudgeGitBranchCache,
|
|
39
39
|
} from "./worktree.js";
|
|
40
|
-
import { MergeConflictError, readIntegrationBranch } from "./git-service.js";
|
|
40
|
+
import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
41
41
|
import { parseRoadmap } from "./files.js";
|
|
42
42
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
43
43
|
import {
|
|
44
44
|
nativeGetCurrentBranch,
|
|
45
45
|
nativeWorkingTreeStatus,
|
|
46
|
-
|
|
46
|
+
nativeAddAllWithExclusions,
|
|
47
47
|
nativeCommit,
|
|
48
48
|
nativeCheckoutBranch,
|
|
49
49
|
nativeMergeSquash,
|
|
@@ -768,7 +768,7 @@ function autoCommitDirtyState(cwd: string): boolean {
|
|
|
768
768
|
try {
|
|
769
769
|
const status = nativeWorkingTreeStatus(cwd);
|
|
770
770
|
if (!status) return false;
|
|
771
|
-
|
|
771
|
+
nativeAddAllWithExclusions(cwd, RUNTIME_EXCLUSION_PATHS);
|
|
772
772
|
const result = nativeCommit(
|
|
773
773
|
cwd,
|
|
774
774
|
"chore: auto-commit before milestone merge",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* One command, one wizard. Routes to smart entry or status.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type
|
|
7
|
+
import { importExtensionModule, type ExtensionAPI, type ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
8
8
|
import type { GSDState } from "./types.js";
|
|
9
9
|
import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
10
10
|
import { homedir } from "node:os";
|
|
@@ -585,7 +585,7 @@ export async function handleGSDCommand(
|
|
|
585
585
|
}
|
|
586
586
|
|
|
587
587
|
if (trimmed === "widget" || trimmed.startsWith("widget ")) {
|
|
588
|
-
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import("./auto-dashboard.js");
|
|
588
|
+
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await importExtensionModule<typeof import("./auto-dashboard.js")>(import.meta.url, "./auto-dashboard.js");
|
|
589
589
|
const arg = trimmed.replace(/^widget\s*/, "").trim();
|
|
590
590
|
if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
|
|
591
591
|
setWidgetMode(arg);
|
|
@@ -280,9 +280,24 @@ async function markSliceDoneInRoadmap(basePath: string, milestoneId: string, sli
|
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
async function markSliceUndoneInRoadmap(basePath: string, milestoneId: string, sliceId: string, fixesApplied: string[]): Promise<void> {
|
|
284
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
285
|
+
if (!roadmapPath) return;
|
|
286
|
+
const content = await loadFile(roadmapPath);
|
|
287
|
+
if (!content) return;
|
|
288
|
+
const updated = content.replace(
|
|
289
|
+
new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"),
|
|
290
|
+
`$1[ ] **${sliceId}:`,
|
|
291
|
+
);
|
|
292
|
+
if (updated !== content) {
|
|
293
|
+
await saveFile(roadmapPath, updated);
|
|
294
|
+
fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
283
298
|
function matchesScope(unitId: string, scope?: string): boolean {
|
|
284
299
|
if (!scope) return true;
|
|
285
|
-
return unitId === scope || unitId.startsWith(`${scope}/`)
|
|
300
|
+
return unitId === scope || unitId.startsWith(`${scope}/`);
|
|
286
301
|
}
|
|
287
302
|
|
|
288
303
|
function auditRequirements(content: string | null): DoctorIssue[] {
|
|
@@ -863,6 +878,12 @@ export async function runGSDDoctor(basePath: string, options?: { fix?: boolean;
|
|
|
863
878
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
864
879
|
fixable: true,
|
|
865
880
|
});
|
|
881
|
+
if (!allTasksDone) {
|
|
882
|
+
dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
|
|
883
|
+
if (shouldFix("slice_checked_missing_summary")) {
|
|
884
|
+
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
866
887
|
}
|
|
867
888
|
|
|
868
889
|
if (slice.done && !hasSliceUat) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { importExtensionModule, type ExtensionAPI, type ExtensionCommandContext } from "@gsd/pi-coding-agent";
|
|
2
2
|
|
|
3
3
|
type StopAutoFn = (ctx: ExtensionCommandContext, pi: ExtensionAPI, reason?: string) => Promise<void>;
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ export function registerExitCommand(
|
|
|
10
10
|
description: "Exit GSD gracefully",
|
|
11
11
|
handler: async (_args: string, ctx: ExtensionCommandContext) => {
|
|
12
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;
|
|
13
|
+
const stopAuto = deps.stopAuto ?? (await importExtensionModule<typeof import("./auto.js")>(import.meta.url, "./auto.js")).stopAuto;
|
|
14
14
|
await stopAuto(ctx, pi, "Graceful exit");
|
|
15
15
|
ctx.shutdown();
|
|
16
16
|
},
|
|
@@ -775,7 +775,7 @@ export function parseTaskPlanIO(content: string): { inputFiles: string[]; output
|
|
|
775
775
|
* The four UAT classification types recognised by GSD auto-mode.
|
|
776
776
|
* `undefined` is returned (not this union) when no type can be determined.
|
|
777
777
|
*/
|
|
778
|
-
export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed';
|
|
778
|
+
export type UatType = 'artifact-driven' | 'live-runtime' | 'human-experience' | 'mixed' | 'browser-executable' | 'runtime-executable';
|
|
779
779
|
|
|
780
780
|
/**
|
|
781
781
|
* Extract the UAT type from a UAT file's raw content.
|
|
@@ -799,6 +799,8 @@ export function extractUatType(content: string): UatType | undefined {
|
|
|
799
799
|
const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
|
|
800
800
|
|
|
801
801
|
if (rawValue.startsWith('artifact-driven')) return 'artifact-driven';
|
|
802
|
+
if (rawValue.startsWith('browser-executable')) return 'browser-executable';
|
|
803
|
+
if (rawValue.startsWith('runtime-executable')) return 'runtime-executable';
|
|
802
804
|
if (rawValue.startsWith('live-runtime')) return 'live-runtime';
|
|
803
805
|
if (rawValue.startsWith('human-experience')) return 'human-experience';
|
|
804
806
|
if (rawValue.startsWith('mixed')) return 'mixed';
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
nativeDetectMainBranch,
|
|
25
25
|
nativeBranchExists,
|
|
26
26
|
nativeHasChanges,
|
|
27
|
-
|
|
27
|
+
nativeAddAllWithExclusions,
|
|
28
28
|
nativeResetPaths,
|
|
29
29
|
nativeHasStagedChanges,
|
|
30
30
|
nativeCommit,
|
|
@@ -95,6 +95,8 @@ export interface TaskCommitContext {
|
|
|
95
95
|
oneLiner?: string;
|
|
96
96
|
/** Files modified by this task (from task summary frontmatter) */
|
|
97
97
|
keyFiles?: string[];
|
|
98
|
+
/** GitHub issue number — appends "Resolves #N" trailer when set. */
|
|
99
|
+
issueNumber?: number;
|
|
98
100
|
}
|
|
99
101
|
|
|
100
102
|
/**
|
|
@@ -118,12 +120,22 @@ export function buildTaskCommitMessage(ctx: TaskCommitContext): string {
|
|
|
118
120
|
const subject = `${type}(${scope}): ${truncated}`;
|
|
119
121
|
|
|
120
122
|
// Build body with key files if available
|
|
123
|
+
const bodyParts: string[] = [];
|
|
124
|
+
|
|
121
125
|
if (ctx.keyFiles && ctx.keyFiles.length > 0) {
|
|
122
126
|
const fileLines = ctx.keyFiles
|
|
123
127
|
.slice(0, 8) // cap at 8 files to keep commit concise
|
|
124
128
|
.map(f => `- ${f}`)
|
|
125
129
|
.join("\n");
|
|
126
|
-
|
|
130
|
+
bodyParts.push(fileLines);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (ctx.issueNumber) {
|
|
134
|
+
bodyParts.push(`Resolves #${ctx.issueNumber}`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (bodyParts.length > 0) {
|
|
138
|
+
return `${subject}\n\n${bodyParts.join("\n\n")}`;
|
|
127
139
|
}
|
|
128
140
|
|
|
129
141
|
return subject;
|
|
@@ -373,7 +385,9 @@ export class GitServiceImpl {
|
|
|
373
385
|
this._runtimeFilesCleanedUp = true;
|
|
374
386
|
}
|
|
375
387
|
|
|
376
|
-
// Stage everything
|
|
388
|
+
// Stage everything using pathspec exclusions so excluded paths are never
|
|
389
|
+
// hashed by git. The old approach of `git add -A` followed by unstaging
|
|
390
|
+
// hangs indefinitely on repos with large untracked artifact trees (#1605).
|
|
377
391
|
//
|
|
378
392
|
// Exclude only RUNTIME paths from staging — not the entire .gsd/ directory.
|
|
379
393
|
// When .gsd/milestones/ files are already tracked in the index (projects
|
|
@@ -383,13 +397,9 @@ export class GitServiceImpl {
|
|
|
383
397
|
// the second half of a milestone's artifacts are never committed (#1326).
|
|
384
398
|
//
|
|
385
399
|
// If .gsd/ IS in .gitignore (the default for external state projects),
|
|
386
|
-
// git add -A already skips it and the
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const runtimeExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
390
|
-
for (const exclusion of runtimeExclusions) {
|
|
391
|
-
try { nativeResetPaths(this.basePath, [exclusion]); } catch { /* path not staged — ignore */ }
|
|
392
|
-
}
|
|
400
|
+
// git add -A already skips it and the exclusions are harmless no-ops.
|
|
401
|
+
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
402
|
+
nativeAddAllWithExclusions(this.basePath, allExclusions);
|
|
393
403
|
}
|
|
394
404
|
|
|
395
405
|
/** Tracks whether runtime file cleanup has run this session. */
|
|
@@ -574,6 +584,30 @@ export class GitServiceImpl {
|
|
|
574
584
|
|
|
575
585
|
}
|
|
576
586
|
|
|
587
|
+
// ─── Draft PR Creation ─────────────────────────────────────────────────────
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* Create a draft pull request for a completed milestone using `gh pr create`.
|
|
591
|
+
* Returns the PR URL on success, or null on failure.
|
|
592
|
+
* Non-fatal: callers should treat failure as best-effort.
|
|
593
|
+
*/
|
|
594
|
+
export function createDraftPR(
|
|
595
|
+
basePath: string,
|
|
596
|
+
milestoneId: string,
|
|
597
|
+
title: string,
|
|
598
|
+
body: string,
|
|
599
|
+
): string | null {
|
|
600
|
+
try {
|
|
601
|
+
const result = execSync(
|
|
602
|
+
`gh pr create --draft --title ${JSON.stringify(title)} --body ${JSON.stringify(body)}`,
|
|
603
|
+
{ cwd: basePath, encoding: "utf8", timeout: 30000, env: GIT_NO_PROMPT_ENV },
|
|
604
|
+
);
|
|
605
|
+
return result.trim();
|
|
606
|
+
} catch {
|
|
607
|
+
return null;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
577
611
|
// ─── Factory ───────────────────────────────────────────────────────────────
|
|
578
612
|
|
|
579
613
|
/** Create a GitServiceImpl with the current effective git preferences. */
|