gsd-pi 2.77.0 → 2.78.0

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 (798) hide show
  1. package/README.md +18 -36
  2. package/dist/claude-cli-check.js +5 -1
  3. package/dist/cli-web-branch.d.ts +1 -0
  4. package/dist/cli-web-branch.js +3 -0
  5. package/dist/cli.js +38 -2
  6. package/dist/extension-discovery.d.ts +6 -0
  7. package/dist/extension-discovery.js +37 -0
  8. package/dist/extension-registry.d.ts +3 -0
  9. package/dist/extension-sort.d.ts +18 -0
  10. package/dist/extension-sort.js +114 -0
  11. package/dist/extension-validator.d.ts +47 -0
  12. package/dist/extension-validator.js +127 -0
  13. package/dist/headless.js +49 -4
  14. package/dist/loader.js +35 -7
  15. package/dist/provider-migrations.d.ts +18 -0
  16. package/dist/provider-migrations.js +14 -0
  17. package/dist/resource-loader.d.ts +40 -0
  18. package/dist/resource-loader.js +32 -13
  19. package/dist/resources/extensions/browser-tools/capture.js +9 -0
  20. package/dist/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  21. package/dist/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  22. package/dist/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  23. package/dist/resources/extensions/browser-tools/tools/forms.js +5 -1
  24. package/dist/resources/extensions/browser-tools/tools/intent.js +5 -1
  25. package/dist/resources/extensions/claude-code-cli/readiness.js +5 -1
  26. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +552 -67
  27. package/dist/resources/extensions/cmux/index.js +20 -0
  28. package/dist/resources/extensions/github-sync/templates.js +103 -0
  29. package/dist/resources/extensions/google-search/extension-manifest.json +5 -4
  30. package/dist/resources/extensions/google-search/index.js +3 -375
  31. package/dist/resources/extensions/gsd/abandon-detect.js +44 -0
  32. package/dist/resources/extensions/gsd/auto/loop.js +124 -2
  33. package/dist/resources/extensions/gsd/auto/phases.js +57 -39
  34. package/dist/resources/extensions/gsd/auto/resolve.js +24 -0
  35. package/dist/resources/extensions/gsd/auto/run-unit.js +10 -2
  36. package/dist/resources/extensions/gsd/auto/session.js +6 -2
  37. package/dist/resources/extensions/gsd/auto/turn-epoch.js +95 -0
  38. package/dist/resources/extensions/gsd/auto-dispatch.js +201 -38
  39. package/dist/resources/extensions/gsd/auto-loop.js +1 -1
  40. package/dist/resources/extensions/gsd/auto-model-selection.js +124 -4
  41. package/dist/resources/extensions/gsd/auto-post-unit.js +215 -64
  42. package/dist/resources/extensions/gsd/auto-prompts.js +372 -104
  43. package/dist/resources/extensions/gsd/auto-recovery.js +210 -24
  44. package/dist/resources/extensions/gsd/auto-start.js +122 -30
  45. package/dist/resources/extensions/gsd/auto-timeout-recovery.js +11 -5
  46. package/dist/resources/extensions/gsd/auto-tool-tracking.js +47 -7
  47. package/dist/resources/extensions/gsd/auto-unit-closeout.js +11 -2
  48. package/dist/resources/extensions/gsd/auto-worktree.js +180 -34
  49. package/dist/resources/extensions/gsd/auto.js +107 -35
  50. package/dist/resources/extensions/gsd/bootstrap/agent-end-recovery.js +19 -1
  51. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +209 -0
  52. package/dist/resources/extensions/gsd/bootstrap/provider-error-resume.js +5 -6
  53. package/dist/resources/extensions/gsd/bootstrap/register-extension.js +11 -0
  54. package/dist/resources/extensions/gsd/bootstrap/register-hooks.js +7 -3
  55. package/dist/resources/extensions/gsd/bootstrap/system-context.js +11 -6
  56. package/dist/resources/extensions/gsd/bootstrap/write-gate.js +127 -9
  57. package/dist/resources/extensions/gsd/commands/handlers/workflow.js +31 -4
  58. package/dist/resources/extensions/gsd/commands-cmux.js +9 -6
  59. package/dist/resources/extensions/gsd/commands-extensions.js +634 -43
  60. package/dist/resources/extensions/gsd/component-loader.js +447 -0
  61. package/dist/resources/extensions/gsd/component-types.js +69 -0
  62. package/dist/resources/extensions/gsd/context-store.js +23 -7
  63. package/dist/resources/extensions/gsd/detection.js +49 -1
  64. package/dist/resources/extensions/gsd/dispatch-guard.js +29 -3
  65. package/dist/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  66. package/dist/resources/extensions/gsd/file-lock.js +49 -9
  67. package/dist/resources/extensions/gsd/forensics.js +106 -0
  68. package/dist/resources/extensions/gsd/gate-registry.js +2 -2
  69. package/dist/resources/extensions/gsd/git-constants.js +28 -1
  70. package/dist/resources/extensions/gsd/git-self-heal.js +27 -0
  71. package/dist/resources/extensions/gsd/git-service.js +127 -2
  72. package/dist/resources/extensions/gsd/gitignore.js +1 -0
  73. package/dist/resources/extensions/gsd/gsd-db.js +6 -3
  74. package/dist/resources/extensions/gsd/guided-flow-queue.js +4 -1
  75. package/dist/resources/extensions/gsd/guided-flow.js +39 -13
  76. package/dist/resources/extensions/gsd/journal.js +17 -2
  77. package/dist/resources/extensions/gsd/memory-extractor.js +7 -1
  78. package/dist/resources/extensions/gsd/milestone-actions.js +15 -0
  79. package/dist/resources/extensions/gsd/milestone-scope-classifier.js +299 -0
  80. package/dist/resources/extensions/gsd/milestone-summary-classifier.js +37 -0
  81. package/dist/resources/extensions/gsd/model-cost-table.js +3 -0
  82. package/dist/resources/extensions/gsd/model-router.js +6 -0
  83. package/dist/resources/extensions/gsd/native-git-bridge.js +34 -4
  84. package/dist/resources/extensions/gsd/notifications.js +30 -16
  85. package/dist/resources/extensions/gsd/preferences-validation.js +23 -0
  86. package/dist/resources/extensions/gsd/prompt-cache-optimizer.js +4 -0
  87. package/dist/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  88. package/dist/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  89. package/dist/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  90. package/dist/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  91. package/dist/resources/extensions/gsd/prompts/system.md +1 -0
  92. package/dist/resources/extensions/gsd/reports.js +5 -4
  93. package/dist/resources/extensions/gsd/safety/git-checkpoint.js +11 -0
  94. package/dist/resources/extensions/gsd/service-tier.js +5 -2
  95. package/dist/resources/extensions/gsd/session-lock.js +19 -10
  96. package/dist/resources/extensions/gsd/skill-manifest.js +168 -0
  97. package/dist/resources/extensions/gsd/slice-cadence.js +238 -0
  98. package/dist/resources/extensions/gsd/slice-parallel-orchestrator.js +278 -8
  99. package/dist/resources/extensions/gsd/state-transition-matrix.js +118 -0
  100. package/dist/resources/extensions/gsd/state.js +69 -58
  101. package/dist/resources/extensions/gsd/sync-lock.js +98 -42
  102. package/dist/resources/extensions/gsd/tools/complete-slice.js +21 -0
  103. package/dist/resources/extensions/gsd/tools/complete-task.js +31 -0
  104. package/dist/resources/extensions/gsd/tools/validate-milestone.js +7 -2
  105. package/dist/resources/extensions/gsd/unit-context-composer.js +147 -0
  106. package/dist/resources/extensions/gsd/unit-context-manifest.js +370 -0
  107. package/dist/resources/extensions/gsd/uok/audit.js +18 -2
  108. package/dist/resources/extensions/gsd/uok/dispatch-envelope.js +33 -0
  109. package/dist/resources/extensions/gsd/uok/execution-graph.js +10 -0
  110. package/dist/resources/extensions/gsd/uok/gate-runner.js +53 -5
  111. package/dist/resources/extensions/gsd/uok/gitops.js +2 -1
  112. package/dist/resources/extensions/gsd/uok/loop-adapter.js +37 -10
  113. package/dist/resources/extensions/gsd/uok/parity-report.js +58 -0
  114. package/dist/resources/extensions/gsd/uok/plan-v2.js +10 -4
  115. package/dist/resources/extensions/gsd/uok/writer.js +82 -0
  116. package/dist/resources/extensions/gsd/workflow-logger.js +10 -2
  117. package/dist/resources/extensions/gsd/workflow-mcp.js +6 -0
  118. package/dist/resources/extensions/gsd/worktree-manager.js +86 -8
  119. package/dist/resources/extensions/gsd/worktree-resolver.js +86 -7
  120. package/dist/resources/extensions/gsd/worktree-telemetry.js +198 -0
  121. package/dist/resources/extensions/mcp-client/auth.js +10 -1
  122. package/dist/resources/extensions/mcp-client/index.js +121 -10
  123. package/dist/resources/extensions/ollama/index.js +5 -1
  124. package/dist/resources/extensions/remote-questions/manager.js +11 -5
  125. package/dist/resources/extensions/shared/cmux-events.js +12 -0
  126. package/dist/resources/extensions/shared/rtk-session-stats.js +1 -2
  127. package/dist/resources/skills/create-skill/SKILL.md +2 -2
  128. package/dist/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  129. package/dist/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  130. package/dist/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  131. package/dist/tsconfig.extensions.tsbuildinfo +1 -1
  132. package/dist/web/standalone/.next/BUILD_ID +1 -1
  133. package/dist/web/standalone/.next/app-path-routes-manifest.json +12 -12
  134. package/dist/web/standalone/.next/build-manifest.json +3 -3
  135. package/dist/web/standalone/.next/react-loadable-manifest.json +1 -1
  136. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  137. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  138. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  139. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  140. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  141. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  142. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  143. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  144. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  145. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  146. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  147. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  148. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  149. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  150. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  151. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  152. package/dist/web/standalone/.next/server/app/api/boot/route.js +1 -1
  153. package/dist/web/standalone/.next/server/app/api/boot/route.js.nft.json +1 -1
  154. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js +1 -1
  155. package/dist/web/standalone/.next/server/app/api/bridge-terminal/input/route.js.nft.json +1 -1
  156. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js +1 -1
  157. package/dist/web/standalone/.next/server/app/api/bridge-terminal/resize/route.js.nft.json +1 -1
  158. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js +1 -1
  159. package/dist/web/standalone/.next/server/app/api/bridge-terminal/stream/route.js.nft.json +1 -1
  160. package/dist/web/standalone/.next/server/app/api/captures/route.js +1 -1
  161. package/dist/web/standalone/.next/server/app/api/captures/route.js.nft.json +1 -1
  162. package/dist/web/standalone/.next/server/app/api/cleanup/route.js +1 -1
  163. package/dist/web/standalone/.next/server/app/api/cleanup/route.js.nft.json +1 -1
  164. package/dist/web/standalone/.next/server/app/api/doctor/route.js +1 -1
  165. package/dist/web/standalone/.next/server/app/api/doctor/route.js.nft.json +1 -1
  166. package/dist/web/standalone/.next/server/app/api/export-data/route.js +1 -1
  167. package/dist/web/standalone/.next/server/app/api/export-data/route.js.nft.json +1 -1
  168. package/dist/web/standalone/.next/server/app/api/files/route.js +1 -1
  169. package/dist/web/standalone/.next/server/app/api/files/route.js.nft.json +1 -1
  170. package/dist/web/standalone/.next/server/app/api/forensics/route.js +1 -1
  171. package/dist/web/standalone/.next/server/app/api/forensics/route.js.nft.json +1 -1
  172. package/dist/web/standalone/.next/server/app/api/git/route.js +1 -1
  173. package/dist/web/standalone/.next/server/app/api/git/route.js.nft.json +1 -1
  174. package/dist/web/standalone/.next/server/app/api/history/route.js +1 -1
  175. package/dist/web/standalone/.next/server/app/api/history/route.js.nft.json +1 -1
  176. package/dist/web/standalone/.next/server/app/api/hooks/route.js +1 -1
  177. package/dist/web/standalone/.next/server/app/api/hooks/route.js.nft.json +1 -1
  178. package/dist/web/standalone/.next/server/app/api/inspect/route.js +1 -1
  179. package/dist/web/standalone/.next/server/app/api/inspect/route.js.nft.json +1 -1
  180. package/dist/web/standalone/.next/server/app/api/knowledge/route.js +1 -1
  181. package/dist/web/standalone/.next/server/app/api/knowledge/route.js.nft.json +1 -1
  182. package/dist/web/standalone/.next/server/app/api/live-state/route.js +1 -1
  183. package/dist/web/standalone/.next/server/app/api/live-state/route.js.nft.json +1 -1
  184. package/dist/web/standalone/.next/server/app/api/notifications/route.js +1 -1
  185. package/dist/web/standalone/.next/server/app/api/notifications/route.js.nft.json +1 -1
  186. package/dist/web/standalone/.next/server/app/api/onboarding/route.js +1 -1
  187. package/dist/web/standalone/.next/server/app/api/onboarding/route.js.nft.json +1 -1
  188. package/dist/web/standalone/.next/server/app/api/projects/route.js +1 -1
  189. package/dist/web/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
  190. package/dist/web/standalone/.next/server/app/api/recovery/route.js +1 -1
  191. package/dist/web/standalone/.next/server/app/api/recovery/route.js.nft.json +1 -1
  192. package/dist/web/standalone/.next/server/app/api/session/browser/route.js +1 -1
  193. package/dist/web/standalone/.next/server/app/api/session/browser/route.js.nft.json +1 -1
  194. package/dist/web/standalone/.next/server/app/api/session/command/route.js +1 -1
  195. package/dist/web/standalone/.next/server/app/api/session/command/route.js.nft.json +1 -1
  196. package/dist/web/standalone/.next/server/app/api/session/events/route.js +1 -1
  197. package/dist/web/standalone/.next/server/app/api/session/events/route.js.nft.json +1 -1
  198. package/dist/web/standalone/.next/server/app/api/session/manage/route.js +1 -1
  199. package/dist/web/standalone/.next/server/app/api/session/manage/route.js.nft.json +1 -1
  200. package/dist/web/standalone/.next/server/app/api/settings-data/route.js +1 -1
  201. package/dist/web/standalone/.next/server/app/api/settings-data/route.js.nft.json +1 -1
  202. package/dist/web/standalone/.next/server/app/api/skill-health/route.js +1 -1
  203. package/dist/web/standalone/.next/server/app/api/skill-health/route.js.nft.json +1 -1
  204. package/dist/web/standalone/.next/server/app/api/steer/route.js +1 -1
  205. package/dist/web/standalone/.next/server/app/api/steer/route.js.nft.json +1 -1
  206. package/dist/web/standalone/.next/server/app/api/switch-root/route.js +1 -1
  207. package/dist/web/standalone/.next/server/app/api/switch-root/route.js.nft.json +1 -1
  208. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js +1 -1
  209. package/dist/web/standalone/.next/server/app/api/terminal/sessions/route.js.nft.json +1 -1
  210. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js +1 -1
  211. package/dist/web/standalone/.next/server/app/api/terminal/stream/route.js.nft.json +1 -1
  212. package/dist/web/standalone/.next/server/app/api/undo/route.js +1 -1
  213. package/dist/web/standalone/.next/server/app/api/undo/route.js.nft.json +1 -1
  214. package/dist/web/standalone/.next/server/app/api/visualizer/route.js +1 -1
  215. package/dist/web/standalone/.next/server/app/api/visualizer/route.js.nft.json +1 -1
  216. package/dist/web/standalone/.next/server/app/index.html +1 -1
  217. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  218. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  219. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  220. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  221. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  222. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  223. package/dist/web/standalone/.next/server/app-paths-manifest.json +12 -12
  224. package/dist/web/standalone/.next/server/chunks/1926.js +1 -0
  225. package/dist/web/standalone/.next/server/chunks/6897.js +2 -2
  226. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  227. package/dist/web/standalone/.next/server/middleware-manifest.json +1 -1
  228. package/dist/web/standalone/.next/server/middleware-react-loadable-manifest.js +1 -1
  229. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  230. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  231. package/dist/web/standalone/.next/static/chunks/2826.e9f5195e91f9cad2.js +11 -0
  232. package/dist/web/standalone/.next/static/chunks/{webpack-5fc74f13a25fa1bb.js → webpack-2e68521d7c82f7c2.js} +1 -1
  233. package/package.json +17 -16
  234. package/packages/daemon/package.json +2 -2
  235. package/packages/daemon/src/logger.ts +4 -3
  236. package/packages/mcp-server/README.md +3 -3
  237. package/packages/mcp-server/dist/env-writer.d.ts +1 -0
  238. package/packages/mcp-server/dist/env-writer.d.ts.map +1 -1
  239. package/packages/mcp-server/dist/env-writer.js +74 -6
  240. package/packages/mcp-server/dist/env-writer.js.map +1 -1
  241. package/packages/mcp-server/dist/server.d.ts +24 -0
  242. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  243. package/packages/mcp-server/dist/server.js +111 -87
  244. package/packages/mcp-server/dist/server.js.map +1 -1
  245. package/packages/mcp-server/dist/workflow-tools.d.ts.map +1 -1
  246. package/packages/mcp-server/dist/workflow-tools.js +15 -6
  247. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  248. package/packages/mcp-server/package.json +7 -2
  249. package/packages/mcp-server/src/env-writer.test.ts +79 -1
  250. package/packages/mcp-server/src/env-writer.ts +76 -6
  251. package/packages/mcp-server/src/mcp-server.test.ts +25 -3
  252. package/packages/mcp-server/src/readers/graph.test.ts +87 -15
  253. package/packages/mcp-server/src/readers/readers.test.ts +5 -1
  254. package/packages/mcp-server/src/secure-env-collect.test.ts +232 -237
  255. package/packages/mcp-server/src/server.ts +158 -105
  256. package/packages/mcp-server/src/workflow-tools.test.ts +85 -0
  257. package/packages/mcp-server/src/workflow-tools.ts +19 -6
  258. package/packages/mcp-server/tsconfig.tsbuildinfo +1 -1
  259. package/packages/native/package.json +7 -2
  260. package/packages/native/src/__tests__/_test-coverage-guard.test.mjs +98 -0
  261. package/packages/native/src/__tests__/clipboard.test.mjs +69 -23
  262. package/packages/native/src/__tests__/module-compat.test.mjs +59 -27
  263. package/packages/native/src/__tests__/ps.test.mjs +14 -8
  264. package/packages/native/src/__tests__/stream-process.test.mjs +23 -2
  265. package/packages/native/src/__tests__/truncate.test.mjs +17 -2
  266. package/packages/native/tsconfig.tsbuildinfo +1 -1
  267. package/packages/pi-agent-core/package.json +6 -1
  268. package/packages/pi-agent-core/src/agent-loop.test.ts +226 -31
  269. package/packages/pi-agent-core/src/agent.test.ts +96 -102
  270. package/packages/pi-agent-core/tsconfig.tsbuildinfo +1 -1
  271. package/packages/pi-ai/dist/models/capability-patches.d.ts.map +1 -1
  272. package/packages/pi-ai/dist/models/capability-patches.js +9 -2
  273. package/packages/pi-ai/dist/models/capability-patches.js.map +1 -1
  274. package/packages/pi-ai/dist/models/generated/index.d.ts +34 -0
  275. package/packages/pi-ai/dist/models/generated/index.d.ts.map +1 -1
  276. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts +17 -0
  277. package/packages/pi-ai/dist/models/generated/openai-codex.d.ts.map +1 -1
  278. package/packages/pi-ai/dist/models/generated/openai-codex.js +17 -0
  279. package/packages/pi-ai/dist/models/generated/openai-codex.js.map +1 -1
  280. package/packages/pi-ai/dist/models/generated/openai.d.ts +17 -0
  281. package/packages/pi-ai/dist/models/generated/openai.d.ts.map +1 -1
  282. package/packages/pi-ai/dist/models/generated/openai.js +17 -0
  283. package/packages/pi-ai/dist/models/generated/openai.js.map +1 -1
  284. package/packages/pi-ai/dist/models.generated.test.js +43 -70
  285. package/packages/pi-ai/dist/models.generated.test.js.map +1 -1
  286. package/packages/pi-ai/dist/models.test.js +36 -11
  287. package/packages/pi-ai/dist/models.test.js.map +1 -1
  288. package/packages/pi-ai/package.json +6 -1
  289. package/packages/pi-ai/scripts/generate-models.ts +44 -0
  290. package/packages/pi-ai/src/models/capability-patches.ts +10 -2
  291. package/packages/pi-ai/src/models/generated/openai-codex.ts +17 -0
  292. package/packages/pi-ai/src/models/generated/openai.ts +17 -0
  293. package/packages/pi-ai/src/models.generated.test.ts +46 -73
  294. package/packages/pi-ai/src/models.test.ts +48 -11
  295. package/packages/pi-ai/tsconfig.tsbuildinfo +1 -1
  296. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js +96 -32
  297. package/packages/pi-coding-agent/dist/core/agent-session-abort-order.test.js.map +1 -1
  298. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js +75 -12
  299. package/packages/pi-coding-agent/dist/core/agent-session-model-switch.test.js.map +1 -1
  300. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js +99 -31
  301. package/packages/pi-coding-agent/dist/core/agent-session-tool-refresh.test.js.map +1 -1
  302. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts +25 -0
  303. package/packages/pi-coding-agent/dist/core/compaction/compaction.d.ts.map +1 -1
  304. package/packages/pi-coding-agent/dist/core/compaction/compaction.js +105 -6
  305. package/packages/pi-coding-agent/dist/core/compaction/compaction.js.map +1 -1
  306. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js +230 -28
  307. package/packages/pi-coding-agent/dist/core/compaction/compaction.test.js.map +1 -1
  308. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts +30 -2
  309. package/packages/pi-coding-agent/dist/core/compaction/utils.d.ts.map +1 -1
  310. package/packages/pi-coding-agent/dist/core/compaction/utils.js +113 -12
  311. package/packages/pi-coding-agent/dist/core/compaction/utils.js.map +1 -1
  312. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts +1 -0
  313. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.d.ts.map +1 -1
  314. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js +29 -18
  315. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.js.map +1 -1
  316. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts +2 -0
  317. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.d.ts.map +1 -0
  318. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js +130 -0
  319. package/packages/pi-coding-agent/dist/core/compaction-orchestrator.test.js.map +1 -0
  320. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js +56 -1
  321. package/packages/pi-coding-agent/dist/core/compaction-utils.test.js.map +1 -1
  322. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js +8 -15
  323. package/packages/pi-coding-agent/dist/core/discovery-cache.test.js.map +1 -1
  324. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.d.ts +25 -0
  325. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.d.ts.map +1 -0
  326. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.js +109 -0
  327. package/packages/pi-coding-agent/dist/core/extensions/extension-discovery.js.map +1 -0
  328. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.d.ts +67 -0
  329. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.d.ts.map +1 -0
  330. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.js +167 -0
  331. package/packages/pi-coding-agent/dist/core/extensions/extension-registry.js.map +1 -0
  332. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts +8 -2
  333. package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
  334. package/packages/pi-coding-agent/dist/core/extensions/loader.js +85 -8
  335. package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
  336. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts +7 -0
  337. package/packages/pi-coding-agent/dist/core/extensions/types.d.ts.map +1 -1
  338. package/packages/pi-coding-agent/dist/core/extensions/types.js.map +1 -1
  339. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js +41 -4
  340. package/packages/pi-coding-agent/dist/core/lsp/lsp-integration.test.js.map +1 -1
  341. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js +19 -2
  342. package/packages/pi-coding-agent/dist/core/model-registry-auth-mode.test.js.map +1 -1
  343. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js +76 -18
  344. package/packages/pi-coding-agent/dist/core/resource-loader-cache-reset.test.js.map +1 -1
  345. package/packages/pi-coding-agent/dist/core/resource-loader.js +1 -1
  346. package/packages/pi-coding-agent/dist/core/resource-loader.js.map +1 -1
  347. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  348. package/packages/pi-coding-agent/dist/core/retry-handler.js +2 -6
  349. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  350. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +5 -1
  351. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  352. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts +18 -0
  353. package/packages/pi-coding-agent/dist/core/retryable-error-regex.d.ts.map +1 -0
  354. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js +18 -0
  355. package/packages/pi-coding-agent/dist/core/retryable-error-regex.js.map +1 -0
  356. package/packages/pi-coding-agent/dist/core/sdk.d.ts +1 -0
  357. package/packages/pi-coding-agent/dist/core/sdk.d.ts.map +1 -1
  358. package/packages/pi-coding-agent/dist/core/sdk.js +4 -1
  359. package/packages/pi-coding-agent/dist/core/sdk.js.map +1 -1
  360. package/packages/pi-coding-agent/dist/core/sdk.test.js +19 -1
  361. package/packages/pi-coding-agent/dist/core/sdk.test.js.map +1 -1
  362. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts +20 -0
  363. package/packages/pi-coding-agent/dist/core/system-prompt.d.ts.map +1 -1
  364. package/packages/pi-coding-agent/dist/core/system-prompt.js +19 -5
  365. package/packages/pi-coding-agent/dist/core/system-prompt.js.map +1 -1
  366. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js +2 -1
  367. package/packages/pi-coding-agent/dist/core/tools/path-utils.test.js.map +1 -1
  368. package/packages/pi-coding-agent/dist/index.d.ts +1 -0
  369. package/packages/pi-coding-agent/dist/index.d.ts.map +1 -1
  370. package/packages/pi-coding-agent/dist/index.js +1 -0
  371. package/packages/pi-coding-agent/dist/index.js.map +1 -1
  372. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js +15 -6
  373. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/provider-display-name.test.js.map +1 -1
  374. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js +36 -5
  375. package/packages/pi-coding-agent/dist/modes/interactive/components/__tests__/tool-execution.test.js.map +1 -1
  376. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js +20 -13
  377. package/packages/pi-coding-agent/dist/modes/interactive/components/dynamic-border.test.js.map +1 -1
  378. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.d.ts.map +1 -1
  379. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js +14 -5
  380. package/packages/pi-coding-agent/dist/modes/interactive/components/footer.js.map +1 -1
  381. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts +7 -1
  382. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  383. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +31 -9
  384. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  385. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  386. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js +30 -12
  387. package/packages/pi-coding-agent/dist/modes/interactive/components/tool-execution.js.map +1 -1
  388. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.d.ts.map +1 -1
  389. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js +18 -3
  390. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.js.map +1 -1
  391. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js +139 -0
  392. package/packages/pi-coding-agent/dist/modes/interactive/controllers/input-controller.test.js.map +1 -1
  393. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts +2 -0
  394. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.d.ts.map +1 -1
  395. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode-state.js.map +1 -1
  396. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts +4 -0
  397. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  398. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js +105 -13
  399. package/packages/pi-coding-agent/dist/modes/interactive/interactive-mode.js.map +1 -1
  400. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts +2 -0
  401. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.d.ts.map +1 -0
  402. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js +130 -0
  403. package/packages/pi-coding-agent/dist/tests/system-prompt-skill-filter.test.js.map +1 -0
  404. package/packages/pi-coding-agent/package.json +6 -1
  405. package/packages/pi-coding-agent/src/core/agent-session-abort-order.test.ts +113 -37
  406. package/packages/pi-coding-agent/src/core/agent-session-model-switch.test.ts +89 -17
  407. package/packages/pi-coding-agent/src/core/agent-session-tool-refresh.test.ts +112 -43
  408. package/packages/pi-coding-agent/src/core/compaction/compaction.test.ts +368 -28
  409. package/packages/pi-coding-agent/src/core/compaction/compaction.ts +122 -6
  410. package/packages/pi-coding-agent/src/core/compaction/utils.ts +111 -13
  411. package/packages/pi-coding-agent/src/core/compaction-orchestrator.test.ts +154 -0
  412. package/packages/pi-coding-agent/src/core/compaction-orchestrator.ts +32 -18
  413. package/packages/pi-coding-agent/src/core/compaction-utils.test.ts +68 -1
  414. package/packages/pi-coding-agent/src/core/discovery-cache.test.ts +9 -18
  415. package/packages/pi-coding-agent/src/core/extensions/extension-discovery.ts +119 -0
  416. package/packages/pi-coding-agent/src/core/extensions/extension-registry.ts +222 -0
  417. package/packages/pi-coding-agent/src/core/extensions/loader.ts +82 -11
  418. package/packages/pi-coding-agent/src/core/extensions/types.ts +8 -0
  419. package/packages/pi-coding-agent/src/core/lsp/lsp-integration.test.ts +48 -4
  420. package/packages/pi-coding-agent/src/core/model-registry-auth-mode.test.ts +22 -2
  421. package/packages/pi-coding-agent/src/core/resource-loader-cache-reset.test.ts +93 -28
  422. package/packages/pi-coding-agent/src/core/resource-loader.ts +1 -1
  423. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +5 -1
  424. package/packages/pi-coding-agent/src/core/retry-handler.ts +2 -8
  425. package/packages/pi-coding-agent/src/core/retryable-error-regex.ts +18 -0
  426. package/packages/pi-coding-agent/src/core/sdk.test.ts +25 -1
  427. package/packages/pi-coding-agent/src/core/sdk.ts +10 -3
  428. package/packages/pi-coding-agent/src/core/system-prompt.ts +38 -4
  429. package/packages/pi-coding-agent/src/core/tools/path-utils.test.ts +2 -1
  430. package/packages/pi-coding-agent/src/index.ts +1 -0
  431. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/provider-display-name.test.ts +17 -7
  432. package/packages/pi-coding-agent/src/modes/interactive/components/__tests__/tool-execution.test.ts +49 -3
  433. package/packages/pi-coding-agent/src/modes/interactive/components/dynamic-border.test.ts +26 -20
  434. package/packages/pi-coding-agent/src/modes/interactive/components/footer.ts +14 -5
  435. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +45 -11
  436. package/packages/pi-coding-agent/src/modes/interactive/components/tool-execution.ts +48 -9
  437. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.test.ts +160 -1
  438. package/packages/pi-coding-agent/src/modes/interactive/controllers/input-controller.ts +20 -3
  439. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode-state.ts +2 -0
  440. package/packages/pi-coding-agent/src/modes/interactive/interactive-mode.ts +119 -13
  441. package/packages/pi-coding-agent/src/tests/system-prompt-skill-filter.test.ts +157 -0
  442. package/packages/pi-coding-agent/tsconfig.tsbuildinfo +1 -1
  443. package/packages/pi-tui/dist/__tests__/autocomplete.test.js +31 -14
  444. package/packages/pi-tui/dist/__tests__/autocomplete.test.js.map +1 -1
  445. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js +128 -17
  446. package/packages/pi-tui/dist/__tests__/overlay-layout.test.js.map +1 -1
  447. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js +51 -6
  448. package/packages/pi-tui/dist/__tests__/stdin-buffer.test.js.map +1 -1
  449. package/packages/pi-tui/dist/__tests__/tui.test.js +18 -30
  450. package/packages/pi-tui/dist/__tests__/tui.test.js.map +1 -1
  451. package/packages/pi-tui/dist/components/__tests__/input.test.js +10 -3
  452. package/packages/pi-tui/dist/components/__tests__/input.test.js.map +1 -1
  453. package/packages/pi-tui/dist/components/__tests__/loader.test.js +53 -9
  454. package/packages/pi-tui/dist/components/__tests__/loader.test.js.map +1 -1
  455. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js +6 -2
  456. package/packages/pi-tui/dist/components/__tests__/markdown-maxlines.test.js.map +1 -1
  457. package/packages/pi-tui/dist/components/editor.d.ts +14 -0
  458. package/packages/pi-tui/dist/components/editor.d.ts.map +1 -1
  459. package/packages/pi-tui/dist/components/editor.js +19 -0
  460. package/packages/pi-tui/dist/components/editor.js.map +1 -1
  461. package/packages/pi-tui/dist/components/image.test.js +6 -5
  462. package/packages/pi-tui/dist/components/image.test.js.map +1 -1
  463. package/packages/pi-tui/dist/editor-component.d.ts +2 -0
  464. package/packages/pi-tui/dist/editor-component.d.ts.map +1 -1
  465. package/packages/pi-tui/dist/editor-component.js.map +1 -1
  466. package/packages/pi-tui/dist/stdin-buffer.d.ts +7 -0
  467. package/packages/pi-tui/dist/stdin-buffer.d.ts.map +1 -1
  468. package/packages/pi-tui/dist/stdin-buffer.js +20 -0
  469. package/packages/pi-tui/dist/stdin-buffer.js.map +1 -1
  470. package/packages/pi-tui/package.json +6 -1
  471. package/packages/pi-tui/src/__tests__/autocomplete.test.ts +46 -15
  472. package/packages/pi-tui/src/__tests__/overlay-layout.test.ts +140 -17
  473. package/packages/pi-tui/src/__tests__/stdin-buffer.test.ts +62 -6
  474. package/packages/pi-tui/src/__tests__/tui.test.ts +18 -37
  475. package/packages/pi-tui/src/components/__tests__/input.test.ts +19 -3
  476. package/packages/pi-tui/src/components/__tests__/loader.test.ts +112 -35
  477. package/packages/pi-tui/src/components/__tests__/markdown-maxlines.test.ts +9 -2
  478. package/packages/pi-tui/src/components/editor.ts +22 -0
  479. package/packages/pi-tui/src/components/image.test.ts +10 -5
  480. package/packages/pi-tui/src/editor-component.ts +3 -0
  481. package/packages/pi-tui/src/stdin-buffer.ts +26 -0
  482. package/packages/pi-tui/tsconfig.tsbuildinfo +1 -1
  483. package/packages/rpc-client/dist/rpc-client.test.js +101 -51
  484. package/packages/rpc-client/dist/rpc-client.test.js.map +1 -1
  485. package/packages/rpc-client/package.json +6 -1
  486. package/packages/rpc-client/src/rpc-client.test.ts +109 -52
  487. package/packages/rpc-client/tsconfig.tsbuildinfo +1 -1
  488. package/pkg/package.json +1 -1
  489. package/scripts/install.js +526 -0
  490. package/scripts/lib/workspace-manifest.cjs +86 -0
  491. package/scripts/link-workspace-packages.cjs +5 -17
  492. package/scripts/postinstall.js +9 -178
  493. package/src/resources/extensions/browser-tools/capture.ts +12 -0
  494. package/src/resources/extensions/browser-tools/tests/browser-tools-integration.test.mjs +8 -59
  495. package/src/resources/extensions/browser-tools/tests/browser-tools-unit.test.cjs +36 -24
  496. package/src/resources/extensions/browser-tools/tests/capture-sharp-optional.test.cjs +69 -71
  497. package/src/resources/extensions/browser-tools/tools/forms.ts +5 -1
  498. package/src/resources/extensions/browser-tools/tools/intent.ts +5 -1
  499. package/src/resources/extensions/claude-code-cli/readiness.ts +5 -1
  500. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +602 -73
  501. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +1028 -91
  502. package/src/resources/extensions/cmux/index.ts +35 -10
  503. package/src/resources/extensions/github-sync/templates.ts +151 -0
  504. package/src/resources/extensions/github-sync/tests/cli.test.ts +76 -7
  505. package/src/resources/extensions/github-sync/tests/templates.test.ts +92 -1
  506. package/src/resources/extensions/google-search/extension-manifest.json +5 -4
  507. package/src/resources/extensions/google-search/index.ts +9 -470
  508. package/src/resources/extensions/gsd/abandon-detect.ts +62 -0
  509. package/src/resources/extensions/gsd/auto/loop-deps.ts +1 -1
  510. package/src/resources/extensions/gsd/auto/loop.ts +142 -2
  511. package/src/resources/extensions/gsd/auto/phases.ts +62 -38
  512. package/src/resources/extensions/gsd/auto/resolve.ts +29 -0
  513. package/src/resources/extensions/gsd/auto/run-unit.ts +16 -2
  514. package/src/resources/extensions/gsd/auto/session.ts +7 -2
  515. package/src/resources/extensions/gsd/auto/turn-epoch.ts +108 -0
  516. package/src/resources/extensions/gsd/auto/types.ts +1 -1
  517. package/src/resources/extensions/gsd/auto-dispatch.ts +214 -37
  518. package/src/resources/extensions/gsd/auto-loop.ts +1 -1
  519. package/src/resources/extensions/gsd/auto-model-selection.ts +131 -4
  520. package/src/resources/extensions/gsd/auto-post-unit.ts +226 -73
  521. package/src/resources/extensions/gsd/auto-prompts.ts +385 -93
  522. package/src/resources/extensions/gsd/auto-recovery.ts +240 -25
  523. package/src/resources/extensions/gsd/auto-start.ts +146 -14
  524. package/src/resources/extensions/gsd/auto-timeout-recovery.ts +12 -5
  525. package/src/resources/extensions/gsd/auto-tool-tracking.ts +51 -7
  526. package/src/resources/extensions/gsd/auto-unit-closeout.ts +14 -3
  527. package/src/resources/extensions/gsd/auto-worktree.ts +190 -31
  528. package/src/resources/extensions/gsd/auto.ts +127 -41
  529. package/src/resources/extensions/gsd/bootstrap/agent-end-recovery.ts +20 -1
  530. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +221 -0
  531. package/src/resources/extensions/gsd/bootstrap/provider-error-resume.ts +6 -6
  532. package/src/resources/extensions/gsd/bootstrap/register-extension.ts +11 -0
  533. package/src/resources/extensions/gsd/bootstrap/register-hooks.ts +7 -3
  534. package/src/resources/extensions/gsd/bootstrap/system-context.ts +13 -9
  535. package/src/resources/extensions/gsd/bootstrap/write-gate.ts +158 -9
  536. package/src/resources/extensions/gsd/commands/handlers/workflow.ts +27 -8
  537. package/src/resources/extensions/gsd/commands-cmux.ts +10 -6
  538. package/src/resources/extensions/gsd/commands-extensions.ts +747 -41
  539. package/src/resources/extensions/gsd/component-loader.ts +598 -0
  540. package/src/resources/extensions/gsd/component-types.ts +362 -0
  541. package/src/resources/extensions/gsd/context-store.ts +25 -8
  542. package/src/resources/extensions/gsd/detection.ts +58 -1
  543. package/src/resources/extensions/gsd/dispatch-guard.ts +26 -2
  544. package/src/resources/extensions/gsd/docs/preferences-reference.md +1 -1
  545. package/src/resources/extensions/gsd/file-lock.ts +84 -11
  546. package/src/resources/extensions/gsd/forensics.ts +118 -1
  547. package/src/resources/extensions/gsd/gate-registry.ts +2 -2
  548. package/src/resources/extensions/gsd/git-constants.ts +30 -1
  549. package/src/resources/extensions/gsd/git-self-heal.ts +31 -0
  550. package/src/resources/extensions/gsd/git-service.ts +150 -2
  551. package/src/resources/extensions/gsd/gitignore.ts +1 -0
  552. package/src/resources/extensions/gsd/gsd-db.ts +6 -3
  553. package/src/resources/extensions/gsd/guided-flow-queue.ts +4 -1
  554. package/src/resources/extensions/gsd/guided-flow.ts +57 -14
  555. package/src/resources/extensions/gsd/journal.ts +38 -3
  556. package/src/resources/extensions/gsd/memory-extractor.ts +11 -3
  557. package/src/resources/extensions/gsd/milestone-actions.ts +18 -0
  558. package/src/resources/extensions/gsd/milestone-scope-classifier.ts +366 -0
  559. package/src/resources/extensions/gsd/milestone-summary-classifier.ts +42 -0
  560. package/src/resources/extensions/gsd/model-cost-table.ts +3 -0
  561. package/src/resources/extensions/gsd/model-router.ts +6 -0
  562. package/src/resources/extensions/gsd/native-git-bridge.ts +34 -4
  563. package/src/resources/extensions/gsd/notifications.ts +27 -15
  564. package/src/resources/extensions/gsd/preferences-validation.ts +21 -0
  565. package/src/resources/extensions/gsd/prompt-cache-optimizer.ts +4 -0
  566. package/src/resources/extensions/gsd/prompts/complete-milestone.md +6 -2
  567. package/src/resources/extensions/gsd/prompts/discuss-headless.md +23 -4
  568. package/src/resources/extensions/gsd/prompts/doctor-heal.md +5 -4
  569. package/src/resources/extensions/gsd/prompts/plan-slice.md +15 -2
  570. package/src/resources/extensions/gsd/prompts/system.md +1 -0
  571. package/src/resources/extensions/gsd/reports.ts +5 -4
  572. package/src/resources/extensions/gsd/safety/git-checkpoint.ts +15 -0
  573. package/src/resources/extensions/gsd/service-tier.ts +5 -2
  574. package/src/resources/extensions/gsd/session-lock.ts +20 -10
  575. package/src/resources/extensions/gsd/skill-manifest.ts +175 -0
  576. package/src/resources/extensions/gsd/slice-cadence.ts +299 -0
  577. package/src/resources/extensions/gsd/slice-parallel-orchestrator.ts +309 -8
  578. package/src/resources/extensions/gsd/state-transition-matrix.ts +152 -0
  579. package/src/resources/extensions/gsd/state.ts +76 -66
  580. package/src/resources/extensions/gsd/sync-lock.ts +97 -39
  581. package/src/resources/extensions/gsd/tests/artifact-retry-cap.test.ts +270 -0
  582. package/src/resources/extensions/gsd/tests/artifacts-table-preserved-on-cache-invalidate.test.ts +2 -1
  583. package/src/resources/extensions/gsd/tests/auto-deterministic-error-classification-4973.test.ts +341 -0
  584. package/src/resources/extensions/gsd/tests/auto-discuss-milestone-deadlock-4973.test.ts +264 -0
  585. package/src/resources/extensions/gsd/tests/auto-loop.test.ts +135 -285
  586. package/src/resources/extensions/gsd/tests/auto-mode-guards.test.ts +79 -0
  587. package/src/resources/extensions/gsd/tests/auto-model-selection-tool-poisoning.test.ts +742 -0
  588. package/src/resources/extensions/gsd/tests/auto-model-selection.test.ts +78 -0
  589. package/src/resources/extensions/gsd/tests/auto-phases-lifecycle.test.ts +61 -0
  590. package/src/resources/extensions/gsd/tests/auto-recovery.test.ts +166 -0
  591. package/src/resources/extensions/gsd/tests/auto-remediate-slice-status.test.ts +4 -1
  592. package/src/resources/extensions/gsd/tests/auto-retry-mcp-churn-fixes.test.ts +8 -194
  593. package/src/resources/extensions/gsd/tests/auto-start-clean-runtime-db-gated.test.ts +64 -0
  594. package/src/resources/extensions/gsd/tests/auto-start-cold-db-bootstrap.test.ts +2 -2
  595. package/src/resources/extensions/gsd/tests/auto-start-needs-discussion.test.ts +15 -58
  596. package/src/resources/extensions/gsd/tests/auto-start-worktree-db-path.test.ts +2 -2
  597. package/src/resources/extensions/gsd/tests/auto-thinking-restore.test.ts +3 -2
  598. package/src/resources/extensions/gsd/tests/auto-warning-noise-regression.test.ts +3 -2
  599. package/src/resources/extensions/gsd/tests/bootstrap-derive-state-db-open.test.ts +2 -1
  600. package/src/resources/extensions/gsd/tests/cache-staleness-regression.test.ts +17 -21
  601. package/src/resources/extensions/gsd/tests/canonical-milestone-root.test.ts +108 -0
  602. package/src/resources/extensions/gsd/tests/cmux.test.ts +5 -9
  603. package/src/resources/extensions/gsd/tests/complete-milestone-excerpt.test.ts +263 -0
  604. package/src/resources/extensions/gsd/tests/complete-milestone.test.ts +25 -0
  605. package/src/resources/extensions/gsd/tests/complete-slice-composer.test.ts +192 -0
  606. package/src/resources/extensions/gsd/tests/complete-slice-verification-gate.test.ts +2 -1
  607. package/src/resources/extensions/gsd/tests/complete-task.test.ts +16 -8
  608. package/src/resources/extensions/gsd/tests/component-loader.test.ts +589 -0
  609. package/src/resources/extensions/gsd/tests/component-types.test.ts +127 -0
  610. package/src/resources/extensions/gsd/tests/context-store.test.ts +79 -0
  611. package/src/resources/extensions/gsd/tests/copy-planning-artifacts-samepath.test.ts +2 -1
  612. package/src/resources/extensions/gsd/tests/crash-recovery.test.ts +50 -1
  613. package/src/resources/extensions/gsd/tests/custom-engine-loop-integration.test.ts +159 -0
  614. package/src/resources/extensions/gsd/tests/db-access-guardrails.test.ts +1 -0
  615. package/src/resources/extensions/gsd/tests/derive-state-crossval.test.ts +3 -3
  616. package/src/resources/extensions/gsd/tests/derive-state-db-disk-reconcile.test.ts +40 -0
  617. package/src/resources/extensions/gsd/tests/derive-state-db.test.ts +91 -3
  618. package/src/resources/extensions/gsd/tests/derive-state.test.ts +4 -4
  619. package/src/resources/extensions/gsd/tests/discuss-slice-structured-questions.test.ts +2 -1
  620. package/src/resources/extensions/gsd/tests/discuss-tool-scope-leak.test.ts +2 -1
  621. package/src/resources/extensions/gsd/tests/dispatch-complete-milestone-guard.test.ts +14 -9
  622. package/src/resources/extensions/gsd/tests/dispatch-guard-summary-db-mismatch.test.ts +77 -0
  623. package/src/resources/extensions/gsd/tests/dispatch-guard.test.ts +25 -0
  624. package/src/resources/extensions/gsd/tests/dispatch-missing-task-plans.test.ts +14 -0
  625. package/src/resources/extensions/gsd/tests/dispatcher-stuck-planning.test.ts +3 -2
  626. package/src/resources/extensions/gsd/tests/double-merge-guard.test.ts +4 -3
  627. package/src/resources/extensions/gsd/tests/empty-content-abort-loop.test.ts +4 -3
  628. package/src/resources/extensions/gsd/tests/execution-entry-missing-context-4671.test.ts +173 -0
  629. package/src/resources/extensions/gsd/tests/extension-bootstrap-isolation.test.ts +139 -129
  630. package/src/resources/extensions/gsd/tests/file-lock.test.ts +86 -12
  631. package/src/resources/extensions/gsd/tests/finalize-timeout-guard.test.ts +8 -104
  632. package/src/resources/extensions/gsd/tests/gate-state-canonicalization.test.ts +102 -0
  633. package/src/resources/extensions/gsd/tests/gate-storage.test.ts +1 -1
  634. package/src/resources/extensions/gsd/tests/google-search-stub.test.ts +131 -0
  635. package/src/resources/extensions/gsd/tests/headless-milestone-parity.test.ts +117 -0
  636. package/src/resources/extensions/gsd/tests/hook-key-parsing.test.ts +4 -55
  637. package/src/resources/extensions/gsd/tests/integration/all-milestones-complete-merge.test.ts +7 -56
  638. package/src/resources/extensions/gsd/tests/integration/auto-recovery.test.ts +20 -0
  639. package/src/resources/extensions/gsd/tests/integration/auto-worktree-milestone-merge.test.ts +30 -0
  640. package/src/resources/extensions/gsd/tests/integration/doctor-proactive.test.ts +18 -2
  641. package/src/resources/extensions/gsd/tests/integration/git-service.test.ts +3 -2
  642. package/src/resources/extensions/gsd/tests/integration/queue-completed-milestone-perf.test.ts +10 -4
  643. package/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +144 -7
  644. package/src/resources/extensions/gsd/tests/integration/state-machine-live-validation.test.ts +4 -0
  645. package/src/resources/extensions/gsd/tests/integration/state-machine-runtime-failures.test.ts +2 -16
  646. package/src/resources/extensions/gsd/tests/integration/worktree-e2e.test.ts +11 -0
  647. package/src/resources/extensions/gsd/tests/interactive-routing-bypass.test.ts +9 -3
  648. package/src/resources/extensions/gsd/tests/interrupted-session-ui.test.ts +6 -9
  649. package/src/resources/extensions/gsd/tests/journal-integration.test.ts +64 -0
  650. package/src/resources/extensions/gsd/tests/knowledge.test.ts +93 -1
  651. package/src/resources/extensions/gsd/tests/mcp-client-security.test.ts +47 -0
  652. package/src/resources/extensions/gsd/tests/memory-extractor.test.ts +5 -15
  653. package/src/resources/extensions/gsd/tests/merge-conflict-stops-loop.test.ts +227 -55
  654. package/src/resources/extensions/gsd/tests/milestone-scope-classifier.test.ts +187 -0
  655. package/src/resources/extensions/gsd/tests/milestone-status-authoritative.test.ts +3 -3
  656. package/src/resources/extensions/gsd/tests/milestone-summary-classifier.test.ts +30 -0
  657. package/src/resources/extensions/gsd/tests/milestone-transition-state-rebuild.test.ts +4 -2
  658. package/src/resources/extensions/gsd/tests/model-cost-table.test.ts +9 -1
  659. package/src/resources/extensions/gsd/tests/model-router.test.ts +1 -1
  660. package/src/resources/extensions/gsd/tests/native-git-bridge-exec-fallback.test.ts +6 -48
  661. package/src/resources/extensions/gsd/tests/notification-widget.test.ts +6 -3
  662. package/src/resources/extensions/gsd/tests/orphaned-worktree-audit.test.ts +59 -2
  663. package/src/resources/extensions/gsd/tests/parallel-commit-scope.test.ts +5 -0
  664. package/src/resources/extensions/gsd/tests/parallel-research-dispatch.test.ts +273 -130
  665. package/src/resources/extensions/gsd/tests/parallel-skill-prompt-integration.test.ts +150 -0
  666. package/src/resources/extensions/gsd/tests/pipeline-variant-dispatch.test.ts +301 -0
  667. package/src/resources/extensions/gsd/tests/pre-execution-pause-wiring.test.ts +32 -1
  668. package/src/resources/extensions/gsd/tests/preferences-worktree-sync.test.ts +2 -1
  669. package/src/resources/extensions/gsd/tests/prompt-cache-optimizer.test.ts +12 -0
  670. package/src/resources/extensions/gsd/tests/prompt-step-ordering.test.ts +15 -4
  671. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +54 -41
  672. package/src/resources/extensions/gsd/tests/queue-auto-guard.test.ts +213 -0
  673. package/src/resources/extensions/gsd/tests/queue-draft-detection.test.ts +3 -2
  674. package/src/resources/extensions/gsd/tests/queued-discuss-fast-path.test.ts +4 -5
  675. package/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts +13 -7
  676. package/src/resources/extensions/gsd/tests/ready-phrase-no-files-4573.test.ts +75 -2
  677. package/src/resources/extensions/gsd/tests/reassess-default-optin.test.ts +132 -0
  678. package/src/resources/extensions/gsd/tests/recovery-attempts-reset.test.ts +8 -40
  679. package/src/resources/extensions/gsd/tests/regex-hardening.test.ts +136 -256
  680. package/src/resources/extensions/gsd/tests/require-slice-discussion-dispatch.test.ts +170 -0
  681. package/src/resources/extensions/gsd/tests/research-milestone-composer.test.ts +114 -0
  682. package/src/resources/extensions/gsd/tests/restore-tools-after-discuss.test.ts +6 -3
  683. package/src/resources/extensions/gsd/tests/rewrite-docs-abandon-detect.test.ts +195 -0
  684. package/src/resources/extensions/gsd/tests/run-uat-composer.test.ts +148 -0
  685. package/src/resources/extensions/gsd/tests/service-tier.test.ts +4 -0
  686. package/src/resources/extensions/gsd/tests/session-lock-regression.test.ts +29 -0
  687. package/src/resources/extensions/gsd/tests/sidecar-queue.test.ts +3 -2
  688. package/src/resources/extensions/gsd/tests/silent-catch-diagnostics.test.ts +55 -95
  689. package/src/resources/extensions/gsd/tests/single-writer-v3-tool-surface.test.ts +158 -0
  690. package/src/resources/extensions/gsd/tests/skill-activation.test.ts +120 -1
  691. package/src/resources/extensions/gsd/tests/skill-manifest.test.ts +112 -0
  692. package/src/resources/extensions/gsd/tests/slice-cadence.test.ts +242 -0
  693. package/src/resources/extensions/gsd/tests/slice-context-injection.test.ts +3 -2
  694. package/src/resources/extensions/gsd/tests/slice-parallel-orchestrator.test.ts +164 -1
  695. package/src/resources/extensions/gsd/tests/smart-entry-draft.test.ts +2 -1
  696. package/src/resources/extensions/gsd/tests/stale-dirlistcache-4648.test.ts +112 -0
  697. package/src/resources/extensions/gsd/tests/state-machine-full-walkthrough.test.ts +29 -5
  698. package/src/resources/extensions/gsd/tests/state-transition-matrix.test.ts +44 -0
  699. package/src/resources/extensions/gsd/tests/stop-auto-race-null-unit.test.ts +3 -3
  700. package/src/resources/extensions/gsd/tests/structured-data-formatter.test.ts +11 -92
  701. package/src/resources/extensions/gsd/tests/stuck-detection-coverage.test.ts +2 -2
  702. package/src/resources/extensions/gsd/tests/subagent-model-dispatch.test.ts +7 -6
  703. package/src/resources/extensions/gsd/tests/survivor-branch-complete.test.ts +102 -101
  704. package/src/resources/extensions/gsd/tests/sync-lock.test.ts +31 -0
  705. package/src/resources/extensions/gsd/tests/sync-worktree-skip-current.test.ts +4 -3
  706. package/src/resources/extensions/gsd/tests/test-helpers.test.ts +98 -0
  707. package/src/resources/extensions/gsd/tests/test-helpers.ts +153 -0
  708. package/src/resources/extensions/gsd/tests/token-profile.test.ts +8 -1
  709. package/src/resources/extensions/gsd/tests/tool-invocation-error-loop-break.test.ts +61 -1
  710. package/src/resources/extensions/gsd/tests/tool-naming.test.ts +8 -1
  711. package/src/resources/extensions/gsd/tests/triage-resolution.test.ts +50 -2
  712. package/src/resources/extensions/gsd/tests/turn-epoch.test.ts +162 -0
  713. package/src/resources/extensions/gsd/tests/unit-context-composer.test.ts +355 -0
  714. package/src/resources/extensions/gsd/tests/unit-context-manifest.test.ts +258 -0
  715. package/src/resources/extensions/gsd/tests/uok-contracts.test.ts +51 -0
  716. package/src/resources/extensions/gsd/tests/uok-execution-graph.test.ts +16 -0
  717. package/src/resources/extensions/gsd/tests/uok-gate-runner.test.ts +75 -0
  718. package/src/resources/extensions/gsd/tests/uok-gitops-wiring.test.ts +49 -26
  719. package/src/resources/extensions/gsd/tests/uok-loop-adapter-writer.test.ts +65 -0
  720. package/src/resources/extensions/gsd/tests/uok-parity-report.test.ts +42 -0
  721. package/src/resources/extensions/gsd/tests/uok-plan-v2-wiring.test.ts +19 -2
  722. package/src/resources/extensions/gsd/tests/uok-writer.test.ts +75 -0
  723. package/src/resources/extensions/gsd/tests/validate-extension-package.test.ts +168 -0
  724. package/src/resources/extensions/gsd/tests/validate-milestone.test.ts +139 -5
  725. package/src/resources/extensions/gsd/tests/verify-artifact-tightened.test.ts +144 -80
  726. package/src/resources/extensions/gsd/tests/visualizer-critical-path.test.ts +20 -54
  727. package/src/resources/extensions/gsd/tests/visualizer-overlay.test.ts +342 -277
  728. package/src/resources/extensions/gsd/tests/worker-model-override.test.ts +37 -29
  729. package/src/resources/extensions/gsd/tests/workflow-mcp.test.ts +25 -2
  730. package/src/resources/extensions/gsd/tests/worktree-db.test.ts +226 -266
  731. package/src/resources/extensions/gsd/tests/worktree-health-monorepo.test.ts +103 -67
  732. package/src/resources/extensions/gsd/tests/worktree-nested-git-safety.test.ts +92 -90
  733. package/src/resources/extensions/gsd/tests/worktree-submodule-safety.test.ts +238 -59
  734. package/src/resources/extensions/gsd/tests/worktree-sync-overwrite-loop.test.ts +113 -161
  735. package/src/resources/extensions/gsd/tests/worktree-telemetry.test.ts +210 -0
  736. package/src/resources/extensions/gsd/tests/write-gate-planning-unit.test.ts +262 -0
  737. package/src/resources/extensions/gsd/tests/write-gate-predicates.test.ts +186 -0
  738. package/src/resources/extensions/gsd/tests/write-gate.test.ts +7 -5
  739. package/src/resources/extensions/gsd/tests/zombie-gsd-state.test.ts +80 -96
  740. package/src/resources/extensions/gsd/tools/complete-slice.ts +38 -0
  741. package/src/resources/extensions/gsd/tools/complete-task.ts +49 -0
  742. package/src/resources/extensions/gsd/tools/validate-milestone.ts +8 -2
  743. package/src/resources/extensions/gsd/types.ts +3 -3
  744. package/src/resources/extensions/gsd/unit-context-composer.ts +218 -0
  745. package/src/resources/extensions/gsd/unit-context-manifest.ts +574 -0
  746. package/src/resources/extensions/gsd/uok/audit.ts +20 -2
  747. package/src/resources/extensions/gsd/uok/contracts.ts +65 -0
  748. package/src/resources/extensions/gsd/uok/dispatch-envelope.ts +56 -0
  749. package/src/resources/extensions/gsd/uok/execution-graph.ts +22 -0
  750. package/src/resources/extensions/gsd/uok/gate-runner.ts +65 -5
  751. package/src/resources/extensions/gsd/uok/gitops.ts +6 -1
  752. package/src/resources/extensions/gsd/uok/loop-adapter.ts +45 -10
  753. package/src/resources/extensions/gsd/uok/parity-report.ts +84 -0
  754. package/src/resources/extensions/gsd/uok/plan-v2.ts +13 -5
  755. package/src/resources/extensions/gsd/uok/writer.ts +113 -0
  756. package/src/resources/extensions/gsd/workflow-logger.ts +22 -3
  757. package/src/resources/extensions/gsd/workflow-mcp.ts +6 -0
  758. package/src/resources/extensions/gsd/worktree-manager.ts +109 -7
  759. package/src/resources/extensions/gsd/worktree-resolver.ts +96 -9
  760. package/src/resources/extensions/gsd/worktree-telemetry.ts +322 -0
  761. package/src/resources/extensions/mcp-client/auth.ts +12 -1
  762. package/src/resources/extensions/mcp-client/index.ts +132 -11
  763. package/src/resources/extensions/mcp-client/tests/server-name-spaces.test.ts +70 -36
  764. package/src/resources/extensions/ollama/index.ts +5 -1
  765. package/src/resources/extensions/ollama/ollama-auth-mode.test.ts +123 -15
  766. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +206 -19
  767. package/src/resources/extensions/remote-questions/manager.ts +36 -4
  768. package/src/resources/extensions/remote-questions/tests/command-polling.test.ts +200 -190
  769. package/src/resources/extensions/shared/cmux-events.ts +59 -0
  770. package/src/resources/extensions/shared/rtk-session-stats.ts +1 -2
  771. package/src/resources/extensions/shared/tests/interview-preview.test.ts +11 -3
  772. package/src/resources/extensions/voice/tests/linux-ready.test.ts +129 -113
  773. package/src/resources/skills/create-skill/SKILL.md +2 -2
  774. package/src/resources/skills/create-skill/references/gsd-skill-ecosystem.md +4 -4
  775. package/src/resources/skills/create-skill/workflows/audit-skill.md +4 -4
  776. package/src/resources/skills/create-skill/workflows/create-new-skill.md +5 -5
  777. package/dist/web/standalone/.next/server/chunks/7461.js +0 -1
  778. package/dist/web/standalone/.next/static/chunks/2826.e59e8578e2e28639.js +0 -9
  779. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts +0 -2
  780. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.d.ts.map +0 -1
  781. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js +0 -289
  782. package/packages/pi-ai/dist/utils/oauth/oauth-providers.test.js.map +0 -1
  783. package/packages/pi-ai/src/utils/oauth/oauth-providers.test.ts +0 -363
  784. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +0 -143
  785. package/src/resources/extensions/gsd/tests/complete-milestone-false-merge.test.ts +0 -142
  786. package/src/resources/extensions/gsd/tests/dashboard-model-label-ordering.test.ts +0 -107
  787. package/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts +0 -48
  788. package/src/resources/extensions/gsd/tests/forensics-context-persist.test.ts +0 -159
  789. package/src/resources/extensions/gsd/tests/forensics-db-completion.test.ts +0 -96
  790. package/src/resources/extensions/gsd/tests/forensics-dedup.test.ts +0 -79
  791. package/src/resources/extensions/gsd/tests/forensics-hook-key-parse.test.ts +0 -74
  792. package/src/resources/extensions/gsd/tests/forensics-journal.test.ts +0 -162
  793. package/src/resources/extensions/gsd/tests/gitignore-bg-shell.test.ts +0 -38
  794. package/src/resources/extensions/gsd/tests/gsd-no-project-error.test.ts +0 -73
  795. package/src/resources/extensions/gsd/tests/idle-watchdog-stall-override.test.ts +0 -125
  796. package/src/resources/extensions/gsd/tests/import-done-milestones.test.ts +0 -42
  797. /package/dist/web/standalone/.next/static/{pV-mPo7rYGb5JBC09C8GG → C1zT2kEfoLhDdbWPWKrXd}/_buildManifest.js +0 -0
  798. /package/dist/web/standalone/.next/static/{pV-mPo7rYGb5JBC09C8GG → C1zT2kEfoLhDdbWPWKrXd}/_ssgManifest.js +0 -0
