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
@@ -5,17 +5,22 @@
5
5
  * state, no globals — every dependency is passed as a parameter or imported as a
6
6
  * utility.
7
7
  */
8
- import { loadFile, parseContinue, parsePlan, parseRoadmap, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection } from "./files.js";
8
+ import { loadFile, parseContinue, parsePlan, parseRoadmap, parseSummary, extractUatType, loadActiveOverrides, formatOverridesSection, parseTaskPlanFile } from "./files.js";
9
9
  import { loadPrompt, inlineTemplate } from "./prompt-loader.js";
10
- import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, } from "./paths.js";
11
- import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences } from "./preferences.js";
12
- import { join } from "node:path";
10
+ import { resolveMilestoneFile, resolveSliceFile, resolveSlicePath, resolveTasksDir, resolveTaskFiles, resolveTaskFile, relMilestoneFile, relSliceFile, relSlicePath, relMilestonePath, resolveGsdRootFile, relGsdRootFile, resolveRuntimeFile, } from "./paths.js";
11
+ import { resolveSkillDiscoveryMode, resolveInlineLevel, loadEffectiveGSDPreferences, resolveAllSkillReferences } from "./preferences.js";
12
+ import { getLoadedSkills } from "@gsd/pi-coding-agent";
13
+ import { join, basename } from "node:path";
13
14
  import { existsSync } from "node:fs";
14
- import { computeBudgets, resolveExecutorContextWindow } from "./context-budget.js";
15
- import { compressToTarget } from "./prompt-compressor.js";
16
- import { distillSummaries } from "./summary-distiller.js";
15
+ import { computeBudgets, resolveExecutorContextWindow, truncateAtSectionBoundary } from "./context-budget.js";
17
16
  import { formatDecisionsCompact, formatRequirementsCompact } from "./structured-data-formatter.js";
18
- import { chunkByRelevance, formatChunks } from "./semantic-chunker.js";
17
+ // ─── Preamble Cap ─────────────────────────────────────────────────────────────
18
+ const MAX_PREAMBLE_CHARS = 30_000;
19
+ function capPreamble(preamble) {
20
+ if (preamble.length <= MAX_PREAMBLE_CHARS)
21
+ return preamble;
22
+ return truncateAtSectionBoundary(preamble, MAX_PREAMBLE_CHARS).content;
23
+ }
19
24
  // ─── Executor Constraints ─────────────────────────────────────────────────────
