gsd-pi 2.38.0-dev.96dc7fb → 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 (217) hide show
  1. package/README.md +15 -11
  2. package/dist/app-paths.js +1 -1
  3. package/dist/extension-registry.js +2 -2
  4. package/dist/remote-questions-config.js +2 -2
  5. package/dist/resource-loader.js +34 -1
  6. package/dist/resources/extensions/browser-tools/index.js +3 -1
  7. package/dist/resources/extensions/browser-tools/tools/verify.js +97 -0
  8. package/dist/resources/extensions/env-utils.js +29 -0
  9. package/dist/resources/extensions/get-secrets-from-user.js +5 -24
  10. package/dist/resources/extensions/github-sync/cli.js +284 -0
  11. package/dist/resources/extensions/github-sync/index.js +73 -0
  12. package/dist/resources/extensions/github-sync/mapping.js +67 -0
  13. package/dist/resources/extensions/github-sync/sync.js +424 -0
  14. package/dist/resources/extensions/github-sync/templates.js +118 -0
  15. package/dist/resources/extensions/github-sync/types.js +7 -0
  16. package/dist/resources/extensions/gsd/auto/session.js +6 -23
  17. package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
  18. package/dist/resources/extensions/gsd/auto-loop.js +636 -594
  19. package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
  20. package/dist/resources/extensions/gsd/auto-prompts.js +202 -48
  21. package/dist/resources/extensions/gsd/auto-start.js +7 -1
  22. package/dist/resources/extensions/gsd/auto-worktree-sync.js +2 -1
  23. package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
  24. package/dist/resources/extensions/gsd/auto.js +143 -96
  25. package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
  26. package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
  27. package/dist/resources/extensions/gsd/commands.js +4 -2
  28. package/dist/resources/extensions/gsd/context-budget.js +2 -10
  29. package/dist/resources/extensions/gsd/detection.js +1 -2
  30. package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  31. package/dist/resources/extensions/gsd/doctor-providers.js +30 -11
  32. package/dist/resources/extensions/gsd/doctor.js +20 -1
  33. package/dist/resources/extensions/gsd/exit-command.js +2 -1
  34. package/dist/resources/extensions/gsd/export.js +1 -1
  35. package/dist/resources/extensions/gsd/files.js +48 -9
  36. package/dist/resources/extensions/gsd/forensics.js +1 -1
  37. package/dist/resources/extensions/gsd/git-service.js +30 -12
  38. package/dist/resources/extensions/gsd/gitignore.js +16 -3
  39. package/dist/resources/extensions/gsd/guided-flow.js +149 -38
  40. package/dist/resources/extensions/gsd/health-widget-core.js +32 -70
  41. package/dist/resources/extensions/gsd/health-widget.js +3 -86
  42. package/dist/resources/extensions/gsd/index.js +24 -20
  43. package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
  44. package/dist/resources/extensions/gsd/migrate-external.js +18 -1
  45. package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
  46. package/dist/resources/extensions/gsd/paths.js +3 -0
  47. package/dist/resources/extensions/gsd/preferences-models.js +0 -12
  48. package/dist/resources/extensions/gsd/preferences-types.js +1 -1
  49. package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
  50. package/dist/resources/extensions/gsd/preferences.js +22 -11
  51. package/dist/resources/extensions/gsd/prompt-loader.js +6 -2
  52. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  53. package/dist/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  54. package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
  55. package/dist/resources/extensions/gsd/prompts/execute-task.md +5 -3
  56. package/dist/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  57. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  58. package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  59. package/dist/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  60. package/dist/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  61. package/dist/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  62. package/dist/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  63. package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  64. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  65. package/dist/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  66. package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
  67. package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  68. package/dist/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  69. package/dist/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  70. package/dist/resources/extensions/gsd/prompts/research-slice.md +1 -1
  71. package/dist/resources/extensions/gsd/prompts/run-uat.md +28 -11
  72. package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  73. package/dist/resources/extensions/gsd/repo-identity.js +21 -4
  74. package/dist/resources/extensions/gsd/resource-version.js +2 -1
  75. package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
  76. package/dist/resources/extensions/gsd/state.js +42 -23
  77. package/dist/resources/extensions/gsd/templates/runtime.md +21 -0
  78. package/dist/resources/extensions/gsd/templates/task-plan.md +3 -0
  79. package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
  80. package/dist/resources/extensions/mcp-client/index.js +14 -1
  81. package/dist/resources/extensions/remote-questions/status.js +4 -1
  82. package/dist/resources/extensions/remote-questions/store.js +4 -1
  83. package/dist/resources/extensions/search-the-web/provider.js +2 -1
  84. package/dist/resources/extensions/shared/frontmatter.js +1 -1
  85. package/dist/resources/extensions/subagent/isolation.js +2 -1
  86. package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
  87. package/package.json +1 -1
  88. package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
  89. package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
  90. package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
  91. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
  93. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/core/skills.d.ts +1 -0
  95. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  96. package/packages/pi-coding-agent/dist/core/skills.js +6 -1
  97. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  98. package/packages/pi-coding-agent/dist/index.d.ts +1 -1
  99. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  100. package/packages/pi-coding-agent/dist/index.js +1 -1
  101. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  102. package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
  103. package/packages/pi-coding-agent/src/core/skills.ts +9 -1
  104. package/packages/pi-coding-agent/src/index.ts +1 -0
  105. package/src/resources/extensions/browser-tools/index.ts +3 -0
  106. package/src/resources/extensions/browser-tools/tools/verify.ts +117 -0
  107. package/src/resources/extensions/env-utils.ts +31 -0
  108. package/src/resources/extensions/get-secrets-from-user.ts +5 -24
  109. package/src/resources/extensions/github-sync/cli.ts +364 -0
  110. package/src/resources/extensions/github-sync/index.ts +93 -0
  111. package/src/resources/extensions/github-sync/mapping.ts +81 -0
  112. package/src/resources/extensions/github-sync/sync.ts +556 -0
  113. package/src/resources/extensions/github-sync/templates.ts +183 -0
  114. package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
  115. package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
  116. package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
  117. package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
  118. package/src/resources/extensions/github-sync/types.ts +47 -0
  119. package/src/resources/extensions/gsd/auto/session.ts +7 -25
  120. package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
  121. package/src/resources/extensions/gsd/auto-loop.ts +526 -545
  122. package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
  123. package/src/resources/extensions/gsd/auto-prompts.ts +247 -50
  124. package/src/resources/extensions/gsd/auto-start.ts +11 -1
  125. package/src/resources/extensions/gsd/auto-worktree-sync.ts +3 -1
  126. package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
  127. package/src/resources/extensions/gsd/auto.ts +139 -101
  128. package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
  129. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
  130. package/src/resources/extensions/gsd/commands.ts +5 -3
  131. package/src/resources/extensions/gsd/context-budget.ts +2 -12
  132. package/src/resources/extensions/gsd/detection.ts +2 -2
  133. package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
  134. package/src/resources/extensions/gsd/doctor-providers.ts +30 -9
  135. package/src/resources/extensions/gsd/doctor.ts +22 -1
  136. package/src/resources/extensions/gsd/exit-command.ts +2 -2
  137. package/src/resources/extensions/gsd/export.ts +1 -1
  138. package/src/resources/extensions/gsd/files.ts +51 -11
  139. package/src/resources/extensions/gsd/forensics.ts +1 -1
  140. package/src/resources/extensions/gsd/git-service.ts +44 -10
  141. package/src/resources/extensions/gsd/gitignore.ts +17 -3
  142. package/src/resources/extensions/gsd/guided-flow.ts +177 -44
  143. package/src/resources/extensions/gsd/health-widget-core.ts +28 -80
  144. package/src/resources/extensions/gsd/health-widget.ts +3 -89
  145. package/src/resources/extensions/gsd/index.ts +24 -17
  146. package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
  147. package/src/resources/extensions/gsd/migrate-external.ts +18 -1
  148. package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
  149. package/src/resources/extensions/gsd/paths.ts +4 -0
  150. package/src/resources/extensions/gsd/preferences-models.ts +0 -12
  151. package/src/resources/extensions/gsd/preferences-types.ts +4 -4
  152. package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
  153. package/src/resources/extensions/gsd/preferences.ts +25 -11
  154. package/src/resources/extensions/gsd/prompt-loader.ts +7 -2
  155. package/src/resources/extensions/gsd/prompts/complete-milestone.md +1 -1
  156. package/src/resources/extensions/gsd/prompts/complete-slice.md +1 -1
  157. package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
  158. package/src/resources/extensions/gsd/prompts/execute-task.md +5 -3
  159. package/src/resources/extensions/gsd/prompts/guided-complete-slice.md +1 -1
  160. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
  161. package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
  162. package/src/resources/extensions/gsd/prompts/guided-execute-task.md +1 -1
  163. package/src/resources/extensions/gsd/prompts/guided-plan-milestone.md +1 -1
  164. package/src/resources/extensions/gsd/prompts/guided-plan-slice.md +1 -1
  165. package/src/resources/extensions/gsd/prompts/guided-research-slice.md +1 -1
  166. package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/plan-milestone.md +1 -1
  168. package/src/resources/extensions/gsd/prompts/plan-slice.md +1 -1
  169. package/src/resources/extensions/gsd/prompts/queue.md +4 -8
  170. package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
  171. package/src/resources/extensions/gsd/prompts/reassess-roadmap.md +1 -1
  172. package/src/resources/extensions/gsd/prompts/research-milestone.md +1 -1
  173. package/src/resources/extensions/gsd/prompts/research-slice.md +1 -1
  174. package/src/resources/extensions/gsd/prompts/run-uat.md +28 -11
  175. package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
  176. package/src/resources/extensions/gsd/repo-identity.ts +23 -4
  177. package/src/resources/extensions/gsd/resource-version.ts +3 -1
  178. package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
  179. package/src/resources/extensions/gsd/state.ts +39 -21
  180. package/src/resources/extensions/gsd/templates/runtime.md +21 -0
  181. package/src/resources/extensions/gsd/templates/task-plan.md +3 -0
  182. package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
  183. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
  184. package/src/resources/extensions/gsd/tests/auto-worktree-milestone-merge.test.ts +4 -3
  185. package/src/resources/extensions/gsd/tests/derive-state.test.ts +43 -0
  186. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
  187. package/src/resources/extensions/gsd/tests/gitignore-tracked-gsd.test.ts +50 -0
  188. package/src/resources/extensions/gsd/tests/health-widget.test.ts +16 -54
  189. package/src/resources/extensions/gsd/tests/parsers.test.ts +131 -14
  190. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +209 -0
  191. package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
  192. package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
  193. package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
  194. package/src/resources/extensions/gsd/tests/run-uat.test.ts +16 -4
  195. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +140 -0
  196. package/src/resources/extensions/gsd/types.ts +18 -1
  197. package/src/resources/extensions/gsd/verification-evidence.ts +16 -0
  198. package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
  199. package/src/resources/extensions/mcp-client/index.ts +17 -1
  200. package/src/resources/extensions/remote-questions/status.ts +5 -1
  201. package/src/resources/extensions/remote-questions/store.ts +5 -1
  202. package/src/resources/extensions/search-the-web/provider.ts +2 -1
  203. package/src/resources/extensions/shared/frontmatter.ts +1 -1
  204. package/src/resources/extensions/subagent/isolation.ts +3 -1
  205. package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
  206. package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
  207. package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
  208. package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
  209. package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
  210. package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
  211. package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
  212. package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
  213. package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
  214. package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
  215. package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
  216. package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
  217. package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