@@ -1,15 +1,18 @@
1
1
  /**
2
2
  * GSD Extensions Command — /gsd extensions
3
3
  *
4
- * Manage the extension registry: list, enable, disable, info.
4
+ * Manage the extension registry: list, enable, disable, info, install.
5
5
  * Self-contained — no imports outside the extensions tree (extensions are loaded
6
6
  * via jiti at runtime from ~/.gsd/agent/, not compiled by tsc).
7
7
  */
8
8
 
9
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";
10
+ import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
11
+ import { dirname, join, resolve } from "node:path";
12
+ import { homedir, tmpdir } from "node:os";
13
+ import { execFileSync } from "node:child_process";
14
+ import { lockSync, unlockSync } from "proper-lockfile";
15
+ import semver from "semver";
13
16
 
14
17
  const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
15
18
 
@@ -40,6 +43,9 @@ interface ExtensionRegistryEntry {
40
43
  source: "bundled" | "user" | "project";
41
44
  disabledAt?: string;
42
45
  disabledReason?: string;
46
+ version?: string;
47
+ installedFrom?: string;
48
+ installType?: "npm" | "git" | "local";
43
49
  }
44
50
 
45
51
  interface ExtensionRegistry {
@@ -82,6 +88,32 @@ function saveRegistry(registry: ExtensionRegistry): void {
82
88
  } catch { /* non-fatal */ }
83
89
  }
