@opengsd/gsd-pi 1.0.2-dev.867e002 → 1.0.2-dev.cce3612

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 (318) hide show
  1. package/dist/onboarding.js +22 -3
  2. package/dist/resources/.managed-resources-content-hash +1 -1
  3. package/dist/resources/extensions/context7/index.js +12 -2
  4. package/dist/resources/extensions/get-secrets-from-user.js +16 -16
  5. package/dist/resources/extensions/google-cli/index.js +30 -0
  6. package/dist/resources/extensions/google-cli/models.js +55 -0
  7. package/dist/resources/extensions/google-cli/package.json +11 -0
  8. package/dist/resources/extensions/google-cli/readiness.js +12 -0
  9. package/dist/resources/extensions/google-cli/stream-adapter.js +191 -0
  10. package/dist/resources/extensions/gsd/auto/loop.js +62 -1
  11. package/dist/resources/extensions/gsd/auto/orchestrator.js +4 -2
  12. package/dist/resources/extensions/gsd/auto/phases.js +37 -0
  13. package/dist/resources/extensions/gsd/auto/run-unit.js +8 -0
  14. package/dist/resources/extensions/gsd/auto/session.js +3 -0
  15. package/dist/resources/extensions/gsd/auto-dispatch.js +17 -7
  16. package/dist/resources/extensions/gsd/auto-post-unit.js +18 -2
  17. package/dist/resources/extensions/gsd/auto-prompts.js +5 -236
  18. package/dist/resources/extensions/gsd/auto-recovery.js +10 -5
  19. package/dist/resources/extensions/gsd/auto-start.js +232 -49
  20. package/dist/resources/extensions/gsd/auto.js +6 -1
  21. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +4 -3
  22. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +39 -5
  23. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +17 -7
  24. package/dist/resources/extensions/gsd/bootstrap/system-context.js +3 -27
  25. package/dist/resources/extensions/gsd/closeout-recovery.js +7 -1
  26. package/dist/resources/extensions/gsd/commands/handlers/auto.js +9 -1
  27. package/dist/resources/extensions/gsd/commands-usage.js +105 -1
  28. package/dist/resources/extensions/gsd/config-overlay.js +20 -14
  29. package/dist/resources/extensions/gsd/context-overlay.js +22 -16
  30. package/dist/resources/extensions/gsd/dashboard-overlay.js +10 -23
  31. package/dist/resources/extensions/gsd/doctor-engine-checks.js +87 -0
  32. package/dist/resources/extensions/gsd/doctor-providers.js +54 -24
  33. package/dist/resources/extensions/gsd/doctor.js +6 -1
  34. package/dist/resources/extensions/gsd/git-conflict-state.js +26 -1
  35. package/dist/resources/extensions/gsd/guided-flow.js +5 -6
  36. package/dist/resources/extensions/gsd/key-manager.js +45 -13
  37. package/dist/resources/extensions/gsd/milestone-reopen-events.js +28 -0
  38. package/dist/resources/extensions/gsd/notification-overlay.js +8 -9
  39. package/dist/resources/extensions/gsd/parallel-monitor-overlay.js +15 -13
  40. package/dist/resources/extensions/gsd/preferences-skills.js +11 -4
  41. package/dist/resources/extensions/gsd/preferences.js +14 -2
  42. package/dist/resources/extensions/gsd/prompt-loader.js +2 -0
  43. package/dist/resources/extensions/gsd/prompts/discuss.md +4 -2
  44. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  45. package/dist/resources/extensions/gsd/prompts/system.md +1 -3
  46. package/dist/resources/extensions/gsd/queue-reorder-ui.js +28 -18
  47. package/dist/resources/extensions/gsd/repository-registry.js +3 -1
  48. package/dist/resources/extensions/gsd/skill-activation.js +233 -0
  49. package/dist/resources/extensions/gsd/skill-catalog.data.js +820 -0
  50. package/dist/resources/extensions/gsd/skill-catalog.install.js +179 -0
  51. package/dist/resources/extensions/gsd/skill-catalog.js +5 -1028
  52. package/dist/resources/extensions/gsd/skill-discovery.js +121 -79
  53. package/dist/resources/extensions/gsd/skill-scope.js +52 -0
  54. package/dist/resources/extensions/gsd/skill-telemetry.js +6 -39
  55. package/dist/resources/extensions/gsd/skills.js +60 -0
  56. package/dist/resources/extensions/gsd/state-reconciliation/drift/artifact-db.js +351 -0
  57. package/dist/resources/extensions/gsd/state-reconciliation/index.js +41 -0
  58. package/dist/resources/extensions/gsd/state-reconciliation/registry.js +4 -0
  59. package/dist/resources/extensions/gsd/tools/complete-task.js +9 -0
  60. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +63 -2
  61. package/dist/resources/extensions/gsd/tui/render-kit.js +51 -0
  62. package/dist/resources/extensions/gsd/unit-context-manifest.js +35 -26
  63. package/dist/resources/extensions/gsd/user-input-boundary.js +1 -1
  64. package/dist/resources/extensions/gsd/vision-ask.js +22 -0
  65. package/dist/resources/extensions/gsd/visualizer-overlay.js +8 -36
  66. package/dist/resources/extensions/gsd/worktree-lifecycle.js +24 -3
  67. package/dist/resources/extensions/search-the-web/native-search.js +57 -8
  68. package/dist/resources/extensions/shared/confirm-ui.js +9 -6
  69. package/dist/resources/extensions/shared/dialog-frame.js +42 -0
  70. package/dist/resources/extensions/shared/interview-ui.js +42 -30
  71. package/dist/resources/extensions/shared/next-action-ui.js +6 -6
  72. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
  73. package/dist/web/standalone/.next/BUILD_ID +1 -1
  74. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  75. package/dist/web/standalone/.next/build-manifest.json +2 -2
  76. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  77. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  78. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  79. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  80. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  81. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  82. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  83. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  84. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  85. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  86. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  87. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  88. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  89. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  90. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  91. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  92. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  93. package/dist/web/standalone/.next/server/app/index.html +1 -1
  94. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  95. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  96. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  97. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  98. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  99. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  100. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  101. package/dist/web/standalone/.next/server/chunks/1834.js +2 -2
  102. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  103. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  104. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  105. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  106. package/package.json +1 -1
  107. package/packages/cloud-mcp-gateway/package.json +2 -2
  108. package/packages/contracts/package.json +1 -1
  109. package/packages/daemon/package.json +4 -4
  110. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts +1 -0
  111. package/packages/gsd-agent-core/dist/session/agent-session-extensions.d.ts.map +1 -1
  112. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js +22 -8
  113. package/packages/gsd-agent-core/dist/session/agent-session-extensions.js.map +1 -1
  114. package/packages/gsd-agent-core/package.json +5 -5
  115. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts +12 -0
  116. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.d.ts.map +1 -0
  117. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js +45 -0
  118. package/packages/gsd-agent-modes/dist/modes/interactive/components/dialog-container.js.map +1 -0
  119. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts +3 -2
  120. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.d.ts.map +1 -1
  121. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js +11 -11
  122. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-editor.js.map +1 -1
  123. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts +3 -3
  124. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.d.ts.map +1 -1
  125. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js +13 -11
  126. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-input.js.map +1 -1
  127. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts +3 -3
  128. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  129. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js +12 -10
  130. package/packages/gsd-agent-modes/dist/modes/interactive/components/extension-selector.js.map +1 -1
  131. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts +1 -0
  132. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.d.ts.map +1 -1
  133. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js +1 -0
  134. package/packages/gsd-agent-modes/dist/modes/interactive/components/index.js.map +1 -1
  135. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts +1 -1
  136. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  137. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js +2 -2
  138. package/packages/gsd-agent-modes/dist/modes/interactive/components/login-dialog.js.map +1 -1
  139. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts +6 -1
  140. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  141. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js +9 -6
  142. package/packages/gsd-agent-modes/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  143. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.d.ts.map +1 -1
  144. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js +0 -1
  145. package/packages/gsd-agent-modes/dist/modes/interactive/components/transcript-design.js.map +1 -1
  146. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts +3 -0
  147. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.d.ts.map +1 -1
  148. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js +144 -2
  149. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-auth.js.map +1 -1
  150. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.d.ts.map +1 -1
  151. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js +2 -14
  152. package/packages/gsd-agent-modes/dist/modes/interactive/interactive-selectors-session.js.map +1 -1
  153. package/packages/gsd-agent-modes/package.json +7 -7
  154. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  155. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  156. package/packages/mcp-server/package.json +3 -3
  157. package/packages/native/package.json +1 -1
  158. package/packages/pi-agent-core/dist/harness/skills.d.ts.map +1 -1
  159. package/packages/pi-agent-core/dist/harness/skills.js +6 -0
  160. package/packages/pi-agent-core/dist/harness/skills.js.map +1 -1
  161. package/packages/pi-agent-core/dist/harness/system-prompt.d.ts +7 -0
  162. package/packages/pi-agent-core/dist/harness/system-prompt.d.ts.map +1 -1
  163. package/packages/pi-agent-core/dist/harness/system-prompt.js +7 -0
  164. package/packages/pi-agent-core/dist/harness/system-prompt.js.map +1 -1
  165. package/packages/pi-agent-core/package.json +1 -1
  166. package/packages/pi-ai/dist/models.generated.d.ts +8 -59
  167. package/packages/pi-ai/dist/models.generated.d.ts.map +1 -1
  168. package/packages/pi-ai/dist/models.generated.js +21 -72
  169. package/packages/pi-ai/dist/models.generated.js.map +1 -1
  170. package/packages/pi-ai/dist/providers/anthropic.d.ts.map +1 -1
  171. package/packages/pi-ai/dist/providers/anthropic.js +50 -0
  172. package/packages/pi-ai/dist/providers/anthropic.js.map +1 -1
  173. package/packages/pi-ai/dist/providers/openai-responses-shared.d.ts.map +1 -1
  174. package/packages/pi-ai/dist/providers/openai-responses-shared.js +28 -4
  175. package/packages/pi-ai/dist/providers/openai-responses-shared.js.map +1 -1
  176. package/packages/pi-ai/dist/types.d.ts +2 -0
  177. package/packages/pi-ai/dist/types.d.ts.map +1 -1
  178. package/packages/pi-ai/dist/types.js.map +1 -1
  179. package/packages/pi-ai/package.json +1 -1
  180. package/packages/pi-coding-agent/README.md +1 -1
  181. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts +2 -2
  182. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.d.ts.map +1 -1
  183. package/packages/pi-coding-agent/dist/core/extensions/extension-upstream-types.js.map +1 -1
  184. package/packages/pi-coding-agent/dist/core/extensions/loader.js +1 -1
  185. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  186. package/packages/pi-coding-agent/dist/core/extensions/runner.d.ts.map +1 -1
  187. package/packages/pi-coding-agent/dist/core/extensions/runner.js +8 -2
  188. package/packages/pi-coding-agent/dist/core/extensions/runner.js.map +1 -1
  189. package/packages/pi-coding-agent/dist/core/skills.d.ts +3 -0
  190. package/packages/pi-coding-agent/dist/core/skills.d.ts.map +1 -1
  191. package/packages/pi-coding-agent/dist/core/skills.js +3 -0
  192. package/packages/pi-coding-agent/dist/core/skills.js.map +1 -1
  193. package/packages/pi-coding-agent/package.json +7 -7
  194. package/packages/pi-tui/package.json +1 -1
  195. package/packages/rpc-client/package.json +2 -2
  196. package/pkg/package.json +1 -1
  197. package/src/resources/extensions/context7/index.ts +15 -2
  198. package/src/resources/extensions/get-secrets-from-user.ts +17 -16
  199. package/src/resources/extensions/google-cli/index.ts +34 -0
  200. package/src/resources/extensions/google-cli/models.ts +57 -0
  201. package/src/resources/extensions/google-cli/package.json +11 -0
  202. package/src/resources/extensions/google-cli/readiness.ts +15 -0
  203. package/src/resources/extensions/google-cli/stream-adapter.ts +245 -0
  204. package/src/resources/extensions/gsd/auto/loop.ts +74 -1
  205. package/src/resources/extensions/gsd/auto/orchestrator.ts +4 -2
  206. package/src/resources/extensions/gsd/auto/phases.ts +46 -0
  207. package/src/resources/extensions/gsd/auto/run-unit.ts +10 -0
  208. package/src/resources/extensions/gsd/auto/session.ts +3 -0
  209. package/src/resources/extensions/gsd/auto-dispatch.ts +31 -11
  210. package/src/resources/extensions/gsd/auto-post-unit.ts +37 -2
  211. package/src/resources/extensions/gsd/auto-prompts.ts +4 -284
  212. package/src/resources/extensions/gsd/auto-recovery.ts +10 -7
  213. package/src/resources/extensions/gsd/auto-start.ts +307 -56
  214. package/src/resources/extensions/gsd/auto.ts +6 -1
  215. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +4 -3
  216. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +42 -5
  217. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +18 -6
  218. package/src/resources/extensions/gsd/bootstrap/system-context.ts +3 -28
  219. package/src/resources/extensions/gsd/closeout-recovery.ts +6 -1
  220. package/src/resources/extensions/gsd/commands/handlers/auto.ts +9 -1
  221. package/src/resources/extensions/gsd/commands-usage.ts +110 -5
  222. package/src/resources/extensions/gsd/config-overlay.ts +19 -16
  223. package/src/resources/extensions/gsd/context-overlay.ts +24 -19
  224. package/src/resources/extensions/gsd/dashboard-overlay.ts +14 -27
  225. package/src/resources/extensions/gsd/doctor-engine-checks.ts +99 -0
  226. package/src/resources/extensions/gsd/doctor-providers.ts +55 -27
  227. package/src/resources/extensions/gsd/doctor-types.ts +2 -0
  228. package/src/resources/extensions/gsd/doctor.ts +6 -1
  229. package/src/resources/extensions/gsd/git-conflict-state.ts +25 -1
  230. package/src/resources/extensions/gsd/guided-flow.ts +5 -6
  231. package/src/resources/extensions/gsd/key-manager.ts +57 -14
  232. package/src/resources/extensions/gsd/milestone-reopen-events.ts +28 -0
  233. package/src/resources/extensions/gsd/notification-overlay.ts +12 -11
  234. package/src/resources/extensions/gsd/parallel-monitor-overlay.ts +16 -12
  235. package/src/resources/extensions/gsd/preferences-skills.ts +11 -4
  236. package/src/resources/extensions/gsd/preferences.ts +17 -2
  237. package/src/resources/extensions/gsd/prompt-loader.ts +2 -0
  238. package/src/resources/extensions/gsd/prompts/discuss.md +4 -2
  239. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +2 -0
  240. package/src/resources/extensions/gsd/prompts/system.md +1 -3
  241. package/src/resources/extensions/gsd/queue-reorder-ui.ts +29 -20
  242. package/src/resources/extensions/gsd/repository-registry.ts +3 -1
  243. package/src/resources/extensions/gsd/skill-activation.ts +292 -0
  244. package/src/resources/extensions/gsd/skill-catalog.data.ts +858 -0
  245. package/src/resources/extensions/gsd/skill-catalog.install.ts +205 -0
  246. package/src/resources/extensions/gsd/skill-catalog.ts +16 -1087
  247. package/src/resources/extensions/gsd/skill-discovery.ts +134 -78
  248. package/src/resources/extensions/gsd/skill-scope.ts +63 -0
  249. package/src/resources/extensions/gsd/skill-telemetry.ts +6 -40
  250. package/src/resources/extensions/gsd/skills.ts +75 -0
  251. package/src/resources/extensions/gsd/state-reconciliation/drift/artifact-db.ts +499 -0
  252. package/src/resources/extensions/gsd/state-reconciliation/index.ts +40 -0
  253. package/src/resources/extensions/gsd/state-reconciliation/registry.ts +8 -0
  254. package/src/resources/extensions/gsd/state-reconciliation/types.ts +30 -0
  255. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +328 -2
  256. package/src/resources/extensions/gsd/tests/auto-orchestrator.test.ts +21 -0
  257. package/src/resources/extensions/gsd/tests/auto-post-unit-artifact-diagnostic.test.ts +28 -2
  258. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +41 -0
  259. package/src/resources/extensions/gsd/tests/auto-start-orphan-bootstrap.test.ts +436 -0
  260. package/src/resources/extensions/gsd/tests/closeout-recovery.test.ts +15 -0
  261. package/src/resources/extensions/gsd/tests/collect-from-manifest.test.ts +31 -0
  262. package/src/resources/extensions/gsd/tests/commands-context.test.ts +5 -3
  263. package/src/resources/extensions/gsd/tests/commands-dispatcher-workspace-git.test.ts +15 -2
  264. package/src/resources/extensions/gsd/tests/commands-usage.test.ts +97 -0
  265. package/src/resources/extensions/gsd/tests/context-chart.test.ts +9 -0
  266. package/src/resources/extensions/gsd/tests/dashboard-overlay.test.ts +25 -0
  267. package/src/resources/extensions/gsd/tests/discord-invite-links.test.ts +1 -0
  268. package/src/resources/extensions/gsd/tests/discuss-prompt.test.ts +4 -2
  269. package/src/resources/extensions/gsd/tests/discuss-tool-scoping.test.ts +1 -1
  270. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +105 -0
  271. package/src/resources/extensions/gsd/tests/doctor-scope-db-unavailable.test.ts +101 -1
  272. package/src/resources/extensions/gsd/tests/guided-discuss-milestone-prompt-rendering.test.ts +6 -0
  273. package/src/resources/extensions/gsd/tests/key-manager.test.ts +23 -4
  274. package/src/resources/extensions/gsd/tests/notification-overlay.test.ts +6 -1
  275. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +70 -10
  276. package/src/resources/extensions/gsd/tests/parallel-monitor-overlay.test.ts +7 -1
  277. package/src/resources/extensions/gsd/tests/post-unit-retry-on-orchestrator-bridge.test.ts +93 -0
  278. package/src/resources/extensions/gsd/tests/queue-reorder-ui.test.ts +46 -0
  279. package/src/resources/extensions/gsd/tests/register-extension-guard.test.ts +116 -11
  280. package/src/resources/extensions/gsd/tests/repository-registry.test.ts +30 -1
  281. package/src/resources/extensions/gsd/tests/show-config-command.test.ts +4 -0
  282. package/src/resources/extensions/gsd/tests/skill-discovery.test.ts +111 -0
  283. package/src/resources/extensions/gsd/tests/skill-scope-auto.test.ts +67 -0
  284. package/src/resources/extensions/gsd/tests/skills.test.ts +55 -0
  285. package/src/resources/extensions/gsd/tests/start-auto-detached.test.ts +13 -2
  286. package/src/resources/extensions/gsd/tests/state-reconciliation-drift.test.ts +303 -0
  287. package/src/resources/extensions/gsd/tests/token-tool-gating.test.ts +19 -0
  288. package/src/resources/extensions/gsd/tests/tool-param-optionality.test.ts +24 -1
  289. package/src/resources/extensions/gsd/tests/tui-border-assertions.ts +28 -0
  290. package/src/resources/extensions/gsd/tests/tui-render-kit.test.ts +14 -0
  291. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +18 -0
  292. package/src/resources/extensions/gsd/tests/user-input-boundary.test.ts +26 -0
  293. package/src/resources/extensions/gsd/tests/vision-ask.test.ts +23 -0
  294. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +6 -1
  295. package/src/resources/extensions/gsd/tests/workflow-mcp-auto-prep.test.ts +74 -1
  296. package/src/resources/extensions/gsd/tests/workflow-tool-executors.test.ts +82 -0
  297. package/src/resources/extensions/gsd/tests/workspace-git-preflight.test.ts +16 -1
  298. package/src/resources/extensions/gsd/tests/worktree-lifecycle.test.ts +28 -0
  299. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +45 -1
  300. package/src/resources/extensions/gsd/tools/complete-task.ts +9 -0
  301. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +82 -5
  302. package/src/resources/extensions/gsd/tui/render-kit.ts +82 -0
  303. package/src/resources/extensions/gsd/unit-context-manifest.ts +37 -26
  304. package/src/resources/extensions/gsd/user-input-boundary.ts +1 -1
  305. package/src/resources/extensions/gsd/vision-ask.ts +28 -0
  306. package/src/resources/extensions/gsd/visualizer-overlay.ts +12 -40
  307. package/src/resources/extensions/gsd/worktree-lifecycle.ts +37 -2
  308. package/src/resources/extensions/search-the-web/native-search.ts +60 -8
  309. package/src/resources/extensions/shared/confirm-ui.ts +8 -12
  310. package/src/resources/extensions/shared/dialog-frame.ts +71 -0
  311. package/src/resources/extensions/shared/interview-ui.ts +43 -42
  312. package/src/resources/extensions/shared/next-action-ui.ts +6 -6
  313. package/src/resources/extensions/shared/tests/confirm-ui.test.ts +57 -0
  314. package/src/resources/extensions/shared/tests/interview-ui-border.test.ts +163 -0
  315. package/src/resources/extensions/shared/tests/next-action-ui-hasui.test.ts +55 -0
  316. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +1 -1
  317. /package/dist/web/standalone/.next/static/{praHP_OATcjBkvAVejjGK → orfEoZqDIo6Be_Z9ZFipD}/_buildManifest.js +0 -0
  318. /package/dist/web/standalone/.next/static/{praHP_OATcjBkvAVejjGK → orfEoZqDIo6Be_Z9ZFipD}/_ssgManifest.js +0 -0