@@ -6,25 +6,32 @@
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 { compressToTarget } from "./prompt-compressor.js";
25
- import { distillSummaries } from "./summary-distiller.js";
24
+ import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
26
25
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
27
- import { chunkByRelevance, formatChunks } from "./semantic-chunker.js";
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
+ }
28
35
 
29
36
  // ─── Executor Constraints ─────────────────────────────────────────────────────
30
37
 
@@ -159,16 +166,9 @@ export async function inlineFileSmart(
159
166
  return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
160
167
  }
161
168
 
162
- // Use semantic chunking for large files
163
- const result = chunkByRelevance(content, query, { maxChunks: 5, minScore: 0.05 });
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}`;
169
+ // For large files, truncate at section boundary
170
+ const truncated = truncateAtSectionBoundary(content, threshold).content;
171
+ return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
172
172
  }
173
173
 
174
174
  /**
@@ -202,21 +202,6 @@ export async function inlineDependencySummaries(
202
202
 
203
203
  const result = sections.join("\n\n");
204
204
  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
205
  return truncateAtSectionBoundary(result, budgetChars).content;
221
206
  }
222
207
  return result;
@@ -313,7 +298,171 @@ export async function inlineProjectFromDb(
313
298
  return inlineGsdRootFile(base, "project.md", "Project");
314
299
  }
315
300
 
316
- // ─── 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
+ }
317
466
 
318
467
  /**
319
468
  * Build the skill discovery template variables for research prompts.
@@ -634,7 +783,7 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
634
783
  if (knowledgeInlineRM) inlined.push(knowledgeInlineRM);
635
784
  inlined.push(inlineTemplate("research", "Research"));
636
785
 
637
- 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")}`);
638
787
 
