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
@@ -8,68 +8,116 @@ const express_1 = require("express");
8
8
  const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const os_1 = __importDefault(require("os"));
11
- // ─── Docs directory resolution ────────────────────────────────────────────────
12
- // Try ~/.specrails/docs/ first (user-editable), then fall back to bundled docs/
13
- function resolveDocsDir() {
14
- const userDocsDir = path_1.default.join(os_1.default.homedir(), '.specrails', 'docs');
15
- if (fs_1.default.existsSync(userDocsDir)) {
16
- return userDocsDir;
11
+ // ─── User-guide docs ────────────────────────────────────────────────────────
12
+ //
13
+ // The Documentation panel serves a dedicated, language-aware USER-GUIDE tree —
14
+ // NOT the developer docs (docs/internals, docs/platforms, top-level dev .md).
15
+ // Layout on disk:
16
+ //
17
+ // docs/guide/<lang>/<category>/<order>-<slug>.md
18
+ //
19
+ // where <lang> is an i18n language id (`en`, `es`, …). English (`en`) is the
20
+ // source-of-truth locale and is always present; any missing language OR missing
21
+ // page falls back to the English file, so an untranslated language still shows
22
+ // real content instead of an empty panel.
23
+ //
24
+ // Resolution precedence for the guide ROOT (the dir that holds <lang>/):
25
+ // 1. ~/.specrails/docs/guide/ (user-editable override — wins when present)
26
+ // 2. bundled Tauri resource (<resource_dir>/docs/guide in the packaged app)
27
+ // 3. repo / compiled dist (docs/guide relative to __dirname)
28
+ // All candidates are existence-gated; a missing tree never crashes — the panel
29
+ // just shows no categories.
30
+ // ─── Default language + validation ──────────────────────────────────────────
31
+ const DEFAULT_LANG = 'en';
32
+ // Mirror of client/src/lib/i18n.ts LANGUAGE_IDS. Kept here (not imported — the
33
+ // server is CommonJS and must not depend on the ESM client) so an unknown / bogus
34
+ // `?lang=` value can never be turned into a filesystem path.
35
+ const SUPPORTED_LANGS = new Set(['en', 'es', 'fr', 'de', 'pt', 'it', 'zh', 'ja']);
36
+ function normalizeLang(raw) {
37
+ if (typeof raw !== 'string')
38
+ return DEFAULT_LANG;
39
+ const base = raw.toLowerCase().split('-')[0].trim();
40
+ return SUPPORTED_LANGS.has(base) ? base : DEFAULT_LANG;
41
+ }
42
+ // ─── Guide-root resolution ──────────────────────────────────────────────────
43
+ /**
44
+ * The bundled (packaged-app) guide root, or null when not running inside a
45
+ * Tauri bundle / the dir is absent. The Tauri host exports
46
+ * `SPECRAILS_BUNDLED_DOCS_PATH=<resource_dir>/docs` (existence-gated, mirroring
47
+ * the runtimes/core envs); as a defence-in-depth fallback we also derive
48
+ * `<resource_dir>/docs` from `SPECRAILS_BUNDLED_RUNTIMES_PATH` (whose parent IS
49
+ * the resource dir) so a build that forgot the docs env still resolves.
50
+ */
51
+ function resolveBundledGuideRoot() {
52
+ const direct = process.env.SPECRAILS_BUNDLED_DOCS_PATH;
53
+ if (direct && direct.length > 0) {
54
+ const guide = path_1.default.join(direct, 'guide');
55
+ if (fs_1.default.existsSync(guide))
56
+ return guide;
17
57
  }
18
- // Bundled docs: try relative to this file (works in dev and compiled)
58
+ const runtimes = process.env.SPECRAILS_BUNDLED_RUNTIMES_PATH;
59
+ if (runtimes && runtimes.length > 0) {
60
+ // <resource_dir>/runtimes → <resource_dir>/docs/guide
61
+ const guide = path_1.default.join(path_1.default.dirname(runtimes), 'docs', 'guide');
62
+ if (fs_1.default.existsSync(guide))
63
+ return guide;
64
+ }
65
+ return null;
66
+ }
67
+ /**
68
+ * Resolve the guide ROOT directory (the one containing <lang>/ subdirs).
69
+ * Returns null when no guide tree can be found anywhere (the panel then shows
70
+ * an empty index rather than crashing).
71
+ */
72
+ function resolveGuideRoot() {
73
+ // 1. User override: ~/.specrails/docs/guide
74
+ const userGuide = path_1.default.join(os_1.default.homedir(), '.specrails', 'docs', 'guide');
75
+ if (fs_1.default.existsSync(userGuide))
76
+ return userGuide;
77
+ // 2. Bundled app resource
78
+ const bundled = resolveBundledGuideRoot();
79
+ if (bundled)
80
+ return bundled;
81
+ // 3. Repo / compiled dist (relative to this file)
19
82
  const candidates = [
20
- path_1.default.resolve(__dirname, '../docs'), // dev: server/ -> ../docs
21
- path_1.default.resolve(__dirname, '../../docs'), // compiled: server/dist/ -> ../../docs
83
+ path_1.default.resolve(__dirname, '../docs/guide'), // dev: server/ -> ../docs/guide
84
+ path_1.default.resolve(__dirname, '../../docs/guide'), // compiled: server/dist/ -> ../../docs/guide
22
85
  ];
23
86
  for (const candidate of candidates) {
24
- if (fs_1.default.existsSync(candidate)) {
87
+ if (fs_1.default.existsSync(candidate))
25
88
  return candidate;
26
- }
27
89
  }
28
- // Fall back to user dir (will be empty until populated)
29
- return userDocsDir;
90
+ return null;
30
91
  }
31
- // ─── Category configuration ───────────────────────────────────────────────────
32
- /**
33
- * Top-level `.md` files are surfaced under a synthetic "guides" category.
34
- * Any subdirectory becomes its own category, with the directory name as slug
35
- * and a friendly label resolved from `CATEGORY_LABELS` (or title-cased).
36
- */
37
- const TOP_LEVEL_CATEGORY_SLUG = 'guides';
92
+ /** The localized directory for a given language under the guide root. */
93
+ function langDir(guideRoot, lang) {
94
+ return path_1.default.join(guideRoot, lang);
95
+ }
96
+ // ─── Category configuration ─────────────────────────────────────────────────
38
97
  const CATEGORY_LABELS = {
39
- guides: 'Guides',
40
- platforms: 'Platforms',
41
- internals: 'Internals',
42
- // Legacy folders (kept so user-customized ~/.specrails/docs trees still work)
43
- general: 'General',
44
- product: 'Product',
45
- engineering: 'Engineering',
46
- operations: 'Operations',
98
+ 'getting-started': 'Getting started',
99
+ specs: 'Specs',
100
+ pipeline: 'Pipeline',
101
+ agents: 'Agents',
102
+ insights: 'Insights',
103
+ integrations: 'Integrations',
104
+ settings: 'Settings',
47
105
  };
48
106
  /**
49
- * Preferred display order. Categories not in this list come after, in the
50
- * order `fs.readdirSync` returns them.
51
- */
52
- const CATEGORY_ORDER = ['guides', 'platforms', 'internals', 'general', 'product', 'engineering', 'operations'];
53
- /**
54
- * Preferred document order within the "guides" category. Files not listed
55
- * here are appended in alphabetical order so adding a new doc never breaks
56
- * the build.
107
+ * Preferred display order. Categories not in this list come after, alphabetically.
57
108
  */
58
- const GUIDES_DOC_ORDER = [
109
+ const CATEGORY_ORDER = [
59
110
  'getting-started',
60
- 'creating-specs',
61
- 'running-pipelines',
62
- 'tracking-cost',
63
- 'customizing',
64
- 'terminal',
65
- 'cli',
66
- 'codex',
111
+ 'specs',
112
+ 'pipeline',
113
+ 'agents',
114
+ 'insights',
115
+ 'integrations',
116
+ 'settings',
67
117
  ];
68
- // ─── Helpers ──────────────────────────────────────────────────────────────────
118
+ // ─── Helpers ────────────────────────────────────────────────────────────────
69
119
  function slugToTitle(slug) {
70
- return slug
71
- .replace(/-/g, ' ')
72
- .replace(/\b\w/g, (c) => c.toUpperCase());
120
+ return slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
73
121
  }
74
122
  function categoryLabel(slug) {
75
123
  return CATEGORY_LABELS[slug] ?? slugToTitle(slug);
@@ -78,130 +126,160 @@ function extractTitle(content, slug) {
78
126
  const match = content.match(/^#\s+(.+)$/m);
79
127
  return match ? match[1].trim() : slugToTitle(slug);
80
128
  }
81
- function sortBy(items, preferredOrder, keyFn) {
82
- const preferredIndex = (key) => {
83
- const i = preferredOrder.indexOf(key);
84
- return i === -1 ? Number.MAX_SAFE_INTEGER : i;
85
- };
86
- return [...items].sort((a, b) => {
87
- const aKey = keyFn(a);
88
- const bKey = keyFn(b);
89
- const ai = preferredIndex(aKey);
90
- const bi = preferredIndex(bKey);
91
- if (ai !== bi)
92
- return ai - bi;
93
- return aKey.localeCompare(bKey);
94
- });
129
+ /** Parse an `<order>-<slug>.md` filename into { order, slug }. */
130
+ function parseGuideFile(file) {
131
+ const m = file.match(/^(\d+)-(.+)\.md$/);
132
+ if (!m)
133
+ return null;
134
+ return { order: parseInt(m[1], 10), slug: m[2] };
95
135
  }
96
- function readMarkdownFiles(dir) {
97
- try {
98
- return fs_1.default.readdirSync(dir).filter((f) => f.endsWith('.md') && f.toLowerCase() !== 'readme.md');
99
- }
100
- catch {
101
- return [];
102
- }
136
+ function categoryRank(slug) {
137
+ const i = CATEGORY_ORDER.indexOf(slug);
138
+ return i === -1 ? Number.MAX_SAFE_INTEGER : i;
103
139
  }
104
- function readDocEntry(file, filePath) {
105
- const slug = file.replace(/\.md$/, '');
140
+ /** List the category subdirectories of a localized lang dir (existence-safe). */
141
+ function listCategories(dir) {
106
142
  try {
107
- const content = fs_1.default.readFileSync(filePath, 'utf-8');
108
- return { title: extractTitle(content, slug), slug };
143
+ return fs_1.default
144
+ .readdirSync(dir, { withFileTypes: true })
145
+ .filter((e) => e.isDirectory())
146
+ .map((e) => e.name);
109
147
  }
110
148
  catch {
111
- return { title: slugToTitle(slug), slug };
149
+ return [];
112
150
  }
113
151
  }
114
- function buildCategories(docsDir) {
115
- if (!fs_1.default.existsSync(docsDir))
152
+ /**
153
+ * Build the full category/doc index for a language, falling back to English
154
+ * per-category and per-page. The English tree defines the canonical set of
155
+ * categories and slugs; a translated language contributes localized titles for
156
+ * the pages it has, and English fills the gaps.
157
+ */
158
+ function buildCategories(guideRoot, lang) {
159
+ const enDir = langDir(guideRoot, DEFAULT_LANG);
160
+ if (!fs_1.default.existsSync(enDir))
116
161
  return [];
162
+ const localizedDir = langDir(guideRoot, lang);
117
163
  const categories = [];
118
- // 1. Top-level .md files → "guides" category
119
- const topFiles = readMarkdownFiles(docsDir);
120
- if (topFiles.length > 0) {
121
- const docs = topFiles.map((f) => readDocEntry(f, path_1.default.join(docsDir, f)));
122
- const ordered = sortBy(docs, GUIDES_DOC_ORDER, (d) => d.slug);
123
- categories.push({ name: categoryLabel(TOP_LEVEL_CATEGORY_SLUG), slug: TOP_LEVEL_CATEGORY_SLUG, docs: ordered });
164
+ for (const cat of listCategories(enDir)) {
165
+ const enCatDir = path_1.default.join(enDir, cat);
166
+ const localizedCatDir = path_1.default.join(localizedDir, cat);
167
+ let files;
168
+ try {
169
+ files = fs_1.default.readdirSync(enCatDir).filter((f) => f.endsWith('.md'));
170
+ }
171
+ catch {
172
+ files = [];
173
+ }
174
+ const parsed = files
175
+ .map((f) => ({ file: f, meta: parseGuideFile(f) }))
176
+ .filter((x) => x.meta !== null)
177
+ .sort((a, b) => a.meta.order - b.meta.order || a.meta.slug.localeCompare(b.meta.slug));
178
+ const docs = parsed.map(({ file, meta }) => {
179
+ // Prefer the localized file's title; fall back to the English file.
180
+ const localizedPath = path_1.default.join(localizedCatDir, file);
181
+ const enPath = path_1.default.join(enCatDir, file);
182
+ const readPath = fs_1.default.existsSync(localizedPath) ? localizedPath : enPath;
183
+ let title = slugToTitle(meta.slug);
184
+ try {
185
+ title = extractTitle(fs_1.default.readFileSync(readPath, 'utf-8'), meta.slug);
186
+ }
187
+ catch {
188
+ /* fall back to slug-derived title */
189
+ }
190
+ return { title, slug: meta.slug };
191
+ });
192
+ if (docs.length > 0) {
193
+ categories.push({ name: categoryLabel(cat), slug: cat, docs });
194
+ }
124
195
  }
125
- // 2. Each subdirectory its own category
126
- let entries;
196
+ return categories.sort((a, b) => categoryRank(a.slug) - categoryRank(b.slug) || a.slug.localeCompare(b.slug));
197
+ }
198
+ // ─── Path-safety guards ─────────────────────────────────────────────────────
199
+ function isSafeSegment(seg) {
200
+ return (typeof seg === 'string' &&
201
+ seg.length > 0 &&
202
+ seg !== '.' &&
203
+ seg !== '..' &&
204
+ seg === path_1.default.basename(seg) &&
205
+ !seg.includes('/') &&
206
+ !seg.includes('\\'));
207
+ }
208
+ /**
209
+ * Resolve the on-disk file for a (lang, category, slug). Tries the localized
210
+ * file first, then English. Returns null when neither exists or a path-safety
211
+ * check fails. The slug in the URL is the *bare* slug (no order prefix); we map
212
+ * it to the `<order>-<slug>.md` file by scanning the English category dir
213
+ * (English is canonical for the set of slugs).
214
+ */
215
+ function resolveDocFile(guideRoot, lang, category, slug) {
216
+ if (!isSafeSegment(category) || !isSafeSegment(slug))
217
+ return null;
218
+ const enCatDir = path_1.default.join(langDir(guideRoot, DEFAULT_LANG), category);
219
+ let files;
127
220
  try {
128
- entries = fs_1.default.readdirSync(docsDir, { withFileTypes: true });
221
+ files = fs_1.default.readdirSync(enCatDir).filter((f) => f.endsWith('.md'));
129
222
  }
130
223
  catch {
131
- entries = [];
132
- }
133
- for (const entry of entries) {
134
- if (!entry.isDirectory())
135
- continue;
136
- // Avoid colliding with synthetic top-level slug
137
- if (entry.name === TOP_LEVEL_CATEGORY_SLUG)
138
- continue;
139
- const catDir = path_1.default.join(docsDir, entry.name);
140
- const files = readMarkdownFiles(catDir);
141
- const docs = files.map((f) => readDocEntry(f, path_1.default.join(catDir, f)));
142
- // Sort docs within unknown categories alphabetically.
143
- docs.sort((a, b) => a.slug.localeCompare(b.slug));
144
- categories.push({ name: categoryLabel(entry.name), slug: entry.name, docs });
224
+ return null;
145
225
  }
146
- return sortBy(categories, CATEGORY_ORDER, (c) => c.slug);
147
- }
148
- function isValidCategorySlug(cat) {
149
- // basename(cat) === cat guards against slashes, but `path.basename('..') === '..'`,
150
- // so '.'/'..' slip through and `path.join(docsDir, '..', ...)` escapes one level
151
- // up (B4). Reject the dot segments explicitly.
152
- return (cat.length > 0 &&
153
- cat !== '.' &&
154
- cat !== '..' &&
155
- cat === path_1.default.basename(cat) &&
156
- !cat.includes('/') &&
157
- !cat.includes('\\'));
226
+ const match = files.find((f) => {
227
+ const meta = parseGuideFile(f);
228
+ return meta?.slug === slug;
229
+ });
230
+ if (!match)
231
+ return null;
232
+ const localizedPath = path_1.default.join(langDir(guideRoot, lang), category, match);
233
+ const enPath = path_1.default.join(enCatDir, match);
234
+ const filePath = fs_1.default.existsSync(localizedPath) ? localizedPath : enPath;
235
+ // Defence-in-depth: the resolved file must live inside the guide root.
236
+ const rel = path_1.default.relative(path_1.default.resolve(guideRoot), path_1.default.resolve(filePath));
237
+ if (rel.startsWith('..') || path_1.default.isAbsolute(rel))
238
+ return null;
239
+ if (!fs_1.default.existsSync(filePath))
240
+ return null;
241
+ return { filePath };
158
242
  }
159
- // ─── Router ───────────────────────────────────────────────────────────────────
243
+ // ─── Router ─────────────────────────────────────────────────────────────────
160
244
  function createDocsRouter() {
161
245
  const router = (0, express_1.Router)();
162
- router.get('/', (_req, res) => {
163
- const docsDir = resolveDocsDir();
164
- const categories = buildCategories(docsDir);
246
+ router.get('/', (req, res) => {
247
+ const lang = normalizeLang(req.query.lang);
248
+ const guideRoot = resolveGuideRoot();
249
+ const categories = guideRoot ? buildCategories(guideRoot, lang) : [];
165
250
  res.json({ categories });
166
251
  });
167
252
  router.get('/:category/:slug', (req, res) => {
168
253
  const { category, slug } = req.params;
169
- if (!isValidCategorySlug(category)) {
254
+ const lang = normalizeLang(req.query.lang);
255
+ if (!isSafeSegment(category)) {
170
256
  res.status(404).json({ error: 'Category not found' });
171
257
  return;
172
258
  }
173
- // Prevent directory traversal in slug too
174
- const safeSlug = path_1.default.basename(slug);
175
- if (safeSlug !== slug || slug.includes('/') || slug.includes('\\')) {
259
+ if (!isSafeSegment(slug)) {
176
260
  res.status(400).json({ error: 'Invalid slug' });
177
261
  return;
178
262
  }
179
- const docsDir = resolveDocsDir();
180
- // For the synthetic "guides" category, files live at the top level of docsDir.
181
- const filePath = category === TOP_LEVEL_CATEGORY_SLUG
182
- ? path_1.default.join(docsDir, `${safeSlug}.md`)
183
- : path_1.default.join(docsDir, category, `${safeSlug}.md`);
184
- // B4: defence-in-depth — never serve a file resolved outside docsDir, even
185
- // if a future change loosens the slug/category validation above.
186
- const rel = path_1.default.relative(path_1.default.resolve(docsDir), path_1.default.resolve(filePath));
187
- if (rel.startsWith('..') || path_1.default.isAbsolute(rel)) {
263
+ const guideRoot = resolveGuideRoot();
264
+ if (!guideRoot) {
188
265
  res.status(404).json({ error: 'Document not found' });
189
266
  return;
190
267
  }
191
- if (!fs_1.default.existsSync(filePath)) {
268
+ const resolved = resolveDocFile(guideRoot, lang, category, slug);
269
+ if (!resolved) {
192
270
  res.status(404).json({ error: 'Document not found' });
193
271
  return;
194
272
  }
195
273
  let content;
196
274
  try {
197
- content = fs_1.default.readFileSync(filePath, 'utf-8');
275
+ content = fs_1.default.readFileSync(resolved.filePath, 'utf-8');
198
276
  }
199
277
  catch {
200
278
  res.status(500).json({ error: 'Failed to read document' });
201
279
  return;
202
280
  }
203
- const title = extractTitle(content, safeSlug);
204
- res.json({ title, content, category, slug: safeSlug });
281
+ const title = extractTitle(content, slug);
282
+ res.json({ title, content, category, slug });
205
283
  });
206
284
  return router;
207
285
  }
@@ -95,20 +95,23 @@ function readSummary(projectPath, relPath) {
95
95
  return null;
96
96
  }
97
97
  }
98
- function writeSummary(projectPath, relPath, payload) {
99
- const dir = summariesDir(projectPath);
98
+ function writeSummary(summaryRoot, relPath, payload,
99
+ /** When false (relocated: summaryRoot is the app-owned workspace), skip the
100
+ * `.gitignore` append — the workspace `.gitignore` is not the user's repo. */
101
+ appendGitignore = true) {
102
+ const dir = summariesDir(summaryRoot);
100
103
  const firstWrite = !fs.existsSync(dir);
101
104
  fs.mkdirSync(dir, { recursive: true });
102
- const final = summaryFilePath(projectPath, relPath);
105
+ const final = summaryFilePath(summaryRoot, relPath);
103
106
  // Atomic write: temp file in the same directory, then rename.
104
107
  const tmp = `${final}.tmp.${(0, crypto_1.randomBytes)(6).toString('hex')}`;
105
108
  fs.writeFileSync(tmp, JSON.stringify(payload), { encoding: 'utf8', mode: 0o600 });
106
109
  fs.renameSync(tmp, final);
107
- if (firstWrite) {
110
+ if (firstWrite && appendGitignore) {
108
111
  // The app appends `.specrails/file-summaries/` to the project `.gitignore`
109
112
  // on first write. Idempotent: only appends when the line is absent.
110
113
  try {
111
- ensureGitignoreLine(projectPath, '.specrails/file-summaries/');
114
+ ensureGitignoreLine(summaryRoot, '.specrails/file-summaries/');
112
115
  }
113
116
  catch { /* non-fatal */ }
114
117
  }
@@ -130,8 +133,13 @@ function ensureGitignoreLine(projectPath, line) {
130
133
  fs.writeFileSync(gi, `${existing}${sep}${line}\n`, 'utf8');
131
134
  return true;
132
135
  }
133
- function sweepOrphans(projectPath, cap = 200) {
134
- const dir = summariesDir(projectPath);
136
+ function sweepOrphans(summaryRoot, cap = 200,
137
+ /** Where to resolve source files (the repo). Relocate-artifacts: summaries
138
+ * live under `summaryRoot` (workspace) but source is under `sourceRoot`
139
+ * (repo). Defaults to `summaryRoot` (legacy, byte-identical). */
140
+ sourceRoot) {
141
+ const dir = summariesDir(summaryRoot);
142
+ const srcRoot = sourceRoot ?? summaryRoot;
135
143
  let deleted = 0;
136
144
  let remaining = 0;
137
145
  let entries;
@@ -154,7 +162,7 @@ function sweepOrphans(projectPath, cap = 200) {
154
162
  catch {
155
163
  continue;
156
164
  }
157
- const sourceAbs = path.join(projectPath, payload.path);
165
+ const sourceAbs = path.join(srcRoot, payload.path);
158
166
  if (fs.existsSync(sourceAbs))
159
167
  continue;
160
168
  if (deleted >= cap) {
@@ -266,7 +274,8 @@ class FileSummaryManager {
266
274
  // summaries; without `force` an explicit "Regenerate" of an unchanged file
267
275
  // would be a silent no-op.
268
276
  const currentLang = this.deps.language?.() ?? 'en';
269
- const existing = readSummary(req.projectPath, req.relPath);
277
+ const summaryRoot = req.summaryRoot ?? req.projectPath;
278
+ const existing = readSummary(summaryRoot, req.relPath);
270
279
  if (!req.force &&
271
280
  existing &&
272
281
  existing.fileHash === newHash &&
@@ -442,7 +451,11 @@ class FileSummaryManager {
442
451
  generatedBy: { model: out.model, promptVersion: CURRENT_PROMPT_VERSION, truncated },
443
452
  triggeredBy: req.triggeredBy,
444
453
  };
445
- writeSummary(req.projectPath, req.relPath, payload);
454
+ // Relocate-artifacts: summaries OUTPUT to the workspace when relocated
455
+ // (summaryRoot ≠ projectPath ⇒ skip the repo .gitignore append); source
456
+ // was read above from req.projectPath (the repo). Legacy ⇒ both equal.
457
+ const summaryRoot = req.summaryRoot ?? req.projectPath;
458
+ writeSummary(summaryRoot, req.relPath, payload, summaryRoot === req.projectPath);
446
459
  // Keep the watcher's negative-cache a correct superset.
447
460
  this.knownSummaries.get(req.projectId)?.add(req.relPath);
448
461
  try {
@@ -531,22 +544,34 @@ class FileSummaryManager {
531
544
  this.activeControllers.delete(controller);
532
545
  }
533
546
  }
534
- markStale(projectPath, projectId, relPath) {
535
- const existing = readSummary(projectPath, relPath);
547
+ /** `summaryRoot` (relocate-artifacts) is where the summary JSON lives — the
548
+ * workspace when relocated, else === projectPath. */
549
+ markStale(projectPath, projectId, relPath, summaryRoot) {
550
+ const existing = readSummary(summaryRoot ?? projectPath, relPath);
536
551
  if (!existing)
537
552
  return;
538
553
  this.deps.broadcast(buildSummaryUpdated(projectId, existing, true));
539
554
  }
540
- attachWatcher(projectId, projectPath) {
555
+ /**
556
+ * Watch the repo SOURCE tree (`projectPath`) for edits and mark the
557
+ * corresponding summary stale. Relocate-artifacts: `summaryRoot` is where the
558
+ * summary JSON lives (workspace when relocated) — source is watched at
559
+ * `projectPath`, summaries are scanned/swept/marked at `summaryRoot`. When
560
+ * omitted `summaryRoot` defaults to `projectPath` (legacy, byte-identical).
561
+ */
562
+ attachWatcher(projectId, projectPath, summaryRoot) {
541
563
  if (this.watchers.has(projectId))
542
564
  return;
543
- this.knownSummaries.set(projectId, this.scanKnownSummaries(projectPath));
565
+ const sumRoot = summaryRoot ?? projectPath;
566
+ this.knownSummaries.set(projectId, this.scanKnownSummaries(sumRoot));
544
567
  // Reclaim summary JSON files whose source file was renamed/deleted since the
545
568
  // last session. Runs once per project per session (attachWatcher is
546
569
  // idempotent) and is capped at 200/pass inside sweepOrphans. The chokidar
547
570
  // watcher only sees 'change', never 'unlink', so this is the only reaper.
571
+ // sweepOrphans resolves source files against the repo, so it must know both:
572
+ // summaries under sumRoot, source under projectPath.
548
573
  try {
549
- sweepOrphans(projectPath);
574
+ sweepOrphans(sumRoot, undefined, projectPath);
550
575
  }
551
576
  catch { /* best effort */ }
552
577
  // CRITICAL: prune build/dep trees (node_modules, dist, target, src-tauri/target,
@@ -574,7 +599,7 @@ class FileSummaryManager {
574
599
  const known = this.knownSummaries.get(projectId);
575
600
  if (known && !known.has(rel))
576
601
  return;
577
- this.markStale(projectPath, projectId, rel);
602
+ this.markStale(projectPath, projectId, rel, sumRoot);
578
603
  });
579
604
  this.watchers.set(projectId, { projectPath, watcher });
580
605
  }