specrails-desktop 2.32.0 → 2.34.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 (228) hide show
  1. package/client/dist/assets/{ActivityFeedPage-DvnFkjVG.js → ActivityFeedPage-DIDxNsDj.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-CBjcliD7.js → AgentBrowserCapture-7pJ2G5VY.js} +1 -1
  3. package/client/dist/assets/AgentModeAnalyticsPane-BSSyTnTM.js +2 -0
  4. package/client/dist/assets/{AgentModeCodePane-CokuFVdV.js → AgentModeCodePane-DzW6gvDw.js} +2 -2
  5. package/client/dist/assets/{AgentModeJobsPane-rf_DlWNo.js → AgentModeJobsPane-9wJff15f.js} +1 -1
  6. package/client/dist/assets/AgentsPage-CIA67psb.js +87 -0
  7. package/client/dist/assets/AnalyticsPage-C0LwYELz.js +1 -0
  8. package/client/dist/assets/CodePage-RtKvHmAO.js +2 -0
  9. package/client/dist/assets/{DesktopAnalyticsPage-gBRVqiw6.js → DesktopAnalyticsPage-BU8Y_Ts1.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-BzO5dg2B.js → DocsDialog-DIpoaGlQ.js} +1 -1
  11. package/client/dist/assets/{DocsPage-Db_uxr1j.js → DocsPage-ME47KZpK.js} +1 -1
  12. package/client/dist/assets/InteractiveJobComposer-BGjIe9R6.js +19 -0
  13. package/client/dist/assets/{JobDetailModal-DPHh2WOS.js → JobDetailModal-1UZkqH4I.js} +1 -1
  14. package/client/dist/assets/JobDetailPage-CN65sAf1.js +1 -0
  15. package/client/dist/assets/JobsPage-f-4dqys7.js +1 -0
  16. package/client/dist/assets/LoopBuilderPage-WeiofeL6.js +7 -0
  17. package/client/dist/assets/LoopPreviewModal-DDTnTyH0.js +1 -0
  18. package/client/dist/assets/LoopsPage-BVNchIex.js +1 -0
  19. package/client/dist/assets/MinimizedChatsContext-CHsu3NCX.js +1 -0
  20. package/client/dist/assets/PluginsPage-CdFzGEfQ.js +2 -0
  21. package/client/dist/assets/ProjectSettingsDialog-BkCXBQWk.js +1 -0
  22. package/client/dist/assets/TemplatePreviewModal-RDi0khFG.js +1 -0
  23. package/client/dist/assets/TicketDetailModal-MhJzrqbi.js +14 -0
  24. package/client/dist/assets/{Trans-B2i5HVcM.js → Trans-CTqw6rr9.js} +1 -1
  25. package/client/dist/assets/{addspec-DhmnXER8.js → addspec-A1lDQa4M.js} +1 -1
  26. package/client/dist/assets/{addspec-BRlk8jON.js → addspec-BHZd0N5F.js} +1 -1
  27. package/client/dist/assets/{addspec-CHW9xf_X.js → addspec-BPpZ_olp.js} +1 -1
  28. package/client/dist/assets/{addspec-Di41DpYV.js → addspec-B_TPJt-R.js} +1 -1
  29. package/client/dist/assets/{addspec-4WmLJmBG.js → addspec-CLYLvaVP.js} +1 -1
  30. package/client/dist/assets/{addspec-C7XRejU4.js → addspec-CQBjzt24.js} +1 -1
  31. package/client/dist/assets/{addspec-x3mizDxc.js → addspec-D2YIQ0GU.js} +1 -1
  32. package/client/dist/assets/{addspec-DTmRrWrF.js → addspec-iFdQ6D99.js} +1 -1
  33. package/client/dist/assets/agent-Bd-q3Wqp.js +1 -0
  34. package/client/dist/assets/agent-BlvdW4Hv.js +1 -0
  35. package/client/dist/assets/agent-C-KQhxeM.js +1 -0
  36. package/client/dist/assets/agent-Ca15w5eu.js +1 -0
  37. package/client/dist/assets/agent-D6fKaoMd.js +1 -0
  38. package/client/dist/assets/agent-HFMYMTbB.js +1 -0
  39. package/client/dist/assets/agent-QIEbkBjg.js +1 -0
  40. package/client/dist/assets/agent-mTCsAbCl.js +1 -0
  41. package/client/dist/assets/agentstudio-BNUth0uj.js +1 -0
  42. package/client/dist/assets/agentstudio-BNdB5hkr.js +1 -0
  43. package/client/dist/assets/agentstudio-C7-bt8FO.js +1 -0
  44. package/client/dist/assets/agentstudio-C8uNchpV.js +1 -0
  45. package/client/dist/assets/agentstudio-CKabyMlg.js +1 -0
  46. package/client/dist/assets/agentstudio-CN2pokQu.js +1 -0
  47. package/client/dist/assets/agentstudio-DR7doNBV.js +1 -0
  48. package/client/dist/assets/agentstudio-y2dVDXeM.js +1 -0
  49. package/client/dist/assets/{analytics-C9RaQcE3.js → analytics-47pYygtV.js} +1 -1
  50. package/client/dist/assets/{analytics-B8Qtptbl.js → analytics-B0vt1g5U.js} +1 -1
  51. package/client/dist/assets/analytics-BMzCvrCU.js +1 -0
  52. package/client/dist/assets/{analytics-BPptRY4f.js → analytics-BlFN3Zwx.js} +1 -1
  53. package/client/dist/assets/{analytics-BxDGOIwf.js → analytics-CZYzL-nR.js} +1 -1
  54. package/client/dist/assets/{analytics-D_OZdQ9O.js → analytics-DAsb9MJ8.js} +1 -1
  55. package/client/dist/assets/{analytics-CJIwqrEl.js → analytics-FXQlQ2Yt.js} +1 -1
  56. package/client/dist/assets/{analytics-We6lUMI8.js → analytics-RnFCNkah.js} +1 -1
  57. package/client/dist/assets/{builder-CWYPw7Cw.js → builder-0IWtosUm.js} +1 -1
  58. package/client/dist/assets/{builder-CX1437hF.js → builder-Bf2N6XWn.js} +1 -1
  59. package/client/dist/assets/{builder-BiUG9Z5K.js → builder-BnqaaBkT.js} +1 -1
  60. package/client/dist/assets/{builder-CGRz_-zP.js → builder-BxxfGgAO.js} +1 -1
  61. package/client/dist/assets/{builder-DwDexYRE.js → builder-C5IIgWsq.js} +1 -1
  62. package/client/dist/assets/builder-CHuahItu.js +1 -0
  63. package/client/dist/assets/{builder-Cowk9jtL.js → builder-CjWosXL_.js} +1 -1
  64. package/client/dist/assets/{builder-3bygQtZi.js → builder-CpCIOoKj.js} +1 -1
  65. package/client/dist/assets/dashboard-BHRqsGCV.js +1 -0
  66. package/client/dist/assets/dashboard-Bfr90vOl.js +1 -0
  67. package/client/dist/assets/dashboard-CTsM8x3m.js +1 -0
  68. package/client/dist/assets/dashboard-CqC6wX0h.js +1 -0
  69. package/client/dist/assets/dashboard-CuZtAszc.js +1 -0
  70. package/client/dist/assets/dashboard-DAcqIc-n.js +1 -0
  71. package/client/dist/assets/dashboard-DekiV-86.js +1 -0
  72. package/client/dist/assets/dashboard-DoDczc5S.js +1 -0
  73. package/client/dist/assets/{dist-js-CkTZKBF9.js → dist-js-BmsFWRly.js} +1 -1
  74. package/client/dist/assets/{dist-js-ClShHcGa.js → dist-js-J5W_N5ng.js} +1 -1
  75. package/client/dist/assets/{formatDistanceToNow-BjGwPf6I.js → formatDistanceToNow-BOuXgebD.js} +1 -1
  76. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-CSCbUI4W.js → getTimezoneOffsetInMilliseconds-EjGOpn4b.js} +1 -1
  77. package/client/dist/assets/{i18n-Ch-Db_x2.js → i18n-nJQvD5zJ.js} +2 -2
  78. package/client/dist/assets/index-C_sdsSZt.js +63 -0
  79. package/client/dist/assets/index-DCN5QbDm.css +2 -0
  80. package/client/dist/assets/{jira-BySfUuQZ.js → jira-BNZu8f9O.js} +1 -1
  81. package/client/dist/assets/{jira-gAvOyije.js → jira-BOoaUXbb.js} +1 -1
  82. package/client/dist/assets/{jira-pRaL7JQo.js → jira-BYVN4L2u.js} +1 -1
  83. package/client/dist/assets/jira-CKJg3HZd.js +1 -0
  84. package/client/dist/assets/{jira-Byw2C0F8.js → jira-DDDsZ4ub.js} +1 -1
  85. package/client/dist/assets/{jira-D38Gj7AT.js → jira-DXwYjVE4.js} +1 -1
  86. package/client/dist/assets/{jira-D11gPhxz.js → jira-Dn6U2nRW.js} +1 -1
  87. package/client/dist/assets/{jira-api-BgFIr8eD.js → jira-api-Bhixwn-d.js} +1 -1
  88. package/client/dist/assets/{jira-CNeYi06w.js → jira-j7hVolR5.js} +1 -1
  89. package/client/dist/assets/nav-ApJVg3xu.js +1 -0
  90. package/client/dist/assets/nav-BKNky1Bg.js +1 -0
  91. package/client/dist/assets/nav-C6vWWKYx.js +1 -0
  92. package/client/dist/assets/nav-CXJ6Aw89.js +1 -0
  93. package/client/dist/assets/nav-CsmFBIDf.js +1 -0
  94. package/client/dist/assets/nav-DjkmQJjd.js +1 -0
  95. package/client/dist/assets/nav-iwjCg5FR.js +1 -0
  96. package/client/dist/assets/nav-pYuXq7sf.js +1 -0
  97. package/client/dist/assets/{provider-capabilities-BvoExILy.js → provider-capabilities-Doej9lfa.js} +1 -1
  98. package/client/dist/assets/setup-3jWaoVUO.js +1 -0
  99. package/client/dist/assets/setup-BjgRneb2.js +1 -0
  100. package/client/dist/assets/setup-BzFIjv-f.js +1 -0
  101. package/client/dist/assets/setup-C4VfQsxE.js +1 -0
  102. package/client/dist/assets/setup-DfUkpDbN.js +1 -0
  103. package/client/dist/assets/setup-ECe90Pmu.js +1 -0
  104. package/client/dist/assets/setup-dlqC4Jmm.js +1 -0
  105. package/client/dist/assets/setup-jwfLMiM2.js +1 -0
  106. package/client/dist/assets/{spending-Dq43JYmD.js → spending-BpYPsevQ.js} +1 -1
  107. package/client/dist/assets/tickets-B4rjuSYi.js +1 -0
  108. package/client/dist/assets/tickets-Bc71ce1C.js +1 -0
  109. package/client/dist/assets/tickets-CLg7VTJ8.js +1 -0
  110. package/client/dist/assets/tickets-CP9U1GJt.js +1 -0
  111. package/client/dist/assets/tickets-CZNBiHbq.js +1 -0
  112. package/client/dist/assets/tickets-DlX2mEkD.js +1 -0
  113. package/client/dist/assets/tickets-W6pBmbrX.js +1 -0
  114. package/client/dist/assets/tickets-_yKvCt4Y.js +1 -0
  115. package/client/dist/assets/{useBrowserCaptureSession-TTO8vHtd.js → useBrowserCaptureSession-BBXBN0Na.js} +1 -1
  116. package/client/dist/assets/useDesktop-B6Du2N83.js +1 -0
  117. package/client/dist/assets/{useSharedWebSocket-DaPh7QBL.js → useSharedWebSocket-DK96ctb7.js} +1 -1
  118. package/client/dist/index.html +24 -24
  119. package/docs/guide/de/getting-started/3-adding-your-first-project.md +13 -32
  120. package/docs/guide/de/integrations/1-ai-providers.md +27 -16
  121. package/docs/guide/en/getting-started/3-adding-your-first-project.md +8 -27
  122. package/docs/guide/en/integrations/1-ai-providers.md +21 -18
  123. package/docs/guide/es/getting-started/3-adding-your-first-project.md +12 -31
  124. package/docs/guide/es/integrations/1-ai-providers.md +24 -16
  125. package/docs/guide/fr/getting-started/3-adding-your-first-project.md +15 -34
  126. package/docs/guide/fr/integrations/1-ai-providers.md +25 -16
  127. package/docs/guide/it/getting-started/3-adding-your-first-project.md +16 -35
  128. package/docs/guide/it/integrations/1-ai-providers.md +25 -16
  129. package/docs/guide/ja/getting-started/3-adding-your-first-project.md +16 -35
  130. package/docs/guide/ja/integrations/1-ai-providers.md +7 -12
  131. package/docs/guide/pt/getting-started/3-adding-your-first-project.md +15 -34
  132. package/docs/guide/pt/integrations/1-ai-providers.md +24 -16
  133. package/docs/guide/zh/getting-started/3-adding-your-first-project.md +18 -37
  134. package/docs/guide/zh/integrations/1-ai-providers.md +7 -12
  135. package/docs/internals/project-builder.md +11 -6
  136. package/package.json +1 -1
  137. package/server/dist/ai-invocations.js +35 -0
  138. package/server/dist/blueprint-commit.js +1 -1
  139. package/server/dist/desktop-db.js +22 -1
  140. package/server/dist/desktop-router.js +132 -43
  141. package/server/dist/framework-reseed.js +144 -0
  142. package/server/dist/index.js +41 -2
  143. package/server/dist/jira/jira-sync-manager.js +49 -2
  144. package/server/dist/jira-router.js +21 -0
  145. package/server/dist/job-phase-breakdown.js +132 -0
  146. package/server/dist/legacy-migration.js +395 -0
  147. package/server/dist/loop-executors.js +5 -1
  148. package/server/dist/offline-assemble.js +32 -4
  149. package/server/dist/pr-body.js +13 -0
  150. package/server/dist/project-router-jobs.js +16 -0
  151. package/server/dist/project-router-spending.js +11 -1
  152. package/server/dist/provider-detection.js +222 -0
  153. package/server/dist/provider-selection.js +71 -19
  154. package/server/dist/queue-manager.js +4 -0
  155. package/server/dist/rail-pr-decision.js +10 -0
  156. package/server/dist/rails-router.js +11 -0
  157. package/server/dist/rails-store.js +5 -1
  158. package/server/dist/repo-map.js +293 -0
  159. package/server/dist/setup-manager.js +57 -0
  160. package/server/dist/spending.js +45 -0
  161. package/client/dist/assets/AgentsPage-B648IzGI.js +0 -87
  162. package/client/dist/assets/AnalyticsPage-0rBoULPX.js +0 -1
  163. package/client/dist/assets/CodePage-ju302Vl7.js +0 -2
  164. package/client/dist/assets/InteractiveJobComposer-_jmsE_Az.js +0 -19
  165. package/client/dist/assets/JobDetailPage-7nwHyHGV.js +0 -1
  166. package/client/dist/assets/JobsPage-C_6YrcmE.js +0 -1
  167. package/client/dist/assets/LoopBuilderPage-DJ1UPDwL.js +0 -7
  168. package/client/dist/assets/LoopPreviewModal-12cUnljK.js +0 -1
  169. package/client/dist/assets/LoopsPage-CHYK0Xjr.js +0 -1
  170. package/client/dist/assets/MinimizedChatsContext-Gt6KQySM.js +0 -1
  171. package/client/dist/assets/PluginsPage-E59oQmwc.js +0 -2
  172. package/client/dist/assets/ProjectSettingsDialog-DQTLQ7ie.js +0 -1
  173. package/client/dist/assets/TemplatePreviewModal-nXUcDgfk.js +0 -1
  174. package/client/dist/assets/TicketDetailModal-C-tHW-2s.js +0 -14
  175. package/client/dist/assets/agent-BKsuIgIT.js +0 -1
  176. package/client/dist/assets/agent-Bs9NilfI.js +0 -1
  177. package/client/dist/assets/agent-C23h684q.js +0 -1
  178. package/client/dist/assets/agent-CqEjy3Z2.js +0 -1
  179. package/client/dist/assets/agent-D5DZTks1.js +0 -1
  180. package/client/dist/assets/agent-DE8hUYPl.js +0 -1
  181. package/client/dist/assets/agent-DsTuJGSc.js +0 -1
  182. package/client/dist/assets/agent-DwZM05-x.js +0 -1
  183. package/client/dist/assets/agentstudio-8TQqBenW.js +0 -1
  184. package/client/dist/assets/agentstudio-B8Z_mmEX.js +0 -1
  185. package/client/dist/assets/agentstudio-BZR9YmLs.js +0 -1
  186. package/client/dist/assets/agentstudio-Bge9pAWb.js +0 -1
  187. package/client/dist/assets/agentstudio-Bz169Vzt.js +0 -1
  188. package/client/dist/assets/agentstudio-D6L2jpNi.js +0 -1
  189. package/client/dist/assets/agentstudio-K2-Z7CL0.js +0 -1
  190. package/client/dist/assets/agentstudio-Zmgzthv8.js +0 -1
  191. package/client/dist/assets/analytics-BHqUazhP.js +0 -1
  192. package/client/dist/assets/builder-skfnZ1uC.js +0 -1
  193. package/client/dist/assets/dashboard-BVq6kRJe.js +0 -1
  194. package/client/dist/assets/dashboard-BaKPPmLH.js +0 -1
  195. package/client/dist/assets/dashboard-Bm8e4zYG.js +0 -1
  196. package/client/dist/assets/dashboard-Bt6IesnM.js +0 -1
  197. package/client/dist/assets/dashboard-C_NQZle_.js +0 -1
  198. package/client/dist/assets/dashboard-CyZKoGoI.js +0 -1
  199. package/client/dist/assets/dashboard-HIUdUBQ0.js +0 -1
  200. package/client/dist/assets/dashboard-pm4Mr229.js +0 -1
  201. package/client/dist/assets/index-Bz0TeGRs.css +0 -2
  202. package/client/dist/assets/index-kCUcnT8G.js +0 -77
  203. package/client/dist/assets/jira-CmKWERE0.js +0 -1
  204. package/client/dist/assets/nav-BHDiSIVu.js +0 -1
  205. package/client/dist/assets/nav-CMiYAq4i.js +0 -1
  206. package/client/dist/assets/nav-CqS7NrDW.js +0 -1
  207. package/client/dist/assets/nav-D-0M7xfv.js +0 -1
  208. package/client/dist/assets/nav-D65O6u9I.js +0 -1
  209. package/client/dist/assets/nav-D_LKYvBr.js +0 -1
  210. package/client/dist/assets/nav-FDTJmU79.js +0 -1
  211. package/client/dist/assets/nav-VaB-C4k5.js +0 -1
  212. package/client/dist/assets/setup-BBiHEO7h.js +0 -1
  213. package/client/dist/assets/setup-BlTWhNIQ.js +0 -1
  214. package/client/dist/assets/setup-Bp2L_oiP.js +0 -1
  215. package/client/dist/assets/setup-CInYXp1-.js +0 -1
  216. package/client/dist/assets/setup-CYH3bP6K.js +0 -1
  217. package/client/dist/assets/setup-DwcQ921P.js +0 -1
  218. package/client/dist/assets/setup-PBLjuDSs.js +0 -1
  219. package/client/dist/assets/setup-bx6FtoBp.js +0 -1
  220. package/client/dist/assets/tickets-BL9r1G0e.js +0 -1
  221. package/client/dist/assets/tickets-B_NyHJ8f.js +0 -1
  222. package/client/dist/assets/tickets-BarF1wuG.js +0 -1
  223. package/client/dist/assets/tickets-CclVhpQA.js +0 -1
  224. package/client/dist/assets/tickets-CqqOHbli.js +0 -1
  225. package/client/dist/assets/tickets-DCkFSiqt.js +0 -1
  226. package/client/dist/assets/tickets-Djn3ccfT.js +0 -1
  227. package/client/dist/assets/tickets-MiHBPsy_.js +0 -1
  228. package/client/dist/assets/useDesktop-DUvS4fuP.js +0 -1
