specrails-desktop 2.8.0 → 2.9.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 (326) hide show
  1. package/README.md +23 -19
  2. package/client/dist/assets/{ActivityFeedPage-LKqd18-G.js → ActivityFeedPage-DNqnf1fZ.js} +1 -1
  3. package/client/dist/assets/{AgentsPage-Cb-b-6Ot.js → AgentsPage-vmNIEbGM.js} +1 -1
  4. package/client/dist/assets/{AnalyticsPage-HVxQQ1wy.js → AnalyticsPage-CdfN0ofZ.js} +1 -1
  5. package/client/dist/assets/{BarChart-BOyHB0dw.js → BarChart-CIkopHjl.js} +1 -1
  6. package/client/dist/assets/{CodePage-DnOnwKGB.js → CodePage-DDRNU5FN.js} +1 -1
  7. package/client/dist/assets/{DesktopAnalyticsPage-D2auU39x.js → DesktopAnalyticsPage-Cl3sKKSG.js} +1 -1
  8. package/client/dist/assets/{DocsDialog-CTuDX3GK.js → DocsDialog-BGrBOfUr.js} +2 -2
  9. package/client/dist/assets/{DocsPage-DRyMmu0Z.js → DocsPage-CY-2SSzw.js} +2 -2
  10. package/client/dist/assets/{ExportDropdown-DO-GGiMh.js → ExportDropdown-BRHcvP0r.js} +1 -1
  11. package/client/dist/assets/{IntegrationsPage-BhbO4jFT.js → IntegrationsPage-nKdLB4Ub.js} +1 -1
  12. package/client/dist/assets/{JobDetailPage-DJooEg1s.js → JobDetailPage-Bf0A6WWQ.js} +1 -1
  13. package/client/dist/assets/{JobsPage-BbaC-YOg.js → JobsPage-Vg4nXPdL.js} +1 -1
  14. package/client/dist/assets/{dist-js-CiIVMsx3.js → dist-js-0i_klubI.js} +1 -1
  15. package/client/dist/assets/{dist-js-Xc2lRKp2.js → dist-js-CUs5GjwA.js} +1 -1
  16. package/client/dist/assets/{index-DK214dak.js → index-BXoHFtfG.js} +8 -8
  17. package/client/dist/assets/index-D6BaYRRU.css +2 -0
  18. package/client/dist/assets/{integrations-2C7MkGT0.js → integrations-7YyTBuU9.js} +1 -1
  19. package/client/dist/assets/{integrations-CX4p_bij.js → integrations-B9CEpNF0.js} +1 -1
  20. package/client/dist/assets/{integrations-C2jQtv-s.js → integrations-BlvAdewo.js} +1 -1
  21. package/client/dist/assets/{integrations-eQPHAYsE.js → integrations-Bw8UM9Xd.js} +1 -1
  22. package/client/dist/assets/{integrations-BDC670cg.js → integrations-C5SxNKnG.js} +1 -1
  23. package/client/dist/assets/{integrations-BqUmRUef.js → integrations-CJQKMmdW.js} +1 -1
  24. package/client/dist/assets/{integrations-CB98NeH5.js → integrations-DWz1eU_K.js} +1 -1
  25. package/client/dist/assets/{integrations-_SuVeQIG.js → integrations-DiPR8Fzp.js} +1 -1
  26. package/client/dist/assets/{lib-Bo5s6xpe.js → lib-D6M_MvoC.js} +1 -1
  27. package/client/dist/assets/setup-B6egeeTM.js +1 -0
  28. package/client/dist/assets/setup-BHroXlke.js +1 -0
  29. package/client/dist/assets/setup-BIXsWUp1.js +1 -0
  30. package/client/dist/assets/setup-BJRdg1iE.js +1 -0
  31. package/client/dist/assets/setup-C0rVGnCy.js +1 -0
  32. package/client/dist/assets/setup-Cpu17hJv.js +1 -0
  33. package/client/dist/assets/setup-D-1r0uSx.js +1 -0
  34. package/client/dist/assets/setup-Dn2-veYO.js +1 -0
  35. package/client/dist/assets/{useProjectCache-DVNypkmR.js → useProjectCache-BeyBSNpD.js} +1 -1
  36. package/client/dist/index.html +4 -4
  37. package/docs/README.md +5 -2
  38. package/docs/agy-cli-provider-study.md +78 -0
  39. package/docs/cli.md +23 -4
  40. package/docs/codex.md +116 -58
  41. package/docs/creating-specs.md +19 -5
  42. package/docs/customizing.md +27 -6
  43. package/docs/gemini.md +225 -73
  44. package/docs/getting-started.md +18 -9
  45. package/docs/guide/de/agents/1-meet-the-agents.md +38 -0
  46. package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +45 -0
  47. package/docs/guide/de/agents/3-customizing-models-per-agent.md +60 -0
  48. package/docs/guide/de/agents/4-custom-agents-catalog.md +43 -0
  49. package/docs/guide/de/getting-started/1-what-is-specrails.md +49 -0
  50. package/docs/guide/de/getting-started/2-installing-and-first-run.md +42 -0
  51. package/docs/guide/de/getting-started/3-adding-your-first-project.md +58 -0
  52. package/docs/guide/de/getting-started/4-the-dashboard-tour.md +53 -0
  53. package/docs/guide/de/insights/1-analytics-and-cost-tracking.md +78 -0
  54. package/docs/guide/de/insights/2-the-integrated-terminal.md +46 -0
  55. package/docs/guide/de/insights/3-code-explorer.md +50 -0
  56. package/docs/guide/de/integrations/1-ai-providers.md +64 -0
  57. package/docs/guide/de/integrations/2-plugins.md +44 -0
  58. package/docs/guide/de/integrations/3-jira-integration.md +71 -0
  59. package/docs/guide/de/integrations/4-mobile-companion.md +38 -0
  60. package/docs/guide/de/pipeline/1-rails-and-jobs.md +94 -0
  61. package/docs/guide/de/pipeline/2-the-job-detail-view.md +90 -0
  62. package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +78 -0
  63. package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +60 -0
  64. package/docs/guide/de/settings/1-themes.md +37 -0
  65. package/docs/guide/de/settings/2-language.md +39 -0
  66. package/docs/guide/de/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
  67. package/docs/guide/de/settings/4-where-your-data-lives.md +48 -0
  68. package/docs/guide/de/specs/1-specs-and-the-backlog.md +52 -0
  69. package/docs/guide/de/specs/2-add-spec-quick-mode.md +45 -0
  70. package/docs/guide/de/specs/3-add-spec-explore-mode.md +68 -0
  71. package/docs/guide/de/specs/4-drafts-and-contract-layer.md +81 -0
  72. package/docs/guide/en/agents/1-meet-the-agents.md +38 -0
  73. package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +45 -0
  74. package/docs/guide/en/agents/3-customizing-models-per-agent.md +60 -0
  75. package/docs/guide/en/agents/4-custom-agents-catalog.md +43 -0
  76. package/docs/guide/en/getting-started/1-what-is-specrails.md +49 -0
  77. package/docs/guide/en/getting-started/2-installing-and-first-run.md +42 -0
  78. package/docs/guide/en/getting-started/3-adding-your-first-project.md +58 -0
  79. package/docs/guide/en/getting-started/4-the-dashboard-tour.md +53 -0
  80. package/docs/guide/en/insights/1-analytics-and-cost-tracking.md +78 -0
  81. package/docs/guide/en/insights/2-the-integrated-terminal.md +46 -0
  82. package/docs/guide/en/insights/3-code-explorer.md +50 -0
  83. package/docs/guide/en/integrations/1-ai-providers.md +64 -0
  84. package/docs/guide/en/integrations/2-plugins.md +44 -0
  85. package/docs/guide/en/integrations/3-jira-integration.md +71 -0
  86. package/docs/guide/en/integrations/4-mobile-companion.md +38 -0
  87. package/docs/guide/en/pipeline/1-rails-and-jobs.md +94 -0
  88. package/docs/guide/en/pipeline/2-the-job-detail-view.md +90 -0
  89. package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +78 -0
  90. package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +60 -0
  91. package/docs/guide/en/settings/1-themes.md +37 -0
  92. package/docs/guide/en/settings/2-language.md +39 -0
  93. package/docs/guide/en/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
  94. package/docs/guide/en/settings/4-where-your-data-lives.md +48 -0
  95. package/docs/guide/en/specs/1-specs-and-the-backlog.md +52 -0
  96. package/docs/guide/en/specs/2-add-spec-quick-mode.md +45 -0
  97. package/docs/guide/en/specs/3-add-spec-explore-mode.md +68 -0
  98. package/docs/guide/en/specs/4-drafts-and-contract-layer.md +81 -0
  99. package/docs/guide/es/agents/1-meet-the-agents.md +38 -0
  100. package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +45 -0
  101. package/docs/guide/es/agents/3-customizing-models-per-agent.md +60 -0
  102. package/docs/guide/es/agents/4-custom-agents-catalog.md +43 -0
  103. package/docs/guide/es/getting-started/1-what-is-specrails.md +49 -0
  104. package/docs/guide/es/getting-started/2-installing-and-first-run.md +42 -0
  105. package/docs/guide/es/getting-started/3-adding-your-first-project.md +58 -0
  106. package/docs/guide/es/getting-started/4-the-dashboard-tour.md +53 -0
  107. package/docs/guide/es/insights/1-analytics-and-cost-tracking.md +78 -0
  108. package/docs/guide/es/insights/2-the-integrated-terminal.md +46 -0
  109. package/docs/guide/es/insights/3-code-explorer.md +50 -0
  110. package/docs/guide/es/integrations/1-ai-providers.md +64 -0
  111. package/docs/guide/es/integrations/2-plugins.md +44 -0
  112. package/docs/guide/es/integrations/3-jira-integration.md +71 -0
  113. package/docs/guide/es/integrations/4-mobile-companion.md +38 -0
  114. package/docs/guide/es/pipeline/1-rails-and-jobs.md +94 -0
  115. package/docs/guide/es/pipeline/2-the-job-detail-view.md +90 -0
  116. package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +78 -0
  117. package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +60 -0
  118. package/docs/guide/es/settings/1-themes.md +37 -0
  119. package/docs/guide/es/settings/2-language.md +39 -0
  120. package/docs/guide/es/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
  121. package/docs/guide/es/settings/4-where-your-data-lives.md +48 -0
  122. package/docs/guide/es/specs/1-specs-and-the-backlog.md +52 -0
  123. package/docs/guide/es/specs/2-add-spec-quick-mode.md +45 -0
  124. package/docs/guide/es/specs/3-add-spec-explore-mode.md +68 -0
  125. package/docs/guide/es/specs/4-drafts-and-contract-layer.md +81 -0
  126. package/docs/guide/fr/agents/1-meet-the-agents.md +38 -0
  127. package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +45 -0
  128. package/docs/guide/fr/agents/3-customizing-models-per-agent.md +60 -0
  129. package/docs/guide/fr/agents/4-custom-agents-catalog.md +43 -0
  130. package/docs/guide/fr/getting-started/1-what-is-specrails.md +49 -0
  131. package/docs/guide/fr/getting-started/2-installing-and-first-run.md +42 -0
  132. package/docs/guide/fr/getting-started/3-adding-your-first-project.md +58 -0
  133. package/docs/guide/fr/getting-started/4-the-dashboard-tour.md +53 -0
  134. package/docs/guide/fr/insights/1-analytics-and-cost-tracking.md +78 -0
  135. package/docs/guide/fr/insights/2-the-integrated-terminal.md +46 -0
  136. package/docs/guide/fr/insights/3-code-explorer.md +50 -0
  137. package/docs/guide/fr/integrations/1-ai-providers.md +64 -0
  138. package/docs/guide/fr/integrations/2-plugins.md +44 -0
  139. package/docs/guide/fr/integrations/3-jira-integration.md +71 -0
  140. package/docs/guide/fr/integrations/4-mobile-companion.md +38 -0
  141. package/docs/guide/fr/pipeline/1-rails-and-jobs.md +94 -0
  142. package/docs/guide/fr/pipeline/2-the-job-detail-view.md +90 -0
  143. package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +78 -0
  144. package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +60 -0
  145. package/docs/guide/fr/settings/1-themes.md +37 -0
  146. package/docs/guide/fr/settings/2-language.md +39 -0
  147. package/docs/guide/fr/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
  148. package/docs/guide/fr/settings/4-where-your-data-lives.md +48 -0
  149. package/docs/guide/fr/specs/1-specs-and-the-backlog.md +52 -0
  150. package/docs/guide/fr/specs/2-add-spec-quick-mode.md +45 -0
  151. package/docs/guide/fr/specs/3-add-spec-explore-mode.md +68 -0
  152. package/docs/guide/fr/specs/4-drafts-and-contract-layer.md +81 -0
  153. package/docs/guide/it/agents/1-meet-the-agents.md +38 -0
  154. package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +45 -0
  155. package/docs/guide/it/agents/3-customizing-models-per-agent.md +60 -0
  156. package/docs/guide/it/agents/4-custom-agents-catalog.md +43 -0
  157. package/docs/guide/it/getting-started/1-what-is-specrails.md +49 -0
  158. package/docs/guide/it/getting-started/2-installing-and-first-run.md +42 -0
  159. package/docs/guide/it/getting-started/3-adding-your-first-project.md +58 -0
  160. package/docs/guide/it/getting-started/4-the-dashboard-tour.md +53 -0
  161. package/docs/guide/it/insights/1-analytics-and-cost-tracking.md +78 -0
  162. package/docs/guide/it/insights/2-the-integrated-terminal.md +46 -0
  163. package/docs/guide/it/insights/3-code-explorer.md +50 -0
  164. package/docs/guide/it/integrations/1-ai-providers.md +64 -0
  165. package/docs/guide/it/integrations/2-plugins.md +44 -0
  166. package/docs/guide/it/integrations/3-jira-integration.md +71 -0
  167. package/docs/guide/it/integrations/4-mobile-companion.md +38 -0
  168. package/docs/guide/it/pipeline/1-rails-and-jobs.md +94 -0
  169. package/docs/guide/it/pipeline/2-the-job-detail-view.md +90 -0
  170. package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +78 -0
  171. package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +60 -0
  172. package/docs/guide/it/settings/1-themes.md +37 -0
  173. package/docs/guide/it/settings/2-language.md +39 -0
  174. package/docs/guide/it/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
  175. package/docs/guide/it/settings/4-where-your-data-lives.md +48 -0
  176. package/docs/guide/it/specs/1-specs-and-the-backlog.md +52 -0
  177. package/docs/guide/it/specs/2-add-spec-quick-mode.md +45 -0
  178. package/docs/guide/it/specs/3-add-spec-explore-mode.md +68 -0
  179. package/docs/guide/it/specs/4-drafts-and-contract-layer.md +81 -0
  180. package/docs/guide/ja/agents/1-meet-the-agents.md +38 -0
  181. package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +45 -0
  182. package/docs/guide/ja/agents/3-customizing-models-per-agent.md +60 -0
  183. package/docs/guide/ja/agents/4-custom-agents-catalog.md +43 -0
  184. package/docs/guide/ja/getting-started/1-what-is-specrails.md +49 -0
  185. package/docs/guide/ja/getting-started/2-installing-and-first-run.md +42 -0
  186. package/docs/guide/ja/getting-started/3-adding-your-first-project.md +58 -0
  187. package/docs/guide/ja/getting-started/4-the-dashboard-tour.md +53 -0
  188. package/docs/guide/ja/insights/1-analytics-and-cost-tracking.md +78 -0
  189. package/docs/guide/ja/insights/2-the-integrated-terminal.md +46 -0
  190. package/docs/guide/ja/insights/3-code-explorer.md +50 -0
  191. package/docs/guide/ja/integrations/1-ai-providers.md +64 -0
  192. package/docs/guide/ja/integrations/2-plugins.md +44 -0
  193. package/docs/guide/ja/integrations/3-jira-integration.md +71 -0
  194. package/docs/guide/ja/integrations/4-mobile-companion.md +38 -0
  195. package/docs/guide/ja/pipeline/1-rails-and-jobs.md +94 -0
  196. package/docs/guide/ja/pipeline/2-the-job-detail-view.md +90 -0
  197. package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +78 -0
  198. package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +60 -0
  199. package/docs/guide/ja/settings/1-themes.md +37 -0
  200. package/docs/guide/ja/settings/2-language.md +39 -0
  201. package/docs/guide/ja/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
  202. package/docs/guide/ja/settings/4-where-your-data-lives.md +48 -0
  203. package/docs/guide/ja/specs/1-specs-and-the-backlog.md +52 -0
  204. package/docs/guide/ja/specs/2-add-spec-quick-mode.md +45 -0
  205. package/docs/guide/ja/specs/3-add-spec-explore-mode.md +68 -0
  206. package/docs/guide/ja/specs/4-drafts-and-contract-layer.md +81 -0
  207. package/docs/guide/pt/agents/1-meet-the-agents.md +38 -0
  208. package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +45 -0
  209. package/docs/guide/pt/agents/3-customizing-models-per-agent.md +60 -0
  210. package/docs/guide/pt/agents/4-custom-agents-catalog.md +43 -0
  211. package/docs/guide/pt/getting-started/1-what-is-specrails.md +49 -0
  212. package/docs/guide/pt/getting-started/2-installing-and-first-run.md +42 -0
  213. package/docs/guide/pt/getting-started/3-adding-your-first-project.md +58 -0
  214. package/docs/guide/pt/getting-started/4-the-dashboard-tour.md +53 -0
  215. package/docs/guide/pt/insights/1-analytics-and-cost-tracking.md +78 -0
  216. package/docs/guide/pt/insights/2-the-integrated-terminal.md +46 -0
  217. package/docs/guide/pt/insights/3-code-explorer.md +50 -0
  218. package/docs/guide/pt/integrations/1-ai-providers.md +64 -0
  219. package/docs/guide/pt/integrations/2-plugins.md +44 -0
  220. package/docs/guide/pt/integrations/3-jira-integration.md +71 -0
  221. package/docs/guide/pt/integrations/4-mobile-companion.md +38 -0
  222. package/docs/guide/pt/pipeline/1-rails-and-jobs.md +94 -0
  223. package/docs/guide/pt/pipeline/2-the-job-detail-view.md +90 -0
  224. package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +78 -0
  225. package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +60 -0
  226. package/docs/guide/pt/settings/1-themes.md +37 -0
  227. package/docs/guide/pt/settings/2-language.md +39 -0
  228. package/docs/guide/pt/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
  229. package/docs/guide/pt/settings/4-where-your-data-lives.md +48 -0
  230. package/docs/guide/pt/specs/1-specs-and-the-backlog.md +52 -0
  231. package/docs/guide/pt/specs/2-add-spec-quick-mode.md +45 -0
  232. package/docs/guide/pt/specs/3-add-spec-explore-mode.md +68 -0
  233. package/docs/guide/pt/specs/4-drafts-and-contract-layer.md +81 -0
  234. package/docs/guide/zh/agents/1-meet-the-agents.md +38 -0
  235. package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +45 -0
  236. package/docs/guide/zh/agents/3-customizing-models-per-agent.md +60 -0
  237. package/docs/guide/zh/agents/4-custom-agents-catalog.md +43 -0
  238. package/docs/guide/zh/getting-started/1-what-is-specrails.md +49 -0
  239. package/docs/guide/zh/getting-started/2-installing-and-first-run.md +42 -0
  240. package/docs/guide/zh/getting-started/3-adding-your-first-project.md +58 -0
  241. package/docs/guide/zh/getting-started/4-the-dashboard-tour.md +53 -0
  242. package/docs/guide/zh/insights/1-analytics-and-cost-tracking.md +78 -0
  243. package/docs/guide/zh/insights/2-the-integrated-terminal.md +46 -0
  244. package/docs/guide/zh/insights/3-code-explorer.md +50 -0
  245. package/docs/guide/zh/integrations/1-ai-providers.md +64 -0
  246. package/docs/guide/zh/integrations/2-plugins.md +44 -0
  247. package/docs/guide/zh/integrations/3-jira-integration.md +71 -0
  248. package/docs/guide/zh/integrations/4-mobile-companion.md +38 -0
  249. package/docs/guide/zh/pipeline/1-rails-and-jobs.md +94 -0
  250. package/docs/guide/zh/pipeline/2-the-job-detail-view.md +90 -0
  251. package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +78 -0
  252. package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +60 -0
  253. package/docs/guide/zh/settings/1-themes.md +37 -0
  254. package/docs/guide/zh/settings/2-language.md +39 -0
  255. package/docs/guide/zh/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
  256. package/docs/guide/zh/settings/4-where-your-data-lives.md +48 -0
  257. package/docs/guide/zh/specs/1-specs-and-the-backlog.md +52 -0
  258. package/docs/guide/zh/specs/2-add-spec-quick-mode.md +45 -0
  259. package/docs/guide/zh/specs/3-add-spec-explore-mode.md +68 -0
  260. package/docs/guide/zh/specs/4-drafts-and-contract-layer.md +81 -0
  261. package/docs/internals/README.md +1 -1
  262. package/docs/internals/adding-a-provider.md +192 -59
  263. package/docs/internals/api-reference.md +130 -21
  264. package/docs/internals/architecture.md +22 -9
  265. package/docs/internals/bundled-framework-build-plan.md +264 -0
  266. package/docs/internals/configuration.md +33 -8
  267. package/docs/internals/global-artifacts-alignment-contract.md +486 -0
  268. package/docs/internals/global-artifacts-relocation-evaluation.md +294 -0
  269. package/docs/internals/operations-runbook.md +16 -5
  270. package/docs/internals/profiles.md +42 -14
  271. package/docs/platforms/macos.md +27 -8
  272. package/docs/platforms/windows.md +20 -6
  273. package/docs/running-pipelines.md +17 -9
  274. package/docs/terminal.md +9 -3
  275. package/docs/tracking-cost.md +17 -11
  276. package/package.json +1 -1
  277. package/server/dist/agent-refine-manager.js +20 -5
  278. package/server/dist/artifact-registry.js +468 -0
  279. package/server/dist/attachment-manager.js +5 -8
  280. package/server/dist/browser-capture-manager.js +4 -4
  281. package/server/dist/bundled-core.js +72 -0
  282. package/server/dist/bundled-openspec.js +58 -0
  283. package/server/dist/chat-manager.js +42 -5
  284. package/server/dist/code-explorer-router.js +10 -7
  285. package/server/dist/config.js +7 -2
  286. package/server/dist/context-budget.js +17 -6
  287. package/server/dist/context-scope.js +6 -2
  288. package/server/dist/contract-refine-runner.js +31 -9
  289. package/server/dist/desktop-router.js +24 -1
  290. package/server/dist/docs-router.js +210 -132
  291. package/server/dist/file-summary-manager.js +41 -16
  292. package/server/dist/framework-manager.js +248 -0
  293. package/server/dist/framework-migration.js +308 -0
  294. package/server/dist/index.js +30 -0
  295. package/server/dist/install-config-path.js +73 -0
  296. package/server/dist/jira/jira-sync-manager.js +23 -11
  297. package/server/dist/openspec-shim.js +153 -0
  298. package/server/dist/plugins-router.js +19 -8
  299. package/server/dist/profiles-router.js +38 -16
  300. package/server/dist/project-registry.js +101 -3
  301. package/server/dist/project-router-chat.js +1 -1
  302. package/server/dist/project-router-helpers.js +25 -12
  303. package/server/dist/project-router-jobs.js +3 -3
  304. package/server/dist/project-router-settings.js +8 -6
  305. package/server/dist/project-router-setup.js +27 -10
  306. package/server/dist/project-router-spending.js +6 -1
  307. package/server/dist/project-router-tickets.js +30 -10
  308. package/server/dist/project-router.js +16 -1
  309. package/server/dist/queue-manager.js +149 -18
  310. package/server/dist/setup-manager.js +131 -29
  311. package/server/dist/smash-runner.js +21 -6
  312. package/server/dist/ticket-store.js +6 -2
  313. package/server/dist/ticket-watcher.js +5 -1
  314. package/server/dist/vitest-setup.js +25 -0
  315. package/server/dist/workspace-manager.js +199 -0
  316. package/server/dist/workspace-resolution.js +147 -0
  317. package/client/dist/assets/index-DgKfQFcf.css +0 -2
  318. package/client/dist/assets/setup-BIIkb-_K.js +0 -1
  319. package/client/dist/assets/setup-BeQxu9kD.js +0 -1
  320. package/client/dist/assets/setup-CPa6GnlI.js +0 -1
  321. package/client/dist/assets/setup-CZl4OEJx.js +0 -1
  322. package/client/dist/assets/setup-ChpodNfn.js +0 -1
  323. package/client/dist/assets/setup-D_fjJH6u.js +0 -1
  324. package/client/dist/assets/setup-YzD8DX4O.js +0 -1
  325. package/client/dist/assets/setup-fRpDozmq.js +0 -1
  326. package/docs/adding-a-provider.md +0 -107
