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
|
@@ -72,11 +72,21 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
72
72
|
const summaryContent = await loadFile(summaryPath);
|
|
73
73
|
if (summaryContent) {
|
|
74
74
|
const summary = parseSummary(summaryContent);
|
|
75
|
+
// Look up GitHub issue number for commit linking
|
|
76
|
+
let ghIssueNumber;
|
|
77
|
+
try {
|
|
78
|
+
const { getTaskIssueNumberForCommit } = await import("../github-sync/sync.js");
|
|
79
|
+
ghIssueNumber = getTaskIssueNumberForCommit(s.basePath, mid, sid, tid) ?? undefined;
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// GitHub sync not available — skip
|
|
83
|
+
}
|
|
75
84
|
taskContext = {
|
|
76
85
|
taskId: `${sid}/${tid}`,
|
|
77
86
|
taskTitle: summary.title?.replace(/^T\d+:\s*/, "") || tid,
|
|
78
87
|
oneLiner: summary.oneLiner || undefined,
|
|
79
88
|
keyFiles: summary.frontmatter.key_files?.filter(f => !f.includes("{{")) || undefined,
|
|
89
|
+
issueNumber: ghIssueNumber,
|
|
80
90
|
};
|
|
81
91
|
}
|
|
82
92
|
}
|
|
@@ -94,6 +104,14 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
94
104
|
catch (e) {
|
|
95
105
|
debugLog("postUnit", { phase: "auto-commit", error: String(e) });
|
|
96
106
|
}
|
|
107
|
+
// GitHub sync (non-blocking, opt-in)
|
|
108
|
+
try {
|
|
109
|
+
const { runGitHubSync } = await import("../github-sync/sync.js");
|
|
110
|
+
await runGitHubSync(s.basePath, s.currentUnit.type, s.currentUnit.id);
|
|
111
|
+
}
|
|
112
|
+
catch (e) {
|
|
113
|
+
debugLog("postUnit", { phase: "github-sync", error: String(e) });
|
|
114
|
+
}
|
|
97
115
|
// Doctor: fix mechanical bookkeeping (skipped for lightweight sidecars)
|
|
98
116
|
if (!opts?.skipDoctor)
|
|
99
117
|
try {
|
|
@@ -105,12 +123,18 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
105
123
|
if (report.fixesApplied.length > 0) {
|
|
106
124
|
ctx.ui.notify(`Post-hook: applied ${report.fixesApplied.length} fix(es).`, "info");
|
|
107
125
|
}
|
|
108
|
-
// Proactive health tracking
|
|
109
|
-
|
|
126
|
+
// Proactive health tracking — filter to current milestone to avoid
|
|
127
|
+
// cross-milestone stale errors inflating the escalation counter
|
|
128
|
+
const currentMilestoneId = s.currentUnit.id.split("/")[0];
|
|
129
|
+
const milestoneIssues = currentMilestoneId
|
|
130
|
+
? report.issues.filter(i => i.unitId === currentMilestoneId ||
|
|
131
|
+
i.unitId.startsWith(`${currentMilestoneId}/`))
|
|
132
|
+
: report.issues;
|
|
133
|
+
const summary = summarizeDoctorIssues(milestoneIssues);
|
|
110
134
|
recordHealthSnapshot(summary.errors, summary.warnings, report.fixesApplied.length);
|
|
111
135
|
// Check if we should escalate to LLM-assisted heal
|
|
112
136
|
if (summary.errors > 0) {
|
|
113
|
-
const unresolvedErrors =
|
|
137
|
+
const unresolvedErrors = milestoneIssues
|
|
114
138
|
.filter(i => i.severity === "error" && !i.fixable)
|
|
115
139
|
.map(i => ({ code: i.code, message: i.message, unitId: i.unitId }));
|
|
116
140
|
const escalation = checkHealEscalation(summary.errors, unresolvedErrors);
|
|
@@ -123,6 +147,7 @@ export async function postUnitPreVerification(pctx, opts) {
|
|
|
123
147
|
const reportText = formatDoctorReport(report, { scope: doctorScope, includeWarnings: true });
|
|
124
148
|
const structuredIssues = formatDoctorIssuesForPrompt(actionable);
|
|
125
149
|
dispatchDoctorHeal(pi, doctorScope, reportText, structuredIssues);
|
|
150
|
+
return "dispatched";
|
|
126
151
|
}
|
|
127
152
|
catch (e) {
|
|
128
153
|
debugLog("postUnit", { phase: "doctor-heal-dispatch", error: String(e) });
|
|
@@ -7,12 +7,19 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { loadFile, parseContinue, parsePlan, parseRoadmap, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection } from "./files.js";
|
|
9
9
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
10
|
-
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, } from "./paths.js";
|
|
10
|
+
import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
|
|
11
11
|
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { existsSync } from "node:fs";
|
|
14
|
-
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
14
|
+
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
15
15
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
16
|
+
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
17
|
+
const MAX_PREAMBLE_CHARS = 30_000;
|
|
18
|
+
function capPreamble(preamble) {
|
|
19
|
+
if (preamble.length <= MAX_PREAMBLE_CHARS)
|
|
20
|
+
return preamble;
|
|
21
|
+
return truncateAtSectionBoundary(preamble, MAX_PREAMBLE_CHARS).content;
|
|
22
|
+
}
|
|
16
23
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
17
24
|
/**
|
|
18
25
|
* Format executor context constraints for injection into the plan-slice prompt.
|
|
@@ -124,7 +131,6 @@ export async function inlineFileSmart(absPath, relPath, label, query, threshold
|
|
|
124
131
|
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
125
132
|
}
|
|
126
133
|
// For large files, truncate at section boundary
|
|
127
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
128
134
|
const truncated = truncateAtSectionBoundary(content, threshold).content;
|
|
129
135
|
return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
|
|
130
136
|
}
|
|
@@ -158,7 +164,6 @@ export async function inlineDependencySummaries(mid, sid, base, budgetChars) {
|
|
|
158
164
|
}
|
|
159
165
|
const result = sections.join("\n\n");
|
|
160
166
|
if (budgetChars !== undefined && result.length > budgetChars) {
|
|
161
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
162
167
|
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
163
168
|
}
|
|
164
169
|
return result;
|
|
@@ -525,7 +530,7 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
|
|
|
525
530
|
if (knowledgeInlineRM)
|
|
526
531
|
inlined.push(knowledgeInlineRM);
|
|
527
532
|
inlined.push(inlineTemplate("research", "Research"));
|
|
528
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
533
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
529
534
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
530
535
|
return loadPrompt("research-milestone", {
|
|
531
536
|
workingDirectory: base,
|
|
@@ -578,7 +583,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
|
|
|
578
583
|
inlined.push(inlineTemplate("plan", "Slice Plan"));
|
|
579
584
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
580
585
|
}
|
|
581
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
586
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
582
587
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
583
588
|
const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
|
|
584
589
|
const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS"));
|
|
@@ -626,7 +631,7 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
626
631
|
const overridesInline = formatOverridesSection(activeOverrides);
|
|
627
632
|
if (overridesInline)
|
|
628
633
|
inlined.unshift(overridesInline);
|
|
629
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
634
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
630
635
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
631
636
|
return loadPrompt("research-slice", {
|
|
632
637
|
workingDirectory: base,
|
|
@@ -672,7 +677,7 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
|
|
|
672
677
|
const planOverridesInline = formatOverridesSection(planActiveOverrides);
|
|
673
678
|
if (planOverridesInline)
|
|
674
679
|
inlined.unshift(planOverridesInline);
|
|
675
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
680
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
676
681
|
// Build executor context constraints from the budget engine
|
|
677
682
|
const executorContextConstraints = formatExecutorConstraints();
|
|
678
683
|
const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
|
|
@@ -760,11 +765,17 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
|
|
|
760
765
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
761
766
|
let finalCarryForward = carryForwardSection;
|
|
762
767
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
763
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
764
768
|
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
765
769
|
}
|
|
770
|
+
// Inline RUNTIME.md if present
|
|
771
|
+
const runtimePath = resolveRuntimeFile(base);
|
|
772
|
+
const runtimeContent = existsSync(runtimePath) ? await loadFile(runtimePath) : null;
|
|
773
|
+
const runtimeContext = runtimeContent
|
|
774
|
+
? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
|
|
775
|
+
: "";
|
|
766
776
|
return loadPrompt("execute-task", {
|
|
767
777
|
overridesSection,
|
|
778
|
+
runtimeContext,
|
|
768
779
|
workingDirectory: base,
|
|
769
780
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
|
|
770
781
|
planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
|
|
@@ -819,7 +830,7 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
|
|
|
819
830
|
const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
|
|
820
831
|
if (completeOverridesInline)
|
|
821
832
|
inlined.unshift(completeOverridesInline);
|
|
822
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
833
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
823
834
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
824
835
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
825
836
|
const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`);
|
|
@@ -875,7 +886,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
|
|
|
875
886
|
if (contextInline)
|
|
876
887
|
inlined.push(contextInline);
|
|
877
888
|
inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
|
|
878
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
889
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
879
890
|
const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
|
|
880
891
|
return loadPrompt("complete-milestone", {
|
|
881
892
|
workingDirectory: base,
|
|
@@ -942,7 +953,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
|
|
|
942
953
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
943
954
|
if (contextInline)
|
|
944
955
|
inlined.push(contextInline);
|
|
945
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
956
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
946
957
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
947
958
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
948
959
|
return loadPrompt("validate-milestone", {
|
|
@@ -990,7 +1001,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
|
|
|
990
1001
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
991
1002
|
if (replanOverridesInline)
|
|
992
1003
|
inlined.unshift(replanOverridesInline);
|
|
993
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1004
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
994
1005
|
const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
|
|
995
1006
|
// Build capture context for replan prompt (captures that triggered this replan)
|
|
996
1007
|
let captureContext = "(none)";
|
|
@@ -1030,7 +1041,7 @@ export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base)
|
|
|
1030
1041
|
const projectInline = await inlineProjectFromDb(base);
|
|
1031
1042
|
if (projectInline)
|
|
1032
1043
|
inlined.push(projectInline);
|
|
1033
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1044
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1034
1045
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
|
|
1035
1046
|
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
1036
1047
|
return loadPrompt("run-uat", {
|
|
@@ -1066,7 +1077,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
|
|
|
1066
1077
|
const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
1067
1078
|
if (knowledgeInlineRA)
|
|
1068
1079
|
inlined.push(knowledgeInlineRA);
|
|
1069
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1080
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1070
1081
|
const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
|
|
1071
1082
|
// Build deferred captures context for reassess prompt
|
|
1072
1083
|
let deferredCaptures = "(none)";
|
|
@@ -15,10 +15,10 @@ import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
|
|
|
15
15
|
import { gsdRoot } from "./paths.js";
|
|
16
16
|
import { createWorktree, removeWorktree, worktreePath, } from "./worktree-manager.js";
|
|
17
17
|
import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
|
|
18
|
-
import { MergeConflictError, readIntegrationBranch } from "./git-service.js";
|
|
18
|
+
import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
|
|
19
19
|
import { parseRoadmap } from "./files.js";
|
|
20
20
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
21
|
-
import { nativeGetCurrentBranch, nativeWorkingTreeStatus,
|
|
21
|
+
import { nativeGetCurrentBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, } from "./native-git-bridge.js";
|
|
22
22
|
// ─── Module State ──────────────────────────────────────────────────────────
|
|
23
23
|
/** Original project root before chdir into auto-worktree. */
|
|
24
24
|
let originalBase = null;
|
|
@@ -656,7 +656,7 @@ function autoCommitDirtyState(cwd) {
|
|
|
656
656
|
const status = nativeWorkingTreeStatus(cwd);
|
|
657
657
|
if (!status)
|
|
658
658
|
return false;
|
|
659
|
-
|
|
659
|
+
nativeAddAllWithExclusions(cwd, RUNTIME_EXCLUSION_PATHS);
|
|
660
660
|
const result = nativeCommit(cwd, "chore: auto-commit before milestone merge");
|
|
661
661
|
return result !== null;
|
|
662
662
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* One command, one wizard. Routes to smart entry or status.
|
|
5
5
|
*/
|
|
6
|
+
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
6
7
|
import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
7
8
|
import { homedir } from "node:os";
|
|
8
9
|
import { join } from "node:path";
|
|
@@ -529,7 +530,7 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
529
530
|
return;
|
|
530
531
|
}
|
|
531
532
|
if (trimmed === "widget" || trimmed.startsWith("widget ")) {
|
|
532
|
-
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import
|
|
533
|
+
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await importExtensionModule(import.meta.url, "./auto-dashboard.js");
|
|
533
534
|
const arg = trimmed.replace(/^widget\s*/, "").trim();
|
|
534
535
|
if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
|
|
535
536
|
setWidgetMode(arg);
|
|
@@ -257,10 +257,23 @@ async function markSliceDoneInRoadmap(basePath, milestoneId, sliceId, fixesAppli
|
|
|
257
257
|
fixesApplied.push(`marked ${sliceId} done in ${roadmapPath}`);
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
|
+
async function markSliceUndoneInRoadmap(basePath, milestoneId, sliceId, fixesApplied) {
|
|
261
|
+
const roadmapPath = resolveMilestoneFile(basePath, milestoneId, "ROADMAP");
|
|
262
|
+
if (!roadmapPath)
|
|
263
|
+
return;
|
|
264
|
+
const content = await loadFile(roadmapPath);
|
|
265
|
+
if (!content)
|
|
266
|
+
return;
|
|
267
|
+
const updated = content.replace(new RegExp(`^(\\s*-\\s+)\\[x\\]\\s+\\*\\*${sliceId}:`, "m"), `$1[ ] **${sliceId}:`);
|
|
268
|
+
if (updated !== content) {
|
|
269
|
+
await saveFile(roadmapPath, updated);
|
|
270
|
+
fixesApplied.push(`unmarked ${sliceId} in ${roadmapPath} (premature completion)`);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
260
273
|
function matchesScope(unitId, scope) {
|
|
261
274
|
if (!scope)
|
|
262
275
|
return true;
|
|
263
|
-
return unitId === scope || unitId.startsWith(`${scope}/`)
|
|
276
|
+
return unitId === scope || unitId.startsWith(`${scope}/`);
|
|
264
277
|
}
|
|
265
278
|
function auditRequirements(content) {
|
|
266
279
|
if (!content)
|
|
@@ -828,6 +841,12 @@ export async function runGSDDoctor(basePath, options) {
|
|
|
828
841
|
file: relSliceFile(basePath, milestoneId, slice.id, "SUMMARY"),
|
|
829
842
|
fixable: true,
|
|
830
843
|
});
|
|
844
|
+
if (!allTasksDone) {
|
|
845
|
+
dryRunCanFix("slice_checked_missing_summary", `uncheck ${slice.id} in roadmap (tasks incomplete)`);
|
|
846
|
+
if (shouldFix("slice_checked_missing_summary")) {
|
|
847
|
+
await markSliceUndoneInRoadmap(basePath, milestoneId, slice.id, fixesApplied);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
831
850
|
}
|
|
832
851
|
if (slice.done && !hasSliceUat) {
|
|
833
852
|
issues.push({
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
1
2
|
export function registerExitCommand(pi, deps = {}) {
|
|
2
3
|
pi.registerCommand("exit", {
|
|
3
4
|
description: "Exit GSD gracefully",
|
|
4
5
|
handler: async (_args, ctx) => {
|
|
5
6
|
// Stop auto-mode first so locks and activity state are cleaned up before shutdown.
|
|
6
|
-
const stopAuto = deps.stopAuto ?? (await import
|
|
7
|
+
const stopAuto = deps.stopAuto ?? (await importExtensionModule(import.meta.url, "./auto.js")).stopAuto;
|
|
7
8
|
await stopAuto(ctx, pi, "Graceful exit");
|
|
8
9
|
ctx.shutdown();
|
|
9
10
|
},
|
|
@@ -692,6 +692,10 @@ export function extractUatType(content) {
|
|
|
692
692
|
const rawValue = modeBullet.slice('UAT mode:'.length).trim().toLowerCase();
|
|
693
693
|
if (rawValue.startsWith('artifact-driven'))
|
|
694
694
|
return 'artifact-driven';
|
|
695
|
+
if (rawValue.startsWith('browser-executable'))
|
|
696
|
+
return 'browser-executable';
|
|
697
|
+
if (rawValue.startsWith('runtime-executable'))
|
|
698
|
+
return 'runtime-executable';
|
|
695
699
|
if (rawValue.startsWith('live-runtime'))
|
|
696
700
|
return 'live-runtime';
|
|
697
701
|
if (rawValue.startsWith('human-experience'))
|
|
@@ -14,7 +14,7 @@ import { gsdRoot } from "./paths.js";
|
|
|
14
14
|
import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
|
|
15
15
|
import { loadEffectiveGSDPreferences } from "./preferences.js";
|
|
16
16
|
import { detectWorktreeName, SLICE_BRANCH_RE, } from "./worktree.js";
|
|
17
|
-
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchExists, nativeHasChanges,
|
|
17
|
+
import { nativeGetCurrentBranch, nativeDetectMainBranch, nativeBranchExists, nativeHasChanges, nativeAddAllWithExclusions, nativeHasStagedChanges, nativeCommit, nativeRmCached, nativeUpdateRef, } from "./native-git-bridge.js";
|
|
18
18
|
import { GSDError, GSD_MERGE_CONFLICT, GSD_GIT_ERROR } from "./errors.js";
|
|
19
19
|
import { getErrorMessage } from "./error-utils.js";
|
|
20
20
|
export const VALID_BRANCH_NAME = /^[a-zA-Z0-9_\-\/.]+$/;
|
|
@@ -36,12 +36,19 @@ export function buildTaskCommitMessage(ctx) {
|
|
|
36
36
|
: description;
|
|
37
37
|
const subject = `${type}(${scope}): ${truncated}`;
|
|
38
38
|
// Build body with key files if available
|
|
39
|
+
const bodyParts = [];
|
|
39
40
|
if (ctx.keyFiles && ctx.keyFiles.length > 0) {
|
|
40
41
|
const fileLines = ctx.keyFiles
|
|
41
42
|
.slice(0, 8) // cap at 8 files to keep commit concise
|
|
42
43
|
.map(f => `- ${f}`)
|
|
43
44
|
.join("\n");
|
|
44
|
-
|
|
45
|
+
bodyParts.push(fileLines);
|
|
46
|
+
}
|
|
47
|
+
if (ctx.issueNumber) {
|
|
48
|
+
bodyParts.push(`Resolves #${ctx.issueNumber}`);
|
|
49
|
+
}
|
|
50
|
+
if (bodyParts.length > 0) {
|
|
51
|
+
return `${subject}\n\n${bodyParts.join("\n\n")}`;
|
|
45
52
|
}
|
|
46
53
|
return subject;
|
|
47
54
|
}
|
|
@@ -254,7 +261,9 @@ export class GitServiceImpl {
|
|
|
254
261
|
}
|
|
255
262
|
this._runtimeFilesCleanedUp = true;
|
|
256
263
|
}
|
|
257
|
-
// Stage everything
|
|
264
|
+
// Stage everything using pathspec exclusions so excluded paths are never
|
|
265
|
+
// hashed by git. The old approach of `git add -A` followed by unstaging
|
|
266
|
+
// hangs indefinitely on repos with large untracked artifact trees (#1605).
|
|
258
267
|
//
|
|
259
268
|
// Exclude only RUNTIME paths from staging — not the entire .gsd/ directory.
|
|
260
269
|
// When .gsd/milestones/ files are already tracked in the index (projects
|
|
@@ -264,15 +273,9 @@ export class GitServiceImpl {
|
|
|
264
273
|
// the second half of a milestone's artifacts are never committed (#1326).
|
|
265
274
|
//
|
|
266
275
|
// If .gsd/ IS in .gitignore (the default for external state projects),
|
|
267
|
-
// git add -A already skips it and the
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
for (const exclusion of runtimeExclusions) {
|
|
271
|
-
try {
|
|
272
|
-
nativeResetPaths(this.basePath, [exclusion]);
|
|
273
|
-
}
|
|
274
|
-
catch { /* path not staged — ignore */ }
|
|
275
|
-
}
|
|
276
|
+
// git add -A already skips it and the exclusions are harmless no-ops.
|
|
277
|
+
const allExclusions = [...RUNTIME_EXCLUSION_PATHS, ...extraExclusions];
|
|
278
|
+
nativeAddAllWithExclusions(this.basePath, allExclusions);
|
|
276
279
|
}
|
|
277
280
|
/** Tracks whether runtime file cleanup has run this session. */
|
|
278
281
|
_runtimeFilesCleanedUp = false;
|
|
@@ -433,6 +436,21 @@ export class GitServiceImpl {
|
|
|
433
436
|
}
|
|
434
437
|
}
|
|
435
438
|
}
|
|
439
|
+
// ─── Draft PR Creation ─────────────────────────────────────────────────────
|
|
440
|
+
/**
|
|
441
|
+
* Create a draft pull request for a completed milestone using `gh pr create`.
|
|
442
|
+
* Returns the PR URL on success, or null on failure.
|
|
443
|
+
* Non-fatal: callers should treat failure as best-effort.
|
|
444
|
+
*/
|
|
445
|
+
export function createDraftPR(basePath, milestoneId, title, body) {
|
|
446
|
+
try {
|
|
447
|
+
const result = execSync(`gh pr create --draft --title ${JSON.stringify(title)} --body ${JSON.stringify(body)}`, { cwd: basePath, encoding: "utf8", timeout: 30000, env: GIT_NO_PROMPT_ENV });
|
|
448
|
+
return result.trim();
|
|
449
|
+
}
|
|
450
|
+
catch {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
436
454
|
// ─── Factory ───────────────────────────────────────────────────────────────
|
|
437
455
|
/** Create a GitServiceImpl with the current effective git preferences. */
|
|
438
456
|
export function createGitService(basePath) {
|