84
90
 
91
+ /**
92
+ * Run a registry load → mutate → save transaction under a cross-process lock.
93
+ * Prevents two concurrent `gsd extensions install/uninstall/update` invocations
94
+ * from trampling each other's registry mutations.
95
+ *
96
+ * Uses proper-lockfile.lockSync against the registry path. Directory is created
97
+ * first so locking works on fresh installs. Lock is always released via finally.
98
+ */
99
+ function withRegistryLock<T>(mutate: (registry: ExtensionRegistry) => T): T {
100
+ const filePath = getRegistryPath();
101
+ mkdirSync(dirname(filePath), { recursive: true });
102
+ // lockSync requires the file to exist — ensure it does before acquiring.
103
+ if (!existsSync(filePath)) {
104
+ writeFileSync(filePath, JSON.stringify({ version: 1, entries: {} }, null, 2), "utf-8");
105
+ }
106
+ lockSync(filePath, { retries: { retries: 5, minTimeout: 50, maxTimeout: 500 } });
107
+ try {
108
+ const registry = loadRegistry();
109
+ const result = mutate(registry);
110
+ saveRegistry(registry);
111
+ return result;
112
+ } finally {
113
+ try { unlockSync(filePath); } catch { /* lock may already be gone */ }
114
+ }
115
+ }
116
+
85
117
  function isEnabled(registry: ExtensionRegistry, id: string): boolean {
86
118
  const entry = registry.entries[id];
87
119
  if (!entry) return true;
@@ -100,18 +132,625 @@ function readManifest(dir: string): ExtensionManifest | null {
100
132
  }
101
133
  }