@@ -20,6 +20,7 @@ const jira_materializer_1 = require("./jira-materializer");
20
20
  const jira_issue_fields_1 = require("./jira-issue-fields");
21
21
  const jira_status_resolver_1 = require("./jira-status-resolver");
22
22
  const jira_db_1 = require("./jira-db");
23
+ const workspace_resolution_1 = require("../workspace-resolution");
23
24
  const POLL_INTERVAL_MS = 60_000;
24
25
  const DRAIN_INTERVAL_MS = 10_000;
25
26
  const POLL_OVERLAP_MS = 2 * 60_000;
@@ -54,6 +55,17 @@ class JiraSyncManager {
54
55
  if (opts.startTimers !== false)
55
56
  this.start();
56
57
  }
58
+ /**
59
+ * Relocate-artifacts gate: the directory whose `.specrails/{local-tickets,
60
+ * backlog-config}.json` Jira sync reads/writes. Relocated ⇒ the workspace dir
61
+ * (the path-deriving helpers join `.specrails/...` under it); legacy ⇒
62
+ * project.path (byte-identical for existing projects). All Jira artifact I/O
63
+ * funnels through here.
64
+ */
65
+ _artifactRoot() {
66
+ const exec = (0, workspace_resolution_1.resolveProjectExecution)({ path: this.projectPath });
67
+ return exec.relocated && exec.workspaceDir ? exec.workspaceDir : this.projectPath;
68
+ }
57
69
  /** Suppress the file-watcher echo for a local write we just made. */