639
788
  const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
640
789
  return loadPrompt("research-milestone", {
@@ -644,6 +793,12 @@ export async function buildResearchMilestonePrompt(mid: string, midTitle: string
644
793
  contextPath: contextRel,
645
794
  outputPath: join(base, outputRelPath),
646
795
  inlinedContext,
796
+ skillActivation: buildSkillActivationBlock({
797
+ base,
798
+ milestoneId: mid,
799
+ milestoneTitle: midTitle,
800
+ extraContext: [inlinedContext],
801
+ }),
647
802
  ...buildSkillDiscoveryVars(),
648
803
  });
649
804
  }
@@ -684,7 +839,7 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
684
839
  inlined.push(inlineTemplate("task-plan", "Task Plan"));
685
840
  }
686
841
 
687
- 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")}`);
688
843
 
689
844
  const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
690
845
  const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
@@ -700,6 +855,12 @@ export async function buildPlanMilestonePrompt(mid: string, midTitle: string, ba
700
855
  secretsOutputPath,
701
856
  inlinedContext,
702
857
  sourceFilePaths: buildSourceFilePaths(base, mid),
858
+ skillActivation: buildSkillActivationBlock({
859
+ base,
860
+ milestoneId: mid,
861
+ milestoneTitle: midTitle,
862
+ extraContext: [inlinedContext],
863
+ }),
703
864
  ...buildSkillDiscoveryVars(),
704
865
  });