102
134
 
135
+ // ─── Package Validation (mirrored — D-14, no src/ imports) ────────────────
136
+
137
+ export interface ValidationResult {
138
+ valid: boolean;
139
+ errors: string[];
140
+ warnings: string[];
141
+ }
142
+
143
+ export function validateExtensionPackage(packageDir: string): ValidationResult {
144
+ const errors: string[] = [];
145
+ const warnings: string[] = [];
146
+
147
+ // Check package.json exists
148
+ const pkgPath = join(packageDir, "package.json");
149
+ if (!existsSync(pkgPath)) {
150
+ return { valid: false, errors: ["package.json not found"], warnings };
151
+ }
152
+
153
+ let pkg: Record<string, unknown>;
154
+ try {
155
+ pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
156
+ } catch {
157
+ return { valid: false, errors: ["package.json is invalid JSON"], warnings };
158
+ }
159
+
160
+ // (a) gsd.extension: true marker (D-12a)
161
+ const gsdField = pkg.gsd as Record<string, unknown> | undefined;
162
+ if (gsdField?.extension !== true) {
163
+ errors.push('package.json missing "gsd": { "extension": true }');
164
+ }
165
+
166
+ // (b) pi.extensions entry paths exist and are resolvable (D-12b)
167
+ const piField = pkg.pi as Record<string, unknown> | undefined;
168
+ const piExtensions = piField?.extensions;
169
+ if (!Array.isArray(piExtensions) || piExtensions.length === 0) {
170
+ errors.push('package.json missing "pi": { "extensions": [...] }');
171
+ } else {
172
+ for (const entry of piExtensions) {
173
+ if (typeof entry === "string") {
174
+ const resolved = join(packageDir, entry);
175
+ if (!existsSync(resolved)) {
176
+ errors.push(`pi.extensions entry not found: ${entry}`);
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ // (c) @gsd/* packages must be in peerDependencies, not dependencies/devDependencies (D-12c)
183
+ // Mirrors validateExtensionManifest below and extension-validator.ts:checkDependencyPlacement.
184
+ for (const field of ["dependencies", "devDependencies"] as const) {
185
+ const deps = (pkg[field] as Record<string, unknown> | undefined) ?? {};
186
+ for (const dep of Object.keys(deps)) {
187
+ if (dep.startsWith("@gsd/")) {
188
+ errors.push(`"${dep}" must be in peerDependencies, not ${field}`);
189
+ }
190
+ }
191
+ }
192
+
193
+ return { valid: errors.length === 0, errors, warnings };
194
+ }
195
+
103
196
  function discoverManifests(): Map<string, ExtensionManifest> {
104
- const extDir = getAgentExtensionsDir();
105
197
  const manifests = new Map<string, ExtensionManifest>();
106
- if (!existsSync(extDir)) return manifests;
107
- for (const entry of readdirSync(extDir, { withFileTypes: true })) {
108
- if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
109
- const m = readManifest(join(extDir, entry.name));
110
- if (m) manifests.set(m.id, m);
198
+ // Scan both bundled/agent dir and user-installed dir so CLI (list/info/
199
+ // enable/disable) sees the same set the loader will merge at runtime.
200
+ // Bundled entries are scanned first so user-installed IDs override on collision.
201
+ const dirs = [getAgentExtensionsDir(), getInstalledExtDir()];
202
+ for (const extDir of dirs) {
203
+ if (!existsSync(extDir)) continue;
204
+ for (const entry of readdirSync(extDir, { withFileTypes: true })) {
205
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
206
+ const m = readManifest(join(extDir, entry.name));
207
+ if (m) manifests.set(m.id, m);
208
+ }
111
209
  }
112
210
  return manifests;
113
211
  }
114
212
 
213
+ function getInstalledExtDir(): string {
214
+ return join(gsdHome, "extensions");
215
+ }
216
+
217
+ // Source: derived from npm/git URL conventions (from RESEARCH.md)
218
+ function detectInstallType(specifier: string): "npm" | "git" | "local" {
219
+ if (
220
+ specifier.startsWith("/") ||
221
+ specifier.startsWith("./") ||
222
+ specifier.startsWith("../") ||
223
+ specifier.startsWith("~/")
224
+ ) return "local";
225
+ if (
226
+ specifier.startsWith("git+") ||
227
+ specifier.startsWith("git://") ||
228
+ specifier.startsWith("github:") ||
229
+ specifier.startsWith("gitlab:") ||
230
+ specifier.startsWith("bitbucket:") ||
231
+ (specifier.startsWith("https://") && specifier.endsWith(".git")) ||
232
+ (specifier.startsWith("http://") && specifier.endsWith(".git"))
233
+ ) return "git";
234
+ return "npm";
235
+ }
236
+
237
+ // ─── Manifest Validation (mirrored from extension-validator.ts) ─────────────
238
+ // Note: distinct from validateExtensionPackage above (which validates a package
239
+ // directory on disk and returns string errors). This one validates an already-
240
+ // parsed package.json object and returns structured errors, used by install.
241
+
242
+ interface ManifestValidationError {
243
+ code: string;
244
+ message: string;
245
+ field?: string;
246
+ }
247
+
248
+ interface ManifestValidationResult {
249
+ valid: boolean;
250
+ errors: ManifestValidationError[];
251
+ }
252
+
253
+ function validateExtensionManifest(pkg: unknown, opts: { extensionId?: string; allowGsdNamespace?: boolean } = {}): ManifestValidationResult {
254
+ const errors: ManifestValidationError[] = [];
255
+
256
+ // Check gsd.extension === true (strict)
257
+ if (typeof pkg !== "object" || pkg === null) {
258
+ errors.push({ code: "MISSING_GSD_MARKER", message: 'package.json must declare "gsd": { "extension": true } to be recognized as a GSD extension.', field: "gsd.extension" });
259
+ } else {
260
+ const obj = pkg as Record<string, unknown>;
261
+ const gsd = obj.gsd;
262
+ if (typeof gsd !== "object" || gsd === null || (gsd as Record<string, unknown>).extension !== true) {
263
+ errors.push({ code: "MISSING_GSD_MARKER", message: 'package.json must declare "gsd": { "extension": true } to be recognized as a GSD extension.', field: "gsd.extension" });
264
+ }
265
+ }
266
+
267
+ // Check namespace reservation
268
+ if (opts.extensionId && opts.extensionId.startsWith("gsd.") && opts.allowGsdNamespace !== true) {
269
+ errors.push({ code: "RESERVED_NAMESPACE", message: `Extension ID "${opts.extensionId}" is reserved for GSD core extensions. Use a different namespace for community extensions.`, field: "extensionId" });
270
+ }
271
+
272
+ // Check dependency placement
273
+ if (typeof pkg === "object" && pkg !== null) {
274
+ const obj = pkg as Record<string, unknown>;
275
+ for (const field of ["dependencies", "devDependencies"] as const) {
276
+ const deps = obj[field];
277
+ if (typeof deps === "object" && deps !== null) {
278
+ for (const pkgName of Object.keys(deps as Record<string, unknown>)) {
279
+ if (pkgName.startsWith("@gsd/")) {
280
+ errors.push({ code: "WRONG_DEP_FIELD", message: `"${pkgName}" must not appear in "${field}". Move it to "peerDependencies".`, field });
281
+ }
282
+ }
283
+ }
284
+ }
285
+ }
286
+
287
+ return { valid: errors.length === 0, errors };
288
+ }
289
+
290
+ // ─── Post-install convergence ────────────────────────────────────────────────
291
+
292
+ /**
293
+ * Allowed characters for an extension id when used as a path segment.
294
+ * Rejects anything that could enable traversal or escape (slashes, "..", backslashes).
295
+ */
296
+ const SAFE_EXTENSION_ID_RE = /^[A-Za-z0-9._-]+$/;
297
+
298
+ function isSafeExtensionId(id: string): boolean {
299
+ if (!id || id === "." || id === "..") return false;
300
+ if (id.includes("/") || id.includes("\\") || id.includes("..")) return false;
301
+ return SAFE_EXTENSION_ID_RE.test(id);
302
+ }
303
+
304
+ /**
305
+ * Post-install convergence: validate package and read manifest.
306
+ * Returns the (validated) extension ID and manifest on success, or null on failure.
307
+ * Caller is responsible for writing the registry entry *after* the final commit
308
+ * rename succeeds so a failed move doesn't leave a dangling registry entry.
309
+ */
310
+ function postInstallValidate(
311
+ destPath: string,
312
+ specifier: string,
313
+ ctx: ExtensionCommandContext,
314
+ ): { id: string; manifest: ExtensionManifest } | null {
315
+ // Read package.json
316
+ const pkgJsonPath = join(destPath, "package.json");
317
+ if (!existsSync(pkgJsonPath)) {
318
+ ctx.ui.notify(`Cannot install "${specifier}": no package.json found.`, "error");
319
+ return null;
320
+ }
321
+ let pkgJson: Record<string, unknown>;
322
+ try {
323
+ pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
324
+ } catch {
325
+ ctx.ui.notify(`Cannot install "${specifier}": malformed package.json.`, "error");
326
+ return null;
327
+ }
328
+
329
+ // Read extension-manifest.json for the ID
330
+ const manifest = readManifest(destPath);
331
+ const extensionId = manifest?.id;
332
+
333
+ // Validate
334
+ const validation = validateExtensionManifest(pkgJson, { extensionId });
335
+ if (!validation.valid) {
336
+ const msgs = validation.errors.map(e => e.message).join("\n");
337
+ ctx.ui.notify(`Cannot install "${specifier}": ${msgs}`, "error");
338
+ return null;
339
+ }
340
+
341
+ if (!manifest || !extensionId) {
342
+ ctx.ui.notify(`Cannot install "${specifier}": no extension-manifest.json with valid id found.`, "error");
343
+ return null;
344
+ }
345
+
346
+ // The id from the manifest is used as a path segment under installedExtDir.
347
+ // Reject unsafe ids before the caller performs any path joins.
348
+ if (!isSafeExtensionId(extensionId)) {
349
+ ctx.ui.notify(
350
+ `Cannot install "${specifier}": extension id "${extensionId}" contains unsafe characters (allowed: alphanumerics, ".", "-", "_").`,
351
+ "error",
352
+ );
353
+ return null;
354
+ }
355
+
356
+ return { id: extensionId, manifest };
357
+ }
358
+
359
+ /**
360
+ * Write the registry entry for a freshly-installed extension. Called after the
361
+ * final destination commit succeeds so a failed rename can't leave a stale entry.
362
+ */
363
+ function writeInstalledRegistryEntry(
364
+ id: string,
365
+ manifest: ExtensionManifest,
366
+ specifier: string,
367
+ installType: "npm" | "git" | "local",
368
+ ): void {
369
+ withRegistryLock((registry) => {
370
+ registry.entries[id] = {
371
+ id,
372
+ enabled: true,
373
+ source: "user",
374
+ version: manifest.version,
375
+ installedFrom: specifier,
376
+ installType,
377
+ };
378
+ });
379
+ }
380
+
381
+ // ─── Uninstall helpers ───────────────────────────────────────────────────────
382
+
383
+ /**
384
+ * Scan installed extensions to find which ones depend on the target ID.
385
+ * Used for dependency warning on uninstall (D-06).
386
+ */
387
+ function findDependents(targetId: string, installedExtDir: string): string[] {
388
+ const dependents: string[] = [];
389
+ if (!existsSync(installedExtDir)) return dependents;
390
+ for (const entry of readdirSync(installedExtDir, { withFileTypes: true })) {
391
+ if (!entry.isDirectory()) continue;
392
+ const manifest = readManifest(join(installedExtDir, entry.name));
393
+ if (!manifest) continue;
394
+ if (manifest.dependencies?.extensions?.includes(targetId)) {
395
+ dependents.push(manifest.id);
396
+ }
397
+ }
398
+ return dependents;
399
+ }
400
+
401
+ function handleUninstall(id: string | undefined, ctx: ExtensionCommandContext): void {
402
+ if (!id) {
403
+ ctx.ui.notify("Usage: /gsd extensions uninstall <id>", "warning");
404
+ return;
405
+ }
406
+
407
+ // Hold the registry lock for the entire uninstall transaction so a concurrent
408
+ // install can't add or re-enable `id` while we're in the middle of removing it.
409
+ const result = withRegistryLock((registry) => {
410
+ const entry = registry.entries[id];
411
+
412
+ // Check if extension exists and is user-installed
413
+ if (!entry || entry.source !== "user") {
414
+ return { ok: false as const, reason: "not-found" as const };
415
+ }
416
+
417
+ const installedExtDir = getInstalledExtDir();
418
+ const extDir = join(installedExtDir, id);
419
+
420
+ // Check for dependents and warn (D-06: warn-then-proceed)
421
+ const dependents = findDependents(id, installedExtDir);
422
+
423
+ // Remove directory first, then registry entry (Pitfall 4 from RESEARCH.md)
424
+ // If rm fails, do NOT remove registry entry — leaves a recoverable state
425
+ try {
426
+ if (existsSync(extDir)) {
427
+ rmSync(extDir, { recursive: true, force: true });
428
+ }
429
+ } catch (err) {
430
+ const msg = err instanceof Error ? err.message : String(err);
431
+ return { ok: false as const, reason: "rm-failed" as const, msg };
432
+ }
433
+
434
+ // Remove registry entry (D-07)
435
+ delete registry.entries[id];
436
+ return { ok: true as const, dependents };
437
+ });
438
+
439
+ if (!result.ok) {
440
+ if (result.reason === "not-found") {
441
+ ctx.ui.notify(
442
+ `Extension "${id}" not found in registry. Run /gsd extensions list to see installed extensions.`,
443
+ "warning",
444
+ );
445
+ } else if (result.reason === "rm-failed") {
446
+ ctx.ui.notify(`Failed to remove extension directory for "${id}": ${result.msg}`, "error");
447
+ }
448
+ return;
449
+ }
450
+
451
+ if (result.dependents.length > 0) {
452
+ ctx.ui.notify(
453
+ `Warning: the following installed extensions depend on "${id}": ${result.dependents.join(", ")}. Removed anyway.`,
454
+ "warning",
455
+ );
456
+ }
457
+ ctx.ui.notify(`Uninstalled "${id}". Restart GSD to deactivate.`, "info");
458
+ }
459
+
460
+ // ─── Update subcommand ───────────────────────────────────────────────────────
461
+
462
+ async function getLatestNpmVersion(packageName: string): Promise<string | null> {
463
+ try {
464
+ const res = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
465
+ signal: AbortSignal.timeout(5000),
466
+ });
467
+ if (!res.ok) return null;
468
+ const data = await res.json() as { version?: string };
469
+ return data.version ?? null;
470
+ } catch {
471
+ return null;
472
+ }
473
+ }
474
+
475
+ async function handleUpdate(id: string | undefined, ctx: ExtensionCommandContext): Promise<void> {
476
+ const registry = loadRegistry();
477
+
478
+ if (id) {
479
+ // Update single extension (D-12)
480
+ await updateSingleExtension(id, registry, ctx);
481
+ } else {
482
+ // Update all installed extensions (D-11)
483
+ await updateAllExtensions(registry, ctx);
484
+ }
485
+ }
486
+
487
+ async function updateSingleExtension(
488
+ id: string,
489
+ registry: ExtensionRegistry,
490
+ ctx: ExtensionCommandContext,
491
+ ): Promise<void> {
492
+ const entry = registry.entries[id];
493
+
494
+ if (!entry || entry.source !== "user") {
495
+ ctx.ui.notify(
496
+ `Extension "${id}" not found in registry. Run /gsd extensions list to see installed extensions.`,
497
+ "warning",
498
+ );
499
+ return;
500
+ }
501
+
502
+ // Git and local installs: "reinstall to update" hint (D-10, D-12)
503
+ if (entry.installType !== "npm") {
504
+ const source = entry.installType ?? "unknown";
505
+ const hint = entry.installedFrom ? `gsd extensions install ${entry.installedFrom}` : `gsd extensions install <specifier>`;
506
+ ctx.ui.notify(
507
+ `"${id}" was installed from ${source}. Reinstall to update: ${hint}`,
508
+ "warning",
509
+ );
510
+ return;
511
+ }
512
+
513
+ // npm extension: check for newer version (D-09)
514
+ const current = entry.version ?? "0.0.0";
515
+ const specifier = entry.installedFrom;
516
+ if (!specifier) {
517
+ ctx.ui.notify(`"${id}" has no recorded install source. Reinstall manually.`, "warning");
518
+ return;
519
+ }
520
+
521
+ // Split npm specifier into name + optional pin.
522
+ // Scoped (`@scope/name[@version]`) vs unscoped (`name[@version]`).
523
+ const { name: packageName, pin } = parseNpmSpecifier(specifier);
524
+
525
+ // Pinned installs: the user explicitly requested a specific version. Don't
526
+ // silently upgrade past the pin — tell them to re-install with a new pin.
527
+ if (pin) {
528
+ ctx.ui.notify(
529
+ `"${id}" was installed with a pinned version (${pin}). To update, run: gsd extensions install ${packageName}@<new-version>`,
530
+ "info",
531
+ );
532
+ return;
533
+ }
534
+
535
+ const latest = await getLatestNpmVersion(packageName);
536
+ if (!latest) {
537
+ ctx.ui.notify(`Could not fetch latest version for "${id}".`, "warning");
538
+ return;
539
+ }
540
+
541
+ if (semver.gt(latest, current)) {
542
+ ctx.ui.notify(`Updating "${id}": v${current} → v${latest}...`, "info");
543
+ await handleInstall(packageName, ctx);
544
+ } else {
545
+ ctx.ui.notify(`"${id}" is already at the latest version (v${current}).`, "info");
546
+ }
547
+ }
548
+
549
+ /**
550
+ * Parse an npm specifier into its package name and optional version pin.
551
+ * Handles scoped (`@scope/name[@version]`) and unscoped (`name[@version]`).
552
+ */
553
+ function parseNpmSpecifier(specifier: string): { name: string; pin: string | null } {
554
+ const isScoped = specifier.startsWith("@");
555
+ const searchFrom = isScoped ? specifier.indexOf("/") + 1 : 0;
556
+ const atIdx = specifier.indexOf("@", searchFrom);
557
+ if (atIdx === -1) return { name: specifier, pin: null };
558
+ return { name: specifier.slice(0, atIdx), pin: specifier.slice(atIdx + 1) };
559
+ }
560
+
561
+ async function updateAllExtensions(
562
+ registry: ExtensionRegistry,
563
+ ctx: ExtensionCommandContext,
564
+ ): Promise<void> {
565
+ // Find all user-installed extensions
566
+ const userEntries = Object.values(registry.entries).filter(e => e.source === "user");
567
+
568
+ if (userEntries.length === 0) {
569
+ ctx.ui.notify("No user-installed extensions found. Use: gsd extensions install <package> to add one.", "warning");
570
+ return;
571
+ }
572
+
573
+ ctx.ui.notify(`Checking ${userEntries.length} installed extension(s) for updates...`, "info");
574
+
575
+ let updated = 0;
576
+ let skipped = 0;
577
+
578
+ for (const entry of userEntries) {
579
+ // Skip non-npm installs (D-11)
580
+ if (entry.installType !== "npm") {
581
+ const source = entry.installType ?? "unknown";
582
+ ctx.ui.notify(` ${entry.id}: installed from ${source} — reinstall to update`, "info");
583
+ skipped++;
584
+ continue;
585
+ }
586
+
587
+ const current = entry.version ?? "0.0.0";
588
+ const packageName = entry.installedFrom;
589
+ if (!packageName) {
590
+ ctx.ui.notify(` ${entry.id}: no recorded install source — skip`, "info");
591
+ skipped++;
592
+ continue;
593
+ }
594
+
595
+ const latest = await getLatestNpmVersion(packageName);
596
+ if (!latest) {
597
+ ctx.ui.notify(` ${entry.id}: could not fetch latest version — skip`, "info");
598
+ skipped++;
599
+ continue;
600
+ }
601
+
602
+ if (semver.gt(latest, current)) {
603
+ ctx.ui.notify(` ${entry.id}: v${current} → v${latest} (updating)`, "info");
604
+ await handleInstall(packageName, ctx);
605
+ updated++;
606
+ } else {
607
+ ctx.ui.notify(` ${entry.id}: v${current} (already up to date)`, "info");
608
+ }
609
+ }
610
+
611
+ ctx.ui.notify(`Updated ${updated} extension(s). ${skipped} skipped (git/local — reinstall to update).`, "info");
612
+ }
613
+
614
+ // ─── Install subcommand ──────────────────────────────────────────────────────
615
+
616
+ async function handleInstall(specifier: string | undefined, ctx: ExtensionCommandContext): Promise<void> {
617
+ if (!specifier) {
618
+ ctx.ui.notify("Usage: /gsd extensions install <npm-package|git-url|local-path>", "warning");
619
+ return;
620
+ }
621
+
622
+ const installType = detectInstallType(specifier);
623
+ const installedExtDir = getInstalledExtDir();
624
+ mkdirSync(installedExtDir, { recursive: true });
625
+
626
+ process.stderr.write(`Installing ${specifier}...\n`);
627
+
628
+ if (installType === "npm") {
629
+ installFromNpm(specifier, installedExtDir, ctx);
630
+ } else if (installType === "git") {
631
+ installFromGit(specifier, installedExtDir, ctx);
632
+ } else {
633
+ installFromLocal(specifier, installedExtDir, ctx);
634
+ }
635
+ }
636
+
637
+ function installFromNpm(specifier: string, installedExtDir: string, ctx: ExtensionCommandContext): void {
638
+ // packDir holds the tarball in tmpdir(). The *extractDir* is staged inside
639
+ // installedExtDir so the final renameSync to destPath stays on a single
640
+ // filesystem (avoids EXDEV when tmpdir() and ~/.gsd live on different mounts).
641
+ const packDir = mkdtempSync(join(tmpdir(), "gsd-install-"));
642
+ let extractDir: string | null = null;
643
+ try {
644
+ // Step 1: npm pack to tmpdir (D-01, D-05)
645
+ execFileSync("npm", ["pack", specifier, "--pack-destination", packDir, "--ignore-scripts"], {
646
+ stdio: "pipe",
647
+ encoding: "utf-8",
648
+ });
649
+
650
+ // Step 2: Find the tarball
651
+ const tgzFile = readdirSync(packDir).find(f => f.endsWith(".tgz"));
652
+ if (!tgzFile) throw new Error("npm pack produced no tarball");
653
+
654
+ // Step 3: Extract via tar into a staging dir *inside* installedExtDir
655
+ extractDir = mkdtempSync(join(installedExtDir, "tmp-npm-"));
656
+ execFileSync("tar", ["xzf", join(packDir, tgzFile), "-C", extractDir, "--strip-components=1"], { stdio: "pipe" });
657
+
658
+ // Step 4: Validate and get extension ID
659
+ const validated = postInstallValidate(extractDir, specifier, ctx);
660
+ if (!validated) {
661
+ return; // Error already notified
662
+ }
663
+
664
+ // Step 5: Move to final destination — same filesystem as extractDir
665
+ const destPath = join(installedExtDir, validated.id);
666
+ if (existsSync(destPath)) {
667
+ rmSync(destPath, { recursive: true, force: true });
668
+ }
669
+ renameSync(extractDir, destPath);
670
+ extractDir = null; // Successfully moved; skip cleanup
671
+
672
+ // Step 6: Commit the registry entry only after the rename succeeds.
673
+ writeInstalledRegistryEntry(validated.id, validated.manifest, specifier, "npm");
674
+ ctx.ui.notify(`Installed "${validated.id}" v${validated.manifest.version ?? "unknown"}. Restart GSD to activate.`, "info");
675
+ } catch (err) {
676
+ const msg = err instanceof Error ? err.message : String(err);
677
+ ctx.ui.notify(`Failed to install "${specifier}": ${msg}`, "error");
678
+ } finally {
679
+ if (extractDir && existsSync(extractDir)) {
680
+ try { rmSync(extractDir, { recursive: true, force: true }); } catch { /* best-effort */ }
681
+ }
682
+ rmSync(packDir, { recursive: true, force: true });
683
+ }
684
+ }
685
+
686
+ function installFromGit(gitUrl: string, installedExtDir: string, ctx: ExtensionCommandContext): void {
687
+ // Clone into temp dir, validate, then rename to real ID (D-02)
688
+ const tmpDir = join(installedExtDir, `__installing-${Date.now()}`);
689
+ try {
690
+ execFileSync("git", ["clone", "--depth=1", gitUrl, tmpDir], { stdio: "pipe" });
691
+
692
+ // Remove .git directory — not needed after clone
693
+ const dotGit = join(tmpDir, ".git");
694
+ if (existsSync(dotGit)) {
695
+ rmSync(dotGit, { recursive: true, force: true });
696
+ }
697
+
698
+ const validated = postInstallValidate(tmpDir, gitUrl, ctx);
699
+ if (!validated) {
700
+ rmSync(tmpDir, { recursive: true, force: true });
701
+ return;
702
+ }
703
+
704
+ const destPath = join(installedExtDir, validated.id);
705
+ if (existsSync(destPath)) {
706
+ rmSync(destPath, { recursive: true, force: true });
707
+ }
708
+ renameSync(tmpDir, destPath);
709
+
710
+ writeInstalledRegistryEntry(validated.id, validated.manifest, gitUrl, "git");
711
+ ctx.ui.notify(`Installed "${validated.id}" v${validated.manifest.version ?? "unknown"}. Restart GSD to activate.`, "info");
712
+ } catch (err) {
713
+ if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true });
714
+ const msg = err instanceof Error ? err.message : String(err);
715
+ ctx.ui.notify(`Failed to install "${gitUrl}": ${msg}`, "error");
716
+ }
717
+ }
718
+
719
+ function installFromLocal(localPath: string, installedExtDir: string, ctx: ExtensionCommandContext): void {
720
+ // Resolve path and copy (not symlink) per D-03
721
+ const sourcePath = resolve(localPath.startsWith("~/") ? join(homedir(), localPath.slice(2)) : localPath);
722
+
723
+ if (!existsSync(sourcePath)) {
724
+ ctx.ui.notify(`Cannot install "${localPath}": path does not exist.`, "error");
725
+ return;
726
+ }
727
+
728
+ // Copy to temp dir first, validate, then rename
729
+ const tmpDir = join(installedExtDir, `__installing-${Date.now()}`);
730
+ try {
731
+ cpSync(sourcePath, tmpDir, { recursive: true });
732
+
733
+ const validated = postInstallValidate(tmpDir, localPath, ctx);
734
+ if (!validated) {
735
+ rmSync(tmpDir, { recursive: true, force: true });
736
+ return;
737
+ }
738
+
739
+ const destPath = join(installedExtDir, validated.id);
740
+ if (existsSync(destPath)) {
741
+ rmSync(destPath, { recursive: true, force: true });
742
+ }
743
+ renameSync(tmpDir, destPath);
744
+
745
+ writeInstalledRegistryEntry(validated.id, validated.manifest, localPath, "local");
746
+ ctx.ui.notify(`Installed "${validated.id}" v${validated.manifest.version ?? "unknown"}. Restart GSD to activate.`, "info");
747
+ } catch (err) {
748
+ if (existsSync(tmpDir)) rmSync(tmpDir, { recursive: true, force: true });
749
+ const msg = err instanceof Error ? err.message : String(err);
750
+ ctx.ui.notify(`Failed to install "${localPath}": ${msg}`, "error");
751
+ }
752
+ }
753
+
115
754
  // ─── Command Handler ────────────────────────────────────────────────────────
116
755
 
117
756
  export async function handleExtensions(args: string, ctx: ExtensionCommandContext): Promise<void> {
@@ -138,8 +777,28 @@ export async function handleExtensions(args: string, ctx: ExtensionCommandContex
138
777
  return;
139
778
  }
140
779
 
780
+ if (subCmd === "install") {
781
+ await handleInstall(parts[1], ctx);
782
+ return;
783
+ }
784
+
785
+ if (subCmd === "uninstall") {
786
+ handleUninstall(parts[1], ctx);
787
+ return;
788
+ }
789
+
790
+ if (subCmd === "update") {
791
+ await handleUpdate(parts[1], ctx);
792
+ return;
793
+ }
794
+
795
+ if (subCmd === "validate") {
796
+ handleValidate(parts[1], ctx);
797
+ return;
798
+ }
799
+
141
800
  ctx.ui.notify(
142
- `Unknown: /gsd extensions ${subCmd}. Usage: /gsd extensions [list|enable|disable|info]`,
801
+ `Unknown: /gsd extensions ${subCmd}. Usage: /gsd extensions [list|enable|disable|info|install|uninstall|update|validate]`,
143
802
  "warning",
144
803
  );
145
804
  }
@@ -180,6 +839,19 @@ function handleList(ctx: ExtensionCommandContext): void {
180
839
  String(cmdCount),
181
840
  );
182
841
 
842
+ // Show source indicator and install info for user-installed extensions
843
+ const regEntry = registry.entries[m.id];
844
+ if (regEntry?.source === "user") {
845
+ // Append [user] tag to the last line
846
+ const lastLine = lines[lines.length - 1];
847
+ lines[lines.length - 1] = lastLine + " [user]";
848
+ if (regEntry.installedFrom) {
849
+ const typePrefix = regEntry.installType ? `${regEntry.installType}:` : "";
850
+ const versionSuffix = regEntry.version ? `@${regEntry.version}` : "";
851
+ lines.push(` installed from: ${typePrefix}${regEntry.installedFrom}${versionSuffix}`);
852
+ }
853
+ }
854
+
183
855
  if (!enabled) {
184
856
  lines.push(` ↳ gsd extensions enable ${m.id}`);
185
857
  }
@@ -200,21 +872,22 @@ function handleEnable(id: string | undefined, ctx: ExtensionCommandContext): voi
200
872
  return;
201
873
  }