@@ -13,6 +13,7 @@ import { formattedShortcutPair } from "./shortcut-defs.js";
13
13
  import { resolveGsdPathContract } from "./paths.js";
14
14
  import {
15
15
  renderBar,
16
+ renderDialogFrame,
16
17
  renderKeyHints,
17
18
  renderProgressBar,
18
19
  safeLine,
@@ -378,19 +379,18 @@ export class ParallelMonitorOverlay {
378
379
  const t = this.theme;
379
380
  const lines: string[] = [];
380
381
  const w = Math.max(1, width);
382
+ const contentWidth = Math.max(1, w - 4);
381
383
 
382
- // Header
383
384
  const totalCost = this.workers.reduce((s, wk) => s + wk.cost, 0);
384
385
  const aliveCount = this.workers.filter((wk) => wk.alive).length;
385
386
  const now = new Date().toLocaleTimeString();
386
387
 
387
- lines.push(t.bold(t.fg("accent", " GSD Parallel Monitor ")));
388
388
  lines.push(
389
389
  t.fg("muted", ` ${now} │ ${aliveCount}/${this.workers.length} alive │ Total: `) +
390
390
  t.bold(`$${totalCost.toFixed(2)}`) +
391
391
  t.fg("muted", " │ 5s refresh"),
392
392
  );
393
- lines.push(renderBar(t, w));
393
+ lines.push(renderBar(t, contentWidth));
394
394
 
395
395
  if (this.workers.length === 0) {
396
396
  lines.push("");
@@ -444,7 +444,7 @@ export class ParallelMonitorOverlay {
444
444
  lines.push(` ${t.fg("muted", "slices")} ${chips.join(" ")}`);
445
445
 
446
446
  // Task progress bar
447
- const barWidth = Math.max(6, Math.min(25, w - 32));
447
+ const barWidth = Math.max(6, Math.min(25, contentWidth - 32));
448
448
  const bar = renderProgressBar(t, wk.doneTasks, wk.totalTasks, barWidth, {
449
449
  filledChar: "█",
450
450
  emptyChar: "░",
@@ -466,7 +466,7 @@ export class ParallelMonitorOverlay {
466
466
 
467
467
  // Event feed
468
468
  lines.push("");
469
- lines.push(renderBar(t, w));
469
+ lines.push(renderBar(t, contentWidth));
470
470
  lines.push(` ${t.bold("Recent Events")}`);
471
471
 
472
472
  if (this.events.length === 0) {
@@ -478,7 +478,6 @@ export class ParallelMonitorOverlay {
478
478
  }
479
479
  }
480
480
 
481
- // Footer
482
481
  lines.push("");
483
482
  const allDone = this.workers.length > 0 && this.workers.every((wk) => !wk.alive);
484
483
  if (allDone) {
@@ -491,17 +490,22 @@ export class ParallelMonitorOverlay {
491
490
  }
492
491
  lines.push(` ${t.bold("Total: $" + this.workers.reduce((s, wk) => s + wk.cost, 0).toFixed(2))}`);
493
492
  }
494
- lines.push(renderKeyHints(t, [`ESC/q/${formattedShortcutPair("parallel")} close`, "↑↓ scroll"], w));
495
493
 
496
494
  // Apply scroll — use terminal rows as height estimate
497
495
  const termHeight = process.stdout.rows || 40;
498
- const maxScroll = Math.max(0, lines.length - termHeight);
496
+ const maxBodyRows = Math.max(1, Math.min(lines.length, termHeight - 12));
497
+ const maxScroll = Math.max(0, lines.length - maxBodyRows);
499
498
  this.scrollOffset = Math.min(Math.max(this.scrollOffset, 0), maxScroll);
500
499
  const visible = lines
501
- .slice(this.scrollOffset, this.scrollOffset + termHeight)
502
- .map((line) => safeLine(line, w));
503
- this.cachedLines = visible;
500
+ .slice(this.scrollOffset, this.scrollOffset + maxBodyRows)
501
+ .map((line) => safeLine(line, contentWidth));
502
+ const footer = renderKeyHints(t, [`ESC/q/${formattedShortcutPair("parallel")} close`, "↑↓ scroll"], contentWidth);
503
+
504
+ this.cachedLines = renderDialogFrame(t, "GSD Parallel Monitor", visible, w, {
505
+ footer,
506
+ scroll: { offset: this.scrollOffset, visibleRows: maxBodyRows, totalRows: lines.length },
507
+ });
504
508
  this.cachedWidth = width;
505
- return visible;
509
+ return this.cachedLines;
506
510
  }
507
511
  }
@@ -23,10 +23,17 @@ import { validatePreferences } from "./preferences-validation.js";
23
23
  export type { GSDSkillRule, SkillDiscoveryMode, SkillResolution, SkillResolutionReport } from "./preferences-types.js";
24
24
 
25
25
  /**
26
- * Known skill directories, in priority order.
27
- * GSD bundled skills live in ~/.gsd/agent/skills/ and win name collisions.
28
- * User-global and project-local ecosystem directories remain available as
29
- * read-only discovery sources, followed by Claude Code compatibility paths.
26
+ * Known skill directories for **preference reference resolution** (bare skill names).
27
+ *
28
+ * Search order (first match wins):
29
+ * 1. ~/.gsd/agent/skills/ (GSD bundled)
30
+ * 2. ~/.agents/skills/ (ecosystem global)
31
+ * 3. .agents/skills/ (project ecosystem)
32
+ * 4. ~/.claude/skills/ and .claude/skills/ (Claude Code compatibility)
33
+ *
34
+ * Note: the ResourceLoader catalog uses PackageManager precedence instead —
35
+ * project `.gsd/skills` and `.agents/skills` can override bundled GSD skills
36
+ * on name collision. See docs/dev/what-is-pi/09-the-customization-stack.md.
30
37
  */
31
38
  export function getSkillSearchDirs(cwd: string): Array<{ dir: string; method: SkillResolution["method"] }> {
32
39
  const dirs: Array<{ dir: string; method: SkillResolution["method"] }> = [
@@ -570,9 +570,14 @@ function mergePreDispatchHooks(
570
570
 
571
571
  // ─── System Prompt Rendering ──────────────────────────────────────────────────
572
572
 
573
- export function renderPreferencesForSystemPrompt(preferences: GSDPreferences, resolutions?: Map<string, SkillResolution>): string {
573
+ export function renderPreferencesForSystemPrompt(
574
+ preferences: GSDPreferences,
575
+ resolutions?: Map<string, SkillResolution>,
576
+ options?: { includeResolvedPaths?: boolean },
577
+ ): string {
574
578
  const validated = validatePreferences(preferences);
575
579
  const lines: string[] = ["## GSD Skill Preferences"];
580
+ const includeResolvedPaths = options?.includeResolvedPaths ?? true;
576
581
 
577
582
  if (validated.errors.length > 0) {
578
583
  lines.push("- Validation: some preference values were ignored because they were invalid.");
@@ -589,7 +594,17 @@ export function renderPreferencesForSystemPrompt(preferences: GSDPreferences, re
589
594
  "- Current user instructions still override these defaults.",
590
595
  );
591
596
 
592
- const fmt = (ref: string) => resolutions ? formatSkillRef(ref, resolutions) : ref;
597
+ const fmt = (ref: string) => {
598
+ if (!resolutions) return ref;
599
+ if (!includeResolvedPaths) {
600
+ const resolution = resolutions.get(ref);
601
+ if (!resolution || resolution.method === "unresolved") {
602
+ return `${ref} (⚠ not found — check skill name or path)`;
603
+ }
604
+ return ref;
605
+ }
606
+ return formatSkillRef(ref, resolutions);
607
+ };
593
608
 
594
609
  if (preferences.always_use_skills && preferences.always_use_skills.length > 0) {
595
610
  lines.push("- Always use these skills when relevant:");
@@ -22,6 +22,7 @@ import { join, dirname } from "node:path";
22
22
  import { fileURLToPath } from "node:url";
23
23
  import { logWarning } from "./workflow-logger.js";
24
24
  import { gsdHome } from "./gsd-home.js";
25
+ import { chooseVisionAskVariant } from "./vision-ask.js";
25
26
 
26
27
  type ExistsFn = (path: string) => boolean;
27
28
 
@@ -184,6 +185,7 @@ export function loadPrompt(name: string, vars: Record<string, string> = {}): str
184
185
  planTemplatePath: join(getTemplatesDir(), "plan.md"),
185
186
  taskPlanTemplatePath: join(getTemplatesDir(), "task-plan.md"),
186
187
  taskSummaryTemplatePath: join(getTemplatesDir(), "task-summary.md"),
188
+ visionAsk: chooseVisionAskVariant(),
187
189
  skillActivation: "If a `GSD Skill Preferences` block is present in system context, use it and the `<available_skills>` catalog in your system prompt to decide which skills to load and follow for this unit, without relaxing required verification or artifact rules.",
188
190
  ...vars,
189
191
  };
@@ -1,8 +1,10 @@
1
1
  {{preamble}}
2
2
 
3
- Ask: "What's the vision?" once, then use the reply as vision input.
3
+ Ask exactly this once: "{{visionAsk}}" Then use the user's reply as vision input.
4
4
 
5
- **Special handling:** If the **user's** message is status, branch state, clarification, or any other non-project-description, treat it as vision input and proceed instead of repeating "What's the vision?" Do **not** treat the system preamble above (e.g. "New milestone M00X.") as vision input — wait for the user.
5
+ The opener is intentionally variable so GSD feels alive across project starts. Keep it natural, easy to answer, and assistant/developer shaped: plain language, light guidance, no corporate wording, no roleplay.
6
+
7
+ **Special handling:** If the **user's** message is status, branch state, clarification, or any other non-project-description, treat it as vision input and proceed instead of repeating the opener. Do **not** treat the system preamble above (e.g. "New milestone M00X.") as vision input — wait for the user.
6
8
 
7
9
  ## Reflection Step
8
10
 
@@ -4,6 +4,8 @@ Discuss milestone {{milestoneId}} ("{{milestoneTitle}}"). Identify real gray are
4
4
 
5
5
  **Structured questions available: {{structuredQuestionsAvailable}}**
6
6
 
7
+ **Conversation opener:** For the first user-facing question round, if you need a broad starting question, use: "{{visionAsk}}" Pair it with investigation-shaped follow-ups. Keep the tone natural and easy to answer, like an assistant/developer helping the user shape the next piece of work.
8
+
7
9
  {{inlinedTemplates}}
8
10
 
9
11
  ---
@@ -19,9 +19,7 @@ Leave the project ready for the next agent to understand and continue. Artifacts
19
19
 
20
20
  ## Skills
21
21
 
22
- GSD ships with bundled skills. When the task matches, load the relevant skill file with `read` before starting. Use bare skill names; GSD resolves paths.
23
-
24
- {{bundledSkillsTable}}
22
+ GSD ships with bundled skills. Installed skills are listed in `<available_skills>` in your system prompt — load the relevant skill file with `read` using the path shown there before starting matching work. Use bare skill names in preferences; GSD resolves paths.
25
23
 
26
24
  ## Hard Rules
27
25
 
@@ -11,9 +11,9 @@
11
11
  import type { ExtensionContext } from "@gsd/pi-coding-agent";
12
12
  import { type Theme } from "@gsd/pi-coding-agent";
13
13
  import { Key, matchesKey, truncateToWidth, type TUI } from "@gsd/pi-tui";
14
- import { makeUI } from "../shared/tui.js";
15
14
  import { GLYPH } from "../shared/mod.js";
16
15
  import { validateQueueOrder, type DependencyValidation } from "./queue-order.js";
16
+ import { renderDialogFrame, renderKeyHints } from "./tui/render-kit.js";
17
17
 
18
18
  export interface ReorderItem {
19
19
  id: string;
@@ -45,6 +45,7 @@ export async function showQueueReorder(
45
45
  let grabbed = false;
46
46
  let scrollOffset = 0;
47
47
  let cachedLines: string[] | undefined;
48
+ let cachedWidth: number | undefined;
48
49
  let validation: DependencyValidation;
49
50
 
50
51
  // Mutable deps map — tracks removals during this session
@@ -66,6 +67,7 @@ export async function showQueueReorder(
66
67
 
67
68
  function refresh() {
68
69
  cachedLines = undefined;
70
+ cachedWidth = undefined;
69
71
  tui.requestRender();
70
72
  }
71
73
 
@@ -151,17 +153,15 @@ export async function showQueueReorder(
151
153
  }
152
154
 
153
155
  function render(width: number): string[] {
154
- if (cachedLines) return cachedLines;
156
+ if (cachedLines && cachedWidth === width) return cachedLines;
155
157
 
156
- const ui = makeUI(theme, width);
158
+ const contentWidth = Math.max(1, width - 4);
157
159
  const lines: string[] = [];
158
160
  const queueRows: string[] = [];
159
- const push = (...rows: string[][]) => { for (const r of rows) lines.push(...r); };
160
- const add = (s: string) => truncateToWidth(s, width);
161
+ const add = (s: string) => truncateToWidth(s, contentWidth);
161
162
  let cursorQueueRow = 0;
162
163
 
163
- const headerText = grabbed ? " Queue Reorder Moving Item" : " Queue Reorder";
164
- push(ui.bar(), ui.blank(), ui.header(headerText), ui.blank());
164
+ const headerText = grabbed ? "Queue Reorder - Moving Item" : "Queue Reorder";
165
165
 
166
166
  // Completed milestones (dimmed)
167
167
  if (completed.length > 0) {
@@ -170,7 +170,7 @@ export async function showQueueReorder(
170
170
  const label = m.title && m.title !== m.id ? `${m.id} ${m.title}` : m.id;
171
171
  lines.push(add(` ${theme.fg("dim", `${GLYPH.statusDone} ${label}`)}`));
172
172
  }
173
- push(ui.blank());
173
+ lines.push("");
174
174
  }
175
175
 
176
176
  // Pending milestones
@@ -223,7 +223,7 @@ export async function showQueueReorder(
223
223
  // Removed deps feedback
224
224
  const trailingLines: string[] = [];
225
225
  if (removedDeps.length > 0) {
226
- trailingLines.push(...ui.blank());
226
+ trailingLines.push("");
227
227
  for (const r of removedDeps) {
228
228
  trailingLines.push(add(` ${theme.fg("success", `${GLYPH.statusDone} Removed: ${r.milestone} depends_on ${r.dep}`)}`));
229
229
  }
@@ -232,12 +232,10 @@ export async function showQueueReorder(
232
232
  // Circular warning
233
233
  const circ = validation.violations.find(v => v.type === 'circular');
234
234
  if (circ) {
235
- trailingLines.push(...ui.blank());
235
+ trailingLines.push("");
236
236
  trailingLines.push(add(` ${theme.fg("error", `${GLYPH.statusWarning} ${circ.message}`)}`));
237
237
  }
238
238
 
239
- trailingLines.push(...ui.blank());
240
-
241
239
  // Hints — context-sensitive based on grab state
242
240
  const hints: string[] = [];
243
241
  if (grabbed) {
@@ -256,10 +254,9 @@ export async function showQueueReorder(
256
254
  }
257
255
  hints.push("esc");
258
256
 
259
- trailingLines.push(...ui.hints(hints), ...ui.bar());
260
-
261
257
  const maxOverlayRows = Math.max(10, process.stdout.rows ? Math.floor(process.stdout.rows * 0.8) : 24);
262
- const availableQueueRows = Math.max(1, maxOverlayRows - lines.length - trailingLines.length);
258
+ const frameRows = 4;
259
+ const availableQueueRows = Math.max(1, maxOverlayRows - frameRows - lines.length - trailingLines.length);
263
260
  const maxScroll = Math.max(0, queueRows.length - availableQueueRows);
264
261
  if (cursorQueueRow < scrollOffset) {
265
262
  scrollOffset = cursorQueueRow;
@@ -268,13 +265,25 @@ export async function showQueueReorder(
268
265
  }
269
266
  scrollOffset = Math.min(Math.max(scrollOffset, 0), maxScroll);
270
267
 
271
- lines.push(...queueRows.slice(scrollOffset, scrollOffset + availableQueueRows), ...trailingLines);
272
-
273
- cachedLines = lines;
274
- return lines;
268
+ const queueStartRow = lines.length;
269
+ const visibleQueueRows = queueRows.slice(scrollOffset, scrollOffset + availableQueueRows);
270
+ lines.push(...visibleQueueRows, ...trailingLines);
271
+
272
+ cachedLines = renderDialogFrame(theme, headerText, lines, width, {
273
+ footer: renderKeyHints(theme, hints, contentWidth),
274
+ scroll: {
275
+ offset: scrollOffset,
276
+ visibleRows: availableQueueRows,
277
+ totalRows: queueRows.length,
278
+ trackOffset: queueStartRow,
279
+ trackRows: visibleQueueRows.length,
280
+ },
281
+ });
282
+ cachedWidth = width;
283
+ return cachedLines;
275
284
  }
276
285
 
277
- return { render, invalidate: () => { cachedLines = undefined; }, handleInput };
286
+ return { render, invalidate: () => { cachedLines = undefined; cachedWidth = undefined; }, handleInput };
278
287
  }, {
279
288
  overlay: true,
280
289
  overlayOptions: { width: "70%", minWidth: 50, maxHeight: "80%", anchor: "center" },
@@ -77,7 +77,9 @@ export function createRepositoryRegistry(
77
77
  workspacePrefs?: WorkspacePreferences,
78
78
  ): RepositoryRegistry {
79
79
  const contract = resolveGsdPathContract(basePath);
80
- const projectRoot = resolveGitWorkingTreeRoot(contract.workRoot) ?? contract.projectRoot;
80
+ const projectRoot = contract.isWorktree
81
+ ? resolveGitWorkingTreeRoot(contract.workRoot) ?? contract.projectRoot
82
+ : contract.projectRoot;
81
83
  const mode = workspacePrefs?.mode ?? "project";
82
84
  const repoMap = new Map<string, RegisteredRepository>();
83
85
 
@@ -0,0 +1,292 @@
1
+ /**
2
+ * Skill activation and discovery prompt blocks for GSD auto-mode units.
3
+ */
4
+
5
+ import { basename } from "node:path";
6
+ import type { Skill } from "@gsd/pi-coding-agent";
7
+ import { parseTaskPlanFile } from "./files.js";
8
+ import {
9
+ loadEffectiveGSDPreferences,
10
+ resolveAllSkillReferences,
11
+ resolveSkillDiscoveryMode,
12
+ } from "./preferences.js";
13
+ import type { GSDPreferences } from "./preferences.js";
14
+ import { filterSkillsByManifest, resolveSkillManifest, warnIfManifestHasMissingSkills } from "./skill-manifest.js";
15
+ import { getInstalledSkills } from "./skills.js";
16
+ import { logWarning } from "./workflow-logger.js";
17
+
18
+ function normalizeSkillReference(ref: string): string {
19
+ const normalized = ref.replace(/\\/g, "/").trim();
20
+ const base = basename(normalized).replace(/\.md$/i, "");
21
+ const name = /^SKILL$/i.test(base)
22
+ ? basename(normalized.replace(/\/SKILL(?:\.md)?$/i, ""))
23
+ : base;
24
+ return name.trim().toLowerCase();
25
+ }
26
+
27
+ function tokenizeSkillContext(...parts: Array<string | null | undefined>): Set<string> {
28
+ const tokens = new Set<string>();
29
+ const addVariants = (raw: string) => {
30
+ const value = raw.trim().toLowerCase();
31
+ if (!value || value.length < 2) return;
32
+ tokens.add(value);
33
+ tokens.add(value.replace(/[-_]+/g, " "));
34
+ tokens.add(value.replace(/\s+/g, "-"));
35
+ tokens.add(value.replace(/\s+/g, ""));
36
+ };
37
+
38
+ for (const part of parts) {
39
+ if (!part) continue;
40
+ const text = part.toLowerCase();
41
+ const phraseMatches = text.match(/[a-z0-9][a-z0-9+.#/_-]{1,}/g) ?? [];
42
+ for (const match of phraseMatches) {
43
+ addVariants(match);
44
+ for (const piece of match.split(/[^a-z0-9+.#]+/g)) {
45
+ if (piece.length >= 3) addVariants(piece);
46
+ }
47
+ }
48
+ }
49
+
50
+ return tokens;
51
+ }
52
+
53
+ function skillMatchesContext(skill: Skill, contextTokens: Set<string>): boolean {
54
+ const haystacks = [
55
+ skill.name.toLowerCase(),
56
+ skill.name.toLowerCase().replace(/[-_]+/g, " "),
57
+ skill.description.toLowerCase(),
58
+ ];
59
+
60
+ return [...contextTokens].some(token =>
61
+ token.length >= 3 && haystacks.some(haystack => haystack.includes(token)),
62
+ );
63
+ }
64
+
65
+ function resolvePreferenceSkillNames(refs: string[], base: string): string[] {
66
+ if (refs.length === 0) return [];
67
+ const prefs: GSDPreferences = { always_use_skills: refs };
68
+ const report = resolveAllSkillReferences(prefs, base);
69
+ return refs.map(ref => {
70
+ const resolution = report.resolutions.get(ref);
71
+ return normalizeSkillReference(resolution?.resolvedPath ?? ref);
72
+ }).filter(Boolean);
73
+ }
74
+
75
+ function ruleMatchesContext(when: string, contextTokens: Set<string>): boolean {
76
+ const whenTokens = tokenizeSkillContext(when);
77
+ return [...whenTokens].some(token =>
78
+ contextTokens.has(token) || [...contextTokens].some(ctx => ctx.includes(token) || token.includes(ctx)),
79
+ );
80
+ }
81
+
82
+ function resolveSkillRuleMatches(
83
+ prefs: GSDPreferences | undefined,
84
+ contextTokens: Set<string>,
85
+ base: string,
86
+ ): { include: string[]; avoid: string[] } {
87
+ if (!prefs?.skill_rules?.length) return { include: [], avoid: [] };
88
+
89
+ const include: string[] = [];
90
+ const avoid: string[] = [];
91
+ for (const rule of prefs.skill_rules) {
92
+ if (!ruleMatchesContext(rule.when, contextTokens)) continue;
93
+ include.push(...resolvePreferenceSkillNames([...(rule.use ?? []), ...(rule.prefer ?? [])], base));
94
+ avoid.push(...resolvePreferenceSkillNames(rule.avoid ?? [], base));
95
+ }
96
+ return { include, avoid };
97
+ }
98
+
99
+ function resolvePreferredSkillNames(
100
+ prefs: GSDPreferences | undefined,
101
+ visibleSkills: Skill[],
102
+ contextTokens: Set<string>,
103
+ base: string,
104
+ ): string[] {
105
+ if (!prefs?.prefer_skills?.length) return [];
106
+ const preferred = new Set(resolvePreferenceSkillNames(prefs.prefer_skills, base));
107
+ return visibleSkills
108
+ .filter(skill => preferred.has(normalizeSkillReference(skill.name)) && skillMatchesContext(skill, contextTokens))
109
+ .map(skill => normalizeSkillReference(skill.name));
110
+ }
111
+
112
+ /** Skill names must be lowercase alphanumeric with hyphens — reject anything else
113
+ * to prevent prompt injection via crafted directory names. */
114
+ const SAFE_SKILL_NAME = /^[a-z0-9][a-z0-9-]*$/;
115
+
116
+ function formatSkillActivationBlock(skillNames: string[]): string {
117
+ const safe = skillNames.filter(name => SAFE_SKILL_NAME.test(name));
118
+ if (safe.length === 0) return "";
119
+ // Use explicit parameter syntax so LLMs pass { skill: "..." } instead of { name: "..." }.
120
+ // The function-call-like syntax `Skill('name')` led LLMs to infer a positional
121
+ // parameter name, causing tool validation failures — see #2224.
122
+ const calls = safe.map(name => `Call Skill({ skill: '${name}' })`).join('. ');
123
+ return `<skill_activation>${calls}.</skill_activation>`;
124
+ }
125
+
126
+ /**
127
+ * Manifest-driven recommendations block — informational only, does NOT
128
+ * auto-invoke. Lists per-unit-type skills that are installed but not already
129
+ * activated by explicit user intent (always_use_skills / prefer_skills /
130
+ * skill_rules / task-plan skills_used). Surfaces relevant skills to the
131
+ * model so they can be invoked when the model judges them useful.
132
+ *
133
+ * This is the additive complement to the existing activation directive:
134
+ * activation force-invokes (explicit intent), recommendations remind
135
+ * (manifest defaults). User intent is preserved as the stronger signal
136
+ * (RFC #4779 design principle); this block only adds visibility.
137
+ */
138
+ function formatSkillRecommendationsBlock(unitType: string | undefined, skillNames: string[]): string {
139
+ if (!unitType) return "";
140
+ const safe = skillNames.filter(name => SAFE_SKILL_NAME.test(name));
141
+ if (safe.length === 0) return "";
142
+ return `<skill_recommendations unit="${unitType}">For this unit type, also consider invoking: ${safe.join(", ")}. Use Skill({ skill: 'name' }) when relevant — these are recommendations, not requirements.</skill_recommendations>`;
143
+ }
144
+
145
+ export function buildSkillActivationBlock(params: {
146
+ base: string;
147
+ milestoneId: string;
148
+ milestoneTitle?: string;
149
+ sliceId?: string;
150
+ sliceTitle?: string;
151
+ taskId?: string;
152
+ taskTitle?: string;
153
+ extraContext?: string[];
154
+ taskPlanContent?: string | null;
155
+ preferences?: GSDPreferences;
156
+ /**
157
+ * Unit type dispatching this prompt. When provided, skills are filtered
158
+ * through the per-unit-type manifest (see `skill-manifest.ts`). Unknown
159
+ * or omitted values retain the pre-manifest behavior (all skills eligible).
160
+ */
161
+ unitType?: string;
162
+ /** Installed skills from resourceLoader; defaults to loader cache via skills facade. */
163
+ skills?: Skill[];
164
+ }): string {
165
+ const prefs = params.preferences ?? loadEffectiveGSDPreferences(params.base)?.preferences;
166
+ const contextTokens = tokenizeSkillContext(
167
+ params.milestoneId,
168
+ params.milestoneTitle,
169
+ params.sliceId,
170
+ params.sliceTitle,
171
+ params.taskId,
172
+ params.taskTitle,
173
+ );
174
+
175
+ const loaded = getInstalledSkills(params.skills).filter(skill => !skill.disableModelInvocation);
176
+
177
+ // Skill activation here is driven entirely by explicit sources
178
+ // (always_use_skills, prefer_skills, skill_rules, task-plan skills_used).
179
+ // Every match is an explicit user/project intent and must not be dropped
180
+ // by the unit-type manifest — user intent is stronger signal than
181
+ // defaults. The manifest's real home is the skill catalog rendering
182
+ // layer (pi-coding-agent `formatSkillsForPrompt`); that wiring is tracked
183
+ // as the "load-time short-circuit" follow-up to RFC #4779.
184
+ //
185
+ // `unitType` stays plumbed so the strict-mode warning can surface
186
+ // manifest entries that reference uninstalled skills, and so the
187
+ // activation-block site is ready to opt in once PR B lands.
188
+ const visibleSkills = loaded;
189
+ const manifestScopedSkills = filterSkillsByManifest(visibleSkills, params.unitType);
190
+ const installedNames = new Set(visibleSkills.map(skill => normalizeSkillReference(skill.name)));
191
+ warnIfManifestHasMissingSkills(params.unitType, installedNames);
192
+ const avoided = new Set(resolvePreferenceSkillNames(prefs?.avoid_skills ?? [], params.base));
193
+ const matched = new Set<string>();
194
+
195
+ for (const name of resolvePreferenceSkillNames(prefs?.always_use_skills ?? [], params.base)) {
196
+ matched.add(name);
197
+ }
198
+
199
+ const ruleMatches = resolveSkillRuleMatches(prefs, contextTokens, params.base);
200
+ for (const name of ruleMatches.include) matched.add(name);
201
+ for (const name of ruleMatches.avoid) avoided.add(name);
202
+
203
+ for (const name of resolvePreferredSkillNames(prefs, visibleSkills, contextTokens, params.base)) {
204
+ matched.add(name);
205
+ }
206
+
207
+ if (params.taskPlanContent) {
208
+ try {
209
+ const taskPlan = parseTaskPlanFile(params.taskPlanContent);
210
+ for (const skillName of taskPlan.frontmatter.skills_used) {
211
+ matched.add(normalizeSkillReference(skillName));
212
+ }
213
+ } catch (err) {
214
+ logWarning("prompt", `parseTaskPlanFile failed: ${err instanceof Error ? err.message : String(err)}`);
215
+ }
216
+ }
217
+
218
+ // Heuristic auto-match (gated on skill_discovery: "auto").
219
+ // For each installed skill, check if its name or description appears in the
220
+ // unit's context tokens (milestone/slice/task titles). Only consider skills
221
+ // already on the unit-type manifest allowlist — this keeps the heuristic
222
+ // narrow and avoids wildly off-topic activations.
223
+ // Users who set `skill_discovery: "off"` or "suggest" do not get
224
+ // auto-matched skills (the recommendations block still surfaces manifest
225
+ // skills passively); only "auto" actually adds them to the activation
226
+ // directive set. Default `skill_discovery` is "suggest", so this is opt-in.
227
+ if ((prefs?.skill_discovery ?? "suggest") === "auto") {
228
+ for (const skill of manifestScopedSkills) {
229
+ const normalized = normalizeSkillReference(skill.name);
230
+ if (matched.has(normalized) || avoided.has(normalized)) continue;
231
+ if (skillMatchesContext(skill, contextTokens)) {
232
+ matched.add(normalized);
233
+ }
234
+ }
235
+ }
236
+
237
+ const ordered = [...matched]
238
+ .filter(name => installedNames.has(name) && !avoided.has(name))
239
+ .sort();
240
+ const activationBlock = formatSkillActivationBlock(ordered);
241
+
242
+ // Omit recommendations when the system catalog is manifest-scoped for this
243
+ // unit — skill names are already listed in <available_skills>.
244
+ let recommendationsBlock = "";
245
+ if (resolveSkillManifest(params.unitType) === null) {
246
+ const matchedSet = new Set(ordered);
247
+ const recommendations = (resolveSkillManifest(params.unitType) ?? [])
248
+ .filter(name => installedNames.has(name) && !avoided.has(name) && !matchedSet.has(name))
249
+ .sort();
250
+ recommendationsBlock = formatSkillRecommendationsBlock(params.unitType, recommendations);
251
+ }
252
+
253
+ if (!activationBlock && !recommendationsBlock) return "";
254
+ if (!activationBlock) return recommendationsBlock;
255
+ if (!recommendationsBlock) return activationBlock;
256
+ return `${activationBlock}\n${recommendationsBlock}`;
257
+ }
258
+
259
+ /**
260
+ * Build the skill discovery template variables for research prompts.
261
+ * Returns { skillDiscoveryMode, skillDiscoveryInstructions } for template substitution.
262
+ */
263
+ export function buildSkillDiscoveryVars(): { skillDiscoveryMode: string; skillDiscoveryInstructions: string } {
264
+ const mode = resolveSkillDiscoveryMode();
265
+
266
+ if (mode === "off") {
267
+ return {
268
+ skillDiscoveryMode: "off",
269
+ skillDiscoveryInstructions: " Skill discovery is disabled. Skip this step.",
270
+ };
271
+ }
272
+
273
+ if (mode === "suggest") {
274
+ return {
275
+ skillDiscoveryMode: mode,
276
+ skillDiscoveryInstructions: `
277
+ Check \`<available_skills>\` for installed skills matching core technologies. For gaps, run \`npx skills find "<technology>"\` and note promising install commands in your research output — do NOT install.`,
278
+ };
279
+ }
280
+
281
+ const autoInstall = mode === "auto";
282
+ const instructions = autoInstall
283
+ ? `
284
+ Check \`<available_skills>\` first. For missing core-technology skills, run \`npx skills find "<technology>"\`, install relevant matches with \`npx skills add <owner/repo@skill> -g -y\`, and record them in "Skills Discovered".`
285
+ : `
286
+ Check \`<available_skills>\` first. For missing core-technology skills, run \`npx skills find "<technology>"\` and note install commands in your research output — do NOT install.`;
287
+
288
+ return {
289
+ skillDiscoveryMode: mode,
290
+ skillDiscoveryInstructions: instructions,
291
+ };
292
+ }