gsd-pi 2.38.0-dev.8f5c161 → 2.38.0-dev.98b44dc
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -11
- 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 +538 -469
- package/dist/resources/extensions/gsd/auto-post-unit.js +28 -3
- package/dist/resources/extensions/gsd/auto-prompts.js +197 -19
- 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-providers.js +3 -0
- 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 +46 -7
- package/dist/resources/extensions/gsd/git-service.js +30 -12
- package/dist/resources/extensions/gsd/gitignore.js +16 -3
- package/dist/resources/extensions/gsd/guided-flow.js +149 -38
- package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
- package/dist/resources/extensions/gsd/health-widget.js +3 -86
- package/dist/resources/extensions/gsd/index.js +22 -19
- package/dist/resources/extensions/gsd/migrate-external.js +18 -1
- 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 +20 -9
- package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
- package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -1
- package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/dist/resources/extensions/gsd/prompts/run-uat.md +3 -1
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +41 -22
- package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
- package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +4 -2
- package/dist/resources/extensions/remote-questions/store.js +4 -2
- package/dist/resources/extensions/shared/frontmatter.js +1 -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/dist/core/skills.d.ts +1 -0
- package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/skills.js +6 -1
- package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts +1 -1
- package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/index.js +1 -1
- package/packages/pi-coding-agent/dist/index.js.map +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/skills.ts +9 -1
- package/packages/pi-coding-agent/src/index.ts +1 -0
- 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 +342 -304
- package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
- package/src/resources/extensions/gsd/auto-prompts.ts +242 -19
- 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-providers.ts +4 -0
- 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 +49 -9
- package/src/resources/extensions/gsd/git-service.ts +44 -10
- package/src/resources/extensions/gsd/gitignore.ts +17 -3
- package/src/resources/extensions/gsd/guided-flow.ts +177 -44
- package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
- package/src/resources/extensions/gsd/health-widget.ts +3 -89
- package/src/resources/extensions/gsd/index.ts +21 -16
- package/src/resources/extensions/gsd/migrate-external.ts +18 -1
- 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 +23 -9
- package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
- package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/execute-task.md +3 -1
- package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
- package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
- package/src/resources/extensions/gsd/prompts/run-uat.md +3 -1
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +38 -20
- package/src/resources/extensions/gsd/templates/runtime.md +21 -0
- package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +106 -31
- package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
- package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
- package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
- package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
- package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
- package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +5 -1
- package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
- package/src/resources/extensions/gsd/types.ts +18 -0
- package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +4 -2
- package/src/resources/extensions/remote-questions/store.ts +4 -2
- package/src/resources/extensions/shared/frontmatter.ts +1 -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
|
}
|
|
@@ -6,23 +6,33 @@
|
|
|
6
6
|
* utility.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { loadFile, parseContinue, parsePlan, parseRoadmap, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection } from "./files.js";
|
|
9
|
+
import { loadFile, parseContinue, parsePlan, parseRoadmap, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
|
|
10
10
|
import type { Override, UatType } from "./files.js";
|
|
11
11
|
import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
|
|
12
12
|
import {
|
|
13
13
|
resolveMilestoneFile, resolveSliceFile, resolveSlicePath,
|
|
14
14
|
resolveTasksDir, resolveTaskFiles, resolveTaskFile,
|
|
15
15
|
relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath,
|
|
16
|
-
resolveGsdRootFile, relGsdRootFile,
|
|
16
|
+
resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile,
|
|
17
17
|
} from "./paths.js";
|
|
18
|
-
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
|
|
18
|
+
import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences, resolveAllSkillReferences } from "./preferences.js";
|
|
19
19
|
import type { GSDState, InlineLevel } from "./types.js";
|
|
20
20
|
import type { GSDPreferences } from "./preferences.js";
|
|
21
|
-
import {
|
|
21
|
+
import { getLoadedSkills, type Skill } from "@gsd/pi-coding-agent";
|
|
22
|
+
import { join, basename } from "node:path";
|
|
22
23
|
import { existsSync } from "node:fs";
|
|
23
|
-
import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
|
|
24
|
+
import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
|
|
24
25
|
import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
|
|
25
26
|
|
|
27
|
+
// ─── Preamble Cap ─────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const MAX_PREAMBLE_CHARS = 30_000;
|
|
30
|
+
|
|
31
|
+
function capPreamble(preamble: string): string {
|
|
32
|
+
if (preamble.length <= MAX_PREAMBLE_CHARS) return preamble;
|
|
33
|
+
return truncateAtSectionBoundary(preamble, MAX_PREAMBLE_CHARS).content;
|
|
34
|
+
}
|
|
35
|
+
|
|
26
36
|
// ─── Executor Constraints ─────────────────────────────────────────────────────
|
|
27
37
|
|
|
28
38
|
/**
|
|
@@ -157,7 +167,6 @@ export async function inlineFileSmart(
|
|
|
157
167
|
}
|
|
158
168
|
|
|
159
169
|
// For large files, truncate at section boundary
|
|
160
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
161
170
|
const truncated = truncateAtSectionBoundary(content, threshold).content;
|
|
162
171
|
return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
|
|
163
172
|
}
|
|
@@ -193,7 +202,6 @@ export async function inlineDependencySummaries(
|
|
|
193
202
|
|
|
194
203
|
const result = sections.join("\n\n");
|
|
195
204
|
if (budgetChars !== undefined && result.length > budgetChars) {
|
|
196
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
197
205
|
return truncateAtSectionBoundary(result, budgetChars).content;
|
|
198
206
|
}
|
|
199
207
|
return result;
|
|
@@ -290,7 +298,171 @@ export async function inlineProjectFromDb(
|
|
|
290
298
|
return inlineGsdRootFile(base, "project.md", "Project");
|
|
291
299
|
}
|
|
292
300
|
|
|
293
|
-
// ─── Skill Discovery
|
|
301
|
+
// ─── Skill Activation & Discovery ─────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
function normalizeSkillReference(ref: string): string {
|
|
304
|
+
const normalized = ref.replace(/\\/g, "/").trim();
|
|
305
|
+
const base = basename(normalized).replace(/\.md$/i, "");
|
|
306
|
+
const name = /^SKILL$/i.test(base)
|
|
307
|
+
? basename(normalized.replace(/\/SKILL(?:\.md)?$/i, ""))
|
|
308
|
+
: base;
|
|
309
|
+
return name.trim().toLowerCase();
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function tokenizeSkillContext(...parts: Array<string | null | undefined>): Set<string> {
|
|
313
|
+
const tokens = new Set<string>();
|
|
314
|
+
const addVariants = (raw: string) => {
|
|
315
|
+
const value = raw.trim().toLowerCase();
|
|
316
|
+
if (!value || value.length < 2) return;
|
|
317
|
+
tokens.add(value);
|
|
318
|
+
tokens.add(value.replace(/[-_]+/g, " "));
|
|
319
|
+
tokens.add(value.replace(/\s+/g, "-"));
|
|
320
|
+
tokens.add(value.replace(/\s+/g, ""));
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
for (const part of parts) {
|
|
324
|
+
if (!part) continue;
|
|
325
|
+
const text = part.toLowerCase();
|
|
326
|
+
const phraseMatches = text.match(/[a-z0-9][a-z0-9+.#/_-]{1,}/g) ?? [];
|
|
327
|
+
for (const match of phraseMatches) {
|
|
328
|
+
addVariants(match);
|
|
329
|
+
for (const piece of match.split(/[^a-z0-9+.#]+/g)) {
|
|
330
|
+
if (piece.length >= 3) addVariants(piece);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return tokens;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function skillMatchesContext(skill: Skill, contextTokens: Set<string>): boolean {
|
|
339
|
+
const haystacks = [
|
|
340
|
+
skill.name.toLowerCase(),
|
|
341
|
+
skill.name.toLowerCase().replace(/[-_]+/g, " "),
|
|
342
|
+
skill.description.toLowerCase(),
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
return [...contextTokens].some(token =>
|
|
346
|
+
token.length >= 3 && haystacks.some(haystack => haystack.includes(token)),
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function resolvePreferenceSkillNames(refs: string[], base: string): string[] {
|
|
351
|
+
if (refs.length === 0) return [];
|
|
352
|
+
const prefs: GSDPreferences = { always_use_skills: refs };
|
|
353
|
+
const report = resolveAllSkillReferences(prefs, base);
|
|
354
|
+
return refs.map(ref => {
|
|
355
|
+
const resolution = report.resolutions.get(ref);
|
|
356
|
+
return normalizeSkillReference(resolution?.resolvedPath ?? ref);
|
|
357
|
+
}).filter(Boolean);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function ruleMatchesContext(when: string, contextTokens: Set<string>): boolean {
|
|
361
|
+
const whenTokens = tokenizeSkillContext(when);
|
|
362
|
+
return [...whenTokens].some(token =>
|
|
363
|
+
contextTokens.has(token) || [...contextTokens].some(ctx => ctx.includes(token) || token.includes(ctx)),
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function resolveSkillRuleMatches(
|
|
368
|
+
prefs: GSDPreferences | undefined,
|
|
369
|
+
contextTokens: Set<string>,
|
|
370
|
+
base: string,
|
|
371
|
+
): { include: string[]; avoid: string[] } {
|
|
372
|
+
if (!prefs?.skill_rules?.length) return { include: [], avoid: [] };
|
|
373
|
+
|
|
374
|
+
const include: string[] = [];
|
|
375
|
+
const avoid: string[] = [];
|
|
376
|
+
for (const rule of prefs.skill_rules) {
|
|
377
|
+
if (!ruleMatchesContext(rule.when, contextTokens)) continue;
|
|
378
|
+
include.push(...resolvePreferenceSkillNames([...(rule.use ?? []), ...(rule.prefer ?? [])], base));
|
|
379
|
+
avoid.push(...resolvePreferenceSkillNames(rule.avoid ?? [], base));
|
|
380
|
+
}
|
|
381
|
+
return { include, avoid };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function resolvePreferredSkillNames(
|
|
385
|
+
prefs: GSDPreferences | undefined,
|
|
386
|
+
visibleSkills: Skill[],
|
|
387
|
+
contextTokens: Set<string>,
|
|
388
|
+
base: string,
|
|
389
|
+
): string[] {
|
|
390
|
+
if (!prefs?.prefer_skills?.length) return [];
|
|
391
|
+
const preferred = new Set(resolvePreferenceSkillNames(prefs.prefer_skills, base));
|
|
392
|
+
return visibleSkills
|
|
393
|
+
.filter(skill => preferred.has(normalizeSkillReference(skill.name)) && skillMatchesContext(skill, contextTokens))
|
|
394
|
+
.map(skill => normalizeSkillReference(skill.name));
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function formatSkillActivationBlock(skillNames: string[]): string {
|
|
398
|
+
if (skillNames.length === 0) return "";
|
|
399
|
+
const calls = skillNames.map(name => `Call Skill('${name}')`).join('. ');
|
|
400
|
+
return `<skill_activation>${calls}.</skill_activation>`;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function buildSkillActivationBlock(params: {
|
|
404
|
+
base: string;
|
|
405
|
+
milestoneId: string;
|
|
406
|
+
milestoneTitle?: string;
|
|
407
|
+
sliceId?: string;
|
|
408
|
+
sliceTitle?: string;
|
|
409
|
+
taskId?: string;
|
|
410
|
+
taskTitle?: string;
|
|
411
|
+
extraContext?: string[];
|
|
412
|
+
taskPlanContent?: string | null;
|
|
413
|
+
preferences?: GSDPreferences;
|
|
414
|
+
}): string {
|
|
415
|
+
const prefs = params.preferences ?? loadEffectiveGSDPreferences()?.preferences;
|
|
416
|
+
const contextTokens = tokenizeSkillContext(
|
|
417
|
+
params.milestoneId,
|
|
418
|
+
params.milestoneTitle,
|
|
419
|
+
params.sliceId,
|
|
420
|
+
params.sliceTitle,
|
|
421
|
+
params.taskId,
|
|
422
|
+
params.taskTitle,
|
|
423
|
+
...(params.extraContext ?? []),
|
|
424
|
+
params.taskPlanContent ?? undefined,
|
|
425
|
+
);
|
|
426
|
+
|
|
427
|
+
const visibleSkills = getLoadedSkills().filter(skill => !skill.disableModelInvocation);
|
|
428
|
+
const installedNames = new Set(visibleSkills.map(skill => normalizeSkillReference(skill.name)));
|
|
429
|
+
const avoided = new Set(resolvePreferenceSkillNames(prefs?.avoid_skills ?? [], params.base));
|
|
430
|
+
const matched = new Set<string>();
|
|
431
|
+
|
|
432
|
+
for (const name of resolvePreferenceSkillNames(prefs?.always_use_skills ?? [], params.base)) {
|
|
433
|
+
matched.add(name);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base);
|
|
437
|
+
for (const name of ruleMatches.include) matched.add(name);
|
|
438
|
+
for (const name of ruleMatches.avoid) avoided.add(name);
|
|
439
|
+
|
|
440
|
+
for (const name of resolvePreferredSkillNames(prefs, visibleSkills, contextTokens, params.base)) {
|
|
441
|
+
matched.add(name);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
if (params.taskPlanContent) {
|
|
445
|
+
try {
|
|
446
|
+
const taskPlan = parseTaskPlanFile(params.taskPlanContent);
|
|
447
|
+
for (const skillName of taskPlan.frontmatter.skills_used) {
|
|
448
|
+
matched.add(normalizeSkillReference(skillName));
|
|
449
|
+
}
|
|
450
|
+
} catch {
|
|
451
|
+
// Non-fatal — malformed task plan should not break prompt construction
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
for (const skill of visibleSkills) {
|
|
456
|
+
if (skillMatchesContext(skill, contextTokens)) {
|
|
457
|
+
matched.add(normalizeSkillReference(skill.name));
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
const ordered = [...matched]
|
|
462
|
+
.filter(name => installedNames.has(name) && !avoided.has(name))
|
|
463
|
+
.sort();
|
|
464
|
+
return formatSkillActivationBlock(ordered);
|
|
465
|
+
}
|
|
294
466
|
|
|
295
467
|
/**
|
|
296
468
|
* Build the skill discovery template variables for research prompts.
|
|
@@ -611,7 +783,7 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
|
|
|
611
783
|
if (knowledgeInlineRM) inlined.push(knowledgeInlineRM);
|
|
612
784
|
inlined.push(inlineTemplate("research", "Research"));
|
|
613
785
|
|
|
614
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
786
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
615
787
|
|
|
616
788
|
const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
|
|
617
789
|
return loadPrompt("research-milestone", {
|
|
@@ -621,6 +793,12 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
|
|
|
621
793
|
contextPath: contextRel,
|
|
622
794
|
outputPath: join(base, outputRelPath),
|
|
623
795
|
inlinedContext,
|
|
796
|
+
skillActivation: buildSkillActivationBlock({
|
|
797
|
+
base,
|
|
798
|
+
milestoneId: mid,
|
|
799
|
+
milestoneTitle: midTitle,
|
|
800
|
+
extraContext: [inlinedContext],
|
|
801
|
+
}),
|
|
624
802
|
...buildSkillDiscoveryVars(),
|
|
625
803
|
});
|
|
626
804
|
}
|
|
@@ -661,7 +839,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
661
839
|
inlined.push(inlineTemplate("task-plan", "Task Plan"));
|
|
662
840
|
}
|
|
663
841
|
|
|
664
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
842
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
665
843
|
|
|
666
844
|
const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
|
|
667
845
|
const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
|
|
@@ -677,6 +855,12 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
|
|
|
677
855
|
secretsOutputPath,
|
|
678
856
|
inlinedContext,
|
|
679
857
|
sourceFilePaths: buildSourceFilePaths(base, mid),
|
|
858
|
+
skillActivation: buildSkillActivationBlock({
|
|
859
|
+
base,
|
|
860
|
+
milestoneId: mid,
|
|
861
|
+
milestoneTitle: midTitle,
|
|
862
|
+
extraContext: [inlinedContext],
|
|
863
|
+
}),
|
|
680
864
|
...buildSkillDiscoveryVars(),
|
|
681
865
|
});
|
|
682
866
|
}
|
|
@@ -710,7 +894,7 @@ export async function buildResearchSlicePrompt(
|
|
|
710
894
|
const overridesInline = formatOverridesSection(activeOverrides);
|
|
711
895
|
if (overridesInline) inlined.unshift(overridesInline);
|
|
712
896
|
|
|
713
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
897
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
714
898
|
|
|
715
899
|
const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
|
|
716
900
|
return loadPrompt("research-slice", {
|
|
@@ -723,6 +907,13 @@ export async function buildResearchSlicePrompt(
|
|
|
723
907
|
outputPath: join(base, outputRelPath),
|
|
724
908
|
inlinedContext,
|
|
725
909
|
dependencySummaries: depContent,
|
|
910
|
+
skillActivation: buildSkillActivationBlock({
|
|
911
|
+
base,
|
|
912
|
+
milestoneId: mid,
|
|
913
|
+
sliceId: sid,
|
|
914
|
+
sliceTitle: sTitle,
|
|
915
|
+
extraContext: [inlinedContext, depContent],
|
|
916
|
+
}),
|
|
726
917
|
...buildSkillDiscoveryVars(),
|
|
727
918
|
});
|
|
728
919
|
}
|
|
@@ -758,7 +949,7 @@ export async function buildPlanSlicePrompt(
|
|
|
758
949
|
const planOverridesInline = formatOverridesSection(planActiveOverrides);
|
|
759
950
|
if (planOverridesInline) inlined.unshift(planOverridesInline);
|
|
760
951
|
|
|
761
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
952
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
762
953
|
|
|
763
954
|
// Build executor context constraints from the budget engine
|
|
764
955
|
const executorContextConstraints = formatExecutorConstraints();
|
|
@@ -781,6 +972,13 @@ export async function buildPlanSlicePrompt(
|
|
|
781
972
|
sourceFilePaths: buildSourceFilePaths(base, mid, sid),
|
|
782
973
|
executorContextConstraints,
|
|
783
974
|
commitInstruction,
|
|
975
|
+
skillActivation: buildSkillActivationBlock({
|
|
976
|
+
base,
|
|
977
|
+
milestoneId: mid,
|
|
978
|
+
sliceId: sid,
|
|
979
|
+
sliceTitle: sTitle,
|
|
980
|
+
extraContext: [inlinedContext, depContent],
|
|
981
|
+
}),
|
|
784
982
|
});
|
|
785
983
|
}
|
|
786
984
|
|
|
@@ -881,12 +1079,19 @@ export async function buildExecuteTaskPrompt(
|
|
|
881
1079
|
const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
|
|
882
1080
|
let finalCarryForward = carryForwardSection;
|
|
883
1081
|
if (carryForwardSection.length > carryForwardBudget) {
|
|
884
|
-
const { truncateAtSectionBoundary } = await import("./context-budget.js");
|
|
885
1082
|
finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
|
|
886
1083
|
}
|
|
887
1084
|
|
|
1085
|
+
// Inline RUNTIME.md if present
|
|
1086
|
+
const runtimePath = resolveRuntimeFile(base);
|
|
1087
|
+
const runtimeContent = existsSync(runtimePath) ? await loadFile(runtimePath) : null;
|
|
1088
|
+
const runtimeContext = runtimeContent
|
|
1089
|
+
? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
|
|
1090
|
+
: "";
|
|
1091
|
+
|
|
888
1092
|
return loadPrompt("execute-task", {
|
|
889
1093
|
overridesSection,
|
|
1094
|
+
runtimeContext,
|
|
890
1095
|
workingDirectory: base,
|
|
891
1096
|
milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
|
|
892
1097
|
planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
|
|
@@ -900,6 +1105,16 @@ export async function buildExecuteTaskPrompt(
|
|
|
900
1105
|
taskSummaryPath,
|
|
901
1106
|
inlinedTemplates,
|
|
902
1107
|
verificationBudget,
|
|
1108
|
+
skillActivation: buildSkillActivationBlock({
|
|
1109
|
+
base,
|
|
1110
|
+
milestoneId: mid,
|
|
1111
|
+
sliceId: sid,
|
|
1112
|
+
sliceTitle: sTitle,
|
|
1113
|
+
taskId: tid,
|
|
1114
|
+
taskTitle: tTitle,
|
|
1115
|
+
taskPlanContent,
|
|
1116
|
+
extraContext: [taskPlanInline, slicePlanExcerpt, finalCarryForward, resumeSection],
|
|
1117
|
+
}),
|
|
903
1118
|
});
|
|
904
1119
|
}
|
|
905
1120
|
|
|
@@ -945,7 +1160,7 @@ export async function buildCompleteSlicePrompt(
|
|
|
945
1160
|
const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
|
|
946
1161
|
if (completeOverridesInline) inlined.unshift(completeOverridesInline);
|
|
947
1162
|
|
|
948
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1163
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
949
1164
|
|
|
950
1165
|
const sliceRel = relSlicePath(base, mid, sid);
|
|
951
1166
|
const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
|
|
@@ -1004,7 +1219,7 @@ export async function buildCompleteMilestonePrompt(
|
|
|
1004
1219
|
if (contextInline) inlined.push(contextInline);
|
|
1005
1220
|
inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
|
|
1006
1221
|
|
|
1007
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1222
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1008
1223
|
|
|
1009
1224
|
const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
|
|
1010
1225
|
|
|
@@ -1075,7 +1290,7 @@ export async function buildValidateMilestonePrompt(
|
|
|
1075
1290
|
const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
|
|
1076
1291
|
if (contextInline) inlined.push(contextInline);
|
|
1077
1292
|
|
|
1078
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1293
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1079
1294
|
|
|
1080
1295
|
const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
|
|
1081
1296
|
const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
|
|
@@ -1129,7 +1344,7 @@ export async function buildReplanSlicePrompt(
|
|
|
1129
1344
|
const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
|
|
1130
1345
|
if (replanOverridesInline) inlined.unshift(replanOverridesInline);
|
|
1131
1346
|
|
|
1132
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1347
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1133
1348
|
|
|
1134
1349
|
const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
|
|
1135
1350
|
|
|
@@ -1158,6 +1373,14 @@ export async function buildReplanSlicePrompt(
|
|
|
1158
1373
|
inlinedContext,
|
|
1159
1374
|
replanPath,
|
|
1160
1375
|
captureContext,
|
|
1376
|
+
skillActivation: buildSkillActivationBlock({
|
|
1377
|
+
base,
|
|
1378
|
+
milestoneId: mid,
|
|
1379
|
+
milestoneTitle: midTitle,
|
|
1380
|
+
sliceId: sid,
|
|
1381
|
+
sliceTitle: sTitle,
|
|
1382
|
+
extraContext: [inlinedContext, captureContext],
|
|
1383
|
+
}),
|
|
1161
1384
|
});
|
|
1162
1385
|
}
|
|
1163
1386
|
|
|
@@ -1177,7 +1400,7 @@ export async function buildRunUatPrompt(
|
|
|
1177
1400
|
const projectInline = await inlineProjectFromDb(base);
|
|
1178
1401
|
if (projectInline) inlined.push(projectInline);
|
|
1179
1402
|
|
|
1180
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1403
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1181
1404
|
|
|
1182
1405
|
const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
|
|
1183
1406
|
const uatType = extractUatType(uatContent) ?? "human-experience";
|
|
@@ -1216,7 +1439,7 @@ export async function buildReassessRoadmapPrompt(
|
|
|
1216
1439
|
const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
|
|
1217
1440
|
if (knowledgeInlineRA) inlined.push(knowledgeInlineRA);
|
|
1218
1441
|
|
|
1219
|
-
const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}
|
|
1442
|
+
const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
|
|
1220
1443
|
|
|
1221
1444
|
const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
|
|
1222
1445
|
|
|
@@ -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);
|
|
@@ -128,6 +128,10 @@ interface KeyLookup {
|
|
|
128
128
|
function resolveKey(providerId: string): KeyLookup {
|
|
129
129
|
const info = PROVIDER_REGISTRY.find(p => p.id === providerId);
|
|
130
130
|
|
|
131
|
+
if (providerId === "anthropic-vertex" && process.env.ANTHROPIC_VERTEX_PROJECT_ID) {
|
|
132
|
+
return { found: true, source: "env", backedOff: false };
|
|
133
|
+
}
|
|
134
|
+
|
|
131
135
|
// Check auth.json
|
|
132
136
|
const authPath = getAuthPath();
|
|
133
137
|
if (existsSync(authPath)) {
|
|
@@ -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
|
},
|