202
874
 
203
- const registry = loadRegistry();
204
- if (isEnabled(registry, id)) {
875
+ const alreadyEnabled = withRegistryLock((registry) => {
876
+ if (isEnabled(registry, id)) return true;
877
+ const entry = registry.entries[id];
878
+ if (entry) {
879
+ entry.enabled = true;
880
+ delete entry.disabledAt;
881
+ delete entry.disabledReason;
882
+ } else {
883
+ registry.entries[id] = { id, enabled: true, source: "bundled" };
884
+ }
885
+ return false;
886
+ });
887
+ if (alreadyEnabled) {
205
888
  ctx.ui.notify(`Extension "${id}" is already enabled.`, "info");
206
889
  return;
207
890
  }
208
-
209
- const entry = registry.entries[id];
210
- if (entry) {
211
- entry.enabled = true;
212
- delete entry.disabledAt;
213
- delete entry.disabledReason;
214
- } else {
215
- registry.entries[id] = { id, enabled: true, source: "bundled" };
216
- }
217
- saveRegistry(registry);
218
891
  ctx.ui.notify(`Enabled "${id}". Restart GSD to activate.`, "info");
219
892
  }
220
893
 
@@ -237,27 +910,28 @@ function handleDisable(id: string | undefined, reason: string, ctx: ExtensionCom
237
910
  return;
238
911
  }
