gsd-pi 2.29.0-dev.77f06e2 → 2.29.0-dev.f08b4fe

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 (251) hide show
  1. package/dist/extension-registry.d.ts +63 -0
  2. package/dist/extension-registry.js +166 -0
  3. package/dist/headless.js +4 -0
  4. package/dist/loader.js +10 -1
  5. package/dist/resource-loader.js +11 -1
  6. package/dist/resources/extensions/async-jobs/extension-manifest.json +13 -0
  7. package/dist/resources/extensions/bg-shell/extension-manifest.json +14 -0
  8. package/dist/resources/extensions/browser-tools/extension-manifest.json +37 -0
  9. package/dist/resources/extensions/context7/extension-manifest.json +12 -0
  10. package/dist/resources/extensions/google-search/extension-manifest.json +12 -0
  11. package/dist/resources/extensions/gsd/auto-dashboard.ts +31 -0
  12. package/dist/resources/extensions/gsd/auto-dispatch.ts +32 -3
  13. package/dist/resources/extensions/gsd/auto-post-unit.ts +39 -10
  14. package/dist/resources/extensions/gsd/auto-prompts.ts +40 -17
  15. package/dist/resources/extensions/gsd/auto-recovery.ts +2 -1
  16. package/dist/resources/extensions/gsd/auto-start.ts +18 -32
  17. package/dist/resources/extensions/gsd/auto-worktree.ts +21 -182
  18. package/dist/resources/extensions/gsd/auto.ts +2 -9
  19. package/dist/resources/extensions/gsd/captures.ts +4 -10
  20. package/dist/resources/extensions/gsd/commands-extensions.ts +328 -0
  21. package/dist/resources/extensions/gsd/commands-handlers.ts +2 -1
  22. package/dist/resources/extensions/gsd/commands.ts +53 -2
  23. package/dist/resources/extensions/gsd/detection.ts +2 -1
  24. package/dist/resources/extensions/gsd/doctor-checks.ts +49 -1
  25. package/dist/resources/extensions/gsd/doctor-types.ts +3 -1
  26. package/dist/resources/extensions/gsd/extension-manifest.json +18 -0
  27. package/dist/resources/extensions/gsd/forensics.ts +2 -2
  28. package/dist/resources/extensions/gsd/git-service.ts +3 -2
  29. package/dist/resources/extensions/gsd/gitignore.ts +9 -63
  30. package/dist/resources/extensions/gsd/gsd-db.ts +1 -165
  31. package/dist/resources/extensions/gsd/guided-flow.ts +8 -5
  32. package/dist/resources/extensions/gsd/index.ts +3 -3
  33. package/dist/resources/extensions/gsd/md-importer.ts +3 -2
  34. package/dist/resources/extensions/gsd/mechanical-completion.ts +430 -0
  35. package/dist/resources/extensions/gsd/migrate/command.ts +3 -2
  36. package/dist/resources/extensions/gsd/migrate/writer.ts +2 -1
  37. package/dist/resources/extensions/gsd/migrate-external.ts +123 -0
  38. package/dist/resources/extensions/gsd/paths.ts +24 -2
  39. package/dist/resources/extensions/gsd/post-unit-hooks.ts +6 -5
  40. package/dist/resources/extensions/gsd/preferences-models.ts +7 -1
  41. package/dist/resources/extensions/gsd/preferences-validation.ts +2 -1
  42. package/dist/resources/extensions/gsd/preferences.ts +10 -5
  43. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
  44. package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  45. package/dist/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
  46. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -1
  47. package/dist/resources/extensions/gsd/repo-identity.ts +148 -0
  48. package/dist/resources/extensions/gsd/resource-version.ts +99 -0
  49. package/dist/resources/extensions/gsd/session-forensics.ts +4 -3
  50. package/dist/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
  51. package/dist/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
  52. package/dist/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
  53. package/dist/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
  54. package/dist/resources/extensions/gsd/tests/feature-branch-lifecycle-integration.test.ts +5 -18
  55. package/dist/resources/extensions/gsd/tests/git-service.test.ts +10 -37
  56. package/dist/resources/extensions/gsd/tests/knowledge.test.ts +4 -4
  57. package/dist/resources/extensions/gsd/tests/mechanical-completion.test.ts +356 -0
  58. package/dist/resources/extensions/gsd/tests/plan-slice-prompt.test.ts +1 -0
  59. package/dist/resources/extensions/gsd/tests/token-profile.test.ts +14 -16
  60. package/dist/resources/extensions/gsd/triage-resolution.ts +2 -1
  61. package/dist/resources/extensions/gsd/types.ts +2 -0
  62. package/dist/resources/extensions/gsd/worktree-command.ts +1 -11
  63. package/dist/resources/extensions/gsd/worktree-manager.ts +3 -2
  64. package/dist/resources/extensions/gsd/worktree.ts +42 -5
  65. package/dist/resources/extensions/mac-tools/extension-manifest.json +16 -0
  66. package/dist/resources/extensions/mcporter/extension-manifest.json +12 -0
  67. package/dist/resources/extensions/remote-questions/extension-manifest.json +11 -0
  68. package/dist/resources/extensions/search-the-web/extension-manifest.json +13 -0
  69. package/dist/resources/extensions/slash-commands/extension-manifest.json +11 -0
  70. package/dist/resources/extensions/subagent/extension-manifest.json +13 -0
  71. package/dist/resources/extensions/ttsr/extension-manifest.json +11 -0
  72. package/dist/resources/extensions/universal-config/extension-manifest.json +13 -0
  73. package/dist/resources/extensions/voice/extension-manifest.json +12 -0
  74. package/dist/resources/skills/create-gsd-extension/SKILL.md +87 -0
  75. package/dist/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  76. package/dist/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  77. package/dist/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  78. package/dist/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  79. package/dist/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  80. package/dist/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  81. package/dist/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  82. package/dist/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  83. package/dist/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  84. package/dist/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  85. package/dist/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  86. package/dist/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  87. package/dist/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  88. package/dist/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  89. package/dist/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  90. package/dist/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  91. package/dist/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  92. package/dist/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  93. package/dist/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  94. package/dist/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  95. package/dist/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  96. package/dist/resources/skills/create-skill/SKILL.md +184 -0
  97. package/dist/resources/skills/create-skill/references/api-security.md +226 -0
  98. package/dist/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  99. package/dist/resources/skills/create-skill/references/common-patterns.md +595 -0
  100. package/dist/resources/skills/create-skill/references/core-principles.md +437 -0
  101. package/dist/resources/skills/create-skill/references/executable-code.md +175 -0
  102. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  103. package/dist/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  104. package/dist/resources/skills/create-skill/references/recommended-structure.md +168 -0
  105. package/dist/resources/skills/create-skill/references/skill-structure.md +372 -0
  106. package/dist/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  107. package/dist/resources/skills/create-skill/references/using-scripts.md +113 -0
  108. package/dist/resources/skills/create-skill/references/using-templates.md +112 -0
  109. package/dist/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  110. package/dist/resources/skills/create-skill/templates/router-skill.md +73 -0
  111. package/dist/resources/skills/create-skill/templates/simple-skill.md +33 -0
  112. package/dist/resources/skills/create-skill/workflows/add-reference.md +96 -0
  113. package/dist/resources/skills/create-skill/workflows/add-script.md +93 -0
  114. package/dist/resources/skills/create-skill/workflows/add-template.md +74 -0
  115. package/dist/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  116. package/dist/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  117. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  118. package/dist/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  119. package/dist/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  120. package/dist/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  121. package/dist/resources/skills/react-best-practices/SKILL.md +1 -1
  122. package/package.json +1 -1
  123. package/packages/native/dist/native.d.ts +2 -0
  124. package/packages/native/dist/native.js +19 -5
  125. package/packages/native/src/native.ts +23 -9
  126. package/packages/pi-coding-agent/dist/core/lsp/client.d.ts.map +1 -1
  127. package/packages/pi-coding-agent/dist/core/lsp/client.js +3 -0
  128. package/packages/pi-coding-agent/dist/core/lsp/client.js.map +1 -1
  129. package/packages/pi-coding-agent/src/core/lsp/client.ts +3 -0
  130. package/src/resources/extensions/async-jobs/extension-manifest.json +13 -0
  131. package/src/resources/extensions/bg-shell/extension-manifest.json +14 -0
  132. package/src/resources/extensions/browser-tools/extension-manifest.json +37 -0
  133. package/src/resources/extensions/context7/extension-manifest.json +12 -0
  134. package/src/resources/extensions/google-search/extension-manifest.json +12 -0
  135. package/src/resources/extensions/gsd/auto-dashboard.ts +31 -0
  136. package/src/resources/extensions/gsd/auto-dispatch.ts +32 -3
  137. package/src/resources/extensions/gsd/auto-post-unit.ts +39 -10
  138. package/src/resources/extensions/gsd/auto-prompts.ts +40 -17
  139. package/src/resources/extensions/gsd/auto-recovery.ts +2 -1
  140. package/src/resources/extensions/gsd/auto-start.ts +18 -32
  141. package/src/resources/extensions/gsd/auto-worktree.ts +21 -182
  142. package/src/resources/extensions/gsd/auto.ts +2 -9
  143. package/src/resources/extensions/gsd/captures.ts +4 -10
  144. package/src/resources/extensions/gsd/commands-extensions.ts +328 -0
  145. package/src/resources/extensions/gsd/commands-handlers.ts +2 -1
  146. package/src/resources/extensions/gsd/commands.ts +53 -2
  147. package/src/resources/extensions/gsd/detection.ts +2 -1
  148. package/src/resources/extensions/gsd/doctor-checks.ts +49 -1
  149. package/src/resources/extensions/gsd/doctor-types.ts +3 -1
  150. package/src/resources/extensions/gsd/extension-manifest.json +18 -0
  151. package/src/resources/extensions/gsd/forensics.ts +2 -2
  152. package/src/resources/extensions/gsd/git-service.ts +3 -2
  153. package/src/resources/extensions/gsd/gitignore.ts +9 -63
  154. package/src/resources/extensions/gsd/gsd-db.ts +1 -165
  155. package/src/resources/extensions/gsd/guided-flow.ts +8 -5
  156. package/src/resources/extensions/gsd/index.ts +3 -3
  157. package/src/resources/extensions/gsd/md-importer.ts +3 -2
  158. package/src/resources/extensions/gsd/mechanical-completion.ts +430 -0
  159. package/src/resources/extensions/gsd/migrate/command.ts +3 -2
  160. package/src/resources/extensions/gsd/migrate/writer.ts +2 -1
  161. package/src/resources/extensions/gsd/migrate-external.ts +123 -0
  162. package/src/resources/extensions/gsd/paths.ts +24 -2
  163. package/src/resources/extensions/gsd/post-unit-hooks.ts +6 -5
  164. package/src/resources/extensions/gsd/preferences-models.ts +7 -1
  165. package/src/resources/extensions/gsd/preferences-validation.ts +2 -1
  166. package/src/resources/extensions/gsd/preferences.ts +10 -5
  167. package/src/resources/extensions/gsd/prompts/discuss-headless.md +4 -2
  168. package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +1 -1
  169. package/src/resources/extensions/gsd/prompts/plan-milestone.md +26 -2
  170. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -1
  171. package/src/resources/extensions/gsd/repo-identity.ts +148 -0
  172. package/src/resources/extensions/gsd/resource-version.ts +99 -0
  173. package/src/resources/extensions/gsd/session-forensics.ts +4 -3
  174. package/src/resources/extensions/gsd/tests/activity-log.test.ts +2 -2
  175. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +3 -3
  176. package/src/resources/extensions/gsd/tests/auto-worktree.test.ts +0 -58
  177. package/src/resources/extensions/gsd/tests/doctor-runtime.test.ts +3 -4
  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/triage-resolution.ts +2 -1
  185. package/src/resources/extensions/gsd/types.ts +2 -0
  186. package/src/resources/extensions/gsd/worktree-command.ts +1 -11
  187. package/src/resources/extensions/gsd/worktree-manager.ts +3 -2
  188. package/src/resources/extensions/gsd/worktree.ts +42 -5
  189. package/src/resources/extensions/mac-tools/extension-manifest.json +16 -0
  190. package/src/resources/extensions/mcporter/extension-manifest.json +12 -0
  191. package/src/resources/extensions/remote-questions/extension-manifest.json +11 -0
  192. package/src/resources/extensions/search-the-web/extension-manifest.json +13 -0
  193. package/src/resources/extensions/slash-commands/extension-manifest.json +11 -0
  194. package/src/resources/extensions/subagent/extension-manifest.json +13 -0
  195. package/src/resources/extensions/ttsr/extension-manifest.json +11 -0
  196. package/src/resources/extensions/universal-config/extension-manifest.json +13 -0
  197. package/src/resources/extensions/voice/extension-manifest.json +12 -0
  198. package/src/resources/skills/create-gsd-extension/SKILL.md +87 -0
  199. package/src/resources/skills/create-gsd-extension/references/compaction-session-control.md +77 -0
  200. package/src/resources/skills/create-gsd-extension/references/custom-commands.md +139 -0
  201. package/src/resources/skills/create-gsd-extension/references/custom-rendering.md +108 -0
  202. package/src/resources/skills/create-gsd-extension/references/custom-tools.md +183 -0
  203. package/src/resources/skills/create-gsd-extension/references/custom-ui.md +490 -0
  204. package/src/resources/skills/create-gsd-extension/references/events-reference.md +126 -0
  205. package/src/resources/skills/create-gsd-extension/references/extension-lifecycle.md +64 -0
  206. package/src/resources/skills/create-gsd-extension/references/extensionapi-reference.md +75 -0
  207. package/src/resources/skills/create-gsd-extension/references/extensioncontext-reference.md +53 -0
  208. package/src/resources/skills/create-gsd-extension/references/key-rules-gotchas.md +36 -0
  209. package/src/resources/skills/create-gsd-extension/references/mode-behavior.md +32 -0
  210. package/src/resources/skills/create-gsd-extension/references/model-provider-management.md +89 -0
  211. package/src/resources/skills/create-gsd-extension/references/packaging-distribution.md +55 -0
  212. package/src/resources/skills/create-gsd-extension/references/remote-execution-overrides.md +90 -0
  213. package/src/resources/skills/create-gsd-extension/references/state-management.md +70 -0
  214. package/src/resources/skills/create-gsd-extension/references/system-prompt-modification.md +52 -0
  215. package/src/resources/skills/create-gsd-extension/templates/extension-skeleton.ts +51 -0
  216. package/src/resources/skills/create-gsd-extension/templates/stateful-tool-skeleton.ts +143 -0
  217. package/src/resources/skills/create-gsd-extension/workflows/add-capability.md +57 -0
  218. package/src/resources/skills/create-gsd-extension/workflows/create-extension.md +156 -0
  219. package/src/resources/skills/create-gsd-extension/workflows/debug-extension.md +74 -0
  220. package/src/resources/skills/create-skill/SKILL.md +184 -0
  221. package/src/resources/skills/create-skill/references/api-security.md +226 -0
  222. package/src/resources/skills/create-skill/references/be-clear-and-direct.md +531 -0
  223. package/src/resources/skills/create-skill/references/common-patterns.md +595 -0
  224. package/src/resources/skills/create-skill/references/core-principles.md +437 -0
  225. package/src/resources/skills/create-skill/references/executable-code.md +175 -0
  226. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +68 -0
  227. package/src/resources/skills/create-skill/references/iteration-and-testing.md +474 -0
  228. package/src/resources/skills/create-skill/references/recommended-structure.md +168 -0
  229. package/src/resources/skills/create-skill/references/skill-structure.md +372 -0
  230. package/src/resources/skills/create-skill/references/use-xml-tags.md +466 -0
  231. package/src/resources/skills/create-skill/references/using-scripts.md +113 -0
  232. package/src/resources/skills/create-skill/references/using-templates.md +112 -0
  233. package/src/resources/skills/create-skill/references/workflows-and-validation.md +510 -0
  234. package/src/resources/skills/create-skill/templates/router-skill.md +73 -0
  235. package/src/resources/skills/create-skill/templates/simple-skill.md +33 -0
  236. package/src/resources/skills/create-skill/workflows/add-reference.md +96 -0
  237. package/src/resources/skills/create-skill/workflows/add-script.md +93 -0
  238. package/src/resources/skills/create-skill/workflows/add-template.md +74 -0
  239. package/src/resources/skills/create-skill/workflows/add-workflow.md +120 -0
  240. package/src/resources/skills/create-skill/workflows/audit-skill.md +148 -0
  241. package/src/resources/skills/create-skill/workflows/create-new-skill.md +196 -0
  242. package/src/resources/skills/create-skill/workflows/get-guidance.md +121 -0
  243. package/src/resources/skills/create-skill/workflows/upgrade-to-router.md +161 -0
  244. package/src/resources/skills/create-skill/workflows/verify-skill.md +204 -0
  245. package/src/resources/skills/react-best-practices/SKILL.md +1 -1
  246. package/dist/resources/extensions/gsd/auto-worktree-sync.ts +0 -199
  247. package/dist/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
  248. package/dist/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
  249. package/src/resources/extensions/gsd/auto-worktree-sync.ts +0 -199
  250. package/src/resources/extensions/gsd/tests/worktree-db-integration.test.ts +0 -205
  251. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +0 -442
