gsd-pi 2.29.0-dev.49d972f → 2.29.0-dev.4c155ee

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 (256) hide show
  1. package/README.md +24 -17
  2. package/dist/headless.js +4 -0
  3. package/dist/resources/extensions/bg-shell/process-manager.ts +13 -0
  4. package/dist/resources/extensions/gsd/auto-dashboard.ts +217 -65
  5. package/dist/resources/extensions/gsd/auto-dispatch.ts +32 -3
  6. package/dist/resources/extensions/gsd/auto-post-unit.ts +44 -12
  7. package/dist/resources/extensions/gsd/auto-prompts.ts +40 -17
  8. package/dist/resources/extensions/gsd/auto-recovery.ts +2 -1
  9. package/dist/resources/extensions/gsd/auto-start.ts +18 -32
  10. package/dist/resources/extensions/gsd/auto-worktree.ts +21 -182
  11. package/dist/resources/extensions/gsd/auto.ts +2 -9
  12. package/dist/resources/extensions/gsd/captures.ts +4 -10
  13. package/dist/resources/extensions/gsd/commands-handlers.ts +2 -1
  14. package/dist/resources/extensions/gsd/commands-prefs-wizard.ts +44 -14
  15. package/dist/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  16. package/dist/resources/extensions/gsd/commands.ts +55 -2
  17. package/dist/resources/extensions/gsd/detection.ts +2 -1
  18. package/dist/resources/extensions/gsd/doctor-checks.ts +49 -1
  19. package/dist/resources/extensions/gsd/doctor-types.ts +3 -1
  20. package/dist/resources/extensions/gsd/forensics.ts +2 -2
  21. package/dist/resources/extensions/gsd/git-service.ts +3 -2
  22. package/dist/resources/extensions/gsd/gitignore.ts +9 -63
  23. package/dist/resources/extensions/gsd/gsd-db.ts +1 -165
  24. package/dist/resources/extensions/gsd/guided-flow.ts +8 -5
  25. package/dist/resources/extensions/gsd/index.ts +3 -3
  26. package/dist/resources/extensions/gsd/md-importer.ts +3 -2
  27. package/dist/resources/extensions/gsd/mechanical-completion.ts +430 -0
  28. package/dist/resources/extensions/gsd/migrate/command.ts +3 -2
  29. package/dist/resources/extensions/gsd/migrate/writer.ts +2 -1
  30. package/dist/resources/extensions/gsd/migrate-external.ts +123 -0
  31. package/dist/resources/extensions/gsd/paths.ts +24 -2
  32. package/dist/resources/extensions/gsd/post-unit-hooks.ts +6 -5
  33. package/dist/resources/extensions/gsd/preferences-models.ts +7 -1
  34. package/dist/resources/extensions/gsd/preferences-validation.ts +2 -1
  35. package/dist/resources/extensions/gsd/preferences.ts +10 -5
  36. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
  37. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  38. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
  39. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -1
  40. package/dist/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  41. package/dist/resources/extensions/gsd/repo-identity.ts +148 -0
  42. package/dist/resources/extensions/gsd/resource-version.ts +99 -0
  43. package/dist/resources/extensions/gsd/session-forensics.ts +4 -3
  44. package/dist/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
  45. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
  46. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
  47. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
  48. package/dist/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  49. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +5 -18
  50. package/dist/resources/extensions/gsd/tests/git-service.test.ts +10 -37
  51. package/dist/resources/extensions/gsd/tests/knowledge.test.ts +4 -4
  52. package/dist/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
  53. package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
  54. package/dist/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
  55. package/dist/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  56. package/dist/resources/extensions/gsd/triage-resolution.ts +2 -1
  57. package/dist/resources/extensions/gsd/types.ts +2 -0
  58. package/dist/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  59. package/dist/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  60. package/dist/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  61. package/dist/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  62. package/dist/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  63. package/dist/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  64. package/dist/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  65. package/dist/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  66. package/dist/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  67. package/dist/resources/extensions/gsd/workflow-templates.ts +241 -0
  68. package/dist/resources/extensions/gsd/worktree-command.ts +1 -11
  69. package/dist/resources/extensions/gsd/worktree-manager.ts +3 -2
  70. package/dist/resources/extensions/gsd/worktree.ts +42 -5
  71. package/dist/resources/extensions/mcp-client/index.ts +459 -0
  72. package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
  73. package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  74. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  75. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  76. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  77. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  78. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  79. package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  80. package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  81. package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  82. package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  83. package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  84. package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  85. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  86. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  87. package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  88. package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  89. package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  90. package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  91. package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  92. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  93. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  94. package/dist/resources/skills/create-skill/SKILL.md +184 -0
  95. package/dist/resources/skills/create-skill/references/api-security.md +226 -0
  96. package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  97. package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
  98. package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
  99. package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
  100. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  101. package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  102. package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
  103. package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
  104. package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  105. package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
  106. package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
  107. package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  108. package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
  109. package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
  110. package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
  111. package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
  112. package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
  113. package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  114. package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  115. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  116. package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  117. package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  118. package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  119. package/dist/resources/skills/react-best-practices/SKILL.md +1 -1
  120. package/package.json +1 -1
  121. package/packages/native/dist/native.d.ts +2 -0
  122. package/packages/native/dist/native.js +19 -5
  123. package/packages/native/src/native.ts +23 -9
  124. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  125. package/packages/pi-coding-agent/dist/core/extensions/loader.js +13 -0
  126. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  128. package/packages/pi-coding-agent/dist/core/lsp/client.js +3 -0
  129. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  130. package/packages/pi-coding-agent/src/core/extensions/loader.ts +13 -0
  131. package/packages/pi-coding-agent/src/core/lsp/client.ts +3 -0
  132. package/src/resources/extensions/bg-shell/process-manager.ts +13 -0
  133. package/src/resources/extensions/gsd/auto-dashboard.ts +217 -65
  134. package/src/resources/extensions/gsd/auto-dispatch.ts +32 -3
  135. package/src/resources/extensions/gsd/auto-post-unit.ts +44 -12
  136. package/src/resources/extensions/gsd/auto-prompts.ts +40 -17
  137. package/src/resources/extensions/gsd/auto-recovery.ts +2 -1
  138. package/src/resources/extensions/gsd/auto-start.ts +18 -32
  139. package/src/resources/extensions/gsd/auto-worktree.ts +21 -182
  140. package/src/resources/extensions/gsd/auto.ts +2 -9
  141. package/src/resources/extensions/gsd/captures.ts +4 -10
  142. package/src/resources/extensions/gsd/commands-handlers.ts +2 -1
  143. package/src/resources/extensions/gsd/commands-prefs-wizard.ts +44 -14
  144. package/src/resources/extensions/gsd/commands-workflow-templates.ts +544 -0
  145. package/src/resources/extensions/gsd/commands.ts +55 -2
  146. package/src/resources/extensions/gsd/detection.ts +2 -1
  147. package/src/resources/extensions/gsd/doctor-checks.ts +49 -1
  148. package/src/resources/extensions/gsd/doctor-types.ts +3 -1
  149. package/src/resources/extensions/gsd/forensics.ts +2 -2
  150. package/src/resources/extensions/gsd/git-service.ts +3 -2
  151. package/src/resources/extensions/gsd/gitignore.ts +9 -63
  152. package/src/resources/extensions/gsd/gsd-db.ts +1 -165
  153. package/src/resources/extensions/gsd/guided-flow.ts +8 -5
  154. package/src/resources/extensions/gsd/index.ts +3 -3
  155. package/src/resources/extensions/gsd/md-importer.ts +3 -2
  156. package/src/resources/extensions/gsd/mechanical-completion.ts +430 -0
  157. package/src/resources/extensions/gsd/migrate/command.ts +3 -2
  158. package/src/resources/extensions/gsd/migrate/writer.ts +2 -1
  159. package/src/resources/extensions/gsd/migrate-external.ts +123 -0
  160. package/src/resources/extensions/gsd/paths.ts +24 -2
  161. package/src/resources/extensions/gsd/post-unit-hooks.ts +6 -5
  162. package/src/resources/extensions/gsd/preferences-models.ts +7 -1
  163. package/src/resources/extensions/gsd/preferences-validation.ts +2 -1
  164. package/src/resources/extensions/gsd/preferences.ts +10 -5
  165. package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
  166. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  167. package/src/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
  168. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -1
  169. package/src/resources/extensions/gsd/prompts/workflow-start.md +28 -0
  170. package/src/resources/extensions/gsd/repo-identity.ts +148 -0
  171. package/src/resources/extensions/gsd/resource-version.ts +99 -0
  172. package/src/resources/extensions/gsd/session-forensics.ts +4 -3
  173. package/src/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
  174. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
  175. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
  176. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
  177. package/src/resources/extensions/gsd/tests/extension-selector-separator.test.ts +60 -38
  178. package/src/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +5 -18
  179. package/src/resources/extensions/gsd/tests/git-service.test.ts +10 -37
  180. package/src/resources/extensions/gsd/tests/knowledge.test.ts +4 -4
  181. package/src/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
  182. package/src/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
  183. package/src/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
  184. package/src/resources/extensions/gsd/tests/workflow-templates.test.ts +173 -0
  185. package/src/resources/extensions/gsd/triage-resolution.ts +2 -1
  186. package/src/resources/extensions/gsd/types.ts +2 -0
  187. package/src/resources/extensions/gsd/workflow-templates/bugfix.md +87 -0
  188. package/src/resources/extensions/gsd/workflow-templates/dep-upgrade.md +74 -0
  189. package/src/resources/extensions/gsd/workflow-templates/full-project.md +41 -0
  190. package/src/resources/extensions/gsd/workflow-templates/hotfix.md +45 -0
  191. package/src/resources/extensions/gsd/workflow-templates/refactor.md +83 -0
  192. package/src/resources/extensions/gsd/workflow-templates/registry.json +85 -0
  193. package/src/resources/extensions/gsd/workflow-templates/security-audit.md +73 -0
  194. package/src/resources/extensions/gsd/workflow-templates/small-feature.md +81 -0
  195. package/src/resources/extensions/gsd/workflow-templates/spike.md +69 -0
  196. package/src/resources/extensions/gsd/workflow-templates.ts +241 -0
  197. package/src/resources/extensions/gsd/worktree-command.ts +1 -11
  198. package/src/resources/extensions/gsd/worktree-manager.ts +3 -2
  199. package/src/resources/extensions/gsd/worktree.ts +42 -5
  200. package/src/resources/extensions/mcp-client/index.ts +459 -0
  201. package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
  202. package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  203. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  204. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  205. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  206. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  207. package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  208. package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  209. package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  210. package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  211. package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  212. package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  213. package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  214. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  215. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  216. package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  217. package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  218. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  219. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  220. package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  221. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  222. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  223. package/src/resources/skills/create-skill/SKILL.md +184 -0
  224. package/src/resources/skills/create-skill/references/api-security.md +226 -0
  225. package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  226. package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
  227. package/src/resources/skills/create-skill/references/core-principles.md +437 -0
  228. package/src/resources/skills/create-skill/references/executable-code.md +175 -0
  229. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  230. package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  231. package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
  232. package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
  233. package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  234. package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
  235. package/src/resources/skills/create-skill/references/using-templates.md +112 -0
  236. package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  237. package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
  238. package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
  239. package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
  240. package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
  241. package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
  242. package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  243. package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  244. package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  245. package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  246. package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  247. package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  248. package/src/resources/skills/react-best-practices/SKILL.md +1 -1
  249. package/dist/resources/extensions/gsd/auto-worktree-sync.ts +0 -199
  250. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
  251. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
  252. package/dist/resources/extensions/mcporter/index.ts +0 -525
  253. package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -199
  254. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
  255. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
  256. package/src/resources/extensions/mcporter/index.ts +0 -525