239
912
 
240
- const registry = loadRegistry();
241
- if (!isEnabled(registry, id)) {
913
+ const alreadyDisabled = withRegistryLock((registry) => {
914
+ if (!isEnabled(registry, id)) return true;
915
+ const entry = registry.entries[id];
916
+ if (entry) {
917
+ entry.enabled = false;
918
+ entry.disabledAt = new Date().toISOString();
919
+ entry.disabledReason = reason || undefined;
920
+ } else {
921
+ registry.entries[id] = {
922
+ id,
923
+ enabled: false,
924
+ source: "bundled",
925
+ disabledAt: new Date().toISOString(),
926
+ disabledReason: reason || undefined,
927
+ };
928
+ }
929
+ return false;
930
+ });
931
+ if (alreadyDisabled) {
242
932
  ctx.ui.notify(`Extension "${id}" is already disabled.`, "info");
243
933
  return;
244
934
  }
245
-
246
- const entry = registry.entries[id];
247
- if (entry) {
248
- entry.enabled = false;
249
- entry.disabledAt = new Date().toISOString();
250
- entry.disabledReason = reason || undefined;
251
- } else {
252
- registry.entries[id] = {
253
- id,
254
- enabled: false,
255
- source: "bundled",
256
- disabledAt: new Date().toISOString(),
257
- disabledReason: reason || undefined,
258
- };
259
- }
260
- saveRegistry(registry);
261
935
  ctx.ui.notify(`Disabled "${id}". Restart GSD to deactivate.`, "info");
262
936
  }