705
866
  }
@@ -733,7 +894,7 @@ export async function buildResearchSlicePrompt(
733
894
  const overridesInline = formatOverridesSection(activeOverrides);
734
895
  if (overridesInline) inlined.unshift(overridesInline);
735
896
 
736
- 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")}`);
737
898
 
738
899
  const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
739
900
  return loadPrompt("research-slice", {
@@ -746,6 +907,13 @@ export async function buildResearchSlicePrompt(
746
907
  outputPath: join(base, outputRelPath),
747
908
  inlinedContext,
748
909
  dependencySummaries: depContent,
910
+ skillActivation: buildSkillActivationBlock({
911
+ base,
912
+ milestoneId: mid,
913
+ sliceId: sid,
914
+ sliceTitle: sTitle,
915
+ extraContext: [inlinedContext, depContent],
916
+ }),
749
917
  ...buildSkillDiscoveryVars(),
750
918
  });
751
919
  }
@@ -781,7 +949,7 @@ export async function buildPlanSlicePrompt(
781
949
  const planOverridesInline = formatOverridesSection(planActiveOverrides);
782
950
  if (planOverridesInline) inlined.unshift(planOverridesInline);
783
951
 
784
- 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")}`);
785
953
 
786
954
  // Build executor context constraints from the budget engine
787
955
  const executorContextConstraints = formatExecutorConstraints();
@@ -804,6 +972,13 @@ export async function buildPlanSlicePrompt(
804
972
  sourceFilePaths: buildSourceFilePaths(base, mid, sid),
805
973
  executorContextConstraints,
806
974
  commitInstruction,
975
+ skillActivation: buildSkillActivationBlock({
976
+ base,
977
+ milestoneId: mid,
978
+ sliceId: sid,
979
+ sliceTitle: sTitle,
980
+ extraContext: [inlinedContext, depContent],
981
+ }),
807
982
  });
808
983
  }
809
984
 