20
25
  /**
21
26
  * Format executor context constraints for injection into the plan-slice prompt.
@@ -126,14 +131,9 @@ export async function inlineFileSmart(absPath, relPath, label, query, threshold
126
131
  if (content.length <= threshold || !query) {
127
132
  return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
128
133
  }
129
- // Use semantic chunking for large files
130
- const result = chunkByRelevance(content, query, { maxChunks: 5, minScore: 0.05 });
131
- // If chunking didn't save much (< 20%), just include full content
132
- if (result.savingsPercent < 20) {
133
- return `### ${label}\nSource: \`${relPath}\`\n\n${content.trim()}`;
134
- }
135
- const formatted = formatChunks(result, relPath);
136
- return `### ${label} (${result.omittedChunks} sections omitted for relevance)\nSource: \`${relPath}\`\n\n${formatted}`;
134
+ // For large files, truncate at section boundary
135
+ const truncated = truncateAtSectionBoundary(content, threshold).content;
136
+ return `### ${label}\nSource: \`${relPath}\`\n\n${truncated}`;
137
137
  }
138
138
  /**
139
139
  * Load and inline dependency slice summaries (full content, not just paths).
@@ -165,21 +165,6 @@ export async function inlineDependencySummaries(mid, sid, base, budgetChars) {
165
165
  }
166
166
  const result = sections.join("\n\n");
167
167
  if (budgetChars !== undefined && result.length > budgetChars) {
168
- // For 3+ summaries, try distillation first (preserves more information)
169
- if (sections.length >= 3) {
170
- const rawSummaries = sections.map(s => {
171
- // Extract content after the header line
172
- const lines = s.split("\n");
173
- const contentStart = lines.findIndex(l => l.startsWith("Source:"));
174
- return contentStart >= 0 ? lines.slice(contentStart + 1).join("\n").trim() : s;
175
- });
176
- const distilled = distillSummaries(rawSummaries, budgetChars);
177
- if (distilled.content.length <= budgetChars) {
178
- return distilled.content;
179
- }
180
- }
181
- // Fall back to section-boundary truncation
182
- const { truncateAtSectionBoundary } = await import("./context-budget.js");
183
168
  return truncateAtSectionBoundary(result, budgetChars).content;
184
169
  }
185
170
  return result;
@@ -266,7 +251,129 @@ export async function inlineProjectFromDb(base) {
266
251
  }
267
252
  return inlineGsdRootFile(base, "project.md", "Project");
268
253
  }
269
- // ─── Skill Discovery ──────────────────────────────────────────────────────
254
+ // ─── Skill Activation & Discovery ─────────────────────────────────────────
255
+ function normalizeSkillReference(ref) {
256
+ const normalized = ref.replace(/\\/g, "/").trim();
257
+ const base = basename(normalized).replace(/\.md$/i, "");
258
+ const name = /^SKILL$/i.test(base)
259
+ ? basename(normalized.replace(/\/SKILL(?:\.md)?$/i, ""))
260
+ : base;
261
+ return name.trim().toLowerCase();
262
+ }
263
+ function tokenizeSkillContext(...parts) {
264
+ const tokens = new Set();
265
+ const addVariants = (raw) => {
266
+ const value = raw.trim().toLowerCase();
267
+ if (!value || value.length < 2)
268
+ return;
269
+ tokens.add(value);
270
+ tokens.add(value.replace(/[-_]+/g, " "));
271
+ tokens.add(value.replace(/\s+/g, "-"));
272
+ tokens.add(value.replace(/\s+/g, ""));
273
+ };
274
+ for (const part of parts) {
275
+ if (!part)
276
+ continue;
277
+ const text = part.toLowerCase();
278
+ const phraseMatches = text.match(/[a-z0-9][a-z0-9+.#/_-]{1,}/g) ?? [];
279
+ for (const match of phraseMatches) {
280
+ addVariants(match);
281
+ for (const piece of match.split(/[^a-z0-9+.#]+/g)) {
282
+ if (piece.length >= 3)
283
+ addVariants(piece);
284
+ }
285
+ }
286
+ }
287
+ return tokens;
288
+ }
289
+ function skillMatchesContext(skill, contextTokens) {
290
+ const haystacks = [
291
+ skill.name.toLowerCase(),
292
+ skill.name.toLowerCase().replace(/[-_]+/g, " "),
293
+ skill.description.toLowerCase(),
294
+ ];
295
+ return [...contextTokens].some(token => token.length >= 3 && haystacks.some(haystack => haystack.includes(token)));
296
+ }
297
+ function resolvePreferenceSkillNames(refs, base) {
298
+ if (refs.length === 0)
299
+ return [];
300
+ const prefs = { always_use_skills: refs };
301
+ const report = resolveAllSkillReferences(prefs, base);
302
+ return refs.map(ref => {
303
+ const resolution = report.resolutions.get(ref);
304
+ return normalizeSkillReference(resolution?.resolvedPath ?? ref);
305
+ }).filter(Boolean);
306
+ }
307
+ function ruleMatchesContext(when, contextTokens) {
308
+ const whenTokens = tokenizeSkillContext(when);
309
+ return [...whenTokens].some(token => contextTokens.has(token) || [...contextTokens].some(ctx => ctx.includes(token) || token.includes(ctx)));
310
+ }
311
+ function resolveSkillRuleMatches(prefs, contextTokens, base) {
312
+ if (!prefs?.skill_rules?.length)
313
+ return { include: [], avoid: [] };
314
+ const include = [];
315
+ const avoid = [];
316
+ for (const rule of prefs.skill_rules) {
317
+ if (!ruleMatchesContext(rule.when, contextTokens))
318
+ continue;
319
+ include.push(...resolvePreferenceSkillNames([...(rule.use ?? []), ...(rule.prefer ?? [])], base));
320
+ avoid.push(...resolvePreferenceSkillNames(rule.avoid ?? [], base));
321
+ }
322
+ return { include, avoid };
323
+ }
324
+ function resolvePreferredSkillNames(prefs, visibleSkills, contextTokens, base) {
325
+ if (!prefs?.prefer_skills?.length)
326
+ return [];
327
+ const preferred = new Set(resolvePreferenceSkillNames(prefs.prefer_skills, base));
328
+ return visibleSkills
329
+ .filter(skill => preferred.has(normalizeSkillReference(skill.name)) && skillMatchesContext(skill, contextTokens))
330
+ .map(skill => normalizeSkillReference(skill.name));
331
+ }
332
+ function formatSkillActivationBlock(skillNames) {
333
+ if (skillNames.length === 0)
334
+ return "";
335
+ const calls = skillNames.map(name => `Call Skill('${name}')`).join('. ');
336
+ return `<skill_activation>${calls}.</skill_activation>`;
337
+ }
338
+ export function buildSkillActivationBlock(params) {
339
+ const prefs = params.preferences ?? loadEffectiveGSDPreferences()?.preferences;
340
+ const contextTokens = tokenizeSkillContext(params.milestoneId, params.milestoneTitle, params.sliceId, params.sliceTitle, params.taskId, params.taskTitle, ...(params.extraContext ?? []), params.taskPlanContent ?? undefined);
341
+ const visibleSkills = getLoadedSkills().filter(skill => !skill.disableModelInvocation);
342
+ const installedNames = new Set(visibleSkills.map(skill => normalizeSkillReference(skill.name)));
343
+ const avoided = new Set(resolvePreferenceSkillNames(prefs?.avoid_skills ?? [], params.base));
344
+ const matched = new Set();
345
+ for (const name of resolvePreferenceSkillNames(prefs?.always_use_skills ?? [], params.base)) {
346
+ matched.add(name);
347
+ }
348
+ const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base);
349
+ for (const name of ruleMatches.include)
350
+ matched.add(name);
351
+ for (const name of ruleMatches.avoid)
352
+ avoided.add(name);
353
+ for (const name of resolvePreferredSkillNames(prefs, visibleSkills, contextTokens, params.base)) {
354
+ matched.add(name);
355
+ }
356
+ if (params.taskPlanContent) {
357
+ try {
358
+ const taskPlan = parseTaskPlanFile(params.taskPlanContent);
359
+ for (const skillName of taskPlan.frontmatter.skills_used) {
360
+ matched.add(normalizeSkillReference(skillName));
361
+ }
362
+ }
363
+ catch {
364
+ // Non-fatal — malformed task plan should not break prompt construction
365
+ }
366
+ }
367
+ for (const skill of visibleSkills) {
368
+ if (skillMatchesContext(skill, contextTokens)) {
369
+ matched.add(normalizeSkillReference(skill.name));
370
+ }
371
+ }
372
+ const ordered = [...matched]
373
+ .filter(name => installedNames.has(name) && !avoided.has(name))
374
+ .sort();
375
+ return formatSkillActivationBlock(ordered);
376
+ }
270
377
  /**
271
378
  * Build the skill discovery template variables for research prompts.
272
379
  * Returns { skillDiscoveryMode, skillDiscoveryInstructions } for template substitution.
@@ -546,7 +653,7 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
546
653
  if (knowledgeInlineRM)
547
654
  inlined.push(knowledgeInlineRM);
548
655
  inlined.push(inlineTemplate("research", "Research"));
549
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
656
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
550
657
  const outputRelPath = relMilestoneFile(base, mid, "RESEARCH");
551
658
  return loadPrompt("research-milestone", {
552
659
  workingDirectory: base,
@@ -555,6 +662,12 @@ export async function buildResearchMilestonePrompt(mid, midTitle, base) {
555
662
  contextPath: contextRel,
556
663
  outputPath: join(base, outputRelPath),
557
664
  inlinedContext,
665
+ skillActivation: buildSkillActivationBlock({
666
+ base,
667
+ milestoneId: mid,
668
+ milestoneTitle: midTitle,
669
+ extraContext: [inlinedContext],
670
+ }),
558
671
  ...buildSkillDiscoveryVars(),
559
672
  });
560
673
  }
@@ -599,7 +712,7 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
599
712
  inlined.push(inlineTemplate("plan", "Slice Plan"));
600
713
  inlined.push(inlineTemplate("task-plan", "Task Plan"));
601
714
  }
602
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
715
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
603
716
  const outputRelPath = relMilestoneFile(base, mid, "ROADMAP");
604
717
  const researchOutputPath = join(base, relMilestoneFile(base, mid, "RESEARCH"));
605
718
  const secretsOutputPath = join(base, relMilestoneFile(base, mid, "SECRETS"));
@@ -614,6 +727,12 @@ export async function buildPlanMilestonePrompt(mid, midTitle, base, level) {
614
727
  secretsOutputPath,
615
728
  inlinedContext,
616
729
  sourceFilePaths: buildSourceFilePaths(base, mid),
730
+ skillActivation: buildSkillActivationBlock({
731
+ base,
732
+ milestoneId: mid,
733
+ milestoneTitle: midTitle,
734
+ extraContext: [inlinedContext],
735
+ }),
617
736
  ...buildSkillDiscoveryVars(),
618
737
  });
619
738
  }
@@ -647,7 +766,7 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
647
766
  const overridesInline = formatOverridesSection(activeOverrides);
648
767
  if (overridesInline)
649
768
  inlined.unshift(overridesInline);
650
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
769
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
651
770
  const outputRelPath = relSliceFile(base, mid, sid, "RESEARCH");
652
771
  return loadPrompt("research-slice", {
653
772
  workingDirectory: base,
@@ -659,6 +778,13 @@ export async function buildResearchSlicePrompt(mid, _midTitle, sid, sTitle, base
659
778
  outputPath: join(base, outputRelPath),
660
779
  inlinedContext,
661
780
  dependencySummaries: depContent,
781
+ skillActivation: buildSkillActivationBlock({
782
+ base,
783
+ milestoneId: mid,
784
+ sliceId: sid,
785
+ sliceTitle: sTitle,
786
+ extraContext: [inlinedContext, depContent],
787
+ }),
662
788
  ...buildSkillDiscoveryVars(),
663
789
  });
664
790
  }
@@ -693,7 +819,7 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
693
819
  const planOverridesInline = formatOverridesSection(planActiveOverrides);
694
820
  if (planOverridesInline)
695
821
  inlined.unshift(planOverridesInline);
696
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
822
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
697
823
  // Build executor context constraints from the budget engine
698
824
  const executorContextConstraints = formatExecutorConstraints();
699
825
  const outputRelPath = relSliceFile(base, mid, sid, "PLAN");
@@ -714,6 +840,13 @@ export async function buildPlanSlicePrompt(mid, _midTitle, sid, sTitle, base, le
714
840
  sourceFilePaths: buildSourceFilePaths(base, mid, sid),
715
841
  executorContextConstraints,
716
842
  commitInstruction,
843
+ skillActivation: buildSkillActivationBlock({
844
+ base,
845
+ milestoneId: mid,
846
+ sliceId: sid,
847
+ sliceTitle: sTitle,
848
+ extraContext: [inlinedContext, depContent],
849
+ }),
717
850
  });
718
851
  }
719
852
  export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base, level) {
@@ -777,18 +910,21 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
777
910
  const contextWindow = resolveExecutorContextWindow(undefined, prefs?.preferences);
778
911
  const budgets = computeBudgets(contextWindow);
779
912
  const verificationBudget = `~${Math.round(budgets.verificationBudgetChars / 1000)}K chars`;
780
- // Compress carry-forward section when it exceeds 40% of inline context budget.
781
- // Only compress when compression_strategy is "compress" (budget/balanced profiles).
913
+ // Truncate carry-forward section when it exceeds 40% of inline context budget.
782
914
  const carryForwardBudget = Math.floor(budgets.inlineContextBudgetChars * 0.4);
783
915
  let finalCarryForward = carryForwardSection;
784
916
  if (carryForwardSection.length > carryForwardBudget) {
785
- const { resolveCompressionStrategy } = await import("./preferences.js");
786
- if (resolveCompressionStrategy() === "compress") {
787
- finalCarryForward = compressToTarget(carryForwardSection, carryForwardBudget).content;
788
- }
917
+ finalCarryForward = truncateAtSectionBoundary(carryForwardSection, carryForwardBudget).content;
789
918
  }
919
+ // Inline RUNTIME.md if present
920
+ const runtimePath = resolveRuntimeFile(base);
921
+ const runtimeContent = existsSync(runtimePath) ? await loadFile(runtimePath) : null;
922
+ const runtimeContext = runtimeContent
923
+ ? `### Runtime Context\nSource: \`.gsd/RUNTIME.md\`\n\n${runtimeContent.trim()}`
924
+ : "";
790
925
  return loadPrompt("execute-task", {
791
926
  overridesSection,
927
+ runtimeContext,
792
928
  workingDirectory: base,
793
929
  milestoneId: mid, sliceId: sid, sliceTitle: sTitle, taskId: tid, taskTitle: tTitle,
794
930
  planPath: join(base, relSliceFile(base, mid, sid, "PLAN")),
@@ -802,6 +938,16 @@ export async function buildExecuteTaskPrompt(mid, sid, sTitle, tid, tTitle, base
802
938
  taskSummaryPath,
803
939
  inlinedTemplates,
804
940
  verificationBudget,
941
+ skillActivation: buildSkillActivationBlock({
942
+ base,
943
+ milestoneId: mid,
944
+ sliceId: sid,
945
+ sliceTitle: sTitle,
946
+ taskId: tid,
947
+ taskTitle: tTitle,
948
+ taskPlanContent,
949
+ extraContext: [taskPlanInline, slicePlanExcerpt, finalCarryForward, resumeSection],
950
+ }),
805
951
  });
806
952
  }
807
953
  export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base, level) {
@@ -843,7 +989,7 @@ export async function buildCompleteSlicePrompt(mid, _midTitle, sid, sTitle, base
843
989
  const completeOverridesInline = formatOverridesSection(completeActiveOverrides);
844
990
  if (completeOverridesInline)
845
991
  inlined.unshift(completeOverridesInline);
846
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
992
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
847
993
  const sliceRel = relSlicePath(base, mid, sid);
848
994
  const sliceSummaryPath = join(base, `${sliceRel}/${sid}-SUMMARY.md`);
849
995
  const sliceUatPath = join(base, `${sliceRel}/${sid}-UAT.md`);
@@ -899,7 +1045,7 @@ export async function buildCompleteMilestonePrompt(mid, midTitle, base, level) {
899
1045
  if (contextInline)
900
1046
  inlined.push(contextInline);
901
1047
  inlined.push(inlineTemplate("milestone-summary", "Milestone Summary"));
902
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1048
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
903
1049
  const milestoneSummaryPath = join(base, `${relMilestonePath(base, mid)}/${mid}-SUMMARY.md`);
904
1050
  return loadPrompt("complete-milestone", {
905
1051
  workingDirectory: base,
@@ -966,7 +1112,7 @@ export async function buildValidateMilestonePrompt(mid, midTitle, base, level) {
966
1112
  const contextInline = await inlineFileOptional(contextPath, contextRel, "Milestone Context");
967
1113
  if (contextInline)
968
1114
  inlined.push(contextInline);
969
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1115
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
970
1116
  const validationOutputPath = join(base, `${relMilestonePath(base, mid)}/${mid}-VALIDATION.md`);
971
1117
  const roadmapOutputPath = `${relMilestonePath(base, mid)}/${mid}-ROADMAP.md`;
972
1118
  return loadPrompt("validate-milestone", {
@@ -1014,7 +1160,7 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
1014
1160
  const replanOverridesInline = formatOverridesSection(replanActiveOverrides);
1015
1161
  if (replanOverridesInline)
1016
1162
  inlined.unshift(replanOverridesInline);
1017
- 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")}`);
1018
1164
  const replanPath = join(base, `${relSlicePath(base, mid, sid)}/${sid}-REPLAN.md`);
1019
1165
  // Build capture context for replan prompt (captures that triggered this replan)
1020
1166
  let captureContext = "(none)";
@@ -1039,6 +1185,14 @@ export async function buildReplanSlicePrompt(mid, midTitle, sid, sTitle, base) {
1039
1185
  inlinedContext,
1040
1186
  replanPath,
1041
1187
  captureContext,
1188
+ skillActivation: buildSkillActivationBlock({
1189
+ base,
1190
+ milestoneId: mid,
1191
+ milestoneTitle: midTitle,
1192
+ sliceId: sid,
1193
+ sliceTitle: sTitle,
1194
+ extraContext: [inlinedContext, captureContext],
1195
+ }),
1042
1196
  });
1043
1197
  }
1044
1198
  export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base) {
@@ -1054,7 +1208,7 @@ export async function buildRunUatPrompt(mid, sliceId, uatPath, uatContent, base)
1054
1208
  const projectInline = await inlineProjectFromDb(base);
1055
1209
  if (projectInline)
1056
1210
  inlined.push(projectInline);
1057
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1211
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1058
1212
  const uatResultPath = join(base, relSliceFile(base, mid, sliceId, "UAT-RESULT"));
1059
1213
  const uatType = extractUatType(uatContent) ?? "human-experience";
1060
1214
  return loadPrompt("run-uat", {
@@ -1090,7 +1244,7 @@ export async function buildReassessRoadmapPrompt(mid, midTitle, completedSliceId
1090
1244
  const knowledgeInlineRA = await inlineGsdRootFile(base, "knowledge.md", "Project Knowledge");
1091
1245
  if (knowledgeInlineRA)
1092
1246
  inlined.push(knowledgeInlineRA);
1093
- const inlinedContext = `## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`;
1247
+ const inlinedContext = capPreamble(`## Inlined Context (preloaded — do not re-read these files)\n\n${inlined.join("\n\n---\n\n")}`);
1094
1248
  const assessmentPath = join(base, relSliceFile(base, mid, completedSliceId, "ASSESSMENT"));
1095
1249
  // Build deferred captures context for reassess prompt
1096
1250
  let deferredCaptures = "(none)";
@@ -11,7 +11,7 @@
11
11
  import { deriveState } from "./state.js";
12
12
  import { loadFile, getManifestStatus } from "./files.js";
13
13
  import { loadEffectiveGSDPreferences, resolveSkillDiscoveryMode, getIsolationMode, } from "./preferences.js";
14
- import { ensureGsdSymlink } from "./repo-identity.js";
14
+ import { ensureGsdSymlink, validateProjectId } from "./repo-identity.js";
15
15
  import { migrateToExternalState, recoverFailedMigration } from "./migrate-external.js";
16
16
  import { collectSecretsFromManifest } from "../get-secrets-from-user.js";
17
17
  import { gsdRoot, resolveMilestoneFile } from "./paths.js";
@@ -63,6 +63,12 @@ export async function bootstrapAutoSession(s, ctx, pi, base, verboseMode, reques
63
63
  return false;
64
64
  }
65
65
  try {
66
+ // Validate GSD_PROJECT_ID early so the user gets immediate feedback
67
+ const customProjectId = process.env.GSD_PROJECT_ID;
68
+ if (customProjectId && !validateProjectId(customProjectId)) {
69
+ ctx.ui.notify(`GSD_PROJECT_ID must contain only alphanumeric characters, hyphens, and underscores. Got: "${customProjectId}"`, "error");
70
+ return releaseLockAndReturn();
71
+ }
66
72
  // Ensure git repo exists
67
73
  if (!nativeIsRepo(base)) {
68
74
  const mainBranch = loadEffectiveGSDPreferences()?.preferences?.git?.main_branch || "main";
@@ -13,6 +13,7 @@ import { existsSync, readFileSync, unlinkSync, readdirSync, } from "node:fs";
13
13
  import { join, sep as pathSep } from "node:path";
14
14
  import { homedir } from "node:os";
15
15
  import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
16
+ const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
16
17
  // ─── Project Root → Worktree Sync ─────────────────────────────────────────
17
18
  /**
18
19
  * Sync milestone artifacts from project root INTO worktree before deriveState.
@@ -75,7 +76,7 @@ export function syncStateToProjectRoot(worktreePath, projectRoot, milestoneId) {
75
76
  * doesn't falsely trigger staleness (#804).
76
77
  */
