specrails-desktop 2.7.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 (329) 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 +39 -14
  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/providers/gemini-adapter.js +4 -0
  310. package/server/dist/providers/gemini-agent-ack.js +65 -0
  311. package/server/dist/queue-manager.js +156 -12
  312. package/server/dist/setup-manager.js +131 -29
  313. package/server/dist/smash-runner.js +21 -6
  314. package/server/dist/ticket-store.js +6 -2
  315. package/server/dist/ticket-watcher.js +5 -1
  316. package/server/dist/util/stream-display.js +18 -3
  317. package/server/dist/vitest-setup.js +25 -0
  318. package/server/dist/workspace-manager.js +199 -0
  319. package/server/dist/workspace-resolution.js +147 -0
  320. package/client/dist/assets/index-DgKfQFcf.css +0 -2
  321. package/client/dist/assets/setup-BIIkb-_K.js +0 -1
  322. package/client/dist/assets/setup-BeQxu9kD.js +0 -1
  323. package/client/dist/assets/setup-CPa6GnlI.js +0 -1
  324. package/client/dist/assets/setup-CZl4OEJx.js +0 -1
  325. package/client/dist/assets/setup-ChpodNfn.js +0 -1
  326. package/client/dist/assets/setup-D_fjJH6u.js +0 -1
  327. package/client/dist/assets/setup-YzD8DX4O.js +0 -1
  328. package/client/dist/assets/setup-fRpDozmq.js +0 -1
  329. package/docs/adding-a-provider.md +0 -107