@@ -0,0 +1,459 @@
1
+ /**
2
+ * MCP Client Extension — Native MCP server integration for pi
3
+ *
4
+ * Provides on-demand access to MCP servers configured in project files
5
+ * (.mcp.json, .gsd/mcp.json) using the @modelcontextprotocol/sdk Client
6
+ * directly — no external CLI dependency required.
7
+ *
8
+ * Three tools:
9
+ * mcp_servers — List available MCP servers from config files
10
+ * mcp_discover — Get tool signatures for a specific server (lazy connect)
11
+ * mcp_call — Call a tool on an MCP server (lazy connect)
12
+ */
13
+
14
+ import type { ExtensionAPI } from "@gsd/pi-coding-agent";
15
+ import {
16
+ truncateHead,
17
+ DEFAULT_MAX_BYTES,
18
+ DEFAULT_MAX_LINES,
19
+ formatSize,
20
+ } from "@gsd/pi-coding-agent";
21
+ import { Text } from "@gsd/pi-tui";
22
+ import { Type } from "@sinclair/typebox";
23
+ import { Client } from "@modelcontextprotocol/sdk/client";
24
+ import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
25
+ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
26
+ import { readFileSync, existsSync } from "node:fs";
27
+ import { join } from "node:path";
28
+
29
+ // ─── Types ────────────────────────────────────────────────────────────────────
30
+
31
+ interface McpServerConfig {
32
+ name: string;
33
+ transport: "stdio" | "http" | "unknown";
34
+ command?: string;
35
+ args?: string[];
36
+ env?: Record<string, string>;
37
+ url?: string;
38
+ cwd?: string;
39
+ }
40
+
41
+ interface McpToolSchema {
42
+ name: string;
43
+ description: string;
44
+ inputSchema?: Record<string, unknown>;
45
+ }
46
+
47
+ interface ManagedConnection {
48
+ client: Client;
49
+ transport: StdioClientTransport | StreamableHTTPClientTransport;
50
+ }
51
+
52
+ // ─── Connection Manager ───────────────────────────────────────────────────────
53
+
54
+ const connections = new Map<string, ManagedConnection>();
55
+ let configCache: McpServerConfig[] | null = null;
56
+ const toolCache = new Map<string, McpToolSchema[]>();
57
+
58
+ function readConfigs(): McpServerConfig[] {
59
+ if (configCache) return configCache;
60
+
61
+ const servers: McpServerConfig[] = [];
62
+ const seen = new Set<string>();
63
+ const configPaths = [
64
+ join(process.cwd(), ".mcp.json"),
65
+ join(process.cwd(), ".gsd", "mcp.json"),
66
+ ];
67
+
68
+ for (const configPath of configPaths) {
69
+ try {
70
+ if (!existsSync(configPath)) continue;
71
+ const raw = readFileSync(configPath, "utf-8");
72
+ const data = JSON.parse(raw) as Record<string, unknown>;
73
+ const mcpServers = (data.mcpServers ?? data.servers) as
74
+ | Record<string, Record<string, unknown>>
75
+ | undefined;
76
+ if (!mcpServers || typeof mcpServers !== "object") continue;
77
+
78
+ for (const [name, config] of Object.entries(mcpServers)) {
79
+ if (seen.has(name)) continue;
80
+ seen.add(name);
81
+
82
+ const hasCommand = typeof config.command === "string";
83
+ const hasUrl = typeof config.url === "string";
84
+ const transport: McpServerConfig["transport"] = hasCommand
85
+ ? "stdio"
86
+ : hasUrl
87
+ ? "http"
88
+ : "unknown";
89
+
90
+ servers.push({
91
+ name,
92
+ transport,
93
+ ...(hasCommand && {
94
+ command: config.command as string,
95
+ args: Array.isArray(config.args) ? (config.args as string[]) : undefined,
96
+ env: config.env && typeof config.env === "object"
97
+ ? (config.env as Record<string, string>)
98
+ : undefined,
99
+ cwd: typeof config.cwd === "string" ? config.cwd : undefined,
100
+ }),
101
+ ...(hasUrl && { url: config.url as string }),
102
+ });
103
+ }
104
+ } catch {
105
+ // Non-fatal — config file may not exist or be malformed
106
+ }
107
+ }
108
+
109
+ configCache = servers;
110
+ return servers;
111
+ }
112
+
113
+ function getServerConfig(name: string): McpServerConfig | undefined {
114
+ return readConfigs().find((s) => s.name === name);
115
+ }
116
+
117
+ async function getOrConnect(name: string, signal?: AbortSignal): Promise<Client> {
118
+ const existing = connections.get(name);
119
+ if (existing) return existing.client;
120
+
121
+ const config = getServerConfig(name);
122
+ if (!config) throw new Error(`Unknown MCP server: "${name}". Use mcp_servers to list available servers.`);
123
+
124
+ const client = new Client({ name: "gsd", version: "1.0.0" });
125
+ let transport: StdioClientTransport | StreamableHTTPClientTransport;
126
+
127
+ if (config.transport === "stdio" && config.command) {
128
+ transport = new StdioClientTransport({
129
+ command: config.command,
130
+ args: config.args,
131
+ env: config.env ? { ...process.env, ...config.env } as Record<string, string> : undefined,
132
+ cwd: config.cwd,
133
+ stderr: "pipe",
134
+ });
135
+ } else if (config.transport === "http" && config.url) {
136
+ transport = new StreamableHTTPClientTransport(new URL(config.url));
137
+ } else {
138
+ throw new Error(`Server "${name}" has unsupported transport: ${config.transport}`);
139
+ }
140
+
141
+ await client.connect(transport, { signal, timeout: 30000 });
142
+ connections.set(name, { client, transport });
143
+ return client;
144
+ }
145
+
146
+ async function closeAll(): Promise<void> {
147
+ const closing = Array.from(connections.entries()).map(async ([name, conn]) => {
148
+ try {
149
+ await conn.client.close();
150
+ } catch {
151
+ // Best-effort cleanup
152
+ }
153
+ connections.delete(name);
154
+ });
155
+ await Promise.allSettled(closing);
156
+ toolCache.clear();
157
+ }
158
+
159
+ // ─── Formatters ───────────────────────────────────────────────────────────────
160
+
161
+ function formatServerList(servers: McpServerConfig[]): string {
162
+ if (servers.length === 0) return "No MCP servers configured. Add servers to .mcp.json or .gsd/mcp.json.";
163
+
164
+ const lines: string[] = [`${servers.length} MCP servers configured:\n`];
165
+
166
+ for (const s of servers) {
167
+ const connected = connections.has(s.name) ? "✓" : "○";
168
+ const cached = toolCache.get(s.name);
169
+ const toolCount = cached ? ` — ${cached.length} tools` : "";
170
+ lines.push(`${connected} ${s.name} (${s.transport})${toolCount}`);
171
+ }
172
+
173
+ lines.push("\nUse mcp_discover to see full tool schemas for a specific server.");
174
+ lines.push("Use mcp_call to invoke a tool: mcp_call(server, tool, args).");
175
+ return lines.join("\n");
176
+ }
177
+
178
+ function formatToolList(serverName: string, tools: McpToolSchema[]): string {
179
+ const lines: string[] = [`${serverName} — ${tools.length} tools:\n`];
180
+
181
+ for (const tool of tools) {
182
+ lines.push(`## ${tool.name}`);
183
+ if (tool.description) lines.push(tool.description);
184
+ if (tool.inputSchema) {
185
+ lines.push("```json");
186
+ lines.push(JSON.stringify(tool.inputSchema, null, 2));
187
+ lines.push("```");
188
+ }
189
+ lines.push("");
190
+ }
191
+
192
+ lines.push(`Call with: mcp_call(server="${serverName}", tool="<tool_name>", args={...})`);
193
+ return lines.join("\n");
194
+ }
195
+
196
+ // ─── Extension ────────────────────────────────────────────────────────────────
197
+
198
+ export default function (pi: ExtensionAPI) {
199
+ // ── mcp_servers ──────────────────────────────────────────────────────────
200
+
201
+ pi.registerTool({
202
+ name: "mcp_servers",
203
+ label: "MCP Servers",
204
+ description:
205
+ "List all available MCP servers configured in project files (.mcp.json, .gsd/mcp.json). " +
206
+ "Shows server names, transport type, and connection status. Use mcp_discover to get full tool schemas for a server.",
207
+ promptSnippet:
208
+ "List available MCP servers from project configuration",
209
+ promptGuidelines: [
210
+ "Call mcp_servers to see what MCP servers are available before trying to use one.",
211
+ "MCP servers provide external integrations (Twitter, Linear, Railway, etc.) via the Model Context Protocol.",
212
+ "After listing, use mcp_discover(server) to get tool schemas, then mcp_call(server, tool, args) to invoke.",
213
+ ],
214
+ parameters: Type.Object({
215
+ refresh: Type.Optional(
216
+ Type.Boolean({ description: "Force refresh the server list (default: use cache)" }),
217
+ ),
218
+ }),
219
+
220
+ async execute(_id, params) {
221
+ if (params.refresh) configCache = null;
222
+
223
+ const servers = readConfigs();
224
+ return {
225
+ content: [{ type: "text", text: formatServerList(servers) }],
226
+ details: {
227
+ serverCount: servers.length,
228
+ cached: !params.refresh && configCache !== null,
229
+ },
230
+ };
231
+ },
232
+
233
+ renderCall(args, theme) {
234
+ let text = theme.fg("toolTitle", theme.bold("mcp_servers"));
235
+ if (args.refresh) text += theme.fg("warning", " (refresh)");
236
+ return new Text(text, 0, 0);
237
+ },
238
+
239
+ renderResult(result, { isPartial }, theme) {
240
+ if (isPartial) return new Text(theme.fg("warning", "Reading MCP config..."), 0, 0);
241
+ const d = result.details as { serverCount: number } | undefined;
242
+ return new Text(
243
+ theme.fg("success", `${d?.serverCount ?? 0} servers configured`),
244
+ 0,
245
+ 0,
246
+ );
247
+ },
248
+ });
249
+
250
+ // ── mcp_discover ─────────────────────────────────────────────────────────
251
+
252
+ pi.registerTool({
253
+ name: "mcp_discover",
254
+ label: "MCP Discover",
255
+ description:
256
+ "Get detailed tool signatures and JSON schemas for a specific MCP server. " +
257
+ "Connects to the server on first call (lazy connection). " +
258
+ "Use this to understand what tools a server provides and what arguments they accept " +
259
+ "before calling them with mcp_call.",
260
+ promptSnippet:
261
+ "Get tool schemas for a specific MCP server before calling its tools",
262
+ promptGuidelines: [
263
+ "Call mcp_discover with a server name to see the full tool signatures before calling mcp_call.",
264
+ "The schemas show required and optional parameters with types and descriptions.",
265
+ ],
266
+ parameters: Type.Object({
267
+ server: Type.String({
268
+ description:
269
+ "MCP server name (from mcp_servers output), e.g. 'railway', 'twitter-mcp', 'linear'",
270
+ }),
271
+ }),
272
+
273
+ async execute(_id, params, signal) {
274
+ try {
275
+ // Return cached tools if available
276
+ const cached = toolCache.get(params.server);
277
+ if (cached) {
278
+ const text = formatToolList(params.server, cached);
279
+ const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
280
+ let finalText = truncation.content;
281
+ if (truncation.truncated) {
282
+ finalText += `\n\n[Truncated: ${truncation.outputLines}/${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
283
+ }
284
+ return {
285
+ content: [{ type: "text", text: finalText }],
286
+ details: { server: params.server, toolCount: cached.length, cached: true },
287
+ };
288
+ }
289
+
290
+ const client = await getOrConnect(params.server, signal);
291
+ const result = await client.listTools(undefined, { signal, timeout: 30000 });
292
+ const tools: McpToolSchema[] = (result.tools ?? []).map((t) => ({
293
+ name: t.name,
294
+ description: t.description ?? "",
295
+ inputSchema: t.inputSchema as Record<string, unknown> | undefined,
296
+ }));
297
+ toolCache.set(params.server, tools);
298
+
299
+ const text = formatToolList(params.server, tools);
300
+ const truncation = truncateHead(text, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
301
+ let finalText = truncation.content;
302
+ if (truncation.truncated) {
303
+ finalText += `\n\n[Truncated: ${truncation.outputLines}/${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
304
+ }
305
+
306
+ return {
307
+ content: [{ type: "text", text: finalText }],
308
+ details: { server: params.server, toolCount: tools.length, cached: false },
309
+ };
310
+ } catch (err: unknown) {
311
+ const msg = err instanceof Error ? err.message : String(err);
312
+ throw new Error(`Failed to discover tools for "${params.server}": ${msg}`);
313
+ }
314
+ },
315
+
316
+ renderCall(args, theme) {
317
+ let text = theme.fg("toolTitle", theme.bold("mcp_discover "));
318
+ text += theme.fg("accent", args.server);
319
+ return new Text(text, 0, 0);
320
+ },
321
+
322
+ renderResult(result, { isPartial }, theme) {
323
+ if (isPartial)
324
+ return new Text(theme.fg("warning", "Discovering tools..."), 0, 0);
325
+ const d = result.details as { server: string; toolCount: number } | undefined;
326
+ return new Text(
327
+ theme.fg("success", `${d?.toolCount ?? 0} tools`) +
328
+ theme.fg("dim", ` · ${d?.server}`),
329
+ 0,
330
+ 0,
331
+ );
332
+ },
333
+ });
334
+
335
+ // ── mcp_call ─────────────────────────────────────────────────────────────
336
+
337
+ pi.registerTool({
338
+ name: "mcp_call",
339
+ label: "MCP Call",
340
+ description:
341
+ "Call a tool on an MCP server. Provide the server name, tool name, and arguments. " +
342
+ "Connects to the server on first call (lazy connection). " +
343
+ "Use mcp_discover first to see available tools and their required arguments.",
344
+ promptSnippet: "Call a tool on an MCP server",
345
+ promptGuidelines: [
346
+ "Always use mcp_discover first to understand the tool's parameters before calling mcp_call.",
347
+ "Arguments are passed as a JSON object matching the tool's input schema.",
348
+ ],
349
+ parameters: Type.Object({
350
+ server: Type.String({
351
+ description: "MCP server name, e.g. 'railway', 'twitter-mcp'",
352
+ }),
353
+ tool: Type.String({
354
+ description: "Tool name on that server, e.g. 'railway_list_projects'",
355
+ }),
356
+ args: Type.Optional(
357
+ Type.Record(Type.String(), Type.Unknown(), {
358
+ description:
359
+ "Tool arguments as key-value pairs matching the tool's input schema",
360
+ }),
361
+ ),
362
+ }),
363
+
364
+ async execute(_id, params, signal) {
365
+ try {
366
+ const client = await getOrConnect(params.server, signal);
367
+ const result = await client.callTool(
368
+ { name: params.tool, arguments: params.args ?? {} },
369
+ undefined,
370
+ { signal, timeout: 60000 },
371
+ );
372
+
373
+ // Serialize result content to text
374
+ const contentItems = result.content as Array<{ type: string; text?: string }>;
375
+ const raw = contentItems
376
+ .map((c) => (c.type === "text" ? c.text ?? "" : JSON.stringify(c)))
377
+ .join("\n");
378
+
379
+ const truncation = truncateHead(raw, { maxLines: DEFAULT_MAX_LINES, maxBytes: DEFAULT_MAX_BYTES });
380
+ let finalText = truncation.content;
381
+ if (truncation.truncated) {
382
+ finalText += `\n\n[Output truncated: ${truncation.outputLines}/${truncation.totalLines} lines (${formatSize(truncation.outputBytes)} of ${formatSize(truncation.totalBytes)})]`;
383
+ }
384
+
385
+ return {
386
+ content: [{ type: "text", text: finalText }],
387
+ details: {
388
+ server: params.server,
389
+ tool: params.tool,
390
+ charCount: finalText.length,
391
+ truncated: truncation.truncated,
392
+ },
393
+ };
394
+ } catch (err: unknown) {
395
+ const msg = err instanceof Error ? err.message : String(err);
396
+ throw new Error(`MCP call failed: ${params.server}.${params.tool}\n${msg}`);
397
+ }
398
+ },
399
+
400
+ renderCall(args, theme) {
401
+ let text = theme.fg("toolTitle", theme.bold("mcp_call "));
402
+ text += theme.fg("accent", `${args.server}.${args.tool}`);
403
+ if (args.args && Object.keys(args.args).length > 0) {
404
+ const preview = Object.entries(args.args)
405
+ .slice(0, 3)
406
+ .map(([k, v]) => {
407
+ const val = typeof v === "string" ? v : JSON.stringify(v);
408
+ return `${k}:${val.length > 30 ? val.slice(0, 30) + "…" : val}`;
409
+ })
410
+ .join(" ");
411
+ text += " " + theme.fg("muted", preview);
412
+ }
413
+ return new Text(text, 0, 0);
414
+ },
415
+
416
+ renderResult(result, { isPartial, expanded }, theme) {
417
+ if (isPartial) return new Text(theme.fg("warning", "Calling MCP tool..."), 0, 0);
418
+
419
+ const d = result.details as {
420
+ server: string;
421
+ tool: string;
422
+ charCount: number;
423
+ truncated: boolean;
424
+ } | undefined;
425
+
426
+ let text = theme.fg("success", `✓ ${d?.server}.${d?.tool}`);
427
+ text += theme.fg("dim", ` · ${(d?.charCount ?? 0).toLocaleString()} chars`);
428
+ if (d?.truncated) text += theme.fg("warning", " · truncated");
429
+
430
+ if (expanded) {
431
+ const content = result.content[0];
432
+ if (content?.type === "text") {
433
+ const preview = content.text.split("\n").slice(0, 15).join("\n");
434
+ text += "\n\n" + theme.fg("dim", preview);
435
+ }
436
+ }
437
+
438
+ return new Text(text, 0, 0);
439
+ },
440
+ });
441
+
442
+ // ── Lifecycle ─────────────────────────────────────────────────────────────
443
+
444
+ pi.on("session_start", async (_event, ctx) => {
445
+ const servers = readConfigs();
446
+ if (servers.length > 0) {
447
+ ctx.ui.notify(`MCP client ready — ${servers.length} server(s) configured`, "info");
448
+ }
449
+ });
450
+
451
+ pi.on("session_shutdown", async () => {
452
+ await closeAll();
453
+ });
454
+
455
+ pi.on("session_switch", async () => {
456
+ await closeAll();
457
+ configCache = null;
458
+ });
459
+ }
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: create-gsd-extension
3
+ description: Create, debug, and iterate on GSD extensions (TypeScript modules that add tools, commands, event hooks, custom UI, and providers to GSD). Use when asked to build an extension, add a tool the LLM can call, register a slash command, hook into GSD events, create custom TUI components, or modify GSD behavior. Triggers on "create extension", "build extension", "add a tool", "register command", "hook into gsd", "custom tool", "gsd plugin", "gsd extension".
4
+ ---
5
+
6
+ <essential_principles>
7
+
8
+ **Extensions are TypeScript modules** that hook into GSD's runtime (built on pi). They export a default function receiving `ExtensionAPI` and use it to subscribe to events, register tools/commands/shortcuts, and interact with the session.
9
+
10
+ **GSD extension paths:**
11
+ - Global extensions: `~/.gsd/agent/extensions/*.ts` or `~/.gsd/agent/extensions/*/index.ts`
12
+ - Project-local extensions: `.gsd/extensions/*.ts` or `.gsd/extensions/*/index.ts`
13
+
14
+ **The three primitives:**
15
+ 1. **Events** — Listen and react (`pi.on("event", handler)`). Can block tool calls, modify messages, inject context.
16
+ 2. **Tools** — Give the LLM new abilities (`pi.registerTool()`). LLM calls them autonomously.
17
+ 3. **Commands** — Give users slash commands (`pi.registerCommand()`). Users type `/mycommand`.
18
+
19
+ **Non-negotiable rules:**
20
+ - Use `StringEnum` from `@mariozechner/pi-ai` for string enum params (NOT `Type.Union`/`Type.Literal` — breaks Google's API)
21
+ - Truncate tool output to 50KB / 2000 lines max (use `truncateHead`/`truncateTail` from `@mariozechner/pi-coding-agent`)
22
+ - Store stateful tool state in `details` for branching support
23
+ - Check `signal?.aborted` in long-running tool executions
24
+ - Use `pi.exec()` not `child_process` for shell commands
25
+ - Check `ctx.hasUI` before dialog methods (non-interactive modes exist)
26
+ - Session control methods (`waitForIdle`, `newSession`, `fork`, `navigateTree`, `reload`) are ONLY available in command handlers — they deadlock in event handlers
27
+ - Lines from `render()` must not exceed `width` — use `truncateToWidth()`
28
+ - Use theme from callback params, never import directly
29
+ - Strip leading `@` from path params in custom tools (some models add it)
30
+
31
+ **Available imports:**
32
+
33
+ | Package | Purpose |
34
+ |---------|---------|
35
+ | `@mariozechner/pi-coding-agent` | `ExtensionAPI`, `ExtensionContext`, `Theme`, event types, tool utilities, `DynamicBorder`, `BorderedLoader`, `CustomEditor`, `highlightCode` |
36
+ | `@sinclair/typebox` | `Type.Object`, `Type.String`, `Type.Number`, `Type.Optional`, `Type.Boolean`, `Type.Array` |
37
+ | `@mariozechner/pi-ai` | `StringEnum` (required for string enums), `Type` re-export |
38
+ | `@mariozechner/pi-tui` | `Text`, `Box`, `Container`, `Spacer`, `Markdown`, `SelectList`, `Input`, `matchesKey`, `Key`, `truncateToWidth`, `visibleWidth` |
39
+ | Node.js built-ins | `node:fs`, `node:path`, `node:child_process`, etc. |
40
+
41
+ </essential_principles>
42
+
43
+ <routing>
44
+ Based on user intent, route to the appropriate workflow:
45
+
46
+ **Building a new extension:**
47
+ - "Create an extension", "build a tool", "I want to add a command" → `workflows/create-extension.md`
48
+
49
+ **Adding capabilities to an existing extension:**
50
+ - "Add a tool to my extension", "add event hook", "add custom rendering" → `workflows/add-capability.md`
51
+
52
+ **Debugging an extension:**
53
+ - "My extension doesn't work", "tool not showing up", "event not firing" → `workflows/debug-extension.md`
54
+
55
+ **If user intent is clear from context, skip the question and go directly to the workflow.**
56
+ </routing>
57
+
58
+ <reference_index>
59
+ All domain knowledge in `references/`:
60
+
61
+ **Core architecture:** extension-lifecycle.md, events-reference.md
62
+ **API surface:** extensionapi-reference.md, extensioncontext-reference.md
63
+ **Capabilities:** custom-tools.md, custom-commands.md, custom-ui.md, custom-rendering.md
64
+ **Patterns:** state-management.md, system-prompt-modification.md, compaction-session-control.md
65
+ **Infrastructure:** model-provider-management.md, remote-execution-overrides.md, packaging-distribution.md, mode-behavior.md
66
+ **Gotchas:** key-rules-gotchas.md
67
+ </reference_index>
68
+
69
+ <workflows_index>
70
+ | Workflow | Purpose |
71
+ |----------|---------|
72
+ | create-extension.md | Build a new extension from scratch |
73
+ | add-capability.md | Add tools, commands, hooks, UI to an existing extension |
74
+ | debug-extension.md | Diagnose and fix extension issues |
75
+ </workflows_index>
76
+
77
+ <success_criteria>
78
+ Extension is complete when:
79
+ - TypeScript compiles without errors (jiti handles this at runtime)
80
+ - Extension loads on GSD startup or `/reload` without errors
81
+ - Tools appear in the LLM's system prompt and are callable
82
+ - Commands respond to `/command` input
83
+ - Event hooks fire at the expected lifecycle points
84
+ - Custom UI renders correctly within terminal width
85
+ - State persists correctly across session restarts (if stateful)
86
+ - Output is truncated to safe limits (if tools produce variable output)
87
+ </success_criteria>
@@ -0,0 +1,77 @@
1
+ <overview>
2
+ Custom compaction hooks, triggering compaction, and session control methods available only in command handlers.
3
+ </overview>
4
+
5
+ <custom_compaction>
6
+ Override default compaction behavior:
7
+
8
+ ```typescript
9
+ pi.on("session_before_compact", async (event, ctx) => {
10
+ const { preparation, branchEntries, customInstructions, signal } = event;
11
+
12
+ // Option 1: Cancel
13
+ return { cancel: true };
14
+
15
+ // Option 2: Custom summary
16
+ return {
17
+ compaction: {
18
+ summary: "Custom summary of conversation so far...",
19
+ firstKeptEntryId: preparation.firstKeptEntryId,
20
+ tokensBefore: preparation.tokensBefore,
21
+ }
22
+ };
23
+ });
24
+ ```
25
+ </custom_compaction>
26
+
27
+ <trigger_compaction>
28
+ Trigger compaction programmatically from any handler:
29
+
30
+ ```typescript
31
+ ctx.compact({
32
+ customInstructions: "Focus on the authentication changes",
33
+ onComplete: (result) => ctx.ui.notify("Compacted!", "info"),
34
+ onError: (error) => ctx.ui.notify(`Failed: ${error.message}`, "error"),
35
+ });
36
+ ```
37
+ </trigger_compaction>
38
+
39
+ <session_control>
40
+ **Only available in command handlers** (deadlocks in event handlers):
41
+
42
+ ```typescript
43
+ pi.registerCommand("handoff", {
44
+ handler: async (args, ctx) => {
45
+ await ctx.waitForIdle();
46
+
47
+ // Create new session with initial context
48
+ const result = await ctx.newSession({
49
+ parentSession: ctx.sessionManager.getSessionFile(),
50
+ setup: async (sm) => {
51
+ sm.appendMessage({
52
+ role: "user",
53
+ content: [{ type: "text", text: `Context: ${args}` }],
54
+ timestamp: Date.now(),
55
+ });
56
+ },
57
+ });
58
+
59
+ if (result.cancelled) { /* extension cancelled via session_before_switch */ }
60
+ },
61
+ });
62
+ ```
63
+
64
+ | Method | Purpose |
65
+ |--------|---------|
66
+ | `ctx.waitForIdle()` | Wait for agent to finish streaming |
67
+ | `ctx.newSession(options?)` | Create a new session |
68
+ | `ctx.fork(entryId)` | Fork from a specific entry |
69
+ | `ctx.navigateTree(targetId, options?)` | Navigate session tree (with optional summary) |
70
+ | `ctx.reload()` | Hot-reload everything (treat as terminal — code after runs pre-reload version) |
71
+
72
+ `navigateTree` options:
73
+ - `summarize: boolean` — generate summary of abandoned branch
74
+ - `customInstructions: string` — instructions for summarizer
75
+ - `replaceInstructions: boolean` — replace default prompt entirely
76
+ - `label: string` — label to attach to branch summary
77
+ </session_control>