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.
Files changed (143) hide show
  1. package/README.md +15 -11
  2. package/dist/resource-loader.js +34 -1
  3. package/dist/resources/extensions/browser-tools/index.js +3 -1
  4. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  5. package/dist/resources/extensions/github-sync/cli.js +284 -0
  6. package/dist/resources/extensions/github-sync/index.js +73 -0
  7. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  8. package/dist/resources/extensions/github-sync/sync.js +424 -0
  9. package/dist/resources/extensions/github-sync/templates.js +118 -0
  10. package/dist/resources/extensions/github-sync/types.js +7 -0
  11. package/dist/resources/extensions/gsd/auto-dispatch.js +1 -1
  12. package/dist/resources/extensions/gsd/auto-loop.js +538 -469
  13. package/dist/resources/extensions/gsd/auto-post-unit.js +28 -3
  14. package/dist/resources/extensions/gsd/auto-prompts.js +197 -19
  15. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  16. package/dist/resources/extensions/gsd/commands.js +2 -1
  17. package/dist/resources/extensions/gsd/doctor-providers.js +3 -0
  18. package/dist/resources/extensions/gsd/doctor.js +20 -1
  19. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  20. package/dist/resources/extensions/gsd/files.js +46 -7
  21. package/dist/resources/extensions/gsd/git-service.js +30 -12
  22. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  23. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  24. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  25. package/dist/resources/extensions/gsd/health-widget.js +3 -86
  26. package/dist/resources/extensions/gsd/index.js +22 -19
  27. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  28. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  29. package/dist/resources/extensions/gsd/paths.js +3 -0
  30. package/dist/resources/extensions/gsd/preferences-types.js +1 -0
  31. package/dist/resources/extensions/gsd/preferences-validation.js +58 -0
  32. package/dist/resources/extensions/gsd/preferences.js +20 -9
  33. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  34. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  35. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  36. package/dist/resources/extensions/gsd/prompts/execute-task.md +3 -1
  37. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  39. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  40. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  41. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  42. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  43. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  44. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  46. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  47. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  48. package/dist/resources/extensions/gsd/prompts/run-uat.md +3 -1
  49. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  50. package/dist/resources/extensions/gsd/state.js +41 -22
  51. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  52. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  53. package/dist/resources/extensions/mcp-client/index.js +14 -1
  54. package/dist/resources/extensions/remote-questions/status.js +4 -2
  55. package/dist/resources/extensions/remote-questions/store.js +4 -2
  56. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  57. package/package.json +1 -1
  58. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  59. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  60. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  61. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  62. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  63. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  64. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  65. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  66. package/packages/pi-coding-agent/dist/core/skills.js +6 -1
  67. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  68. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  69. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  70. package/packages/pi-coding-agent/dist/index.js +1 -1
  71. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  72. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  73. package/packages/pi-coding-agent/src/core/skills.ts +9 -1
  74. package/packages/pi-coding-agent/src/index.ts +1 -0
  75. package/src/resources/extensions/browser-tools/index.ts +3 -0
  76. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  77. package/src/resources/extensions/github-sync/cli.ts +364 -0
  78. package/src/resources/extensions/github-sync/index.ts +93 -0
  79. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  80. package/src/resources/extensions/github-sync/sync.ts +556 -0
  81. package/src/resources/extensions/github-sync/templates.ts +183 -0
  82. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  83. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  84. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  85. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  86. package/src/resources/extensions/github-sync/types.ts +47 -0
  87. package/src/resources/extensions/gsd/auto-dispatch.ts +1 -1
  88. package/src/resources/extensions/gsd/auto-loop.ts +342 -304
  89. package/src/resources/extensions/gsd/auto-post-unit.ts +29 -3
  90. package/src/resources/extensions/gsd/auto-prompts.ts +242 -19
  91. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  92. package/src/resources/extensions/gsd/commands.ts +2 -2
  93. package/src/resources/extensions/gsd/doctor-providers.ts +4 -0
  94. package/src/resources/extensions/gsd/doctor.ts +22 -1
  95. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  96. package/src/resources/extensions/gsd/files.ts +49 -9
  97. package/src/resources/extensions/gsd/git-service.ts +44 -10
  98. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  99. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  100. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  101. package/src/resources/extensions/gsd/health-widget.ts +3 -89
  102. package/src/resources/extensions/gsd/index.ts +21 -16
  103. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  104. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  105. package/src/resources/extensions/gsd/paths.ts +4 -0
  106. package/src/resources/extensions/gsd/preferences-types.ts +4 -0
  107. package/src/resources/extensions/gsd/preferences-validation.ts +50 -0
  108. package/src/resources/extensions/gsd/preferences.ts +23 -9
  109. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  110. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  111. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  112. package/src/resources/extensions/gsd/prompts/execute-task.md +3 -1
  113. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  114. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  115. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  116. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  117. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  118. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  119. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  120. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  121. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  122. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  123. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  124. package/src/resources/extensions/gsd/prompts/run-uat.md +3 -1
  125. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  126. package/src/resources/extensions/gsd/state.ts +38 -20
  127. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  128. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  129. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +106 -31
  130. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  131. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  132. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  133. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  134. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  135. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  136. package/src/resources/extensions/gsd/tests/run-uat.test.ts +5 -1
  137. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  138. package/src/resources/extensions/gsd/types.ts +18 -0
  139. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  140. package/src/resources/extensions/mcp-client/index.ts +17 -1
  141. package/src/resources/extensions/remote-questions/status.ts +4 -2
  142. package/src/resources/extensions/remote-questions/store.ts +4 -2
  143. 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
- const summary = summarizeDoctorIssues(report.issues);
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 = report.issues
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 { join } from "node:path";
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
- nativeAddAll,
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
- nativeAddAll(cwd);
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 { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
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}/`) || 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 { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
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
  },