263
937
 
@@ -294,6 +968,16 @@ function handleInfo(id: string | undefined, ctx: ExtensionCommandContext): void
294
968
  lines.push(` Reason: ${entry.disabledReason}`);
295
969
  }
296
970
 
971
+ // Phase 8 fields for user-installed extensions (per UI-SPEC)
972
+ if (entry?.source === "user") {
973
+ if (entry.installedFrom) {
974
+ lines.push(` Installed from: ${entry.installedFrom}`);
975
+ }
976
+ if (entry.installType) {
977
+ lines.push(` Install type: ${entry.installType}`);
978
+ }
979
+ }
980
+
297
981
  if (manifest.provides) {
298
982
  lines.push("");
299
983
  lines.push(" Provides:");
@@ -325,6 +1009,28 @@ function handleInfo(id: string | undefined, ctx: ExtensionCommandContext): void
325
1009
  ctx.ui.notify(lines.join("\n"), "info");
326
1010
  }
327
1011
 
1012
+ function handleValidate(path: string | undefined, ctx: ExtensionCommandContext): void {
1013
+ if (!path) {
1014
+ ctx.ui.notify("Usage: /gsd extensions validate <path>", "warning");
1015
+ return;
1016
+ }
1017
+ const resolved = resolve(path);
1018
+ if (!existsSync(resolved)) {
1019
+ ctx.ui.notify(`Path not found: ${resolved}`, "warning");
1020
+ return;
1021
+ }
1022
+ const result = validateExtensionPackage(resolved);
1023
+ if (result.valid) {
1024
+ ctx.ui.notify(`Valid extension package: ${resolved}`, "info");
1025
+ } else {
1026
+ ctx.ui.notify(
1027
+ `Invalid extension package: ${resolved}\n` +
1028
+ result.errors.map(e => ` - ${e}`).join("\n"),
1029
+ "warning",
1030
+ );
1031
+ }
1032
+ }
1033
+
328
1034
  function padRight(str: string, len: number): string {
329
1035
  return str.length >= len ? str + " " : str + " ".repeat(len - str.length);
330
1036
  }