@@ -0,0 +1,293 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildRepoMap = buildRepoMap;
37
+ exports.formatRepoMap = formatRepoMap;
38
+ exports.isRepoMapEnabled = isRepoMapEnabled;
39
+ exports.ensureRepoMapFile = ensureRepoMapFile;
40
+ exports.injectRepoMapEnv = injectRepoMapEnv;
41
+ exports._clearRepoMapCache = _clearRepoMapCache;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const crypto = __importStar(require("crypto"));
45
+ const artifact_registry_1 = require("./artifact-registry");
46
+ const SKIP_DIRS = new Set([
47
+ 'node_modules', 'vendor', 'dist', 'build', 'out', 'coverage', 'target',
48
+ '__pycache__', 'venv', '.venv', 'Pods', 'DerivedData',
49
+ ]);
50
+ /** Manifest filename → ecosystem label. First match wins (iteration order). */
51
+ const MANIFESTS = [
52
+ ['package.json', 'node'],
53
+ ['pyproject.toml', 'python'],
54
+ ['setup.py', 'python'],
55
+ ['requirements.txt', 'python'],
56
+ ['go.mod', 'go'],
57
+ ['Cargo.toml', 'rust'],
58
+ ['pom.xml', 'jvm'],
59
+ ['build.gradle', 'jvm'],
60
+ ['build.gradle.kts', 'jvm'],
61
+ ['Package.swift', 'swift'],
62
+ ['Gemfile', 'ruby'],
63
+ ['composer.json', 'php'],
64
+ ];
65
+ const MAX_SERVICES = 40;
66
+ const README_MAX_LINES = 12;
67
+ const README_MAX_CHARS = 800;
68
+ /** File-count scan caps — the bucket is a rough size label, not a census. */
69
+ const SCAN_MAX_DEPTH = 2;
70
+ const SCAN_MAX_ENTRIES = 500;
71
+ function buildRepoMap(repoDir) {
72
+ const services = [];
73
+ const seen = new Set();
74
+ const addService = (relDir) => {
75
+ if (seen.has(relDir) || services.length >= MAX_SERVICES + 1)
76
+ return;
77
+ const abs = relDir === '' ? repoDir : path.join(repoDir, relDir);
78
+ const eco = detectEcosystem(abs);
79
+ if (!eco)
80
+ return;
81
+ seen.add(relDir);
82
+ services.push({
83
+ dir: relDir,
84
+ name: packageName(abs) ?? (relDir === '' ? path.basename(repoDir) : path.basename(relDir)),
85
+ ecosystem: eco,
86
+ sizeBucket: sizeBucket(abs),
87
+ hasReadme: readmePath(abs) !== null,
88
+ });
89
+ };
90
+ addService('');
91
+ for (const entry of listDirs(repoDir)) {
92
+ addService(entry);
93
+ }
94
+ // npm/yarn workspaces can nest packages one level below the top-level scan.
95
+ for (const rel of resolveNpmWorkspaces(repoDir)) {
96
+ addService(rel);
97
+ }
98
+ const truncated = services.length > MAX_SERVICES;
99
+ return {
100
+ readmeExcerpt: readReadmeExcerpt(repoDir),
101
+ services: services.slice(0, MAX_SERVICES),
102
+ truncated,
103
+ };
104
+ }
105
+ function formatRepoMap(map) {
106
+ const lines = ['# Repository map (deterministic — generated by Specrails, no AI)', ''];
107
+ if (map.readmeExcerpt) {
108
+ lines.push('## README excerpt', '', map.readmeExcerpt, '');
109
+ }
110
+ lines.push('## Packages');
111
+ if (map.services.length === 0) {
112
+ lines.push('- (no recognized package manifests found)');
113
+ }
114
+ for (const s of map.services) {
115
+ const dirLabel = s.dir === '' ? './' : `${s.dir}/`;
116
+ const readme = s.hasReadme ? 'has README' : 'no README';
117
+ lines.push(`- \`${dirLabel}\` — ${s.name} (${s.ecosystem}, ${s.sizeBucket}, ${readme})`);
118
+ }
119
+ if (map.truncated) {
120
+ lines.push(`- … more packages omitted (cap ${MAX_SERVICES})`);
121
+ }
122
+ lines.push('', 'Use this map to orient exploration without spending turns on top-level discovery.', '');
123
+ return lines.join('\n');
124
+ }
125
+ const cache = new Map();
126
+ const CACHE_TTL_MS = 60_000;
127
+ function isRepoMapEnabled() {
128
+ return process.env.SPECRAILS_REPO_MAP !== 'false';
129
+ }
130
+ /**
131
+ * Build (or reuse, 60s TTL) the on-disk map for a repo/worktree. Returns the
132
+ * file path, or null on any failure — the map is an accelerator, never a
133
+ * spawn blocker.
134
+ */
135
+ function ensureRepoMapFile(repoDir) {
136
+ try {
137
+ const real = fs.realpathSync(repoDir);
138
+ const hit = cache.get(real);
139
+ if (hit && Date.now() - hit.at < CACHE_TTL_MS && fs.existsSync(hit.filePath)) {
140
+ return hit.filePath;
141
+ }
142
+ const content = formatRepoMap(buildRepoMap(real));
143
+ const dir = path.join((0, artifact_registry_1.resolveHome)(), '.specrails', 'repo-maps');
144
+ fs.mkdirSync(dir, { recursive: true });
145
+ const filePath = path.join(dir, `${sha1(real)}.md`);
146
+ const tmp = `${filePath}.tmp-${process.pid}`;
147
+ fs.writeFileSync(tmp, content, 'utf8');
148
+ fs.renameSync(tmp, filePath);
149
+ cache.set(real, { filePath, at: Date.now() });
150
+ return filePath;
151
+ }
152
+ catch {
153
+ return null;
154
+ }
155
+ }
156
+ /** Add `SPECRAILS_REPO_MAP_PATH` to a spawn env. Never throws, never blocks. */
157
+ function injectRepoMapEnv(env, repoDir) {
158
+ if (!repoDir || !isRepoMapEnabled())
159
+ return env;
160
+ const filePath = ensureRepoMapFile(repoDir);
161
+ return filePath ? { ...env, SPECRAILS_REPO_MAP_PATH: filePath } : env;
162
+ }
163
+ /** Test hook — clears the TTL cache. */
164
+ function _clearRepoMapCache() {
165
+ cache.clear();
166
+ }
167
+ // ─── Internals ────────────────────────────────────────────────────────────────
168
+ function sha1(value) {
169
+ return crypto.createHash('sha1').update(value).digest('hex');
170
+ }
171
+ function listDirs(dir) {
172
+ let entries;
173
+ try {
174
+ entries = fs.readdirSync(dir, { withFileTypes: true });
175
+ }
176
+ catch {
177
+ return [];
178
+ }
179
+ return entries
180
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.') && !SKIP_DIRS.has(e.name))
181
+ .map((e) => e.name)
182
+ .sort();
183
+ }
184
+ function detectEcosystem(dir) {
185
+ for (const [manifest, eco] of MANIFESTS) {
186
+ if (fs.existsSync(path.join(dir, manifest)))
187
+ return eco;
188
+ }
189
+ return null;
190
+ }
191
+ function packageName(dir) {
192
+ try {
193
+ const raw = fs.readFileSync(path.join(dir, 'package.json'), 'utf8');
194
+ const parsed = JSON.parse(raw);
195
+ return typeof parsed.name === 'string' && parsed.name.trim() ? parsed.name.trim() : null;
196
+ }
197
+ catch {
198
+ return null;
199
+ }
200
+ }
201
+ function readmePath(dir) {
202
+ for (const name of ['README.md', 'readme.md', 'README.txt', 'README']) {
203
+ const p = path.join(dir, name);
204
+ if (fs.existsSync(p))
205
+ return p;
206
+ }
207
+ return null;
208
+ }
209
+ function readReadmeExcerpt(repoDir) {
210
+ const p = readmePath(repoDir);
211
+ if (!p)
212
+ return null;
213
+ try {
214
+ const lines = fs.readFileSync(p, 'utf8').split('\n').slice(0, README_MAX_LINES);
215
+ const text = lines.join('\n').trim();
216
+ if (!text)
217
+ return null;
218
+ return text.length > README_MAX_CHARS ? `${text.slice(0, README_MAX_CHARS)}…` : text;
219
+ }
220
+ catch {
221
+ return null;
222
+ }
223
+ }
224
+ /** Rough size label from a capped, shallow file count. */
225
+ function sizeBucket(dir) {
226
+ let count = 0;
227
+ const walk = (d, depth) => {
228
+ if (depth > SCAN_MAX_DEPTH || count > SCAN_MAX_ENTRIES)
229
+ return;
230
+ let entries;
231
+ try {
232
+ entries = fs.readdirSync(d, { withFileTypes: true });
233
+ }
234
+ catch {
235
+ return;
236
+ }
237
+ for (const e of entries) {
238
+ if (count > SCAN_MAX_ENTRIES)
239
+ return;
240
+ if (e.name.startsWith('.') || SKIP_DIRS.has(e.name))
241
+ continue;
242
+ if (e.isDirectory()) {
243
+ walk(path.join(d, e.name), depth + 1);
244
+ }
245
+ else {
246
+ count++;
247
+ }
248
+ }
249
+ };
250
+ walk(dir, 0);
251
+ if (count < 20)
252
+ return 'small';
253
+ if (count < 150)
254
+ return 'medium';
255
+ return 'large';
256
+ }
257
+ /**
258
+ * Resolve npm/yarn `workspaces` from the root package.json. Literal paths and
259
+ * trailing `/*` globs only — deliberately no glob library; anything fancier
260
+ * falls back to the top-level scan.
261
+ */
262
+ function resolveNpmWorkspaces(repoDir) {
263
+ let patterns = [];
264
+ try {
265
+ const raw = fs.readFileSync(path.join(repoDir, 'package.json'), 'utf8');
266
+ const parsed = JSON.parse(raw);
267
+ const ws = parsed.workspaces;
268
+ if (Array.isArray(ws)) {
269
+ patterns = ws.filter((w) => typeof w === 'string');
270
+ }
271
+ else if (ws && typeof ws === 'object' && Array.isArray(ws.packages)) {
272
+ patterns = (ws.packages).filter((w) => typeof w === 'string');
273
+ }
274
+ }
275
+ catch {
276
+ return [];
277
+ }
278
+ const out = [];
279
+ for (const pattern of patterns) {
280
+ const clean = pattern.replace(/^\.\//, '');
281
+ if (clean.endsWith('/*')) {
282
+ const base = clean.slice(0, -2);
283
+ const abs = path.join(repoDir, base);
284
+ for (const child of listDirs(abs)) {
285
+ out.push(path.join(base, child));
286
+ }
287
+ }
288
+ else if (!clean.includes('*')) {
289
+ out.push(clean);
290
+ }
291
+ }
292
+ return out;
293
+ }
@@ -732,6 +732,63 @@ class SetupManager {
732
732
  this._projectTiers = new Map();
733
733
  this._projectNames = new Map();
734
734
  }
735
+ // ─── Silent assemble (global-core-zero-friction) ─────────────────────────────
736
+ // Per-project silent-assemble state: running guard + failed providers for the
737
+ // retry affordance. Assembly failure NEVER rolls back registration.
738
+ _silentAssembles = new Map();
739
+ silentAssembleState(projectId) {
740
+ return this._silentAssembles.get(projectId) ?? { running: false, failed: [] };
741
+ }
742
+ /**
743
+ * Wizard-less background workspace assembly: one offline `init` per provider,
744
+ * sequential, per-provider failure isolation. Progress rides the app-level
745
+ * `project.assemble_progress` WS event ({projectId, provider, status}); the
746
+ * only client surface is the project card's subtle indicator. Idempotent per
747
+ * project (a second call while running is a no-op; a later call retries only
748
+ * failed/missing providers via core's own skip-when-populated behaviour).
749
+ */
750
+ startSilentAssemble(projectId, projectPath, slug, providers, opts) {
751
+ const state = this.silentAssembleState(projectId);
752
+ if (state.running)
753
+ return;
754
+ const list = (opts?.providersFilter ?? providers).filter((p) => (0, providers_1.hasAdapter)(p));
755
+ if (list.length === 0)
756
+ return;
757
+ this._silentAssembles.set(projectId, { running: true, failed: [] });
758
+ const emit = (provider, status, error) => {
759
+ this._broadcast({
760
+ type: 'project.assemble_progress',
761
+ projectId,
762
+ provider,
763
+ status,
764
+ ...(error ? { error } : {}),
765
+ });
766
+ };
767
+ void (0, offline_assemble_1.assembleProjectOffline)({
768
+ projectPath,
769
+ slug,
770
+ desktopProjectId: projectId,
771
+ providers: list,
772
+ continueOnError: true,
773
+ onProviderStart: (p) => emit(p, 'running'),
774
+ onProviderResult: (r) => emit(r.provider, r.ok ? 'done' : 'failed', r.error),
775
+ })
776
+ .then((results) => {
777
+ const failed = results.filter((r) => !r.ok).map((r) => r.provider);
778
+ this._silentAssembles.set(projectId, { running: false, failed });
779
+ if (failed.length > 0) {
780
+ console.warn(`[SetupManager] silent assemble: failed providers for ${projectId}: ${failed.join(', ')}`);
781
+ }
782
+ })
783
+ .catch((err) => {
784
+ // Workspace-missing or other terminal failure: mark every provider
785
+ // failed so the retry affordance covers the whole set.
786
+ this._silentAssembles.set(projectId, { running: false, failed: list });
787
+ console.error(`[SetupManager] silent assemble failed for ${projectId}:`, err);
788
+ for (const p of list)
789
+ emit(p, 'failed', err instanceof Error ? err.message : String(err));
790
+ });
791
+ }
735
792
  // ─── Full Install: TUI installer (npx specrails-core) ────────────────────────
736
793
  /**
737
794
  * Install ALL of the given providers in one shot: installs the first, then
@@ -4,6 +4,7 @@ exports.getSpending = getSpending;
4
4
  exports.summariseExplorePrompt = summariseExplorePrompt;
5
5
  exports.getInvocations = getInvocations;
6
6
  exports.parseSpendingFilters = parseSpendingFilters;
7
+ exports.getAgentMissionSpending = getAgentMissionSpending;
7
8
  // The surfaces enumerated in the bySurface breakdown. Keep the original seven
8
9
  // analytics surfaces first (stable ordering / column layout), then the
9
10
  // cost-accounting-audit additions so a newly-recorded billable surface is
@@ -731,3 +732,47 @@ function parseSpendingFilters(query) {
731
732
  }
732
733
  return f;
733
734
  }
735
+ function getAgentMissionSpending(desktopDb, projectId, filters) {
736
+ const range = resolveRange(filters);
737
+ const summaryRow = desktopDb.prepare(`
738
+ SELECT
739
+ COALESCE(SUM(total_cost_usd), 0) AS total,
740
+ COALESCE(SUM(CASE WHEN total_cost_usd_estimated = 1 THEN total_cost_usd ELSE 0 END), 0) AS estimated,
741
+ COUNT(*) AS turns,
742
+ COALESCE(SUM(tokens_in), 0) AS tokensIn,
743
+ COALESCE(SUM(tokens_out), 0) AS tokensOut
744
+ FROM agent_invocations
745
+ WHERE project_id = ? AND started_at >= ? AND started_at <= ?
746
+ `).get(projectId, range.from, range.to);
747
+ const prevRow = desktopDb.prepare(`
748
+ SELECT COALESCE(SUM(total_cost_usd), 0) AS total
749
+ FROM agent_invocations
750
+ WHERE project_id = ? AND started_at >= ? AND started_at < ?
751
+ `).get(projectId, range.prevFrom, range.prevTo);
752
+ const topConversations = (desktopDb.prepare(`
753
+ SELECT
754
+ ai.conversation_id AS conversationId,
755
+ ac.title AS title,
756
+ COALESCE(SUM(ai.total_cost_usd), 0) AS costUsd,
757
+ COALESCE(SUM(CASE WHEN ai.total_cost_usd_estimated = 1 THEN ai.total_cost_usd ELSE 0 END), 0) AS estimatedCostUsd,
758
+ COUNT(*) AS turns,
759
+ MAX(ai.started_at) AS lastAt
760
+ FROM agent_invocations ai
761
+ LEFT JOIN agent_conversations ac ON ac.id = ai.conversation_id
762
+ WHERE ai.project_id = ? AND ai.started_at >= ? AND ai.started_at <= ?
763
+ GROUP BY ai.conversation_id
764
+ ORDER BY costUsd DESC
765
+ LIMIT 5
766
+ `).all(projectId, range.from, range.to));
767
+ return {
768
+ summary: {
769
+ totalCostUsd: summaryRow.total,
770
+ estimatedCostUsd: summaryRow.estimated,
771
+ prevTotalCostUsd: prevRow.total,
772
+ turns: summaryRow.turns,
773
+ tokensIn: summaryRow.tokensIn,
774
+ tokensOut: summaryRow.tokensOut,
775
+ },
776
+ topConversations,
777
+ };
778
+ }