58
70
  notifyLocalWrite(revision) {
59
71
  try {
@@ -185,7 +197,7 @@ class JiraSyncManager {
185
197
  if (input.discardStatus !== undefined) {
186
198
  (0, jira_db_1.setDiscardStatus)(this.db, this.projectId, input.discardStatus);
187
199
  }
188
- (0, jira_backlog_config_1.writeJiraBacklogConfig)(this.projectPath);
200
+ (0, jira_backlog_config_1.writeJiraBacklogConfig)(this._artifactRoot());
189
201
  // Discover the sprint custom-field id (best-effort) so sprint capture works
190
202
  // from the first poll. Non-fatal — the poll re-discovers if this fails.
191
203
  try {
@@ -248,11 +260,11 @@ class JiraSyncManager {
248
260
  setEnabled(enabled) {
249
261
  (0, jira_db_1.setConnectionEnabled)(this.db, this.projectId, enabled);
250
262
  if (enabled) {
251
- (0, jira_backlog_config_1.writeJiraBacklogConfig)(this.projectPath);
263
+ (0, jira_backlog_config_1.writeJiraBacklogConfig)(this._artifactRoot());
252
264
  this.start();
253
265
  }
254
266
  else {
255
- (0, jira_backlog_config_1.writeLocalBacklogConfig)(this.projectPath);
267
+ (0, jira_backlog_config_1.writeLocalBacklogConfig)(this._artifactRoot());
256
268
  }
257
269
  }
258
270
  /** Configure (or clear) the status a discarded spec is moved to. */
@@ -289,7 +301,7 @@ class JiraSyncManager {
289
301
  /** Remove the connection entirely and restore local backlog config. */
290
302
  disconnect() {
291
303
  (0, jira_db_1.deleteConnection)(this.db, this.projectId);
292
- (0, jira_backlog_config_1.writeLocalBacklogConfig)(this.projectPath);
304
+ (0, jira_backlog_config_1.writeLocalBacklogConfig)(this._artifactRoot());
293
305
  this.stop();
294
306
  }
295
307
  // ─── Create a spec in Jira (Add Spec when source = Jira) ───────────────────
@@ -323,11 +335,11 @@ class JiraSyncManager {
323
335
  const issue = full.ok
324
336
  ? full.data
325
337
  : { id: created.data.id, key: created.data.key, fields: { summary: input.title, labels: input.labels ?? [] } };
326
- const r = (0, jira_materializer_1.upsertIssuesIntoStore)(this.db, this.projectPath, conn, [issue], new Set());
338
+ const r = (0, jira_materializer_1.upsertIssuesIntoStore)(this.db, this._artifactRoot(), conn, [issue], new Set());
327
339
  if (r.wrote)
328
340
  this.notifyLocalWrite(r.revision);
329
341
  const localId = r.changedLocalIds[0];
330
- const t = readTicket(this.projectPath, localId);
342
+ const t = readTicket(this._artifactRoot(), localId);
331
343
  if (t)
332
344
  this.broadcast({ type: 'ticket_created', ticket: t, projectId: this.projectId, timestamp: t.updated_at });
333
345
  return { ok: true, localId, jiraKey: created.data.key };
@@ -351,7 +363,7 @@ class JiraSyncManager {
351
363
  const client = this.buildClient();
352
364
  if (!client)
353
365
  return { ok: false, error: 'no jira credentials' };
354
- const file = (0, ticket_store_1.resolveTicketStoragePath)(this.projectPath);
366
+ const file = (0, ticket_store_1.resolveTicketStoragePath)(this._artifactRoot());
355
367
  const ticket = (0, ticket_store_1.readStore)(file).tickets[String(localId)];
356
368
  if (!ticket)
357
369
  return { ok: false, error: 'ticket not found' };
@@ -449,7 +461,7 @@ class JiraSyncManager {
449
461
  }
450
462
  const issues = res.data.issues ?? [];
451
463
  if (issues.length > 0) {
452
- const r = (0, jira_materializer_1.upsertIssuesIntoStore)(this.db, this.projectPath, conn, issues, frozen);
464
+ const r = (0, jira_materializer_1.upsertIssuesIntoStore)(this.db, this._artifactRoot(), conn, issues, frozen);
453
465
  // Suppress the watcher echo for our own write (avoids the full-board
454
466
  // refresh flicker). When nothing changed, `wrote` is false and we also
455
467
  // skip the granular broadcasts below — the board stays perfectly still.
@@ -459,7 +471,7 @@ class JiraSyncManager {
459
471
  if (r.maxUpdatedMs > maxUpdated)
460
472
  maxUpdated = r.maxUpdatedMs;
461
473
  for (const localId of r.changedLocalIds) {
462
- const t = readTicket(this.projectPath, localId);
474
+ const t = readTicket(this._artifactRoot(), localId);
463
475
  if (t)
464
476
  this.broadcast({ type: 'ticket_updated', ticket: t, projectId: this.projectId, timestamp: t.updated_at });
465
477
  }
@@ -826,7 +838,7 @@ class JiraSyncManager {
826
838
  if (localIds.length === 0)
827
839
  return;
828
840
  try {
829
- const file = (0, ticket_store_1.resolveTicketStoragePath)(this.projectPath);
841
+ const file = (0, ticket_store_1.resolveTicketStoragePath)(this._artifactRoot());
830
842
  const ids = new Set(localIds.map(String));
831
843
  const now = new Date().toISOString();
832
844
  const store = (0, ticket_store_1.mutateStore)(file, (s) => {
@@ -885,7 +897,7 @@ class JiraSyncManager {
885
897
  const metaRes = await client.getFieldsFull();
886
898
  const fieldMeta = metaRes.ok ? metaRes.data : [];
887
899
  // 3) Materialized ticket flags suppress already-shown info (no extra HTTP).
888
- const ticket = (0, ticket_store_1.readStore)((0, ticket_store_1.resolveTicketStoragePath)(this.projectPath)).tickets[String(localId)];
900
+ const ticket = (0, ticket_store_1.readStore)((0, ticket_store_1.resolveTicketStoragePath)(this._artifactRoot())).tickets[String(localId)];
889
901
  const fields = (0, jira_issue_fields_1.formatIssueFields)({
890
902
  fields: issueRes.data.fields,
891
903
  fieldMeta,
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.OPENSPEC_NPX_SPEC = void 0;
7
+ exports.openspecShimDir = openspecShimDir;
8
+ exports.ensureOpenspecShim = ensureOpenspecShim;
9
+ exports.prependShimToPath = prependShimToPath;
10
+ exports.removeOpenspecShim = removeOpenspecShim;
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const path_1 = __importDefault(require("path"));
14
+ const bundled_openspec_1 = require("./bundled-openspec");
15
+ /**
16
+ * openspec PATH shim — the robustness backstop for relocated Claude rails.
17
+ *
18
+ * THE PROBLEM. When a project's artifacts are RELOCATED to the home workspace,
19
+ * the rail's `claude` child is spawned with `cwd = <workspace>`. The repo's
20
+ * `openspec/` tree (changes, specs, .git) lives in the REPO, not the workspace.
21
+ * specrails-core's stage-3 templates wrap repo-resident `openspec <verb>` calls
22
+ * in `(cd "${SPECRAILS_REPO_DIR:-.}" && openspec …)` so they land in the repo —
23
+ * but a BARE `openspec …` that a skill, sub-agent, or an un-wrapped template
24
+ * line emits would run from the workspace cwd, where there is no OpenSpec
25
+ * project → "Error: not an OpenSpec project".
26
+ *
27
+ * THE FIX. For a relocated rail we materialize a per-job shim directory and
28
+ * PREPEND it to the spawn `PATH`. It contains an `openspec` executable (POSIX
29
+ * `sh` script + a Windows `openspec.cmd`) that unconditionally does:
30
+ *
31
+ * cd "$SPECRAILS_REPO_DIR" && exec <real openspec> "$@"
32
+ *
33
+ * so EVERY `openspec` invocation — wrapped OR bare, skill- OR template-driven —
34
+ * resolves against the repo. The "real openspec" is the bundled openspec
35
+ * (`node <cli>`) when present, else the legacy `npx @fission-ai/openspec@<pin>`.
36
+ *
37
+ * EXISTENCE-GATED. Only injected for a RELOCATED rail. Legacy (in-repo) rails
38
+ * spawn from the repo cwd where a bare `openspec` already resolves correctly, so
39
+ * the shim is never created and `PATH` is byte-identical to today.
40
+ */
41
+ /** Pinned openspec npm spec for the `npx` fallback when no bundled openspec is
42
+ * present. Matches specrails-core's `pinned-versions.json` openspec pin and the
43
+ * desktop OPENSPEC_BUNDLE_VERSION in the release workflow. */
44
+ exports.OPENSPEC_NPX_SPEC = '@fission-ai/openspec@1.4.1';
45
+ /** `~/.specrails/projects/<slug>/openspec-shim/<jobId>` — the per-job shim dir. */
46
+ function openspecShimDir(slug, jobId, home = os_1.default.homedir()) {
47
+ return path_1.default.join(home, '.specrails', 'projects', slug, 'openspec-shim', jobId);
48
+ }
49
+ /**
50
+ * Build the command the shim should `exec` to run the REAL openspec, as an argv
51
+ * array already shell-quoted into a single string. Prefers the bundled openspec
52
+ * (`node <cli>`); falls back to `npx -y <pinned openspec>`.
53
+ */
54
+ function realOpenspecInvocation() {
55
+ const cli = (0, bundled_openspec_1.getBundledOpenspecCli)();
56
+ if (cli) {
57
+ // node "<cli>" — invoke the bundled openspec as a node script (Tauri strips
58
+ // exec bits from bundled resources, so it can't be run as a binary).
59
+ return {
60
+ posix: `node ${shq(cli)}`,
61
+ windows: `node ${winq(cli)}`,
62
+ };
63
+ }
64
+ // Legacy fallback: npx resolves + caches openspec. `-y` skips the install
65
+ // confirmation prompt (rails are headless).
66
+ return {
67
+ posix: `npx -y ${shq(exports.OPENSPEC_NPX_SPEC)}`,
68
+ windows: `npx -y ${winq(exports.OPENSPEC_NPX_SPEC)}`,
69
+ };
70
+ }
71
+ /** POSIX single-quote a string for embedding in an `sh` script. */
72
+ function shq(s) {
73
+ return `'${s.replace(/'/g, `'\\''`)}'`;
74
+ }
75
+ /** Windows double-quote a string for a `.cmd` script. */
76
+ function winq(s) {
77
+ return `"${s.replace(/"/g, '""')}"`;
78
+ }
79
+ /**
80
+ * Materialize the per-job openspec shim directory and return its absolute path
81
+ * (to be PREPENDED to the spawn PATH). Writes both an `openspec` POSIX script
82
+ * (chmod 755 inside a 700 dir) and an `openspec.cmd` for Windows. The shim cd's
83
+ * into `$SPECRAILS_REPO_DIR` (defaulting to `.` so a missing var degrades to the
84
+ * cwd) before exec'ing the real openspec, so the repo's OpenSpec project is
85
+ * always the one operated on.
86
+ *
87
+ * Returns null when the shim could not be created (best-effort — a failure must
88
+ * never block a spawn; the rail still runs, just without the shim's safety net).
89
+ */
90
+ function ensureOpenspecShim(slug, jobId, home = os_1.default.homedir()) {
91
+ try {
92
+ const dir = openspecShimDir(slug, jobId, home);
93
+ fs_1.default.mkdirSync(dir, { recursive: true, mode: 0o700 });
94
+ try {
95
+ fs_1.default.chmodSync(dir, 0o700);
96
+ }
97
+ catch {
98
+ /* chmod may be unsupported (Windows) — non-fatal */
99
+ }
100
+ const { posix, windows } = realOpenspecInvocation();
101
+ // POSIX shim: `openspec` (no extension). `${SPECRAILS_REPO_DIR:-.}` makes a
102
+ // missing var degrade to the current dir instead of an unbound-var error.
103
+ const posixScript = '#!/bin/sh\n' +
104
+ '# Auto-generated by specrails-desktop (relocated-rail openspec shim).\n' +
105
+ '# Re-points every bare `openspec` call at the repo working tree.\n' +
106
+ 'cd "${SPECRAILS_REPO_DIR:-.}" || exit 1\n' +
107
+ `exec ${posix} "$@"\n`;
108
+ const posixPath = path_1.default.join(dir, 'openspec');
109
+ fs_1.default.writeFileSync(posixPath, posixScript, { mode: 0o755 });
110
+ try {
111
+ fs_1.default.chmodSync(posixPath, 0o755);
112
+ }
113
+ catch {
114
+ /* non-fatal */
115
+ }
116
+ // Windows shim: `openspec.cmd`. cmd.exe resolves `openspec` to this on PATH.
117
+ const winScript = '@echo off\r\n' +
118
+ 'rem Auto-generated by specrails-desktop (relocated-rail openspec shim).\r\n' +
119
+ 'if defined SPECRAILS_REPO_DIR cd /d "%SPECRAILS_REPO_DIR%"\r\n' +
120
+ `${windows} %*\r\n`;
121
+ fs_1.default.writeFileSync(path_1.default.join(dir, 'openspec.cmd'), winScript);
122
+ return dir;
123
+ }
124
+ catch {
125
+ return null;
126
+ }
127
+ }
128
+ /**
129
+ * Prepend `shimDir` to a PATH string using the platform delimiter, de-duping a
130
+ * leading entry that already equals `shimDir`. Returns the original `pathValue`
131
+ * unchanged when `shimDir` is null/empty.
132
+ */
133
+ function prependShimToPath(pathValue, shimDir) {
134
+ const base = pathValue ?? '';
135
+ if (!shimDir)
136
+ return base;
137
+ const sep = path_1.default.delimiter;
138
+ const segments = base.split(sep).filter((s) => s.length > 0);
139
+ if (segments[0] === shimDir)
140
+ return base;
141
+ return [shimDir, ...segments].join(sep);
142
+ }
143
+ /** Remove a per-job shim dir (best-effort cleanup). No-op when absent. */
144
+ function removeOpenspecShim(slug, jobId, home = os_1.default.homedir()) {
145
+ try {
146
+ const dir = openspecShimDir(slug, jobId, home);
147
+ if (fs_1.default.existsSync(dir))
148
+ fs_1.default.rmSync(dir, { recursive: true, force: true });
149
+ }
150
+ catch {
151
+ /* best-effort */
152
+ }
153
+ }
@@ -7,6 +7,17 @@ const manager_1 = require("./plugins/manager");
7
7
  const prereq_installer_1 = require("./plugins/prereq-installer");
8
8
  const path_resolver_1 = require("./path-resolver");
9
9
  const claude_approval_1 = require("./plugins/claude-approval");
10
+ const workspace_resolution_1 = require("./workspace-resolution");
11
+ /**
12
+ * Relocate-artifacts gate: the dir the PluginManager mutates (`.mcp.json` +
13
+ * `.specrails/plugins/` state + `.claude/agents/custom-*.md`). Relocated ⇒ the
14
+ * workspace (so the relocated CLI, spawned with cwd=workspace, loads the
15
+ * `.mcp.json` the manager wrote); legacy ⇒ project.path (byte-identical).
16
+ */
17
+ function pluginRoot(project) {
18
+ const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
19
+ return exec.relocated && exec.workspaceDir ? exec.workspaceDir : project.path;
20
+ }
10
21
  function pluginsSectionEnabled() {
11
22
  return process.env.SPECRAILS_PLUGINS_SECTION !== 'false';
12
23
  }
@@ -46,7 +57,7 @@ function createPluginsRouter() {
46
57
  router.get('/', async (req, res) => {
47
58
  try {
48
59
  const { project } = ctx(req);
49
- const list = await (0, manager_1.getPluginManager)().listAvailable(project.path, project.provider);
60
+ const list = await (0, manager_1.getPluginManager)().listAvailable(pluginRoot(project), project.provider);
50
61
  res.json({ plugins: list });
51
62
  }
52
63
  catch (err) {
@@ -57,7 +68,7 @@ function createPluginsRouter() {
57
68
  router.get('/:name/preview-install', async (req, res) => {
58
69
  try {
59
70
  const { project } = ctx(req);
60
- const result = await (0, manager_1.getPluginManager)().previewInstall(project.path, project.id, req.params.name);
71
+ const result = await (0, manager_1.getPluginManager)().previewInstall(pluginRoot(project), project.id, req.params.name);
61
72
  res.json(result);
62
73
  }
63
74
  catch (err) {
@@ -68,7 +79,7 @@ function createPluginsRouter() {
68
79
  router.post('/:name/install', async (req, res) => {
69
80
  try {
70
81
  const { project, broadcast } = ctx(req);
71
- await (0, manager_1.getPluginManager)().install(project.path, project.id, req.params.name, broadcast, project.provider);
82
+ await (0, manager_1.getPluginManager)().install(pluginRoot(project), project.id, req.params.name, broadcast, project.provider);
72
83
  res.status(200).json({ ok: true });
73
84
  }
74
85
  catch (err) {
@@ -79,7 +90,7 @@ function createPluginsRouter() {
79
90
  router.delete('/:name', async (req, res) => {
80
91
  try {
81
92
  const { project, broadcast } = ctx(req);
82
- await (0, manager_1.getPluginManager)().uninstall(project.path, project.id, req.params.name, broadcast, project.provider);
93
+ await (0, manager_1.getPluginManager)().uninstall(pluginRoot(project), project.id, req.params.name, broadcast, project.provider);
83
94
  res.status(200).json({ ok: true });
84
95
  }
85
96
  catch (err) {
@@ -171,7 +182,7 @@ function createPluginsRouter() {
171
182
  router.post('/:name/activate', async (req, res) => {
172
183
  try {
173
184
  const { project, broadcast } = ctx(req);
174
- await (0, manager_1.getPluginManager)().setActive(project.path, project.id, req.params.name, true, broadcast, project.provider);
185
+ await (0, manager_1.getPluginManager)().setActive(pluginRoot(project), project.id, req.params.name, true, broadcast, project.provider);
175
186
  res.json({ ok: true });
176
187
  }
177
188
  catch (err) {
@@ -182,7 +193,7 @@ function createPluginsRouter() {
182
193
  router.post('/:name/deactivate', async (req, res) => {
183
194
  try {
184
195
  const { project, broadcast } = ctx(req);
185
- await (0, manager_1.getPluginManager)().setActive(project.path, project.id, req.params.name, false, broadcast, project.provider);
196
+ await (0, manager_1.getPluginManager)().setActive(pluginRoot(project), project.id, req.params.name, false, broadcast, project.provider);
186
197
  res.json({ ok: true });
187
198
  }
188
199
  catch (err) {
@@ -193,7 +204,7 @@ function createPluginsRouter() {
193
204
  router.post('/:name/update', async (req, res) => {
194
205
  try {
195
206
  const { project, broadcast } = ctx(req);
196
- await (0, manager_1.getPluginManager)().updateMcpEntry(project.path, project.id, req.params.name, broadcast, project.provider);
207
+ await (0, manager_1.getPluginManager)().updateMcpEntry(pluginRoot(project), project.id, req.params.name, broadcast, project.provider);
197
208
  res.json({ ok: true });
198
209
  }
199
210
  catch (err) {
@@ -204,7 +215,7 @@ function createPluginsRouter() {
204
215
  router.get('/:name/health', async (req, res) => {
205
216
  try {
206
217
  const { project, broadcast } = ctx(req);
207
- const result = await (0, manager_1.getPluginManager)().verify(project.path, project.id, req.params.name, broadcast);
218
+ const result = await (0, manager_1.getPluginManager)().verify(pluginRoot(project), project.id, req.params.name, broadcast);
208
219
  res.json(result);
209
220
  }
210
221
  catch (err) {
@@ -12,6 +12,27 @@ const agent_refine_db_1 = require("./agent-refine-db");
12
12
  const agent_refine_manager_1 = require("./agent-refine-manager");
13
13
  const providers_1 = require("./providers");
14
14
  const profile_manager_1 = require("./profile-manager");
15
+ const workspace_resolution_1 = require("./workspace-resolution");
16
+ /**
17
+ * Relocate-artifacts gate: the dir whose `.specrails/{profiles,specrails-version}`
18
+ * AND whose `.claude/agents/**` catalog the profiles surface reads/writes.
19
+ * Relocated ⇒ the workspace dir (where core assembled the agents catalog +
20
+ * `.specrails/profiles`); legacy ⇒ project.path (byte-identical to today).
21
+ *
22
+ * The `.claude/agents/**` catalog MUST follow this same root: when relocated,
23
+ * core materializes the agents into `<workspace>/.claude/agents`, so a profiles
24
+ * catalog read/write rooted at `project.path` would (a) read a stale/absent repo
25
+ * copy and (b) write custom-* agents into the repo — violating repo-immutability
26
+ * and never reaching the workspace the rails actually load from.
27
+ */
28
+ function specRoot(project) {
29
+ const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
30
+ return exec.relocated && exec.workspaceDir ? exec.workspaceDir : project.path;
31
+ }
32
+ /** The agents-catalog directory for the relocate-aware root (`<root>/.claude/agents`). */
33
+ function agentsCatalogDir(project) {
34
+ return path_1.default.join(specRoot(project), '.claude', 'agents');
35
+ }
15
36
  const AGENTS_SECTION_ENABLED = process.env.SPECRAILS_AGENTS_SECTION !== 'false';
16
37
  function handleError(res, err) {
17
38
  if (err instanceof profile_manager_1.ProfileValidationError) {
@@ -48,7 +69,7 @@ function createProfilesRouter() {
48
69
  router.post('/migrate-from-settings', (req, res) => {
49
70
  try {
50
71
  const { project, broadcast } = ctx(req);
51
- const agentsDir = path_1.default.join(project.path, '.claude', 'agents');
72
+ const agentsDir = agentsCatalogDir(project);
52
73
  if (!fs_1.default.existsSync(agentsDir)) {
53
74
  res.status(400).json({ error: 'no .claude/agents/ directory found' });
54
75
  return;
@@ -129,7 +150,7 @@ function createProfilesRouter() {
129
150
  ],
130
151
  };
131
152
  try {
132
- (0, profile_manager_1.createProfile)(project.path, profile, provider);
153
+ (0, profile_manager_1.createProfile)(specRoot(project), profile, provider);
133
154
  }
134
155
  catch (err) {
135
156
  if (err instanceof profile_manager_1.ProfileConflictError) {
@@ -189,8 +210,9 @@ function createProfilesRouter() {
189
210
  router.get('/core-version', (req, res) => {
190
211
  try {
191
212
  const { project } = ctx(req);
213
+ const root = specRoot(project);
192
214
  const candidates = [
193
- path_1.default.join(project.path, '.specrails', 'specrails-version'),
215
+ path_1.default.join(root, '.specrails', 'specrails-version'),
194
216
  path_1.default.join(project.path, '.specrails-version'),
195
217
  ];
196
218
  let version = null;
@@ -230,7 +252,7 @@ function createProfilesRouter() {
230
252
  router.get('/catalog', (req, res) => {
231
253
  try {
232
254
  const { project } = ctx(req);
233
- const dir = path_1.default.join(project.path, '.claude', 'agents');
255
+ const dir = agentsCatalogDir(project);
234
256
  if (!fs_1.default.existsSync(dir)) {
235
257
  res.json({ agents: [] });
236
258
  return;
@@ -305,7 +327,7 @@ function createProfilesRouter() {
305
327
  res.status(400).json({ error: 'invalid agent id' });
306
328
  return;
307
329
  }
308
- const file = path_1.default.join(project.path, '.claude', 'agents', `${agentId}.md`);
330
+ const file = path_1.default.join(agentsCatalogDir(project), `${agentId}.md`);
309
331
  if (!fs_1.default.existsSync(file)) {
310
332
  res.status(404).json({ error: 'agent not found' });
311
333
  return;
@@ -333,7 +355,7 @@ function createProfilesRouter() {
333
355
  res.status(400).json({ error: 'body is required' });
334
356
  return;
335
357
  }
336
- const agentsDir = path_1.default.join(project.path, '.claude', 'agents');
358
+ const agentsDir = agentsCatalogDir(project);
337
359
  fs_1.default.mkdirSync(agentsDir, { recursive: true });
338
360
  const file = path_1.default.join(agentsDir, `${id}.md`);
339
361
  if (fs_1.default.existsSync(file)) {
@@ -366,7 +388,7 @@ function createProfilesRouter() {
366
388
  res.status(400).json({ error: 'body is required' });
367
389
  return;
368
390
  }
369
- const file = path_1.default.join(project.path, '.claude', 'agents', `${agentId}.md`);
391
+ const file = path_1.default.join(agentsCatalogDir(project), `${agentId}.md`);
370
392
  if (!fs_1.default.existsSync(file)) {
371
393
  res.status(404).json({ error: 'agent not found' });
372
394
  return;
@@ -394,7 +416,7 @@ function createProfilesRouter() {
394
416
  res.status(403).json({ error: 'only custom-* agents can be deleted' });
395
417
  return;
396
418
  }
397
- const file = path_1.default.join(project.path, '.claude', 'agents', `${agentId}.md`);
419
+ const file = path_1.default.join(agentsCatalogDir(project), `${agentId}.md`);
398
420
  if (!fs_1.default.existsSync(file)) {
399
421
  res.status(404).json({ error: 'agent not found' });
400
422
  return;
@@ -647,7 +669,7 @@ function createProfilesRouter() {
647
669
  router.get('/', (req, res) => {
648
670
  try {
649
671
  const { project } = ctx(req);
650
- res.json({ profiles: (0, profile_manager_1.listProfiles)(project.path) });
672
+ res.json({ profiles: (0, profile_manager_1.listProfiles)(specRoot(project)) });
651
673
  }
652
674
  catch (err) {
653
675
  handleError(res, err);
@@ -658,7 +680,7 @@ function createProfilesRouter() {
658
680
  try {
659
681
  const { project } = ctx(req);
660
682
  const explicit = typeof req.query.profile === 'string' ? req.query.profile : undefined;
661
- const resolved = (0, profile_manager_1.resolveProfile)(project.path, explicit, project.provider ?? 'claude');
683
+ const resolved = (0, profile_manager_1.resolveProfile)(specRoot(project), explicit, project.provider ?? 'claude');
662
684
  if (!resolved) {
663
685
  res.json({ resolved: null });
664
686
  return;
@@ -674,7 +696,7 @@ function createProfilesRouter() {
674
696
  try {
675
697
  const { project, broadcast } = ctx(req);
676
698
  const body = req.body;
677
- (0, profile_manager_1.createProfile)(project.path, body, project.provider ?? 'claude');
699
+ (0, profile_manager_1.createProfile)(specRoot(project), body, project.provider ?? 'claude');
678
700
  broadcast({ type: 'profile.changed', projectId: project.id, name: body.name });
679
701
  res.status(201).json({ profile: body });
680
702
  }
@@ -691,7 +713,7 @@ function createProfilesRouter() {
691
713
  res.status(400).json({ error: "body field 'name' is required" });
692
714
  return;
693
715
  }
694
- const copy = (0, profile_manager_1.duplicateProfile)(project.path, req.params.name, newName, project.provider ?? 'claude');
716
+ const copy = (0, profile_manager_1.duplicateProfile)(specRoot(project), req.params.name, newName, project.provider ?? 'claude');
695
717
  broadcast({ type: 'profile.changed', projectId: project.id, name: newName });
696
718
  res.status(201).json({ profile: copy });
697
719
  }
@@ -708,7 +730,7 @@ function createProfilesRouter() {
708
730
  res.status(400).json({ error: "body field 'name' is required" });
709
731
  return;
710
732
  }
711
- const renamed = (0, profile_manager_1.renameProfile)(project.path, req.params.name, newName, project.provider ?? 'claude');
733
+ const renamed = (0, profile_manager_1.renameProfile)(specRoot(project), req.params.name, newName, project.provider ?? 'claude');
712
734
  broadcast({ type: 'profile.changed', projectId: project.id, name: newName });
713
735
  res.json({ profile: renamed });
714
736
  }
@@ -720,7 +742,7 @@ function createProfilesRouter() {
720
742
  router.get('/:name', (req, res) => {
721
743
  try {
722
744
  const { project } = ctx(req);
723
- res.json({ profile: (0, profile_manager_1.getProfile)(project.path, req.params.name, project.provider ?? 'claude') });
745
+ res.json({ profile: (0, profile_manager_1.getProfile)(specRoot(project), req.params.name, project.provider ?? 'claude') });
724
746
  }
725
747
  catch (err) {
726
748
  handleError(res, err);
@@ -735,7 +757,7 @@ function createProfilesRouter() {
735
757
  res.status(400).json({ error: "body.name must match path parameter (use /rename to change name)" });
736
758
  return;
737
759
  }
738
- (0, profile_manager_1.updateProfile)(project.path, body, project.provider ?? 'claude');
760
+ (0, profile_manager_1.updateProfile)(specRoot(project), body, project.provider ?? 'claude');
739
761
  broadcast({ type: 'profile.changed', projectId: project.id, name: body.name });
740
762
  res.json({ profile: body });
741
763
  }
@@ -747,7 +769,7 @@ function createProfilesRouter() {
747
769
  router.delete('/:name', (req, res) => {
748
770
  try {
749
771
  const { project, broadcast } = ctx(req);
750
- (0, profile_manager_1.deleteProfile)(project.path, req.params.name);
772
+ (0, profile_manager_1.deleteProfile)(specRoot(project), req.params.name);
751
773
  broadcast({ type: 'profile.changed', projectId: project.id, name: req.params.name, deleted: true });
752
774
  res.json({ ok: true });
753
775
  }