@@ -0,0 +1,328 @@
1
+ /**
2
+ * GSD Extensions Command — /gsd extensions
3
+ *
4
+ * Manage the extension registry: list, enable, disable, info.
5
+ * Self-contained — no imports outside the extensions tree (extensions are loaded
6
+ * via jiti at runtime from ~/.gsd/agent/, not compiled by tsc).
7
+ */
8
+
9
+ import type { ExtensionCommandContext } from "@gsd/pi-coding-agent";
10
+ import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
11
+ import { dirname, join } from "node:path";
12
+ import { homedir } from "node:os";
13
+
14
+ // ─── Types (mirrored from extension-registry.ts) ────────────────────────────
15
+
16
+ interface ExtensionManifest {
17
+ id: string;
18
+ name: string;
19
+ version: string;
20
+ description: string;
21
+ tier: "core" | "bundled" | "community";
22
+ requires: { platform: string };
23
+ provides?: {
24
+ tools?: string[];
25
+ commands?: string[];
26
+ hooks?: string[];
27
+ shortcuts?: string[];
28
+ };
29
+ dependencies?: {
30
+ extensions?: string[];
31
+ runtime?: string[];
32
+ };
33
+ }
34
+
35
+ interface ExtensionRegistryEntry {
36
+ id: string;
37
+ enabled: boolean;
38
+ source: "bundled" | "user" | "project";
39
+ disabledAt?: string;
40
+ disabledReason?: string;
41
+ }
42
+
43
+ interface ExtensionRegistry {
44
+ version: 1;
45
+ entries: Record<string, ExtensionRegistryEntry>;
46
+ }
47
+
48
+ // ─── Registry I/O ───────────────────────────────────────────────────────────
49
+
50
+ function getRegistryPath(): string {
51
+ return join(homedir(), ".gsd", "extensions", "registry.json");
52
+ }
53
+
54
+ function getAgentExtensionsDir(): string {
55
+ return join(homedir(), ".gsd", "agent", "extensions");
56
+ }
57
+
58
+ function loadRegistry(): ExtensionRegistry {
59
+ const filePath = getRegistryPath();
60
+ try {
61
+ if (!existsSync(filePath)) return { version: 1, entries: {} };
62
+ const raw = readFileSync(filePath, "utf-8");
63
+ const parsed = JSON.parse(raw);
64
+ if (typeof parsed === "object" && parsed !== null && parsed.version === 1 && typeof parsed.entries === "object") {
65
+ return parsed as ExtensionRegistry;
66
+ }
67
+ return { version: 1, entries: {} };
68
+ } catch {
69
+ return { version: 1, entries: {} };
70
+ }
71
+ }
72
+
73
+ function saveRegistry(registry: ExtensionRegistry): void {
74
+ const filePath = getRegistryPath();
75
+ try {
76
+ mkdirSync(dirname(filePath), { recursive: true });
77
+ const tmp = filePath + ".tmp";
78
+ writeFileSync(tmp, JSON.stringify(registry, null, 2), "utf-8");
79
+ renameSync(tmp, filePath);
80
+ } catch { /* non-fatal */ }
81
+ }
82
+
83
+ function isEnabled(registry: ExtensionRegistry, id: string): boolean {
84
+ const entry = registry.entries[id];
85
+ if (!entry) return true;
86
+ return entry.enabled;
87
+ }
88
+
89
+ function readManifest(dir: string): ExtensionManifest | null {
90
+ const mPath = join(dir, "extension-manifest.json");
91
+ if (!existsSync(mPath)) return null;
92
+ try {
93
+ const raw = JSON.parse(readFileSync(mPath, "utf-8"));
94
+ if (typeof raw?.id === "string" && typeof raw?.name === "string") return raw as ExtensionManifest;
95
+ return null;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ function discoverManifests(): Map<string, ExtensionManifest> {
102
+ const extDir = getAgentExtensionsDir();
103
+ const manifests = new Map<string, ExtensionManifest>();
104
+ if (!existsSync(extDir)) return manifests;
105
+ for (const entry of readdirSync(extDir, { withFileTypes: true })) {
106
+ if (!entry.isDirectory()) continue;
107
+ const m = readManifest(join(extDir, entry.name));
108
+ if (m) manifests.set(m.id, m);
109
+ }
110
+ return manifests;
111
+ }
112
+
113
+ // ─── Command Handler ────────────────────────────────────────────────────────
114
+
115
+ export async function handleExtensions(args: string, ctx: ExtensionCommandContext): Promise<void> {
116
+ const parts = args.split(/\s+/).filter(Boolean);
117
+ const subCmd = parts[0] ?? "list";
118
+
119
+ if (subCmd === "list") {
120
+ handleList(ctx);
121
+ return;
122
+ }
123
+
124
+ if (subCmd === "enable") {
125
+ handleEnable(parts[1], ctx);
126
+ return;
127
+ }
128
+
129
+ if (subCmd === "disable") {
130
+ handleDisable(parts[1], parts.slice(2).join(" "), ctx);
131
+ return;
132
+ }
133
+
134
+ if (subCmd === "info") {
135
+ handleInfo(parts[1], ctx);
136
+ return;
137
+ }
138
+
139
+ ctx.ui.notify(
140
+ `Unknown: /gsd extensions ${subCmd}. Usage: /gsd extensions [list|enable|disable|info]`,
141
+ "warning",
142
+ );
143
+ }
144
+
145
+ function handleList(ctx: ExtensionCommandContext): void {
146
+ const manifests = discoverManifests();
147
+ const registry = loadRegistry();
148
+
149
+ if (manifests.size === 0) {
150
+ ctx.ui.notify("No extension manifests found.", "warning");
151
+ return;
152
+ }
153
+
154
+ // Sort: core first, then alphabetical
155
+ const sorted = [...manifests.values()].sort((a, b) => {
156
+ if (a.tier === "core" && b.tier !== "core") return -1;
157
+ if (b.tier === "core" && a.tier !== "core") return 1;
158
+ return a.id.localeCompare(b.id);
159
+ });
160
+
161
+ const lines: string[] = [];
162
+ const hdr = padRight("Extensions", 38) + padRight("Status", 10) + padRight("Tier", 10) + padRight("Tools", 7) + "Commands";
163
+ lines.push(hdr);
164
+ lines.push("─".repeat(hdr.length));
165
+
166
+ for (const m of sorted) {
167
+ const enabled = isEnabled(registry, m.id);
168
+ const status = enabled ? "enabled" : "disabled";
169
+ const toolCount = m.provides?.tools?.length ?? 0;
170
+ const cmdCount = m.provides?.commands?.length ?? 0;
171
+ const label = `${m.id} (${m.name})`;
172
+
173
+ lines.push(
174
+ padRight(label, 38) +
175
+ padRight(status, 10) +
176
+ padRight(m.tier, 10) +
177
+ padRight(String(toolCount), 7) +
178
+ String(cmdCount),
179
+ );
180
+
181
+ if (!enabled) {
182
+ lines.push(` ↳ gsd extensions enable ${m.id}`);
183
+ }
184
+ }
185
+
186
+ ctx.ui.notify(lines.join("\n"), "info");
187
+ }
188
+
189
+ function handleEnable(id: string | undefined, ctx: ExtensionCommandContext): void {
190
+ if (!id) {
191
+ ctx.ui.notify("Usage: /gsd extensions enable <id>", "warning");
192
+ return;
193
+ }
194
+
195
+ const manifests = discoverManifests();
196
+ if (!manifests.has(id)) {
197
+ ctx.ui.notify(`Extension "${id}" not found. Run /gsd extensions list to see available extensions.`, "warning");
198
+ return;
199
+ }
200
+
201
+ const registry = loadRegistry();
202
+ if (isEnabled(registry, id)) {
203
+ ctx.ui.notify(`Extension "${id}" is already enabled.`, "info");
204
+ return;
205
+ }
206
+
207
+ const entry = registry.entries[id];
208
+ if (entry) {
209
+ entry.enabled = true;
210
+ delete entry.disabledAt;
211
+ delete entry.disabledReason;
212
+ } else {
213
+ registry.entries[id] = { id, enabled: true, source: "bundled" };
214
+ }
215
+ saveRegistry(registry);
216
+ ctx.ui.notify(`Enabled "${id}". Restart GSD to activate.`, "info");
217
+ }
218
+
219
+ function handleDisable(id: string | undefined, reason: string, ctx: ExtensionCommandContext): void {
220
+ if (!id) {
221
+ ctx.ui.notify("Usage: /gsd extensions disable <id>", "warning");
222
+ return;
223
+ }
224
+
225
+ const manifests = discoverManifests();
226
+ const manifest = manifests.get(id) ?? null;
227
+
228
+ if (!manifests.has(id)) {
229
+ ctx.ui.notify(`Extension "${id}" not found. Run /gsd extensions list to see available extensions.`, "warning");
230
+ return;
231
+ }
232
+
233
+ if (manifest?.tier === "core") {
234
+ ctx.ui.notify(`Cannot disable "${id}" — it is a core extension.`, "warning");
235
+ return;
236
+ }
237
+
238
+ const registry = loadRegistry();
239
+ if (!isEnabled(registry, id)) {
240
+ ctx.ui.notify(`Extension "${id}" is already disabled.`, "info");
241
+ return;
242
+ }
243
+
244
+ const entry = registry.entries[id];
245
+ if (entry) {
246
+ entry.enabled = false;
247
+ entry.disabledAt = new Date().toISOString();
248
+ entry.disabledReason = reason || undefined;
249
+ } else {
250
+ registry.entries[id] = {
251
+ id,
252
+ enabled: false,
253
+ source: "bundled",
254
+ disabledAt: new Date().toISOString(),
255
+ disabledReason: reason || undefined,
256
+ };
257
+ }
258
+ saveRegistry(registry);
259
+ ctx.ui.notify(`Disabled "${id}". Restart GSD to deactivate.`, "info");
260
+ }
261
+
262
+ function handleInfo(id: string | undefined, ctx: ExtensionCommandContext): void {
263
+ if (!id) {
264
+ ctx.ui.notify("Usage: /gsd extensions info <id>", "warning");
265
+ return;
266
+ }
267
+
268
+ const manifests = discoverManifests();
269
+ const manifest = manifests.get(id);
270
+ if (!manifest) {
271
+ ctx.ui.notify(`Extension "${id}" not found.`, "warning");
272
+ return;
273
+ }
274
+
275
+ const registry = loadRegistry();
276
+ const enabled = isEnabled(registry, id);
277
+ const entry = registry.entries[id];
278
+
279
+ const lines: string[] = [
280
+ `${manifest.name} (${manifest.id})`,
281
+ "",
282
+ ` Version: ${manifest.version}`,
283
+ ` Description: ${manifest.description}`,
284
+ ` Tier: ${manifest.tier}`,
285
+ ` Status: ${enabled ? "enabled" : "disabled"}`,
286
+ ];
287
+
288
+ if (entry?.disabledAt) {
289
+ lines.push(` Disabled at: ${entry.disabledAt}`);
290
+ }
291
+ if (entry?.disabledReason) {
292
+ lines.push(` Reason: ${entry.disabledReason}`);
293
+ }
294
+
295
+ if (manifest.provides) {
296
+ lines.push("");
297
+ lines.push(" Provides:");
298
+ if (manifest.provides.tools?.length) {
299
+ lines.push(` Tools: ${manifest.provides.tools.join(", ")}`);
300
+ }
301
+ if (manifest.provides.commands?.length) {
302
+ lines.push(` Commands: ${manifest.provides.commands.join(", ")}`);
303
+ }
304
+ if (manifest.provides.hooks?.length) {
305
+ lines.push(` Hooks: ${manifest.provides.hooks.join(", ")}`);
306
+ }
307
+ if (manifest.provides.shortcuts?.length) {
308
+ lines.push(` Shortcuts: ${manifest.provides.shortcuts.join(", ")}`);
309
+ }
310
+ }
311
+
312
+ if (manifest.dependencies) {
313
+ lines.push("");
314
+ lines.push(" Dependencies:");
315
+ if (manifest.dependencies.extensions?.length) {
316
+ lines.push(` Extensions: ${manifest.dependencies.extensions.join(", ")}`);
317
+ }
318
+ if (manifest.dependencies.runtime?.length) {
319
+ lines.push(` Runtime: ${manifest.dependencies.runtime.join(", ")}`);
320
+ }
321
+ }
322
+
323
+ ctx.ui.notify(lines.join("\n"), "info");
324
+ }
325
+
326
+ function padRight(str: string, len: number): string {
327
+ return str.length >= len ? str + " " : str + " ".repeat(len - str.length);
328
+ }
@@ -9,6 +9,7 @@ import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent
9
9
  import { existsSync, readFileSync, mkdirSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
  import { deriveState } from "./state.js";
12
+ import { gsdRoot } from "./paths.js";
12
13
  import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captures.js";
13
14
  import { appendOverride, appendKnowledge } from "./files.js";
14
15
  import {
@@ -136,7 +137,7 @@ export async function handleCapture(args: string, ctx: ExtensionCommandContext):
136
137
  const basePath = process.cwd();
137
138
 
138
139
  // Ensure .gsd/ exists — capture should work even without a milestone
139
- const gsdDir = join(basePath, ".gsd");
140
+ const gsdDir = gsdRoot(basePath);
140
141
  if (!existsSync(gsdDir)) {
141
142
  mkdirSync(gsdDir, { recursive: true });
142
143
  }
@@ -6,8 +6,10 @@
6
6
 
7
7
  import type { ExtensionAPI, ExtensionCommandContext } from "@gsd/pi-coding-agent";
8
8
  import type { GSDState } from "./types.js";
9
- import { existsSync, readFileSync, unlinkSync } from "node:fs";
9
+ import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
10
+ import { homedir } from "node:os";
10
11
  import { join } from "node:path";
12
+ import { gsdRoot } from "./paths.js";
11
13
  import { enableDebug } from "./debug-logger.js";
12
14
  import { deriveState } from "./state.js";
13
15
  import { GSDDashboardOverlay } from "./dashboard-overlay.js";
@@ -100,6 +102,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
100
102
  { cmd: "update", desc: "Update GSD to the latest version" },
101
103
  { cmd: "start", desc: "Start a workflow template (bugfix, spike, feature, etc.)" },
102
104
  { cmd: "templates", desc: "List available workflow templates" },
105
+ { cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
103
106
  ];
104
107
  const parts = prefix.trim().split(/\s+/);
105
108
 
@@ -321,6 +324,47 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
321
324
  .map((c) => ({ value: `templates ${c.value}`, label: c.label, description: c.description }));
322
325
  }
323
326
 
327
+ if (parts[0] === "extensions") {
328
+ if (parts.length <= 2) {
329
+ const subPrefix = parts[1] ?? "";
330
+ const subs = [
331
+ { cmd: "list", desc: "List all extensions and their status" },
332
+ { cmd: "enable", desc: "Enable a disabled extension" },
333
+ { cmd: "disable", desc: "Disable an extension" },
334
+ { cmd: "info", desc: "Show extension details" },
335
+ ];
336
+ return subs
337
+ .filter((s) => s.cmd.startsWith(subPrefix))
338
+ .map((s) => ({ value: `extensions ${s.cmd}`, label: s.cmd, description: s.desc }));
339
+ }
340
+ if (parts.length === 3 && ["enable", "disable", "info"].includes(parts[1])) {
341
+ const idPrefix = parts[2] ?? "";
342
+ try {
343
+ const extDir = join(homedir(), ".gsd", "agent", "extensions");
344
+ const ids: { id: string; name: string }[] = [];
345
+ for (const entry of readdirSync(extDir, { withFileTypes: true })) {
346
+ if (!entry.isDirectory()) continue;
347
+ const mPath = join(extDir, entry.name, "extension-manifest.json");
348
+ if (!existsSync(mPath)) continue;
349
+ try {
350
+ const m = JSON.parse(readFileSync(mPath, "utf-8"));
351
+ if (typeof m?.id === "string") ids.push({ id: m.id, name: m.name ?? m.id });
352
+ } catch { /* skip malformed */ }
353
+ }
354
+ return ids
355
+ .filter((e) => e.id.startsWith(idPrefix))
356
+ .map((e) => ({
357
+ value: `extensions ${parts[1]} ${e.id}`,
358
+ label: e.id,
359
+ description: e.name,
360
+ }));
361
+ } catch {
362
+ return [];
363
+ }
364
+ }
365
+ return [];
366
+ }
367
+
324
368
  if (parts[0] === "doctor") {
325
369
  const modePrefix = parts[1] ?? "";
326
370
  const modes = [
@@ -698,7 +742,7 @@ export function registerGSDCommand(pi: ExtensionAPI): void {
698
742
 
699
743
  if (trimmed === "new-milestone") {
700
744
  const basePath = projectRoot();
701
- const headlessContextPath = join(basePath, ".gsd", "runtime", "headless-context.md");
745
+ const headlessContextPath = join(gsdRoot(basePath), "runtime", "headless-context.md");
702
746
  if (existsSync(headlessContextPath)) {
703
747
  const seedContext = readFileSync(headlessContextPath, "utf-8");
704
748
  try { unlinkSync(headlessContextPath); } catch { /* non-fatal */ }
@@ -829,6 +873,12 @@ Examples:
829
873
  return;
830
874
  }
831
875
 
876
+ if (trimmed === "extensions" || trimmed.startsWith("extensions ")) {
877
+ const { handleExtensions } = await import("./commands-extensions.js");
878
+ await handleExtensions(trimmed.replace(/^extensions\s*/, "").trim(), ctx);
879
+ return;
880
+ }
881
+
832
882
  ctx.ui.notify(
833
883
  `Unknown: /gsd ${trimmed}. Run /gsd help for available commands.`,
834
884
  "warning",
@@ -877,6 +927,7 @@ function showHelp(ctx: ExtensionCommandContext): void {
877
927
  " /gsd config Set API keys for external tools",
878
928
  " /gsd keys API key manager [list|add|remove|test|rotate|doctor]",
879
929
  " /gsd hooks Show post-unit hook configuration",
930
+ " /gsd extensions Manage extensions [list|enable|disable|info]",
880
931
  "",
881
932
  "MAINTENANCE",
882
933
  " /gsd doctor Diagnose and repair .gsd/ state [audit|fix|heal] [scope]",
@@ -9,6 +9,7 @@
9
9
  import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
  import { homedir } from "node:os";
12
+ import { gsdRoot } from "./paths.js";
12
13
 
13
14
  // ─── Types ──────────────────────────────────────────────────────────────────────
14
15
 
@@ -214,7 +215,7 @@ export function detectV1Planning(basePath: string): V1Detection | null {
214
215
  // ─── V2 GSD Detection ──────────────────────────────────────────────────────────
215
216
 
216
217
  function detectV2Gsd(basePath: string): V2Detection | null {
217
- const gsdPath = join(basePath, ".gsd");
218
+ const gsdPath = gsdRoot(basePath);
218
219
 
219
220
  if (!existsSync(gsdPath)) return null;
220
221
 
@@ -1,4 +1,4 @@
1
- import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
1
+ import { existsSync, lstatSync, readdirSync, readFileSync, realpathSync, statSync } from "node:fs";
2
2
  import { join, sep } from "node:path";
3
3
 
4
4
  import type { DoctorIssue, DoctorIssueCode } from "./doctor-types.js";
@@ -13,6 +13,7 @@ import { nativeIsRepo, nativeWorktreeRemove, nativeBranchList, nativeBranchDelet
13
13
  import { readCrashLock, isLockProcessAlive, clearLock } from "./crash-recovery.js";
14
14
  import { ensureGitignore } from "./gitignore.js";
15
15
  import { readAllSessionStatuses, isSessionStale, removeSessionStatus } from "./session-status-io.js";
16
+ import { recoverFailedMigration } from "./migrate-external.js";
16
17
 
17
18
  export async function checkGitHealth(
18
19
  basePath: string,
@@ -508,6 +509,53 @@ export async function checkRuntimeHealth(
508
509
  } catch {
509
510
  // Non-fatal — gitignore check failed
510
511
  }
512
+
513
+ // ── External state symlink health ──────────────────────────────────────
514
+ try {
515
+ const localGsd = join(basePath, ".gsd");
516
+ if (existsSync(localGsd)) {
517
+ const stat = lstatSync(localGsd);
518
+
519
+ // Check for .gsd.migrating (failed migration)
520
+ const migratingPath = join(basePath, ".gsd.migrating");
521
+ if (existsSync(migratingPath)) {
522
+ issues.push({
523
+ severity: "error",
524
+ code: "failed_migration",
525
+ scope: "project",
526
+ unitId: "project",
527
+ message: "Found .gsd.migrating — a previous external state migration failed. State may be incomplete.",
528
+ file: ".gsd.migrating",
529
+ fixable: true,
530
+ });
531
+
532
+ if (shouldFix("failed_migration")) {
533
+ if (recoverFailedMigration(basePath)) {
534
+ fixesApplied.push("recovered failed migration (.gsd.migrating → .gsd)");
535
+ }
536
+ }
537
+ }
538
+
539
+ // Check symlink target exists
540
+ if (stat.isSymbolicLink()) {
541
+ try {
542
+ realpathSync(localGsd);
543
+ } catch {
544
+ issues.push({
545
+ severity: "error",
546
+ code: "broken_symlink",
547
+ scope: "project",
548
+ unitId: "project",
549
+ message: ".gsd symlink target does not exist. External state directory may have been deleted.",
550
+ file: ".gsd",
551
+ fixable: false,
552
+ });
553
+ }
554
+ }
555
+ }
556
+ } catch {
557
+ // Non-fatal — external state check failed
558
+ }
511
559
  }
512
560
 
513
561
  /**
@@ -30,7 +30,9 @@ export type DoctorIssueCode =
30
30
  | "state_file_stale"
31
31
  | "state_file_missing"
32
32
  | "gitignore_missing_patterns"
33
- | "unresolvable_dependency";
33
+ | "unresolvable_dependency"
34
+ | "failed_migration"
35
+ | "broken_symlink";
34
36
 
35
37
  /**
36
38
  * Issue codes that represent expected completion-transition states.
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "gsd",
3
+ "name": "GSD Workflow",
4
+ "version": "1.0.0",
5
+ "description": "Core GSD workflow engine — milestone planning, execution, and tracking",
6
+ "tier": "core",
7
+ "requires": { "platform": ">=2.29.0" },
8
+ "provides": {
9
+ "tools": [
10
+ "bash", "write", "read", "edit",
11
+ "gsd_save_decision", "gsd_save_summary",
12
+ "gsd_update_requirement", "gsd_generate_milestone_id"
13
+ ],
14
+ "commands": ["gsd", "kill", "worktree", "exit"],
15
+ "hooks": ["session_start"],
16
+ "shortcuts": ["Ctrl+Alt+G"]
17
+ }
18
+ }
@@ -268,7 +268,7 @@ function resolveActivityDirs(basePath: string, activeMilestone?: string | null):
268
268
  if (activeMilestone) {
269
269
  const wtPath = getAutoWorktreePath(basePath, activeMilestone);
270
270
  if (wtPath) {
271
- const wtActivityDir = join(wtPath, ".gsd", "activity");
271
+ const wtActivityDir = join(gsdRoot(wtPath), "activity");
272
272
  if (existsSync(wtActivityDir)) {
273
273
  dirs.push(wtActivityDir);
274
274
  }
@@ -285,7 +285,7 @@ function resolveActivityDirs(basePath: string, activeMilestone?: string | null):
285
285
  // ─── Completed Keys Loader ────────────────────────────────────────────────────
286
286
 
287
287
  function loadCompletedKeys(basePath: string): string[] {
288
- const file = join(basePath, ".gsd", "completed-units.json");
288
+ const file = join(gsdRoot(basePath), "completed-units.json");
289
289
  try {
290
290
  if (existsSync(file)) {
291
291
  return JSON.parse(readFileSync(file, "utf-8"));
@@ -11,6 +11,7 @@
11
11
  import { execFileSync, execSync } from "node:child_process";
12
12
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
13
  import { join } from "node:path";
14
+ import { gsdRoot } from "./paths.js";
14
15
  import { GIT_NO_PROMPT_ENV } from "./git-constants.js";
15
16
 
16
17
  import {
@@ -193,7 +194,7 @@ export const RUNTIME_EXCLUSION_PATHS: readonly string[] = [
193
194
  * Format: .gsd/milestones/<MID>/<MID>-META.json
194
195
  */
195
196
  function milestoneMetaPath(basePath: string, milestoneId: string): string {
196
- return join(basePath, ".gsd", "milestones", milestoneId, `${milestoneId}-META.json`);
197
+ return join(gsdRoot(basePath), "milestones", milestoneId, `${milestoneId}-META.json`);
197
198
  }
198
199
 
199
200
  /**
@@ -237,7 +238,7 @@ export function writeIntegrationBranch(basePath: string, milestoneId: string, br
237
238
  if (existingBranch === branch) return;
238
239
 
239
240
  const metaFile = milestoneMetaPath(basePath, milestoneId);
240
- mkdirSync(join(basePath, ".gsd", "milestones", milestoneId), { recursive: true });
241
+ mkdirSync(join(gsdRoot(basePath), "milestones", milestoneId), { recursive: true });
241
242
 
242
243
  // Merge with existing metadata if present
243
244
  let existing: Record<string, unknown> = {};