@@ -0,0 +1,486 @@
1
+ # Contrato de alineación specrails-core ↔ specrails-desktop: relocalización de artefactos a `$HOME`
2
+
3
+ > Documento de arquitectura. Audiencia: product owner + mantenedores de **specrails-core** y **specrails-desktop**.
4
+ > Antecedentes técnicos completos: [`global-artifacts-relocation-evaluation.md`](./global-artifacts-relocation-evaluation.md).
5
+ > Evolución posterior (framework bundleado): [`bundled-framework-build-plan.md`](./bundled-framework-build-plan.md).
6
+ > Estado: **IMPLEMENTADO** en la rama `feat/relocate-artifacts-to-home` (ambos repos). Las secciones 2-10 describen el DISEÑO original; la **Reconciliación as-built** (abajo) registra los deltas entre el diseño y lo construido — donde difieran, **gana el as-built**.
7
+
8
+ ---
9
+
10
+ ## As-built reconciliation (post-implementación)
11
+
12
+ Lo construido sigue el contrato salvo estos deltas, descubiertos durante implementación/validación (incl. PoC + capstone 9/9 con un rail real):
13
+
14
+ 1. **El modelo de install evolucionó de "core copia el framework por proyecto" a "framework bundleado, instalado una vez, enlazado por symlink"** (decisión posterior del owner; ver el build-plan). El *framework* provider-invariante (agents `sr-*`, commands, skills, rules, instrucciones) se materializa **una sola vez** en `~/.specrails/framework/<version>/<provider>/` (bundleado dentro del `.dmg`/`.exe` como los runtimes) y cada workspace lo **symlinkea** vía `framework/current`; un app-update re-materializa + hace **swap atómico de `current`** → actualiza todos los workspaces. **Se elimina el `npx specrails-core init` por proyecto** (alta de proyecto offline e instantánea). Las §3/§5 ("core instala en el workspace") siguen siendo correctas para la CAPA PROYECTO; la CAPA FRAMEWORK es ahora compartida. Lo que hace viable la copia compartida: los `${SPECRAILS_REPO_DIR:-.}` en los prompts (§5.3) re-apuntan la I/O al repo **en runtime**, así un fichero de framework idéntico sirve a todos.
15
+
16
+ 2. **codex: NO se hace override de `CODEX_HOME` en los rails.** El §6.1 recomendaba conservar `CODEX_HOME` per-project para rails — **incorrecto**: el PoC demostró que `CODEX_HOME` es todo-o-nada e incluye `auth.json` → 401. Los rails de codex usan **cwd-discovery** (igual que claude), sin override de `CODEX_HOME`. El `CODEX_HOME` per-project se conserva SOLO para el registro MCP del plugin (`codex mcp add`, invocación aparte).
17
+
18
+ 3. **El gate de activación es de DOS partes** (`server/workspace-resolution.ts` `resolveProjectExecution`): "relocated" = existe entrada en el registry **Y** el workspace está poblado (`<workspace>/.specrails/specrails-version`). Un proyecto in-repo existente sin workspace poblado → **legacy** (cwd=project.path, byte-idéntico). Esto hace toda la activación **regression-safe**: solo proyectos realmente reubicados cambian.
19
+
20
+ 4. **Slug inmutable + divergencia standalone↔desktop**: el `slug`+`workspaceDir` de una entrada son **inmutables una vez creada** (`buildMirroredEntry`). Para un repo init'd standalone-luego-importado, el slug del registry (de core) puede diferir del `desktop.sqlite` slug; **el slug del registry es autoritativo para la ubicación de artefactos**. Desktop SIEMPRE resuelve el workspace desde la **entrada del registry** (`resolveArtifacts`), nunca lo computa desde el slug de `desktop.sqlite`.
21
+
22
+ 5. **Red de seguridad de tests**: `resolveHome()` honra `SPECRAILS_REGISTRY_HOME` (core y desktop) y un `vitest-setup.ts` (setupFiles, ambos repos) lo fija a un tmp, de modo que **ningún test escribe el `~/.specrails/registry.json` real** (se cazó y arregló un leak de 115 entradas).
23
+
24
+ 6. **Definición vs configuración de agentes**: las DEFINICIONES (`sr-*.md`) son framework (compartidas, symlink por-fichero para que los `custom-*.md` convivan); la CONFIGURACIÓN (modelos/routing = *profiles*) es per-proyecto en `<workspace>/.specrails/profiles/`, seleccionable por rail. Se siembra un perfil **"balance"** por defecto. `agent-memory/` es siempre dir REAL; los ficheros de instrucción (`CLAUDE/AGENTS/GEMINI.md`) se siembran per-workspace (llevan el nombre de proyecto), los settings provider-invariantes se enlazan.
25
+
26
+ 7. **openspec offline (fase 8)**: `@fission-ai/openspec` se pinea a **1.4.1** y se bundlea en el app; el alta de proyecto pasa a ser **totalmente offline** (era la última llamada de red). Sigue siendo repo-resident (escribe `openspec/**` en el repo). Fallback a `npx` cuando no hay bundle.
27
+
28
+ 8. **Validación**: capstone 9/9 (sr-developer real desde cwd=workspace generó código en el repo, openspec en el repo, workspace limpio) + PoC de discovery por symlink (agents/commands/skills). El bundling Tauri/CI (core+framework+openspec) replica el patrón de runtimes verbatim pero **solo se valida con un build real** del `.dmg`/`.exe`.
29
+
30
+ ---
31
+
32
+ ## 0. Decisiones bloqueadas (entrada del product owner)
33
+
34
+ Estas cuatro decisiones están **cerradas** y son la premisa de todo el documento:
35
+
36
+ 1. **specrails-core relocaliza SIEMPRE.** Incluye el `npx specrails-core init` que un desarrollador ejecuta en su propio repo, sin desktop presente. Efecto neto: specrails-core **nunca** vuelve a dejar artefactos dentro del repo objetivo. El comportamiento legacy "por defecto = dentro del repo" queda **reemplazado**.
37
+ 2. **La fuente de verdad compartida es un fichero inspeccionable**, `$HOME/.specrails/registry.json`, versionado por esquema. specrails-desktop lo **escribe**; specrails-core lo **lee** (y escribe/asigna una entrada cuando corre standalone sin desktop). Mapea cada repo a la ubicación de sus artefactos.
38
+ 3. **Los activos de equipo versionables** (profiles, `.claude/agents/custom-*.md`, entradas `.mcp.json` de plugins) **se mueven a `$HOME` ahora.** El affordance git de "exportar al repo" se **difiere** a un cambio posterior.
39
+ 4. **Carve-outs que SE QUEDAN en el repo** (lo único que puede permanecer): `openspec/**` (entregable de spec versionado, escrito por el binario externo `@fission-ai/openspec` apuntando a `repoRoot`) y los **git worktrees** (deben compartir el `.git`/object-store del repo). Todo lo demás vive bajo `$HOME`.
40
+
41
+ ---
42
+
43
+ ## 1. Resumen ejecutivo
44
+
45
+ specrails-core resuelve hoy un único `repoRoot = path.resolve(flags['root-dir'] ?? process.cwd())` y lo usa como base para **todos** los joins de `.specrails/.claude/.codex/.gemini`, tanto en el instalador como en el texto de los prompts de los agentes en runtime. El nuevo modelo **divide ese root en dos**: `codeRoot` (= el repo, conserva solo `openspec/**` y los worktrees) y `artifactRoot` (un directorio bajo `$HOME/.specrails/projects/<slug>/workspace` que contiene **todo lo demás**). La indirección que conecta ambos lados es `$HOME/.specrails/registry.json`, un fichero inspeccionable versionado por esquema que **mapea la realpath canónica del repo → la ubicación de sus artefactos**; specrails-desktop es el escritor primario (proyección de su `desktop.sqlite`), specrails-core es lector y, en modo standalone, asigna su propia entrada. El re-apuntado en runtime replica un precedente **ya en producción** — `SPECRAILS_PROFILE_PATH` se lee primero y cae al path repo-relativo solo si está sin definir (`implement.md:70-91`) — generalizándolo a un conjunto de variables de entorno (`SPECRAILS_TICKETS_PATH`, `SPECRAILS_BACKLOG_CONFIG_PATH`, `SPECRAILS_STATE_DIR`, `SPECRAILS_REPO_DIR`, `SPECRAILS_WORKSPACE_DIR`) inyectadas en el spawn, con el patrón `${ENV:-legacy}` de modo que **"sin definir ⇒ comportamiento legacy byte-idéntico"**. La propiedad de corrección dominante: tras un ciclo completo de setup + job con relocalización activa, el working tree del repo del usuario queda **byte-inalterado** salvo `openspec/**` y los worktrees gitignored. El único riesgo de fallo silencioso es un literal `.specrails/...` o un `path.join(repoRoot, …)` no migrado, que se mitiga con un test de inmutabilidad del repo y un lint de literales sobre `templates/**`.
46
+
47
+ ---
48
+
49
+ ## 2. El contrato compartido: `$HOME/.specrails/registry.json`
50
+
51
+ Esta es la pieza central. `registry.json` es la **única fuente de verdad inspeccionable** que mapea la ruta canónica de un repo a la ubicación `$HOME` de los artefactos relocalizados de ese repo. Existe porque la auditoría de runtime confirmó **cero** lecturas de `registry.json` / `SPECRAILS_STATE_DIR` / `SPECRAILS_WORKSPACE_DIR` en core hoy: la única lectura `$HOME` que existe es `doctor.ts` apendando a `~/.specrails/doctor.log`. Es por tanto código net-new del lado de core (un resolver) y plumbing net-new del lado de desktop (proyección desde la DB).
52
+
53
+ ### 2.1 Esquema final
54
+
55
+ **Nivel superior** — `projects` es un **objeto JSON keyado por la ruta canónica del repo**, no un array. Justificación: (a) la unicidad de la clave es estructural en JSON (dos escritores concurrentes no pueden apendar entradas duplicadas para el mismo repo); (b) lookup O(1); (c) replica el precedente ya existente `~/.gemini/acknowledgments/agents.json` (keyado por `repoRoot`); (d) es amigable a merge atómico (un escritor muta exactamente una clave y reescribe).
56
+
57
+ | Campo (top-level) | Tipo | Notas |
58
+ |---|---|---|
59
+ | `schemaVersion` | integer | Empieza en `1`. Un lector que ve un `schemaVersion` mayor del que entiende DEBE tratar todas las entradas como ausentes (fallback legacy), nunca malparsear. |
60
+ | `generator` | string | `"specrails-desktop@2.8.0"` / `"specrails-core@4.9.0"` del último escritor. No load-bearing. |
61
+ | `updatedAt` | ISO-8601 | Timestamp de la última escritura del fichero. |
62
+ | `projects` | object | Mapa: ruta-canónica-repo → `ProjectEntry`. |
63
+
64
+ **`ProjectEntry`** (todos los paths son **absolutos** y en separador nativo de la plataforma; los consumidores los tratan como opacos y nunca re-derivan):
65
+
66
+ | Campo | Load-bearing | Notas |
67
+ |---|---|---|
68
+ | `repoPath` | sí | La realpath canónica. Espejo de la clave del mapa (value-iteration self-describing). |
69
+ | `slug` | sí | El slug compartido. DEBE igualar `desktop.sqlite projects.slug` para el mismo repo. Algoritmo en §2.4. |
70
+ | `workspaceDir` | sí | `~/.specrails/projects/<slug>/workspace` — raíz `$HOME` del proyecto. Todo lo demás cuelga de aquí. |
71
+ | `artifactRoot` | sí | El dir que core trata como raíz `.specrails`/install en lugar de `repoRoot`. = `<workspaceDir>` (ver §4 layout). |
72
+ | `codeRoot` | sí | Siempre el repo (`= repoPath`). Lleva los carve-outs `openspec/**` + worktrees. Se inyecta como `SPECRAILS_REPO_DIR`. |
73
+ | `stateDir` | sí | Base de estado runtime (`agent-memory`, `pipeline-state`, `health-history`, `compat-snapshots`, `backlog-cache.json`, `.dry-run`). Inyectado como `SPECRAILS_STATE_DIR`. |
74
+ | `ticketsPath` | sí | Absoluto al `local-tickets.json` relocalizado. Inyectado como `SPECRAILS_TICKETS_PATH`. |
75
+ | `backlogConfigPath` | sí | Absoluto al `backlog-config.json` (el switch read-only de Jira). Inyectado como `SPECRAILS_BACKLOG_CONFIG_PATH`. **Crítico**: si core no lo encuentra, dispara su rama "default write_access=true" y re-entra a su rama de escritura, rompiendo el contrato read-only de Jira. |
76
+ | `profilesDir` | sí | `.specrails/profiles/` relocalizado. El snapshot por job sigue ganando vía `SPECRAILS_PROFILE_PATH`; este es el fallback standalone. |
77
+ | `pluginsStateDir` | desktop-only | `.specrails/plugins/`. Core ignora plugins; presente por completitud/inspeccionabilidad. |
78
+ | `fileSummariesDir` | desktop-only | `.specrails/file-summaries/`. Propiedad de desktop (Code explorer). |
79
+ | `providers` | sí | `["claude","codex","gemini"]`. Espejo de `desktop.sqlite projects.providers`. Permite a core detectar el provider desde el registry en vez de sondear `.claude`/`.codex`/`.gemini` bajo el repo (ahora vacío). |
80
+ | `primaryProvider` | sí | `providers[0]`. Espejo de `desktop.sqlite projects.provider`. |
81
+ | `coreVersion` | sí | El pin `specrails-version` (nombre y formato **congelados** — desktop lo regex-matchea; solo su ubicación se mueve). |
82
+ | `createdAt` | no | Primera asignación de la entrada. |
83
+ | `lastInstallAt` | no | Última finalización de `init`/`update`. |
84
+ | `source` | sí | `"desktop"` \| `"core-standalone"`. Codifica el dueño único-en-cada-momento; gobierna la reconciliación (§5). |
85
+ | `desktopProjectId` | opcional | El `projects.id` UUID de desktop cuando `source:"desktop"`, para re-link robusto de mudanza de repo (§3). Aditivo; lectores antiguos lo ignoran. |
86
+
87
+ **Por qué se almacenan los sub-paths derivados explícitamente** en lugar de derivarlos de `artifactRoot`: core y desktop deben coincidir byte-a-byte en estos paths o una escritura y una lectura caen en sitios distintos. Escribiendo la ruta resuelta en el fichero, **el layout del escritor gana y el lector es layout-agnóstico** — exactamente la propiedad de robustez del snapshot `SPECRAILS_PROFILE_PATH`.
88
+
89
+ ### 2.2 Ejemplo concreto
90
+
91
+ ```json
92
+ {
93
+ "schemaVersion": 1,
94
+ "generator": "specrails-desktop@2.8.0",
95
+ "updatedAt": "2026-06-18T10:42:11.004Z",
96
+ "projects": {
97
+ "/Users/javi/repos/acme-api": {
98
+ "repoPath": "/Users/javi/repos/acme-api",
99
+ "slug": "acme-api",
100
+ "workspaceDir": "/Users/javi/.specrails/projects/acme-api/workspace",
101
+ "artifactRoot": "/Users/javi/.specrails/projects/acme-api/workspace",
102
+ "codeRoot": "/Users/javi/repos/acme-api",
103
+ "stateDir": "/Users/javi/.specrails/projects/acme-api/workspace/.claude",
104
+ "ticketsPath": "/Users/javi/.specrails/projects/acme-api/workspace/.specrails/local-tickets.json",
105
+ "backlogConfigPath": "/Users/javi/.specrails/projects/acme-api/workspace/.specrails/backlog-config.json",
106
+ "profilesDir": "/Users/javi/.specrails/projects/acme-api/workspace/.specrails/profiles",
107
+ "pluginsStateDir": "/Users/javi/.specrails/projects/acme-api/workspace/.specrails/plugins",
108
+ "fileSummariesDir": "/Users/javi/.specrails/projects/acme-api/workspace/.specrails/file-summaries",
109
+ "providers": ["claude", "codex"],
110
+ "primaryProvider": "claude",
111
+ "coreVersion": "4.9.0",
112
+ "createdAt": "2026-05-02T08:15:00.000Z",
113
+ "lastInstallAt": "2026-06-18T10:42:11.000Z",
114
+ "source": "desktop",
115
+ "desktopProjectId": "b1f0…"
116
+ },
117
+ "/Users/javi/work/acme-web/services/acme-api": {
118
+ "repoPath": "/Users/javi/work/acme-web/services/acme-api",
119
+ "slug": "acme-api-2",
120
+ "workspaceDir": "/Users/javi/.specrails/projects/acme-api-2/workspace",
121
+ "artifactRoot": "/Users/javi/.specrails/projects/acme-api-2/workspace",
122
+ "codeRoot": "/Users/javi/work/acme-web/services/acme-api",
123
+ "stateDir": "/Users/javi/.specrails/projects/acme-api-2/workspace/.claude",
124
+ "ticketsPath": "/Users/javi/.specrails/projects/acme-api-2/workspace/.specrails/local-tickets.json",
125
+ "backlogConfigPath": "/Users/javi/.specrails/projects/acme-api-2/workspace/.specrails/backlog-config.json",
126
+ "profilesDir": "/Users/javi/.specrails/projects/acme-api-2/workspace/.specrails/profiles",
127
+ "providers": ["claude"],
128
+ "primaryProvider": "claude",
129
+ "coreVersion": "4.9.0",
130
+ "createdAt": "2026-06-10T12:00:00.000Z",
131
+ "lastInstallAt": "2026-06-10T12:03:30.000Z",
132
+ "source": "core-standalone"
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ Las dos entradas son dos repos distintos que comparten el basename `acme-api` — el segundo recibió el sufijo de dedup `-2` (§2.4, edge case "mismo basename"). El primero lo creó desktop; el segundo, un `npx specrails-core init` standalone.
139
+
140
+ ### 2.3 Keying: la realpath canónica
141
+
142
+ La **clave es la realpath canónica** del repo, calculada idénticamente en ambas herramientas:
143
+
144
+ ```
145
+ abs = path.resolve(repoPathInput) // relativo → absoluto contra cwd
146
+ canon = realpathSafe(abs) // fs.realpathSync; si lanza, cae a abs
147
+ key = normalizeKey(canon) // §6: case-fold en macOS/Windows, conserva forma almacenada
148
+ ```
149
+
150
+ desktop ya hace exactamente esto en `desktop-router.ts:canonicalizePath` (`fs.realpathSync`, fallback al path sin resolver si lanza). core ya resuelve `path.resolve(flags['root-dir'] ?? process.cwd())`. Los **symlinks colapsan al target real**, de modo que añadir `/Users/javi/link-to-acme` y `/Users/javi/repos/acme` (mismo target) mapea a una sola entrada.
151
+
152
+ ### 2.4 Regla de acuerdo de slug (DEBE coincidir en ambas herramientas)
153
+
154
+ Hoy desktop deriva el slug del **nombre** del proyecto (`slugify(derivedName)`, `derivedName` por defecto = `path.basename(canonicalPath)`), **sin sufijo de dedup**, confiando en el `UNIQUE(slug)` de SQLite para hacer 409 ante colisión. Eso es insuficiente para un fichero compartido escrito por dos procesos independientes. El contrato fija un algoritmo **determinista, derivado del basename, con sufijo de dedup `-N`** que ambas herramientas implementan idénticamente:
155
+
156
+ ```
157
+ function allocateSlug(canonicalRepoPath, existingSlugs):
158
+ base = slugify(basename(canonicalRepoPath)) // mismo slugify que desktop-router.ts:24
159
+ if base == "": base = "project" // guard basename todo-símbolos
160
+ if base not in existingSlugs: return base
161
+ n = 2
162
+ while (base + "-" + n) in existingSlugs: n += 1
163
+ return base + "-" + n
164
+ ```
165
+
166
+ - `slugify` es la función **existente** de desktop, byte-a-byte: `toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-|-$/g,'')` (`desktop-router.ts:24-26`). core la copia textualmente — es pequeña y estable; duplicarla es el patrón ya sancionado en el código (igual que `THEME_ID_ALLOWLIST` / `LANGUAGE_ID_ALLOWLIST`).
167
+ - `existingSlugs` = unión de (a) todo slug ya en `registry.json` y (b) — cuando desktop es el asignador — todo slug en `desktop.sqlite` (para que un registry momentáneamente atrasado respecto a la DB no reuse un slug vivo). core-standalone usa solo `registry.json`.
168
+ - **Derivar del basename, no del nombre**: hace que el slug sea una función determinista de la clave, así dos herramientas que ven el mismo repo calculan el mismo `base` y solo divergen en el sufijo `-N`, que el read-before-allocate resuelve.
169
+
170
+ **Cómo la SEGUNDA herramienta reusa el slug de la PRIMERA (read-before-allocate):** la asignación NUNCA ocurre sin leer primero el fichero bajo el lock y comprobar si la clave canónica ya tiene entrada. Si la tiene, se devuelve el `slug` existente sin recalcular. Una vez que *cualquiera* de las dos ha escrito una entrada, el slug queda **congelado para siempre** para ese repo. El loop de dedup solo corre para una clave genuinamente nueva, y corre bajo el lock para que dos asignadores concurrentes no reclamen ambos `acme-api-2`.
171
+
172
+ ### 2.5 Regla de escritura atómica + lock
173
+
174
+ - **Exclusión mutua = lock-file advisory** `registry.json.lock` adquirido por creación exclusiva atómica (`fs.openSync(..., 'wx')`) con spin-retry acotado (p.ej. 50ms × hasta ~2s) y **ruptura de lock obsoleto** (si el mtime del lock supera un TTL, p.ej. 30s, se reclama — cubre un escritor crasheado). Ambas herramientas usan el mismo path y protocolo.
175
+ - **Toda escritura es temp-en-mismo-dir + fsync + rename atómico**, así un lector nunca ve un fichero a medio escribir **aun sin el lock** — el lock solo serializa *escritores*, el rename garantiza que los *lectores* ven un fichero completo viejo-o-nuevo.
176
+ - **Los lectores read-only NUNCA toman el lock.** Las lecturas de runtime de core (resolver `SPECRAILS_TICKETS_PATH` etc.) y el lado lector de desktop son lookups puros. Solo `init`/`update` (que crean/mutan el install) y el writer de proyección de desktop toman el lock.
177
+
178
+ ---
179
+
180
+ ## 3. Algoritmo de resolución repo → artefactos
181
+
182
+ Una única rutina compartida, `resolveArtifacts(repoPathInput, opts)`, que **ambas** herramientas ejecutan. core la implementa en un módulo nuevo `src/installer/util/registry.ts` que alimenta tanto los joins del instalador como las env-vars inyectadas en los prompts; desktop implementa la mitad escritora desde su DB (§6).
183
+
184
+ ```
185
+ resolveArtifacts(repoPathInput, { allocate, allocator /* "desktop" | "core-standalone" */ }):
186
+ # 1. Canonicalizar idénticamente en ambas herramientas.
187
+ abs = path.resolve(repoPathInput)
188
+ canon = realpathSafe(abs) # fs.realpathSync; cae a abs si lanza
189
+ key = normalizeKey(canon) # §6: case-fold por plataforma
190
+
191
+ registryPath = path.join(HOME, ".specrails", "registry.json")
192
+
193
+ # 2. Fast-path sin lock: lectura pura para un lookup.
194
+ reg = readRegistryOrEmpty(registryPath) # parse total; {} si falta/corrupto/schema-mayor
195
+ entry = reg.projects[key]
196
+ if entry: # ya asignada → reusar SIEMPRE (read y allocate)
197
+ return entryToResolution(entry)
198
+
199
+ if not allocate:
200
+ # Lector sin entrada: fallback al layout legacy in-repo (artifactRoot = codeRoot).
201
+ # Red de seguridad del core-version-gate / primer arranque.
202
+ return legacyResolution(canon)
203
+
204
+ # 3. Path de asignación: lock advisory, re-leer, doble-check, escribir atómico.
205
+ withFileLock(registryPath + ".lock"):
206
+ reg = readRegistryOrEmpty(registryPath)
207
+ entry = reg.projects[key]
208
+ if entry: # alguien asignó entre el paso 2 y el lock
209
+ return entryToResolution(entry)
210
+ slug = allocateSlug(canon, unionOfSlugs(reg, allocator))
211
+ entry = buildEntry(key, canon, slug, allocator) # rellena cada campo §2.1
212
+ reg.projects[key] = entry
213
+ reg.schemaVersion = 1
214
+ reg.updatedAt = now()
215
+ atomicWrite(registryPath, reg) # temp en mismo dir + fsync + rename
216
+ return entryToResolution(entry)
217
+ ```
218
+
219
+ Notas:
220
+ - **`readRegistryOrEmpty` es total y fail-open** — fichero ausente, error de parse, o `schemaVersion` mayor del entendido devuelven `{}`, así el caller lo trata como "sin entrada". Un core standalone contra un registry escrito por un desktop futuro NO debe crashear; asigna una entrada fresca que sí entiende (peor caso: un duplicado transitorio que desktop reconcilia). Esto **sesga hacia disponibilidad sobre consistencia estricta**, correcto para un fichero local inspeccionable.
221
+ - `legacyResolution(canon)` devuelve `artifactRoot = codeRoot = canon`, `ticketsPath = canon/.specrails/local-tickets.json`, etc. — el comportamiento exacto pre-relocalización. Es lo que hace cierto "sin definir ⇒ byte-idéntico legacy" y es el fallback cuando un desktop nuevo habla con un core viejo (y viceversa).
222
+
223
+ ### 3.1 Repo movido / renombrado (la clave es el path → entrada huérfana)
224
+
225
+ Mover el repo en disco **huerfaniza** la entrada: una resolución para el path nuevo no encuentra nada y asignaría una entrada fresca (slug nuevo, workspace vacío), perdiendo silenciosamente el install antiguo.
226
+
227
+ - **desktop es la autoridad para el re-link** porque ya aprende el path nuevo (el usuario re-añade, o `GET /api/resolve?path=` resuelve un repo movido, o un affordance "este proyecto se movió"). Cuando desktop observa que un repo que rastrea (mismo `projects.id` en `desktop.sqlite`) tiene ahora un path canónico distinto, **reescribe la clave del registry in-place**: borra la clave vieja, inserta la misma entrada bajo la clave nueva con `repoPath`/`codeRoot` actualizados, **slug y `workspaceDir` sin cambios** (los artefactos `$HOME` no se mueven, solo el puntero al repo). Esto mantiene estable `~/.specrails/projects/<slug>/`, esencial porque `jobs.sqlite`/telemetría/etc. ya van keyados por slug. El `desktopProjectId` opcional permite hacer este match sin adivinar.
228
+ - **core-standalone no puede detectar fiablemente una mudanza** (no tiene id de proyecto persistente, solo el path). Para standalone, un repo movido simplemente asigna entrada nueva; la vieja se GC como obsoleta (§5). Aceptable porque standalone no tiene historial de jobs keyado por slug que preservar.
229
+
230
+ ---
231
+
232
+ ## 4. Layout final bajo `$HOME/.specrails/projects/<slug>/`
233
+
234
+ ```
235
+ $HOME/.specrails/
236
+ ├── registry.json # CONTRATO COMPARTIDO (desktop escribe, core lee/asigna)
237
+ ├── registry.json.lock # lock advisory (creación 'wx' + TTL de obsolescencia)
238
+ ├── desktop.sqlite # registro de proyectos de desktop (CANÓNICO, desktop-only)
239
+ ├── jira-secret.key # keyfile AES-256-GCM (0600), desktop-only
240
+ ├── doctor.log # log append-only, core-only (única lectura $HOME de core hoy)
241
+ └── projects/<slug>/
242
+ ├── workspace/ # = artifactRoot. cwd de spawn de todas las AI-CLI
243
+ │ ├── .specrails/
244
+ │ │ ├── local-tickets.json (+ .lock) ← SPECRAILS_TICKETS_PATH [core escribe/lee · desktop escribe]
245
+ │ │ ├── backlog-config.json ← SPECRAILS_BACKLOG_CONFIG_PATH [desktop escribe · core lee]
246
+ │ │ ├── specrails-version (nombre CONGELADO) [core]
247
+ │ │ ├── specrails-manifest.json [core]
248
+ │ │ ├── install-config.yaml [desktop escribe · core lee]
249
+ │ │ ├── setup-templates/** [core]
250
+ │ │ ├── profiles/*.json (+ .user-preferred.json) ← SPECRAILS_PROFILES_DIR [desktop · CARVE-OUT reservado]
251
+ │ │ ├── plugins/{state.json,snapshots/} [desktop-only]
252
+ │ │ └── file-summaries/** [desktop-only]
253
+ │ ├── .claude/ ← SPECRAILS_STATE_DIR (base estado runtime)
254
+ │ │ ├── agents/{sr-*.md, custom-<plugin>.md} [core sr-* · desktop/plugins custom-* · CARVE-OUT]
255
+ │ │ ├── commands/{sr,specrails}/** [core]
256
+ │ │ ├── skills/** [core]
257
+ │ │ ├── rules/** [core]
258
+ │ │ ├── agent-memory/** [core runtime]
259
+ │ │ ├── pipeline-state/** [core runtime]
260
+ │ │ ├── health-history/**, compat-snapshots/** [core runtime]
261
+ │ │ └── backlog-cache.json, .dry-run [core runtime]
262
+ │ ├── .codex/config.toml [core]
263
+ │ ├── .gemini/{settings.json,agents/,commands/} [core]
264
+ │ ├── CLAUDE.md / AGENTS.md / GEMINI.md (instrucción framework — autodescubierta por cwd) [core]
265
+ │ ├── .mcp.json [desktop/plugins · CARVE-OUT reservado]
266
+ │ └── project -> <project.path> (symlink/junction; project-path.txt fallback)
267
+ ├── jobs.sqlite [desktop-only]
268
+ ├── jobs/<jobId>/{profile.json, plugins.json} (snapshots chmod 400, YA $HOME) [desktop-only]
269
+ ├── codex-home/ (CODEX_HOME per-proyecto) [desktop-only]
270
+ ├── user-mcp.json (chmod 600) [desktop-only]
271
+ ├── telemetry/<jobId>.ndjson.gz [desktop-only]
272
+ └── terminals/<sessionId>/ (shims shell-integration) [desktop-only]
273
+ ```
274
+
275
+ **Carve-outs residentes en el repo (`codeRoot = project.path`):**
276
+
277
+ ```
278
+ <project.path>/
279
+ ├── openspec/ # entregable de spec versionado, escrito por @fission-ai/openspec [CARVE-OUT #1]
280
+ │ ├── changes/**
281
+ │ └── specs/**
282
+ ├── .git/ # object-store compartido por los worktrees
283
+ └── .claude/worktrees/** # git worktrees (deben compartir .git) [CARVE-OUT #2]
284
+ ```
285
+
286
+ > **Regla general de propiedad de path.** Todo lo que es un **artefacto propiedad de Specrails** (`.specrails/**`, `.claude/{agents,commands,skills,rules,agent-memory,…}/**`, `.codex/**`, `.gemini/**`, `CLAUDE.md`/`AGENTS.md`/`GEMINI.md` de framework, `.mcp.json`) → **workspace**. Todo lo que es **el código fuente del usuario, git, coverage, o los registros de aprobación/MCP propios del usuario** → **se queda en `project.path`**.
287
+
288
+ ---
289
+
290
+ ## 5. Cambios en specrails-core
291
+
292
+ ### 5.1 El split `codeRoot` vs `artifactRoot`
293
+
294
+ `ScaffoldInput` / `BuildManifestInput` ganan `artifactRoot`. Se recomienda **renombrar `repoRoot` → `codeRoot`** para forzar al compilador a marcar cada callsite no migrado. La resolución se hace una vez, al inicio de `runInit`/`runUpdate`/`runDoctor`, y el par `{ codeRoot, artifactRoot }` se hila por todo.
295
+
296
+ **Callsites del instalador que mueven base a `artifactRoot`** (grupos A–I de `scaffold.ts` ~40 sitios + `manifest.ts` + `update.ts` + `doctor.ts`):
297
+
298
+ - **A** — skeleton del provider dir (`scaffold.ts:251-267`).
299
+ - **B** — skeleton `.specrails/setup-templates/**` (`:268-275`).
300
+ - **C** — copia de templates + bundled commands (`:283-301`, `:368-438`); los `scriptDir` (paquete npx) **no cambian**.
301
+ - **D** — `placeQuickTierArtefacts` (`:836-1020`), incluidos los mkdir de `agent-memory`. Los **valores de placeholder** (`MEMORY_PATH`, `SECURITY_EXEMPTIONS_PATH`, `PERSONA_DIR`) se escriben *dentro* del prompt y se tratan en §5.3.
302
+ - **E** — `placeSkills` (`:1134-1210`).
303
+ - **F** — `placeGeminiAgents`/`writeGeminiAgentFromTemplate` (`:591-657`). **Especial**: `writeGeminiAgentAcknowledgments` (`:676-699`) keya `~/.gemini/acknowledgments/agents.json` en el repoRoot y hashea `<root>/.gemini/agents/<id>.md` — **tanto la clave como el path hasheado pasan a `artifactRoot`** (re-keying, no relocaliza el ack file global).
304
+ - **G** — settings codex/gemini + ficheros de instrucción (`applyCodexSettings :1038-1072`, `applyGeminiSettings :723-757`); `AGENTS.md`/`GEMINI.md` → `artifactRoot` (la CLI los autodescubre desde el cwd = workspace).
305
+ - **H** — manifest (`manifest.ts:78-79`): `specrails-manifest.json` + `specrails-version` → `artifactRoot`. **Nombres congelados** (desktop regex-matchea `specrails-version`), solo cambia el dir base. Los logs `path.relative(repoRoot, …)` → `path.relative(artifactRoot, …)`.
306
+ - **I** — `.gitignore` + prune (§5.4).
307
+ - **`detectExistingSetup`** (`:224-235`): los sondeos de provider-dir → `artifactRoot`; el sondeo `openspec` → **`codeRoot`** (carve-out).
308
+
309
+ ### 5.2 El carve-out (lo que conserva `codeRoot`)
310
+
311
+ Explícitamente **se queda en `codeRoot`**:
312
+ 1. **Invocación openspec** (`init.ts:193-233`): `openspec init --tools <provider> <repoRoot>` con `cwd: repoRoot` — **sin cambios**. openspec escribe `openspec/**` en el repo (entregable intencionado). El residuo de dirs de comandos que openspec deja en el repo (p.ej. `.claude/commands/opsx`) es residuo conocido y aceptado — openspec es un binario externo que no modificamos.
313
+ 2. **Lecturas runtime de `openspec/changes/**` y `openspec/specs/**`** en prompts: cuando el cwd del rail es el workspace, los paths relativos `openspec/...` se reescriben como `${SPECRAILS_REPO_DIR:-.}/openspec/...` para resolver contra el repo.
314
+ 3. **git worktrees**: las ops usan `git -C "$SPECRAILS_REPO_DIR"` para compartir el object-store.
315
+ 4. **`CLAUDE.md` por-capa del usuario** (`{{LAYER_CLAUDE_MD_PATHS}}`, `sr-developer.md:83`) y scans de "Project README / CLAUDE.md" (`explore-spec.md:25`, `doctor.md:14`) — resuelven contra `${SPECRAILS_REPO_DIR:-.}`. **Hay que desambiguarlos** del `CLAUDE.md`/`AGENTS.md`/`GEMINI.md` de framework que la CLI autodescubre desde el cwd = workspace.
316
+
317
+ ### 5.3 Re-apuntado de lecturas en runtime — inyección de env-vars (RECOMENDADO sobre que los agentes lean `registry.json`)
318
+
319
+ **Decisión: inyectar env-vars en el spawn; NO hacer que los prompts markdown parseen `registry.json`.** Razones: (a) hay precedente ya en producción — `SPECRAILS_PROFILE_PATH` se lee **primero**, fallback repo-relativo **después** (`implement.md:70-91`); (b) pedir a un prompt en bash que parsee JSON, canonicalice su cwd, sha256ee e indexe es frágil e inverificable en ~8 templates; (c) el spawner (desktop, o un shim de core-standalone) ya conoce el workspace; (d) mantiene el legacy byte-idéntico (**sin definir ⇒ fallback repo-relativo**).
320
+
321
+ **Conjunto de env-vars (todas con default = fallback legacy):**
322
+
323
+ | Env var | Apunta a | Reemplaza el literal | Read-first / fallback |
324
+ |---|---|---|---|
325
+ | `SPECRAILS_TICKETS_PATH` | `<artifactRoot>/.specrails/local-tickets.json` | `.specrails/local-tickets.json` | env → repo-relativo |
326
+ | `SPECRAILS_BACKLOG_CONFIG_PATH` | `<artifactRoot>/.specrails/backlog-config.json` | `.specrails/backlog-config.json` | env → repo-relativo |
327
+ | `SPECRAILS_PROFILES_DIR` | `<artifactRoot>/.specrails/profiles/` | `.specrails/profiles/` | `SPECRAILS_PROFILE_PATH` (snapshot job, gana) → env → repo-relativo |
328
+ | `SPECRAILS_STATE_DIR` | `<artifactRoot>/.claude` (o `<artifactRoot>/state`) | `.claude/{agent-memory,pipeline-state,health-history,compat-snapshots,backlog-cache.json,.dry-run}` | env → `.claude` |
329
+ | `SPECRAILS_REPO_DIR` | `<codeRoot>` | `openspec/**`, `CLAUDE.md` del usuario, git/worktree | env → `.` (cwd) |
330
+ | `SPECRAILS_WORKSPACE_DIR` | `<artifactRoot>` (base instalador; también cwd de rails) | n/a (flag instalador) | flag/env → registry → asignar |
331
+
332
+ **La edición de mayor palanca** es la **tabla de placeholders `BACKLOG_*` de `enrich.md`** (`:501,848,1232-1239`) — es el template que GENERA el texto de file-ops inline de cada comando. Re-apuntar ahí los literales de tickets/backlog-config a `${SPECRAILS_TICKETS_PATH:-.specrails/local-tickets.json}` y `${SPECRAILS_BACKLOG_CONFIG_PATH:-.specrails/backlog-config.json}` **una vez** y todos los comandos downstream lo heredan. Luego, barrido exhaustivo de los literales restantes.
333
+
334
+ **Must-not-miss callsites** (un solo literal no migrado lee silenciosamente de la ubicación vacía equivocada y rompe el pipeline sin error):
335
+
336
+ - `local-tickets.json`: `implement.md:33,316,500,1212`; `propose-spec`/`get-backlog-specs`/`explore-spec`/`enrich`/`auto-propose`; rails `sr-architect`/`sr-developer`/`sr-reviewer`/`sr-product-*` en `templates/agents/*` + sus mirrors `templates/codex-skills/rails/*/SKILL.md`. **El `.lock` sigue la misma base.** Las líneas guard `Do NOT update .specrails/local-tickets.json` también deben referenciar el path resuelto o el guard deja de matchear el fichero real.
337
+ - `backlog-config.json`: `implement.md:29`; `enrich.md:160,280,502,851,1018,1260`; `propose-spec.md:50,52`; `auto-propose:70,199,265`. **Peligro**: la rama "default write_access=true cuando ausente" (`propose-spec.md:52`) debe disparar solo cuando **ambos** —el path resuelto por env Y el repo-relativo— estén ausentes; un env-var definido con fichero faltante significa "aún no escrito", no "default a write".
338
+ - `profiles/project-default.json`: `implement.md:71,90-91`; `batch-implement.md:37,194-195`. Ya env-first vía `SPECRAILS_PROFILE_PATH`; añadir un **escalón intermedio** `${SPECRAILS_PROFILES_DIR:-.specrails/profiles}/project-default.json`.
339
+ - **Templates lectoras puras** (rompen en silencio si solo el escritor relocaliza): `why.md:17` y `memory-inspect.md:43` (leen `agent-memory`), `retry.md:41,68` (lee `pipeline-state` que escribe `implement.md`), `compat-check.md` (baseline), `backlog-cache.json` (`get-backlog-specs.md:213` escribe, `implement.md:332,1210` lee+diff). Todas → `${SPECRAILS_STATE_DIR:-.claude}/…`.
340
+ - `health-history/` (`health-check.md`, `telemetry.md`, `vpc-drift.md`), `compat-snapshots/` (`compat-check.md`), dirs de marcador `.dry-run` → `${SPECRAILS_STATE_DIR:-.claude}/…`.
341
+
342
+ **Convergencia dual para standalone (sin spawner vivo):** además del `${ENV:-…}` en los templates, `scaffold.ts` sustituye los **paths absolutos resueltos** dentro de los cuerpos de prompt generados en tiempo de install vía placeholders nuevos `{{TICKETS_PATH}}`/`{{STATE_DIR}}`/`{{REPO_DIR}}`/`{{PROFILES_DIR}}`/`{{BACKLOG_CONFIG_PATH}}` — así los agentes colocados llevan paths concretos aun sin env en runtime; los snapshots por-job de desktop siguen ganando. Net: dos mecanismos independientes convergen (sustitución en install + `${ENV:-…}` en runtime).
343
+
344
+ ### 5.4 `.gitignore` + `pruneLegacyArtifacts`
345
+
346
+ - **`ensureGitignore` (`:277-280`) pasa a no-op cuando `artifactRoot != codeRoot`** — nada propiedad de Specrails aterriza en el repo, no hay nada que ignorar. Guard: `if (artifactRoot === codeRoot) ensureGitignore(...)`.
347
+ - **`pruneLegacyArtifacts` (`:301,784-814`)** opera sobre `artifactRoot` y gana una **aserción dura: todo target de prune está dentro de `artifactRoot`/`$HOME`**; cualquier target que resolvería dentro de `codeRoot` se **salta**. Prune se vuelve un no-op estructural contra el repo del usuario. **Nunca `rmSync` dentro de `codeRoot`.**
348
+ - **Barrido in-repo no destructivo (opt-in):** `reportInRepoArtifacts(codeRoot)` **detecta** (no borra) artefactos in-repo preexistentes e imprime un aviso. La eliminación es opt-in tras un flag `--clean-repo` en `doctor`/`init`/`update`. `openspec/**` y worktrees **nunca** se listan para limpieza.
349
+
350
+ ### 5.5 UX standalone (discoverability sin pointer en el repo)
351
+
352
+ 1. **`init` imprime el workspace resuelto** encima del sentinel congelado `init complete` (que **no cambia** — desktop lo matchea byte-a-byte): `Installed specrails for <repo> → <artifactRoot>` + `Registry: ~/.specrails/registry.json`.
353
+ 2. **`doctor` resuelve vía registry** y sondea provider-dir/agents/instrucción bajo `artifactRoot`; imprime `Workspace:` y `Code root:`. El check git sigue en `codeRoot`. Nuevo `doctor --where` imprime solo el workspace para scripting (`cd "$(specrails-core doctor --where)"`).
354
+ 3. **`resolveExistingProvider`/`resolveInstalledProvider`** (`update.ts:196-211`, `doctor.ts:159-163`) detectan provider desde `artifactRoot` o, mejor, **leen el provider del manifest/registry** (más robusto).
355
+
356
+ ### 5.6 Esfuerzo
357
+
358
+ Medio-alto y mecánicamente uniforme pero **exhaustivo**: el módulo `path-resolver` (net-new), el split `codeRoot/artifactRoot` en ~40 callsites del instalador, el barrido de ~8 templates de runtime + sus mirrors. El riesgo está concentrado en la exhaustividad, no en la dificultad conceptual — de ahí los tests guard (§9).
359
+
360
+ ---
361
+
362
+ ## 6. Cambios y contratos en specrails-desktop
363
+
364
+ El lado desktop ya tiene el precedente load-bearing en producción (`explore-cwd-manager.ts`), el árbol `$HOME` por-proyecto, el patrón env-pointer `SPECRAILS_PROFILE_PATH`/`SPECRAILS_PLUGINS_*`, y el registro slug↔repoPath en `desktop.sqlite`. El trabajo es **generalizar el explore-cwd en un `WorkspaceManager` universal**, **re-apuntar cada `path.join(project.path, '.specrails'|'.claude'|…)` al workspace**, y **escribir `registry.json`**.
365
+
366
+ ### 6.1 cwd de spawn + env por provider
367
+
368
+ `ExploreCwdManager` se generaliza a un `WorkspaceManager` que materializa `~/.specrails/projects/<slug>/workspace/` con `./project -> <project.path>` (symlink/junction, fallback `project-path.txt` — la lógica existente `ensureProjectLink`). **Todas las AI-CLI mueven cwd al workspace.**
369
+
370
+ **Managers que MUEVEN cwd → workspace:** `QueueManager._startJob` (rails — el grande), `ChatManager` (Explore + sidebar), `AgentRefineManager` (ai-edit), `ContractRefineRunner`, `project-router /tickets/generate-spec` (quick-spec), `FileSummaryManager`, `SetupManager.startInstall` (corre `npx specrails-core init --root-dir <project.path> --workspace-dir <workspace>`).
371
+
372
+ **Managers que CONSERVAN cwd = `project.path`:** `TerminalManager` (shell repo-bound), `file-provenance.ts` (git repo-bound), `code-explorer-router` (lee fuente), lecturas de fuente de `FileSummaryManager`, `metrics.ts` (coverage).
373
+
374
+ **El split crítico de corrección:** `QueueManager` parte su `_cwd` único en `_workspace` (cwd de spawn) y `_codeRoot` (= `project.path`). Las llamadas de provenance/git (`queue-manager.ts:1169,1180` que hoy pasan `this._cwd`) **deben re-apuntarse a `_codeRoot`, NO al workspace** — si no, snapshotean un workspace vacío y rompen la atribución "touched by AI" del Code explorer **sin error**.
375
+
376
+ **Recipe de spawn por provider (cwd=workspace):**
377
+ - **claude**: autodescubre `.claude/{agents,commands,skills}` + `CLAUDE.md` + `.mcp.json` desde cwd — **sin flags nuevos en el happy path**. Añade `--add-dir <workspace>/project` para que las tools file alcancen el repo por path absoluto. Conserva `--setting-sources project,local`.
378
+ - **codex**: conserva `CODEX_HOME=~/.specrails/projects/<slug>/codex-home` (ya $HOME). Añade `-c project_root_markers=[]` (defensivo) para que no camine ancestros a un `AGENTS.md` repo-side.
379
+ - **gemini**: inyecta `GEMINI_CLI_TRUST_WORKSPACE=true` (ancla el root confiable al workspace). **Re-keya** `~/.gemini/acknowledgments/agents.json` al workspace.
380
+ - Los tres: el workspace **estrictamente fuera del repo** y el discovery no debe seguir `./project` (mitiga la fuga por ancestor-walk de codex/gemini).
381
+ - **Caso especial Explore `mcp=true`**: hoy `chat-manager.ts:326` conmuta cwd a `project.path` para cargar el `.mcp.json` del repo — tras relocalizar, `.mcp.json` está en el workspace, así que este special-case se **ELIMINA**, no se re-apunta.
382
+
383
+ ### 6.2 Sitios de re-apuntado de path (`project.path` → workspace)
384
+
385
+ `resolveArtifactRoot(slug) → <workspace>`; `project.path` sigue siendo `codeRoot`. Sitios: `ticket-store.ts:126` (+ el guard de traversal de `integration-contract.json` cuya raíz pasa al workspace), `ticket-watcher.ts:47`, `jira-materializer.ts:204,209,237`, `jira-backlog-config.ts:21`, `profile-manager.ts:93` (+ `projectSupportsProfiles` `queue-manager.ts:66-69`), `plugins/paths.ts` (+ `plugin-manager.ts`, `claude-md-mutation.ts:50`), `file-summary-manager.ts:111,119` (el append `.gitignore :160` pasa a **no-op**, las lecturas de fuente `:200,309,459` **se quedan** en `project.path`), `context-scope.ts:199`, `setup-manager.ts:137,164-165,248-250`, `desktop-router.ts:82-83` (sonda workspace **Y** project.path por migración), `profiles-router.ts`, `project-router-helpers.ts`. **Conservan `project.path`:** `plugins/claude-approval.ts` (registro de aprobación del usuario, keyado en el repo) y `metrics.ts` (coverage).
386
+
387
+ ### 6.3 Protocolo `desktop.sqlite` ↔ `registry.json`
388
+
389
+ **`desktop.sqlite` es CANÓNICO; `registry.json` es una proyección slim, write-mostly que desktop emite desde su DB.** Nunca pueden divergir.
390
+
391
+ - **desktop → registry.json (escritura):** nuevo `server/registry-mirror.ts` con `upsertRegistryEntry(project)` / `removeRegistryEntry(repoRoot)`, llamado en `addProject` (tras insert OK), `ProjectRegistry.removeProject` (tras delete + `removeExploreCwd`), cambio de provider/path. **Reconciliación al arranque:** el constructor de `ProjectRegistry` reescribe `registry.json` completo desde `desktop.sqlite` — así un hand-edit o una escritura parcial crasheada se auto-curan. desktop **nunca** lee sus propios datos de vuelta del registry.
392
+ - **core ← registry.json (lectura), con un carve-out de escritura:** core lee para cada resolución install-time y runtime. core solo ESCRIBE cuando es el *asignador* (standalone `init` sin entrada previa, `source:"core-standalone"`) o al refrescar `coreVersion`/`providers`/`lastInstallAt` de la entrada que está instalando. core **NO** debe reescribir `slug`/`key`/`workspaceDir`/path-fields de una entrada existente.
393
+ - **Standalone-luego-importado (caso anti-divergencia):** cuando desktop importa después un repo que core ya asignó standalone, desktop encuentra la entrada `source:"core-standalone"` por clave canónica, **adopta su slug** (lo escribe en `desktop.sqlite` en vez de asignar uno nuevo), voltea `source:"desktop"`, adjunta `desktopProjectId`, y desde ahí lo posee. El slug nunca cambia; el `~/.specrails/projects/<slug>/` que el install standalone pobló se reusa intacto.
394
+
395
+ La regla que evita divergencia: **para cualquier repo, exactamente un escritor es el dueño en cada momento** — desktop una vez conoce el proyecto, core solo mientras un repo standalone es desconocido para desktop. `source` codifica la propiedad; la adopción es un flip de un solo sentido core→desktop. Dos desktops en máquinas distintas nunca comparten `$HOME` ⇒ no hay divergencia cross-máquina.
396
+
397
+ ### 6.4 Precedencia env-pointer en el spawn
398
+
399
+ A nivel de rail, desktop inyecta `SPECRAILS_WORKSPACE_DIR`, `SPECRAILS_STATE_DIR`, `SPECRAILS_TICKETS_PATH`, `SPECRAILS_BACKLOG_CONFIG_PATH`, `SPECRAILS_REPO_DIR` (= `project.path`), junto a los ya existentes `SPECRAILS_PROFILE_PATH`/`SPECRAILS_PLUGINS_*`/OTEL. Esto hace `registry.json` el **fallback** del core standalone (sin env) y las env-vars el **fast path** de los spawns de desktop — no pueden discrepar porque desktop deriva el env del mismo workspace que escribió en `registry.json`.
400
+
401
+ ### 6.5 file-provenance / code-explorer (sigue repo-bound)
402
+
403
+ `file-provenance.ts` (que ya despoja `GIT_DIR`/`GIT_WORK_TREE` y fija git a su cwd pasado) **no necesita cambios** — solo el argumento del caller en `QueueManager` (`this._cwd` → `_codeRoot`). Lecturas de fuente de `code-explorer-router` y de `FileSummaryManager` se quedan en `project.path`; los **outputs** de summary relocalizan.
404
+
405
+ ---
406
+
407
+ ## 7. Contratos que cambian
408
+
409
+ | Contrato | Estado hoy | Cambio requerido | Riesgo |
410
+ |---|---|---|---|
411
+ | **Jira "ZERO core changes"** (CLAUDE.md / `jira-backlog-config.ts`) | "Desktop es la capa de sync; core intacto" apoyado en `local-tickets.json`+`backlog-config.json` repo-relativos que core lee de un path fijo del repo. | **RE-ENUNCIADO**: "core lee el `local-tickets.json`/`backlog-config.json` relocalizado vía `SPECRAILS_TICKETS_PATH`/`SPECRAILS_BACKLOG_CONFIG_PATH` (env-first) o `registry.json` (fallback). `write_access:false` sigue forzando la rama read-only; el outbox + `applyJobOutcomeToTickets` de desktop siguen siendo la única autoridad de estado." Ya **no** es "zero core changes". | **ALTO** — si el config relocalizado no se encuentra, core dispara su default `write_access=true` y empieza a mutar, defaiteando el contrato read-only. Mitigación: el pointer debe resolver **antes** de que dispare el default; desktop **siempre** inyecta `SPECRAILS_BACKLOG_CONFIG_PATH`; test de integración que asserta que un proyecto Jira nunca re-entra a la rama de escritura. |
412
+ | **`local-tickets.json` ubicación** | `<repo>/.specrails/local-tickets.json` (+ `.lock`), leído por todos los rails + comandos. | Base → `<workspace>/.specrails/`. Todo literal en prompts → `${SPECRAILS_TICKETS_PATH:-…}`; el `.lock` y las líneas guard siguen la misma base. | **ALTO** — un literal no migrado lee de ubicación vacía y rompe el pipeline en silencio. |
413
+ | **`backlog-config.json` ubicación** | `<repo>/.specrails/backlog-config.json`, leído por core (switch read-only). | Base → `<workspace>/.specrails/`; resolución env → registry → repo-relativo. Default-write solo si **ambos** paths ausentes. | **ALTO** — ver fila Jira. |
414
+ | **Reserved-paths** (`.specrails/profiles/`, `.claude/agents/custom-*`) | "Activos de equipo commiteables; core nunca los toca en `init`/`update`." | Semántica persiste **relativa a `artifactRoot`** (workspace), no al repo. Actualizar el wording de CLAUDE.md. | **MEDIO** (producto) — profiles/file-summaries/`.mcp.json` **pierden** la propiedad de "activo de equipo commiteable"; git-export diferido (decisión #3, aceptada). |
415
+ | **`integration-contract.json`** | `schemaVersion "3.0"`; `configSchema.file` y `ticketProvider.storagePath` repo-relativos. | **Bump `"3.0" → "4.0"`** (breaking). Bloque nuevo `artifactLocation` (registry path/schema, `--workspace-dir`/env, contrato de env runtime, `repoResident` carve-outs). `storagePath` ahora artifactRoot-relativo; el guard de traversal de desktop usa la raíz workspace. | **MEDIO** — consumidores que hardcodearon paths `.specrails/...` repo-relativos deben actualizar. |
416
+ | **Core-version gate** (NUEVO, load-bearing) | No existe. | desktop exige un core que soporte `--workspace-dir` + env-pointers (espejo del gate `>= 4.1.0` de profiles, `projectSupportsProfiles`). Contra core viejo: fallback a cwd=`project.path`, sin `--workspace-dir` ni env (legacy byte-idéntico) + banner de upgrade. | **ALTO** — sin el gate, desktop nuevo + core viejo vuelca artefactos en el repo (el core viejo ignora el flag desconocido). |
417
+ | **Mobile wire** (`specrailshub`, `hub.*`, `hubInstanceId`, `hub_daily_budget_exceeded`) | Contrato congelado consumido por la app móvil v1. | **NINGUNO — confirmado no afectado.** El wire móvil codifica strings de identidad/wire, **no** paths de fichero de artefactos. | **NULO.** |
418
+ | **WS/REST público** | Endpoints devuelven `project.path` (raíz del repo); ZIP de diagnóstico y árbol `code` devuelven paths relativos a `project.path` (fuente). | **NINGUNO.** El repo sigue en `project.path`; ningún API público devuelve paths `.specrails`-relativos. El único consumidor interno de paths de artefacto es desktop. | **BAJO.** |
419
+ | **`SPECRAILS_PROFILE_PATH` / snapshots plugins** | Snapshot job en `~/.specrails/projects/<slug>/jobs/<jobId>/` (ya $HOME, chmod 400). | **CONFIRMADO NO AFECTADO** más allá de que el `profilesDir`/`pluginsDir` *fuente* (catálogos, no snapshots) se mueva al workspace. Es el template del patrón env-pointer nuevo. | **NULO.** |
420
+
421
+ ---
422
+
423
+ ## 8. Carve-outs y por qué
424
+
425
+ **`openspec/**` se queda en el repo** porque es el **entregable de spec versionado** del proyecto, escrito por el binario externo `@fission-ai/openspec` invocado como `openspec init --tools <provider> <repoRoot>` con `cwd: repoRoot`. No modificamos openspec; está diseñado para escribir un árbol de trabajo repo-relativo (`openspec/changes/**`, `openspec/specs/**`) que se commitea como cualquier otro código. Relocalizarlo rompería su contrato externo y separaría el spec de su historial git. Las lecturas runtime de los rails se resuelven contra `${SPECRAILS_REPO_DIR:-.}/openspec/...`.
426
+
427
+ **Los git worktrees se quedan en el repo** porque **deben compartir el `.git`/object-store** del repo para que el merge-back funcione. Un worktree creado fuera del object-store del repo no puede mergear de vuelta — corrompería silenciosamente los merges de rails multi-feature. Las ops de worktree usan `git -C "$SPECRAILS_REPO_DIR" worktree …` de modo que, aunque el cwd de spawn sea el workspace, git opere contra el `.git` real.
428
+
429
+ Estos dos son los **únicos** residentes en el repo permitidos. Todo lo demás —artefactos de framework, estado runtime, tickets, config, profiles, plugins, summaries, ficheros de instrucción— vive bajo `$HOME` (decisión #4).
430
+
431
+ ---
432
+
433
+ ## 9. Plan de migración + gate de versión
434
+
435
+ ### 9.1 Migración de installs in-repo existentes (no destructiva, copy-never-move)
436
+
437
+ Los installs existentes tienen artefactos en `codeRoot/.specrails`, `codeRoot/<providerDir>`, etc. La migración **honra "nunca modificar el repo"**:
438
+
439
+ 1. En el **próximo `init`/`update`** (core) o al **cargar el registry / primer job tras upgrade** (desktop): resolver/asignar la entrada del registry.
440
+ 2. **Copiar (nunca mover)** el subconjunto propiedad-de-app que el scaffold NO regenera: `local-tickets.json`, `backlog-config.json`, `profiles/*.json` + `.user-preferred.json`, `plugins/state.json`, `file-summaries/`, `install-config.yaml`. core ofrece `migrateInRepoArtifacts(codeRoot, artifactRoot)` idempotente (salta ficheros ya presentes en `artifactRoot`); desktop ofrece `migrateProjectArtifactsToHome(slug)`.
441
+ 3. (Phase B) re-correr `npx specrails-core init --root-dir <project.path> --workspace-dir <workspace>` para regenerar los artefactos canónicos en `$HOME` y (re)escribir la entrada del registry.
442
+ 4. desktop marca un flag por-proyecto `artifacts_relocated` en `desktop.sqlite`; lecturas subsecuentes usan el workspace.
443
+ 5. Los ficheros in-repo se **dejan en sitio, inertes**. El affordance `--clean-repo` (core) / "limpiar ficheros viejos de Specrails del repo" (desktop) es **lo único que muta el repo, nunca automático**. `openspec/**` y worktrees **nunca** se listan.
444
+
445
+ ### 9.2 GC de entradas obsoletas
446
+
447
+ `registry.json` **nunca** se auto-GC en lectura (un volumen desmontado / repo en disco externo no debe ser desalojado). desktop hace GC lazy de entradas `source:"desktop"` huérfanas (proyecto removido en la UI). Entradas `source:"core-standalone"` cuyo `repoPath` ya no existe se podan **solo** vía `specrails-core doctor --prune` explícito. Borrar una entrada del registry **nunca** borra los datos `~/.specrails/projects/<slug>/`.
448
+
449
+ ### 9.3 Gate de versión en lockstep (load-bearing)
450
+
451
+ desktop **debe** gatear el paso de `--workspace-dir` + env-pointers sobre `specrails-core >= <relocation-version>` (espejo del gate `>= 4.1.0` de profiles). Un desktop nuevo contra un core **viejo** que no entiende `--workspace-dir` volcaría artefactos en el repo (el core viejo ignora el flag desconocido). Por tanto desktop **no debe** enviarlo y debe caer a su comportamiento in-repo actual hasta que el core del usuario se actualice. **El número de versión exacto se elige y pinea en ambos repos simultáneamente** una vez core publique el soporte — es una pregunta abierta para firmar (§10).
452
+
453
+ ---
454
+
455
+ ## 10. Riesgos y preguntas abiertas para firmar
456
+
457
+ ### 10.1 Dos ítems de PoC aún pendientes (load-bearing — bloquean el modelo)
458
+
459
+ 1. **¿claude registra y EJECUTA subagentes project-scope `sr-*` nativamente desde cwd=workspace cuando `./project` es un symlink al repo?** Esta es la asunción load-bearing de toda la relocalización de rails (Opción 1 de la evaluación). Si claude no descubre los `sr-*` desde el workspace, la relocalización de rails es inválida y hay que repensar el spawn de rails **antes** de cualquier cambio de core. **PoC requerido.**
460
+ 2. **¿`@fission-ai/openspec init` acepta un target que sea el workspace (fuera del repo, posiblemente sin `.git`) o requiere `repoRoot`?** El carve-out asume que openspec sigue apuntando a `repoRoot` con `cwd: repoRoot`; confirmar que las lecturas openspec de los rails resuelven contra `project.path` cuando cwd=workspace (vía `${SPECRAILS_REPO_DIR:-.}`/`--add-dir`). **PoC requerido.** (Relacionado: ¿`git -C <project.path> worktree-add` funciona correctamente cuando el cwd del proceso spawneador es el workspace? — tercer PoC recomendado para los worktrees.)
461
+
462
+ ### 10.2 Preguntas abiertas de diseño
463
+
464
+ - **`SPECRAILS_STATE_DIR` base:** ¿un `<artifactRoot>/state/` dedicado (más limpio, provider-agnóstico) o reusar `<artifactRoot>/.claude` (menor diff, pero acopla el estado runtime al dir del provider claude y es incómodo para rails codex/gemini)? Recomendación: dir dedicado con fallback `${SPECRAILS_STATE_DIR:-.claude}`. **Decisión necesaria** (afecta el layout de telemetry/health-history/compat-snapshots).
465
+ - **Materialización del workspace en standalone:** ¿core debe materializar él mismo el workspace + symlink `./project` (generalizando el `explore-cwd-manager` de desktop), o asume que el spawner lo creó? Para un `npx specrails-core init` standalone limpio, core probablemente debe crear `artifactRoot` él mismo — pero si también crea el symlink y pone cwd=workspace (Phase B) o deja cwd=repo para standalone está sin resolver.
466
+ - **Provider detection:** ¿leer el provider del manifest/registry (robusto) o seguir sondeando `.claude`/`.codex`/`.gemini` bajo `artifactRoot` (espejo de hoy)? Recomendado el manifest, cambia ligeramente el contrato de detección.
467
+ - **Rutinas compartidas byte-idénticas:** ¿publicar canonicalize/slugify/lock/normalizeKey como un paquete npm versionado consumido por ambos repos (single source, pero añade dependencia de release-lockstep), o duplicarlas byte-a-byte (el patrón ya usado para `THEME_ID_ALLOWLIST`/`LANGUAGE_ID_ALLOWLIST`, sin dependencia nueva pero con riesgo de drift)? La corrección del contrato depende de que sean idénticas.
468
+ - **Semántica exacta del lock advisory** (`open(path,'wx')` + spin-retry + ruptura por TTL de mtime) sobre un `$HOME` montado por red (NFS/SMB) donde la atomicidad de O_EXCL y la granularidad de mtime son más débiles — o si se requiere que `$HOME` sea local.
469
+ - **¿Necesita core-standalone escribir `registry.json` en v1?** ¿O se difiere la primera escritura standalone a un writer desktop-only, con core read-only + fallback legacy hasta importarse? La decisión #2 dice que core puede escribir standalone, pero minimizar la superficie de escritura de core elimina la carrera de dos-escritores por completo.
470
+ - **`artifactRoot` con `/workspace`** ahora (layout Phase B) vs igual a `workspaceDir` para un rollout Phase-A read-only — ¿per-entrada (permitiendo migración por fases)? El esquema soporta ambos; el orden de migración necesita decisión de producto.
471
+ - **Número de versión exacto de core para el gate** (espejo del `>= 4.1.0`) — debe fijarse una vez core publique `--workspace-dir` + `SPECRAILS_*`, en ambos repos simultáneamente.
472
+ - **Residuo `opsx` de openspec:** `@fission-ai/openspec init` escribe dirs de comandos provider (p.ej. `.claude/commands/opsx`) en el repo como efecto colateral. El carve-out cubre `openspec/**`, ¿es aceptable ese residuo `opsx` en el `.claude/` del repo, o debe relocalizarse (lo cual openspec, binario externo, no puede)?
473
+ - **Lanzamiento "Open AI CLI" del terminal:** lanza un provider CLI desde cwd=`project.path` (shell repo-bound). ¿Necesita la config relocalizada del workspace, o es aceptable un lanzamiento ad-hoc repo-cwd para esa superficie interactiva?
474
+ - **Confirmación de producto** de que perder "activo de equipo commiteable" para profiles/file-summaries/`.mcp.json` es aceptable en v1 con git-export diferido (decisión #3 dice que sí, pero los workflows de `.gitignore`-share de file-summary y commit-de-equipo de profiles son features documentadas que se retiran).
475
+
476
+ ---
477
+
478
+ ## Apéndice: invariantes de corrección (deben testearse)
479
+
480
+ 1. Tras un ciclo completo setup + rail job con relocalización activa, el **working tree del repo queda byte-inalterado** salvo `openspec/changes/**` y worktrees gitignored (assert vía `git status`).
481
+ 2. El cwd de spawn del rail es el workspace; las llamadas git de provenance corren contra `project.path` (el split).
482
+ 3. Los proyectos en modo Jira **nunca** re-entran a la rama de escritura de core post-relocalización (backlog-config resuelve antes del default write-enabled).
483
+ 4. `desktop.sqlite` y `registry.json` se reconcilian al arranque; un `registry.json` hand-editado se auto-cura.
484
+ 5. Proyectos single- y multi-provider resuelven su workspace correctamente (el workspace es per-proyecto, no per-provider; codex conserva su `CODEX_HOME`).
485
+ 6. **Round-trip del registry**: asignar standalone → re-resolver → idempotente; desktop-pre-escribe-luego-core-lee → sin segunda asignación.
486
+ 7. **Lint de literales de template**: un test unitario que grepea `templates/**` por literales `.specrails/local-tickets.json` / `.claude/agent-memory/` residuales NO envueltos en `${SPECRAILS_*:-…}` ni en un `{{TOKEN}}`, fallando el build si alguno se cuela. El seguro más barato contra el fallo "lee silenciosamente la ubicación vacía equivocada".