gsd-pi 2.38.0-dev.bc2e21e → 2.38.0-dev.d533afb
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/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/session.js +3 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
- package/dist/resources/extensions/gsd/auto-loop.js +292 -263
- package/dist/resources/extensions/gsd/auto-post-unit.js +28 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +23 -43
- package/dist/resources/extensions/gsd/auto-start.js +7 -1
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +143 -80
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +2 -1
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-providers.js +27 -11
- 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 +15 -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/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +58 -10
- package/dist/resources/extensions/gsd/preferences.js +4 -2
- package/dist/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/dist/resources/extensions/gsd/repo-identity.js +19 -3
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -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/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/session.ts +3 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
- package/src/resources/extensions/gsd/auto-loop.ts +382 -360
- package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +25 -45
- package/src/resources/extensions/gsd/auto-start.ts +11 -1
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +139 -86
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +2 -2
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-providers.ts +26 -9
- 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 +20 -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/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +50 -10
- package/src/resources/extensions/gsd/preferences.ts +3 -2
- package/src/resources/extensions/gsd/prompts/run-uat.md +2 -0
- package/src/resources/extensions/gsd/repo-identity.ts +20 -3
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/types.ts +0 -1
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -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
|
}
|
|
@@ -20,11 +20,17 @@ 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";
|
|
24
|
-
import { compressToTarget } from "./prompt-compressor.js";
|
|
25
|
-
import { distillSummaries } from "./summary-distiller.js";
|
|
23
|
+
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
26
24
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
27
|
-
|
|
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
|
+
}
|
|
28
34
|
|
|
29
35
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
30
36
|
|
|
@@ -159,16 +165,9 @@ export async function inlineFileSmart(
|
|
|
159
165
|
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
160
166
|
}
|
|
161
167
|
|
|
162
|
-
//
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
// If chunking didn't save much (< 20%), just include full content
|
|
166
|
-
if (result.savingsPercent < 20) {
|
|
167
|
-
return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const formatted = formatChunks(result, relPath);
|
|
171
|
-
return `### ${label} (${result.omittedChunks} sections omitted for relevance)\nSource: \`${relPath}\`\n\n${formatted}`;
|
|
168
|
+
// For large files, truncate at section boundary
|
|
169
|
+
const truncated = truncateAtSectionBoundary(content, threshold).content;
|
|
170
|
+
return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
/**
|
|
@@ -202,21 +201,6 @@ export async function inlineDependencySummaries(
|
|
|
202
201
|
|
|
203
202
|
const result = sections.join("\n\n");
|
|
204
203
|
if (budgetChars !== undefined && result.length > budgetChars) {
|
|
205
|
-
// For 3+ summaries, try distillation first (preserves more information)
|
|
206
|
-
if (sections.length >= 3) {
|
|
207
|
-
const rawSummaries = sections.map(s => {
|
|
208
|
-
// Extract content after the header line
|
|
209
|
-
const lines = s.split("\n");
|
|
210
|
-
const contentStart = lines.findIndex(l => l.startsWith("Source:"));
|
|
211
|
-
return contentStart >= 0 ? lines.slice(contentStart + 1).join("\n").trim() : s;
|
|
212
|
-
});
|
|
213
|
-
const distilled = distillSummaries(rawSummaries, budgetChars);
|
|
214
|
-
if (distilled.content.length <= budgetChars) {
|
|
215
|
-
return distilled.content;
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
// Fall back to section-boundary truncation
|
|
219
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
220
204
|
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
221
205
|
}
|
|
222
206
|
return result;
|
|
@@ -634,7 +618,7 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
|
|
|
634
618
|
if (knowledgeInlineRM) inlined.push(knowledgeInlineRM);
|
|
635
619
|
inlined.push(inlineTemplate("research", "Research"));
|
|
636
620
|
|
|
637
|
-
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")}`);
|
|
638
622
|
|
|
639
623
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
640
624
|
return loadPrompt("research-milestone", {
|
|
@@ -684,7 +668,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
684
668
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
685
669
|
}
|
|
686
670
|
|
|
687
|
-
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")}`);
|
|
688
672
|
|
|
689
673
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
690
674
|
const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
|
|
@@ -733,7 +717,7 @@ export async function buildResearchSlicePrompt(
|
|
|
733
717
|
const overridesInline = formatOverridesSection(activeOverrides);
|
|
734
718
|
if (overridesInline) inlined.unshift(overridesInline);
|
|
735
719
|
|
|
736
|
-
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")}`);
|
|
737
721
|
|
|
738
722
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
739
723
|
return loadPrompt("research-slice", {
|
|
@@ -781,7 +765,7 @@ export async function buildPlanSlicePrompt(
|
|
|
781
765
|
const planOverridesInline = formatOverridesSection(planActiveOverrides);
|
|
782
766
|
if (planOverridesInline) inlined.unshift(planOverridesInline);
|
|
783
767
|
|
|
784
|
-
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")}`);
|
|
785
769
|
|
|
786
770
|
// Build executor context constraints from the budget engine
|
|
787
771
|
const executorContextConstraints = formatExecutorConstraints();
|
|
@@ -900,15 +884,11 @@ export async function buildExecuteTaskPrompt(
|
|
|
900
884
|
const budgets = computeBudgets(contextWindow);
|
|
901
885
|
const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
|
|
902
886
|
|
|
903
|
-
//
|
|
904
|
-
// Only compress when compression_strategy is "compress" (budget/balanced profiles).
|
|
887
|
+
// Truncate carry-forward section when it exceeds 40% of inline context budget.
|
|
905
888
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
906
889
|
let finalCarryForward = carryForwardSection;
|
|
907
890
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
908
|
-
|
|
909
|
-
if (resolveCompressionStrategy() === "compress") {
|
|
910
|
-
finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
|
|
911
|
-
}
|
|
891
|
+
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
912
892
|
}
|
|
913
893
|
|
|
914
894
|
return loadPrompt("execute-task", {
|
|
@@ -971,7 +951,7 @@ export async function buildCompleteSlicePrompt(
|
|
|
971
951
|
const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
|
|
972
952
|
if (completeOverridesInline) inlined.unshift(completeOverridesInline);
|
|
973
953
|
|
|
974
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
954
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
975
955
|
|
|
976
956
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
977
957
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
@@ -1030,7 +1010,7 @@ export async function buildCompleteMilestonePrompt(
|
|
|
1030
1010
|
if (contextInline) inlined.push(contextInline);
|
|
1031
1011
|
inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
|
|
1032
1012
|
|
|
1033
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1013
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1034
1014
|
|
|
1035
1015
|
const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
|
|
1036
1016
|
|
|
@@ -1101,7 +1081,7 @@ export async function buildValidateMilestonePrompt(
|
|
|
1101
1081
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
1102
1082
|
if (contextInline) inlined.push(contextInline);
|
|
1103
1083
|
|
|
1104
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1084
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1105
1085
|
|
|
1106
1086
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
1107
1087
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
@@ -1155,7 +1135,7 @@ export async function buildReplanSlicePrompt(
|
|
|
1155
1135
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
1156
1136
|
if (replanOverridesInline) inlined.unshift(replanOverridesInline);
|
|
1157
1137
|
|
|
1158
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1138
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1159
1139
|
|
|
1160
1140
|
const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
|
|
1161
1141
|
|
|
@@ -1203,7 +1183,7 @@ export async function buildRunUatPrompt(
|
|
|
1203
1183
|
const projectInline = await inlineProjectFromDb(base);
|
|
1204
1184
|
if (projectInline) inlined.push(projectInline);
|
|
1205
1185
|
|
|
1206
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1186
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1207
1187
|
|
|
1208
1188
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
|
|
1209
1189
|
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
@@ -1242,7 +1222,7 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1242
1222
|
const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
1243
1223
|
if (knowledgeInlineRA) inlined.push(knowledgeInlineRA);
|
|
1244
1224
|
|
|
1245
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1225
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1246
1226
|
|
|
1247
1227
|
const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
|
|
1248
1228
|
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
resolveSkillDiscoveryMode,
|
|
21
21
|
getIsolationMode,
|
|
22
22
|
} from "./preferences.js";
|
|
23
|
-
import { ensureGsdSymlink } from "./repo-identity.js";
|
|
23
|
+
import { ensureGsdSymlink, validateProjectId } from "./repo-identity.js";
|
|
24
24
|
import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
|
|
25
25
|
import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
|
|
26
26
|
import { gsdRoot, resolveMilestoneFile, milestonesDir } from "./paths.js";
|
|
@@ -130,6 +130,16 @@ export async function bootstrapAutoSession(
|
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
try {
|
|
133
|
+
// Validate GSD_PROJECT_ID early so the user gets immediate feedback
|
|
134
|
+
const customProjectId = process.env.GSD_PROJECT_ID;
|
|
135
|
+
if (customProjectId && !validateProjectId(customProjectId)) {
|
|
136
|
+
ctx.ui.notify(
|
|
137
|
+
`GSD_PROJECT_ID must contain only alphanumeric characters, hyphens, and underscores. Got: "${customProjectId}"`,
|
|
138
|
+
"error",
|
|
139
|
+
);
|
|
140
|
+
return releaseLockAndReturn();
|
|
141
|
+
}
|
|
142
|
+
|
|
133
143
|
// Ensure git repo exists
|
|
134
144
|
if (!nativeIsRepo(base)) {
|
|
135
145
|
const mainBranch =
|
|
@@ -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",
|
|
@@ -536,114 +536,167 @@ export async function stopAuto(
|
|
|
536
536
|
if (!s.active && !s.paused) return;
|
|
537
537
|
const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
|
|
538
538
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
539
|
-
clearUnitTimeout();
|
|
540
|
-
if (lockBase()) clearLock(lockBase());
|
|
541
|
-
if (lockBase()) releaseSessionLock(lockBase());
|
|
542
|
-
clearSkillSnapshot();
|
|
543
|
-
resetSkillTelemetry();
|
|
544
539
|
|
|
545
|
-
|
|
546
|
-
|
|
540
|
+
try {
|
|
541
|
+
// ── Step 1: Timers and locks ──
|
|
542
|
+
try {
|
|
543
|
+
clearUnitTimeout();
|
|
544
|
+
if (lockBase()) clearLock(lockBase());
|
|
545
|
+
if (lockBase()) releaseSessionLock(lockBase());
|
|
546
|
+
} catch (e) {
|
|
547
|
+
debugLog("stop-cleanup-locks", { error: e instanceof Error ? e.message : String(e) });
|
|
548
|
+
}
|
|
547
549
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
});
|
|
556
|
-
}
|
|
550
|
+
// ── Step 2: Skill state ──
|
|
551
|
+
try {
|
|
552
|
+
clearSkillSnapshot();
|
|
553
|
+
resetSkillTelemetry();
|
|
554
|
+
} catch (e) {
|
|
555
|
+
debugLog("stop-cleanup-skills", { error: e instanceof Error ? e.message : String(e) });
|
|
556
|
+
}
|
|
557
557
|
|
|
558
|
-
|
|
559
|
-
if (isDbAvailable()) {
|
|
558
|
+
// ── Step 3: SIGTERM handler ──
|
|
560
559
|
try {
|
|
561
|
-
|
|
562
|
-
closeDatabase();
|
|
560
|
+
deregisterSigtermHandler();
|
|
563
561
|
} catch (e) {
|
|
564
|
-
debugLog("
|
|
565
|
-
error: e instanceof Error ? e.message : String(e),
|
|
566
|
-
});
|
|
562
|
+
debugLog("stop-cleanup-sigterm", { error: e instanceof Error ? e.message : String(e) });
|
|
567
563
|
}
|
|
568
|
-
}
|
|
569
564
|
|
|
570
|
-
|
|
571
|
-
s.basePath = s.originalBasePath;
|
|
565
|
+
// ── Step 4: Auto-worktree exit ──
|
|
572
566
|
try {
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
567
|
+
if (s.currentMilestoneId) {
|
|
568
|
+
const notifyCtx = ctx
|
|
569
|
+
? { notify: ctx.ui.notify.bind(ctx.ui) }
|
|
570
|
+
: { notify: () => {} };
|
|
571
|
+
buildResolver().exitMilestone(s.currentMilestoneId, notifyCtx, {
|
|
572
|
+
preserveBranch: true,
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
} catch (e) {
|
|
576
|
+
debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
|
|
576
577
|
}
|
|
577
|
-
}
|
|
578
578
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
579
|
+
// ── Step 5: DB cleanup ──
|
|
580
|
+
if (isDbAvailable()) {
|
|
581
|
+
try {
|
|
582
|
+
const { closeDatabase } = await import("./gsd-db.js");
|
|
583
|
+
closeDatabase();
|
|
584
|
+
} catch (e) {
|
|
585
|
+
debugLog("db-close-failed", {
|
|
586
|
+
error: e instanceof Error ? e.message : String(e),
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
}
|
|
589
590
|
|
|
590
|
-
|
|
591
|
+
// ── Step 6: Restore basePath and chdir ──
|
|
591
592
|
try {
|
|
592
|
-
|
|
593
|
+
if (s.originalBasePath) {
|
|
594
|
+
s.basePath = s.originalBasePath;
|
|
595
|
+
try {
|
|
596
|
+
process.chdir(s.basePath);
|
|
597
|
+
} catch {
|
|
598
|
+
/* best-effort */
|
|
599
|
+
}
|
|
600
|
+
}
|
|
593
601
|
} catch (e) {
|
|
594
|
-
debugLog("stop-
|
|
595
|
-
error: e instanceof Error ? e.message : String(e),
|
|
596
|
-
});
|
|
602
|
+
debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
|
|
597
603
|
}
|
|
598
|
-
}
|
|
599
604
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
605
|
+
// ── Step 7: Ledger notification ──
|
|
606
|
+
try {
|
|
607
|
+
const ledger = getLedger();
|
|
608
|
+
if (ledger && ledger.units.length > 0) {
|
|
609
|
+
const totals = getProjectTotals(ledger.units);
|
|
610
|
+
ctx?.ui.notify(
|
|
611
|
+
`Auto-mode stopped${reasonSuffix}. Session: ${formatCost(totals.cost)} · ${formatTokenCount(totals.tokens.total)} tokens · ${ledger.units.length} units`,
|
|
612
|
+
"info",
|
|
613
|
+
);
|
|
614
|
+
} else {
|
|
615
|
+
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
|
|
616
|
+
}
|
|
617
|
+
} catch (e) {
|
|
618
|
+
debugLog("stop-cleanup-ledger", { error: e instanceof Error ? e.message : String(e) });
|
|
619
|
+
}
|
|
606
620
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
621
|
+
// ── Step 8: Rebuild state ──
|
|
622
|
+
if (s.basePath) {
|
|
623
|
+
try {
|
|
624
|
+
await rebuildState(s.basePath);
|
|
625
|
+
} catch (e) {
|
|
626
|
+
debugLog("stop-rebuild-state-failed", {
|
|
627
|
+
error: e instanceof Error ? e.message : String(e),
|
|
628
|
+
});
|
|
629
|
+
}
|
|
611
630
|
}
|
|
612
|
-
}
|
|
613
631
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
632
|
+
// ── Step 9: Cmux sidebar / event log ──
|
|
633
|
+
try {
|
|
634
|
+
clearCmuxSidebar(loadedPreferences);
|
|
635
|
+
logCmuxEvent(
|
|
636
|
+
loadedPreferences,
|
|
637
|
+
`Auto-mode stopped${reasonSuffix || ""}.`,
|
|
638
|
+
reason?.startsWith("Blocked:") ? "warning" : "info",
|
|
639
|
+
);
|
|
640
|
+
} catch (e) {
|
|
641
|
+
debugLog("stop-cleanup-cmux", { error: e instanceof Error ? e.message : String(e) });
|
|
642
|
+
}
|
|
618
643
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
);
|
|
631
|
-
if (original) await pi.setModel(original);
|
|
632
|
-
}
|
|
644
|
+
// ── Step 10: Debug summary ──
|
|
645
|
+
try {
|
|
646
|
+
if (isDebugEnabled()) {
|
|
647
|
+
const logPath = writeDebugSummary();
|
|
648
|
+
if (logPath) {
|
|
649
|
+
ctx?.ui.notify(`Debug log written → ${logPath}`, "info");
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
} catch (e) {
|
|
653
|
+
debugLog("stop-cleanup-debug", { error: e instanceof Error ? e.message : String(e) });
|
|
654
|
+
}
|
|
633
655
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
656
|
+
// ── Step 11: Reset metrics, routing, hooks ──
|
|
657
|
+
try {
|
|
658
|
+
resetMetrics();
|
|
659
|
+
resetRoutingHistory();
|
|
660
|
+
resetHookState();
|
|
661
|
+
if (s.basePath) clearPersistedHookState(s.basePath);
|
|
662
|
+
} catch (e) {
|
|
663
|
+
debugLog("stop-cleanup-metrics", { error: e instanceof Error ? e.message : String(e) });
|
|
664
|
+
}
|
|
639
665
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
666
|
+
// ── Step 12: Remove paused-session metadata (#1383) ──
|
|
667
|
+
try {
|
|
668
|
+
const pausedPath = join(gsdRoot(s.originalBasePath || s.basePath), "runtime", "paused-session.json");
|
|
669
|
+
if (existsSync(pausedPath)) unlinkSync(pausedPath);
|
|
670
|
+
} catch { /* non-fatal */ }
|
|
644
671
|
|
|
645
|
-
|
|
646
|
-
|
|
672
|
+
// ── Step 13: Restore original model (before reset clears IDs) ──
|
|
673
|
+
try {
|
|
674
|
+
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
675
|
+
const original = ctx.modelRegistry.find(
|
|
676
|
+
s.originalModelProvider,
|
|
677
|
+
s.originalModelId,
|
|
678
|
+
);
|
|
679
|
+
if (original) await pi.setModel(original);
|
|
680
|
+
}
|
|
681
|
+
} catch (e) {
|
|
682
|
+
debugLog("stop-cleanup-model", { error: e instanceof Error ? e.message : String(e) });
|
|
683
|
+
}
|
|
684
|
+
} finally {
|
|
685
|
+
// ── Critical invariants: these MUST execute regardless of errors ──
|
|
686
|
+
// External cleanup (not covered by session reset)
|
|
687
|
+
clearInFlightTools();
|
|
688
|
+
clearSliceProgressCache();
|
|
689
|
+
clearActivityLogState();
|
|
690
|
+
resetProactiveHealing();
|
|
691
|
+
|
|
692
|
+
// UI cleanup
|
|
693
|
+
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
694
|
+
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
695
|
+
ctx?.ui.setFooter(undefined);
|
|
696
|
+
|
|
697
|
+
// Reset all session state in one call
|
|
698
|
+
s.reset();
|
|
699
|
+
}
|
|
647
700
|
}
|
|
648
701
|
|
|
649
702
|
/**
|
|
@@ -745,7 +745,7 @@ export function serializePreferencesToFrontmatter(prefs: Record<string, unknown>
|
|
|
745
745
|
"dynamic_routing", "token_profile", "phases", "parallel",
|
|
746
746
|
"auto_visualize", "auto_report",
|
|
747
747
|
"verification_commands", "verification_auto_fix", "verification_max_retries",
|
|
748
|
-
"search_provider", "
|
|
748
|
+
"search_provider", "context_selection",
|
|
749
749
|
];
|
|
750
750
|
|
|
751
751
|
const seen = new Set<string>();
|
|
@@ -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);
|
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { type TokenProvider, getCharsPerToken } from "./token-counter.js";
|
|
12
|
-
import { compressToTarget } from "./prompt-compressor.js";
|
|
13
12
|
|
|
14
13
|
// ─── Budget ratio constants ──────────────────────────────────────────────────
|
|
15
14
|
// Percentages of total context window allocated to each budget category.
|
|
@@ -202,22 +201,13 @@ export function resolveExecutorContextWindow(
|
|
|
202
201
|
}
|
|
203
202
|
|
|
204
203
|
/**
|
|
205
|
-
*
|
|
206
|
-
* Returns the content within budget with maximum information preservation.
|
|
204
|
+
* Reduce content to fit within budget using section-boundary truncation.
|
|
207
205
|
*/
|
|
208
206
|
export function reduceToFit(content: string, budgetChars: number): TruncationResult {
|
|
209
207
|
if (!content || content.length <= budgetChars) {
|
|
210
208
|
return { content, droppedSections: 0 };
|
|
211
209
|
}
|
|
212
|
-
|
|
213
|
-
// Step 1: Try compression
|
|
214
|
-
const compressed = compressToTarget(content, budgetChars);
|
|
215
|
-
if (compressed.compressedChars <= budgetChars) {
|
|
216
|
-
return { content: compressed.content, droppedSections: 0 };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Step 2: Truncate the compressed content at section boundaries
|
|
220
|
-
return truncateAtSectionBoundary(compressed.content, budgetChars);
|
|
210
|
+
return truncateAtSectionBoundary(content, budgetChars);
|
|
221
211
|
}
|
|
222
212
|
|
|
223
213
|
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
@@ -194,8 +194,6 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
194
194
|
|
|
195
195
|
- `search_provider`: `"brave"`, `"tavily"`, `"ollama"`, `"native"`, or `"auto"` — selects the search backend for research phases. `"native"` forces Anthropic's built-in web search only; provider values force that backend and disable native search; `"auto"` uses the default heuristic. Default: `"auto"`.
|
|
196
196
|
|
|
197
|
-
- `compression_strategy`: `"truncate"` or `"compress"` — controls how context that exceeds the budget is reduced. `"truncate"` (default) drops sections from the end. `"compress"` applies heuristic compression before truncating, preserving more content at the cost of some fidelity. Default: `"truncate"`.
|
|
198
|
-
|
|
199
197
|
- `context_selection`: `"full"` or `"smart"` — controls how files are inlined into context. `"full"` inlines entire files; `"smart"` uses semantic chunking to include only the most relevant sections. Default is derived from `token_profile`.
|
|
200
198
|
|
|
201
199
|
- `parallel`: configures parallel orchestration for running multiple slices concurrently. Keys:
|