@@ -900,19 +1075,23 @@ export async function buildExecuteTaskPrompt(
900
1075
  const budgets = computeBudgets(contextWindow);
901
1076
  const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
902
1077
 
903
- // Compress carry-forward section when it exceeds 40% of inline context budget.
904
- // Only compress when compression_strategy is "compress" (budget/balanced profiles).
1078
+ // Truncate carry-forward section when it exceeds 40% of inline context budget.
905
1079
  const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
906
1080
  let finalCarryForward = carryForwardSection;
907
1081
  if (carryForwardSection.length > carryForwardBudget) {
908
- const { resolveCompressionStrategy } = await import("./preferences.js");
909
- if (resolveCompressionStrategy() === "compress") {
910
- finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
911
- }
1082
+ finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
912
1083
  }
913
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
+
914
1092
  return loadPrompt("execute-task", {
915
1093
  overridesSection,
1094
+ runtimeContext,
916
1095
  workingDirectory: base,
917
1096
  milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
918
1097
  planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
@@ -926,6 +1105,16 @@ export async function buildExecuteTaskPrompt(
926
1105
  taskSummaryPath,
927
1106
  inlinedTemplates,
928
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
+ }),
929
1118
  });
930
1119
  }
931
1120
 
@@ -971,7 +1160,7 @@ export async function buildCompleteSlicePrompt(
971
1160
  const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
972
1161
  if (completeOverridesInline) inlined.unshift(completeOverridesInline);
973
1162
 
974
- 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")}`);
975
1164
 
976
1165
  const sliceRel = relSlicePath(base, mid, sid);
977
1166
  const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
@@ -1030,7 +1219,7 @@ export async function buildCompleteMilestonePrompt(
1030
1219
  if (contextInline) inlined.push(contextInline);
1031
1220
  inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
1032
1221
 
1033
- 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")}`);
1034
1223
 
1035
1224
  const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
1036
1225
 
@@ -1101,7 +1290,7 @@ export async function buildValidateMilestonePrompt(
1101
1290
  const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
1102
1291
  if (contextInline) inlined.push(contextInline);
1103
1292
 
1104
- 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")}`);
1105
1294
 
1106
1295
  const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
1107
1296
  const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
@@ -1155,7 +1344,7 @@ export async function buildReplanSlicePrompt(
1155
1344
  const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
1156
1345
  if (replanOverridesInline) inlined.unshift(replanOverridesInline);
1157
1346
 
1158
- 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")}`);
1159
1348
 
1160
1349
  const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
1161
1350
 
@@ -1184,6 +1373,14 @@ export async function buildReplanSlicePrompt(
1184
1373
  inlinedContext,
1185
1374
  replanPath,
1186
1375
  captureContext,
1376
+ skillActivation: buildSkillActivationBlock({
1377
+ base,
1378
+ milestoneId: mid,
1379
+ milestoneTitle: midTitle,
1380
+ sliceId: sid,
1381
+ sliceTitle: sTitle,
1382
+ extraContext: [inlinedContext, captureContext],
1383
+ }),
1187
1384
  });
1188
1385
  }
1189
1386
 
@@ -1203,7 +1400,7 @@ export async function buildRunUatPrompt(
1203
1400
  const projectInline = await inlineProjectFromDb(base);
1204
1401
  if (projectInline) inlined.push(projectInline);
1205
1402
 
1206
- 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")}`);
1207
1404
 
1208
1405
  const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
1209
1406
  const uatType = extractUatType(uatContent) ?? "human-experience";
@@ -1242,7 +1439,7 @@ export async function buildReassessRoadmapPrompt(
1242
1439
  const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
1243
1440
  if (knowledgeInlineRA) inlined.push(knowledgeInlineRA);
1244
1441
 
1245
- 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")}`);
1246
1443
 
1247
1444
  const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
1248
1445
 
@@ -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 =
@@ -22,6 +22,8 @@ import { join, sep as pathSep } from "node:path";
22
22
  import { homedir } from "node:os";
23
23
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
24
24
 
25
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
26
+
25
27
  // ─── Project Root → Worktree Sync ─────────────────────────────────────────
26
28
 
27
29
  /**
@@ -111,7 +113,7 @@ export function syncStateToProjectRoot(
111
113
  */
112
114
  export function readResourceVersion(): string | null {
113
115
  const agentDir =
114
- process.env.GSD_CODING_AGENT_DIR || join(homedir(), ".gsd", "agent");
116
+ process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
115
117
  const manifestPath = join(agentDir, "managed-resources.json");
116
118
  try {
117
119
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -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",