77
78
  export function readResourceVersion() {
78
- const agentDir = process.env.GSD_CODING_AGENT_DIR || join(homedir(), ".gsd", "agent");
79
+ const agentDir = process.env.GSD_CODING_AGENT_DIR || join(gsdHome, "agent");
79
80
  const manifestPath = join(agentDir, "managed-resources.json");
80
81
  try {
81
82
  const manifest = JSON.parse(readFileSync(manifestPath, "utf-8"));
@@ -15,10 +15,10 @@ import { safeCopy, safeCopyRecursive } from "./safe-fs.js";
15
15
  import { gsdRoot } from "./paths.js";
16
16
  import { createWorktree, removeWorktree, worktreePath, } from "./worktree-manager.js";
17
17
  import { detectWorktreeName, nudgeGitBranchCache, } from "./worktree.js";
18
- import { MergeConflictError, readIntegrationBranch } from "./git-service.js";
18
+ import { MergeConflictError, readIntegrationBranch, RUNTIME_EXCLUSION_PATHS } from "./git-service.js";
19
19
  import { parseRoadmap } from "./files.js";
20
20
  import { loadEffectiveGSDPreferences } from "./preferences.js";
21
- import { nativeGetCurrentBranch, nativeWorkingTreeStatus, nativeAddAll, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, } from "./native-git-bridge.js";
21
+ import { nativeGetCurrentBranch, nativeWorkingTreeStatus, nativeAddAllWithExclusions, nativeCommit, nativeCheckoutBranch, nativeMergeSquash, nativeConflictFiles, nativeCheckoutTheirs, nativeAddPaths, nativeRmForce, nativeBranchDelete, nativeBranchExists, } from "./native-git-bridge.js";
22
22
  // ─── Module State ──────────────────────────────────────────────────────────
23
23
  /** Original project root before chdir into auto-worktree. */
24
24
  let originalBase = null;
@@ -656,7 +656,7 @@ function autoCommitDirtyState(cwd) {
656
656
  const status = nativeWorkingTreeStatus(cwd);
657
657
  if (!status)
658
658
  return false;
659
- nativeAddAll(cwd);
659
+ nativeAddAllWithExclusions(cwd, RUNTIME_EXCLUSION_PATHS);
660
660
  const result = nativeCommit(cwd, "chore: auto-commit before milestone merge");
661
661
  return result !== null;
662
662
  }