specrails-desktop 2.29.4 → 2.30.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 (169) hide show
  1. package/client/dist/assets/{ActivityFeedPage-CDMzpGXd.js → ActivityFeedPage-AQgpr9qG.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-BVTacapZ.js → AgentBrowserCapture-I7a0usG7.js} +1 -1
  3. package/client/dist/assets/AgentModeCodePane-BqOVg15n.js +2 -0
  4. package/client/dist/assets/{AgentModeJobsPane-DAjN8Au5.js → AgentModeJobsPane-4SG6FF27.js} +1 -1
  5. package/client/dist/assets/{AgentsPage-CebykajE.js → AgentsPage-C3FgYlEi.js} +1 -1
  6. package/client/dist/assets/{AnalyticsPage-BiPrVvgv.js → AnalyticsPage-BcMNByW5.js} +1 -1
  7. package/client/dist/assets/{CodePage-Dw9eQFRS.js → CodePage-CgdDkXp-.js} +1 -1
  8. package/client/dist/assets/{DesktopAnalyticsPage-cCG_JAEi.js → DesktopAnalyticsPage-DPMcS88n.js} +1 -1
  9. package/client/dist/assets/{DocsDialog-C1kZ_mRP.js → DocsDialog-BC7574Zi.js} +1 -1
  10. package/client/dist/assets/{DocsPage-3GivxWJm.js → DocsPage-Dd9yilUg.js} +1 -1
  11. package/client/dist/assets/{InteractiveJobComposer-KuZXGdr8.js → InteractiveJobComposer-CqC_0VQL.js} +1 -1
  12. package/client/dist/assets/{JobDetailModal-DWpRALkB.js → JobDetailModal-BFNXOhk1.js} +1 -1
  13. package/client/dist/assets/{JobDetailPage-mrXDKafj.js → JobDetailPage-DM-6zYpA.js} +1 -1
  14. package/client/dist/assets/{JobsPage-CUYXjcoc.js → JobsPage-1dkjPk1k.js} +1 -1
  15. package/client/dist/assets/{LoopBuilderPage-CwjBD5Gm.js → LoopBuilderPage-BPeqz6cG.js} +1 -1
  16. package/client/dist/assets/{LoopPreviewModal-CO9ton0-.js → LoopPreviewModal-EdkyYaGN.js} +1 -1
  17. package/client/dist/assets/{LoopsPage-CLL5UuvN.js → LoopsPage-nZfgiaK8.js} +1 -1
  18. package/client/dist/assets/{MinimizedChatsContext-DJAkRYps.js → MinimizedChatsContext-Cs34FPTz.js} +1 -1
  19. package/client/dist/assets/{PluginsPage-DZvCC5Ri.js → PluginsPage-CwFfFBzW.js} +1 -1
  20. package/client/dist/assets/{ProjectSettingsDialog-C7Hesadg.js → ProjectSettingsDialog-DHsxLFMT.js} +1 -1
  21. package/client/dist/assets/{TemplatePreviewModal-DczCzUzP.js → TemplatePreviewModal-wcI3nGNr.js} +1 -1
  22. package/client/dist/assets/TicketDetailModal-7EloKP1C.js +14 -0
  23. package/client/dist/assets/{Trans-Cm9rhdnG.js → Trans-CPISVji8.js} +1 -1
  24. package/client/dist/assets/agent-BABjRt0P.js +1 -0
  25. package/client/dist/assets/agent-BuUpgqVx.js +1 -0
  26. package/client/dist/assets/agent-CCDZesgU.js +1 -0
  27. package/client/dist/assets/agent-CVn64zWH.js +1 -0
  28. package/client/dist/assets/agent-CWeOny0E.js +1 -0
  29. package/client/dist/assets/agent-DOysmad_.js +1 -0
  30. package/client/dist/assets/agent-DSsjTBhn.js +1 -0
  31. package/client/dist/assets/agent-DlZMxfM9.js +1 -0
  32. package/client/dist/assets/builder-BH8Fua-g.js +1 -0
  33. package/client/dist/assets/builder-BoV7DgKd.js +1 -0
  34. package/client/dist/assets/builder-Cc_Lf79C.js +1 -0
  35. package/client/dist/assets/builder-CwA4T7gP.js +1 -0
  36. package/client/dist/assets/builder-CwZ7Bme2.js +1 -0
  37. package/client/dist/assets/builder-CxW-JFsR.js +1 -0
  38. package/client/dist/assets/builder-D94lrV7x.js +1 -0
  39. package/client/dist/assets/builder-cqHpCyGa.js +1 -0
  40. package/client/dist/assets/{dist-js-BERxOT0X.js → dist-js-Bt2zW2YG.js} +1 -1
  41. package/client/dist/assets/{dist-js-Di8lflPo.js → dist-js-zvt5ap3o.js} +1 -1
  42. package/client/dist/assets/{formatDistanceToNow-BAXZRbEO.js → formatDistanceToNow-ByKli7Q_.js} +1 -1
  43. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DwzZXHtg.js → getTimezoneOffsetInMilliseconds-qrcBaLW1.js} +1 -1
  44. package/client/dist/assets/i18n-Bf9Ckd10.js +2 -0
  45. package/client/dist/assets/index-Dh9i4CnN.css +2 -0
  46. package/client/dist/assets/index-xkzK3E5i.js +76 -0
  47. package/client/dist/assets/setup-3832BJCo.js +1 -0
  48. package/client/dist/assets/setup-B8J-gECe.js +1 -0
  49. package/client/dist/assets/setup-BCIOxVsc.js +1 -0
  50. package/client/dist/assets/setup-BciLj_tU.js +1 -0
  51. package/client/dist/assets/setup-CITtPjed.js +1 -0
  52. package/client/dist/assets/setup-CTkE3cfh.js +1 -0
  53. package/client/dist/assets/setup-ajB4rYm-.js +1 -0
  54. package/client/dist/assets/setup-xFO1wNl0.js +1 -0
  55. package/client/dist/assets/{spending-BLb5pj6G.js → spending-DMnCXy84.js} +1 -1
  56. package/client/dist/assets/{useBrowserCaptureSession-DR1_jL56.js → useBrowserCaptureSession-CkBJShUw.js} +1 -1
  57. package/client/dist/assets/{useDesktop-D4DxBGPU.js → useDesktop-B4pSBIDd.js} +1 -1
  58. package/client/dist/index.html +14 -13
  59. package/docs/codex.md +2 -1
  60. package/docs/customizing.md +1 -1
  61. package/docs/internals/gemini-mcp-registration.md +1 -1
  62. package/docs/internals/global-artifacts-alignment-contract.md +4 -4
  63. package/docs/internals/project-builder.md +306 -0
  64. package/docs/internals/safe-pr-review-flow.md +29 -19
  65. package/package.json +1 -1
  66. package/server/dist/agent-operator-prompt.js +7 -1
  67. package/server/dist/blueprint-chat-manager.js +335 -0
  68. package/server/dist/blueprint-commit.js +335 -0
  69. package/server/dist/blueprint-draft-parser.js +168 -0
  70. package/server/dist/blueprint-operator-prompt.js +149 -0
  71. package/server/dist/blueprint-render.js +105 -0
  72. package/server/dist/blueprint-router.js +173 -0
  73. package/server/dist/blueprint-spec-quality.js +167 -0
  74. package/server/dist/blueprint-store.js +49 -0
  75. package/server/dist/blueprint-types.js +29 -0
  76. package/server/dist/builder-cwd-manager.js +57 -0
  77. package/server/dist/chat-manager.js +435 -23
  78. package/server/dist/context-scope.js +6 -3
  79. package/server/dist/contract-refine-runner.js +143 -72
  80. package/server/dist/desktop-db.js +27 -0
  81. package/server/dist/index.js +15 -0
  82. package/server/dist/offline-assemble.js +203 -0
  83. package/server/dist/pricing.js +5 -0
  84. package/server/dist/project-router-chat.js +14 -2
  85. package/server/dist/project-router.js +130 -7
  86. package/server/dist/providers/claude-adapter.js +8 -2
  87. package/server/dist/providers/codex-adapter.js +23 -10
  88. package/server/dist/providers/gemini-adapter.js +16 -7
  89. package/server/dist/rail-isolated-launch.js +11 -1
  90. package/server/dist/setup-manager.js +6 -54
  91. package/server/dist/spec-models.js +3 -0
  92. package/server/dist/workspace-manager.js +3 -1
  93. package/server/dist/worktree-overlay.js +233 -51
  94. package/client/dist/assets/AgentModeCodePane-D9bhFyuy.js +0 -2
  95. package/client/dist/assets/TicketDetailModal-CFh7GOv5.js +0 -14
  96. package/client/dist/assets/agent-BXc59ARK2.js +0 -1
  97. package/client/dist/assets/agent-BbIFXdDq.js +0 -1
  98. package/client/dist/assets/agent-BqEfA5u5.js +0 -1
  99. package/client/dist/assets/agent-BtGzgTC4.js +0 -1
  100. package/client/dist/assets/agent-CIhys_5d.js +0 -1
  101. package/client/dist/assets/agent-CJPaqsVR.js +0 -1
  102. package/client/dist/assets/agent-n-hY4GuL.js +0 -1
  103. package/client/dist/assets/agent-tJ-efa3_2.js +0 -1
  104. package/client/dist/assets/i18n-cK5pPbj8.js +0 -2
  105. package/client/dist/assets/index-BT6xDQjB.css +0 -2
  106. package/client/dist/assets/index-CCq_kIrF.js +0 -76
  107. package/client/dist/assets/setup-CGZjy2IF2.js +0 -1
  108. package/client/dist/assets/setup-CXa0Sx16.js +0 -1
  109. package/client/dist/assets/setup-CmK87LuS.js +0 -1
  110. package/client/dist/assets/setup-D2ktKKMF2.js +0 -1
  111. package/client/dist/assets/setup-DG2H7ha5.js +0 -1
  112. package/client/dist/assets/setup-DK5bHDUq2.js +0 -1
  113. package/client/dist/assets/setup-DrWlIQTZ.js +0 -1
  114. package/client/dist/assets/setup-rzHUVgAu.js +0 -1
  115. /package/client/dist/assets/{activity-C6kJpm9q2.js → activity-C6kJpm9q.js} +0 -0
  116. /package/client/dist/assets/{activity-DAyekdlt2.js → activity-DAyekdlt.js} +0 -0
  117. /package/client/dist/assets/{addspec-DbNBbrha2.js → addspec-DbNBbrha.js} +0 -0
  118. /package/client/dist/assets/{addspec-htHGZHOe2.js → addspec-htHGZHOe.js} +0 -0
  119. /package/client/dist/assets/{agents-6ZOJXWVk2.js → agents-6ZOJXWVk.js} +0 -0
  120. /package/client/dist/assets/{agents-Bz-thHkB2.js → agents-Bz-thHkB.js} +0 -0
  121. /package/client/dist/assets/{agentstudio-BLGvxWBx2.js → agentstudio-BLGvxWBx.js} +0 -0
  122. /package/client/dist/assets/{agentstudio-D9UKiWTj2.js → agentstudio-D9UKiWTj.js} +0 -0
  123. /package/client/dist/assets/{aiedit-BoG24nlg2.js → aiedit-BoG24nlg.js} +0 -0
  124. /package/client/dist/assets/{aiedit-olnv3QSt2.js → aiedit-olnv3QSt.js} +0 -0
  125. /package/client/dist/assets/{analytics-CdsAPAwZ2.js → analytics-CdsAPAwZ.js} +0 -0
  126. /package/client/dist/assets/{analytics-IEsKZvpW2.js → analytics-IEsKZvpW.js} +0 -0
  127. /package/client/dist/assets/{attachments-D0wXXt9T2.js → attachments-D0wXXt9T.js} +0 -0
  128. /package/client/dist/assets/{attachments-V_WZAYXx2.js → attachments-V_WZAYXx.js} +0 -0
  129. /package/client/dist/assets/{browser-CyR1Kp3m2.js → browser-CyR1Kp3m.js} +0 -0
  130. /package/client/dist/assets/{browser-DwZljcRh2.js → browser-DwZljcRh.js} +0 -0
  131. /package/client/dist/assets/{chat-BR8oe53x2.js → chat-BR8oe53x.js} +0 -0
  132. /package/client/dist/assets/{chat-DZyu3ImY2.js → chat-DZyu3ImY.js} +0 -0
  133. /package/client/dist/assets/{code-D20ef9Cf2.js → code-D20ef9Cf.js} +0 -0
  134. /package/client/dist/assets/{code-geHmknB92.js → code-geHmknB9.js} +0 -0
  135. /package/client/dist/assets/{commands-BEwbAjtD2.js → commands-BEwbAjtD.js} +0 -0
  136. /package/client/dist/assets/{commands-DTQBF_q52.js → commands-DTQBF_q5.js} +0 -0
  137. /package/client/dist/assets/{common-BNbnv8cE2.js → common-BNbnv8cE.js} +0 -0
  138. /package/client/dist/assets/{common-DBtTzeYm2.js → common-DBtTzeYm.js} +0 -0
  139. /package/client/dist/assets/{dashboard-Cqmmbh3l2.js → dashboard-Cqmmbh3l.js} +0 -0
  140. /package/client/dist/assets/{dashboard-Dy2Um-US2.js → dashboard-Dy2Um-US.js} +0 -0
  141. /package/client/dist/assets/{explore-C0TjprJW2.js → explore-C0TjprJW.js} +0 -0
  142. /package/client/dist/assets/{explore-DsXAgnmJ2.js → explore-DsXAgnmJ.js} +0 -0
  143. /package/client/dist/assets/{integrations-CH0zy1Mw2.js → integrations-CH0zy1Mw.js} +0 -0
  144. /package/client/dist/assets/{integrations-b8pH-vfl2.js → integrations-b8pH-vfl.js} +0 -0
  145. /package/client/dist/assets/{jira-CNeYi06w2.js → jira-CNeYi06w.js} +0 -0
  146. /package/client/dist/assets/{jira-pRaL7JQo2.js → jira-pRaL7JQo.js} +0 -0
  147. /package/client/dist/assets/{jobs-B-sDHEay2.js → jobs-B-sDHEay.js} +0 -0
  148. /package/client/dist/assets/{jobs-DzSIR8V62.js → jobs-DzSIR8V6.js} +0 -0
  149. /package/client/dist/assets/{loops-Crc9It8o2.js → loops-Crc9It8o.js} +0 -0
  150. /package/client/dist/assets/{loops-DH2hZcbK2.js → loops-DH2hZcbK.js} +0 -0
  151. /package/client/dist/assets/{loops-DS5TmvIU2.js → loops-DS5TmvIU.js} +0 -0
  152. /package/client/dist/assets/{mcp-Czd_iyp02.js → mcp-Czd_iyp0.js} +0 -0
  153. /package/client/dist/assets/{mcp-D4ZjtIx32.js → mcp-D4ZjtIx3.js} +0 -0
  154. /package/client/dist/assets/{mcp-DIfBUv2D2.js → mcp-DIfBUv2D.js} +0 -0
  155. /package/client/dist/assets/{nav-BRJBxaGt2.js → nav-BRJBxaGt.js} +0 -0
  156. /package/client/dist/assets/{nav-CNskRJKQ2.js → nav-CNskRJKQ.js} +0 -0
  157. /package/client/dist/assets/{nav-TvumAFD22.js → nav-TvumAFD2.js} +0 -0
  158. /package/client/dist/assets/{settings-DAZqjX2a2.js → settings-DAZqjX2a.js} +0 -0
  159. /package/client/dist/assets/{settings-DRh39v1S2.js → settings-DRh39v1S.js} +0 -0
  160. /package/client/dist/assets/{settings-Mj1OJbfd2.js → settings-Mj1OJbfd.js} +0 -0
  161. /package/client/dist/assets/{specs-BcyxaCwf2.js → specs-BcyxaCwf.js} +0 -0
  162. /package/client/dist/assets/{specs-CLZKysq-2.js → specs-CLZKysq-.js} +0 -0
  163. /package/client/dist/assets/{specs-dV3Mq5Mu2.js → specs-dV3Mq5Mu.js} +0 -0
  164. /package/client/dist/assets/{terminal-C2NAd7BW2.js → terminal-C2NAd7BW.js} +0 -0
  165. /package/client/dist/assets/{terminal-IahE4LW92.js → terminal-IahE4LW9.js} +0 -0
  166. /package/client/dist/assets/{terminal-QpXZU2Bn2.js → terminal-QpXZU2Bn.js} +0 -0
  167. /package/client/dist/assets/{tickets-Bs37scwY2.js → tickets-Bs37scwY.js} +0 -0
  168. /package/client/dist/assets/{tickets-DQp7JS2R2.js → tickets-DQp7JS2R.js} +0 -0
  169. /package/client/dist/assets/{tickets-DpmdE8tv2.js → tickets-DpmdE8tv.js} +0 -0
@@ -11,6 +11,10 @@ const code_explorer_router_1 = require("./code-explorer-router");
11
11
  const jira_router_1 = require("./jira-router");
12
12
  const ticket_store_1 = require("./ticket-store");
13
13
  const workspace_resolution_1 = require("./workspace-resolution");
14
+ const workspace_manager_1 = require("./workspace-manager");
15
+ const blueprint_render_1 = require("./blueprint-render");
16
+ const blueprint_draft_parser_1 = require("./blueprint-draft-parser");
17
+ const blueprint_spec_quality_1 = require("./blueprint-spec-quality");
14
18
  const file_story_manager_1 = require("./file-story-manager");
15
19
  const file_summary_generator_1 = require("./file-summary-generator");
16
20
  const providers_1 = require("./providers");
@@ -24,15 +28,16 @@ const project_router_terminals_1 = require("./project-router-terminals");
24
28
  const project_router_settings_1 = require("./project-router-settings");
25
29
  const project_router_loop_runs_1 = require("./project-router-loop-runs");
26
30
  const project_router_git_1 = require("./project-router-git");
31
+ const project_router_helpers_1 = require("./project-router-helpers");
27
32
  // Re-export the spec helpers from their new home so existing importers
28
33
  // (`import { ... } from './project-router'`) keep working unchanged.
29
- var project_router_helpers_1 = require("./project-router-helpers");
30
- Object.defineProperty(exports, "stripSpecMetadataSections", { enumerable: true, get: function () { return project_router_helpers_1.stripSpecMetadataSections; } });
31
- Object.defineProperty(exports, "extractShortSummary", { enumerable: true, get: function () { return project_router_helpers_1.extractShortSummary; } });
32
- Object.defineProperty(exports, "deriveFallbackShortSummary", { enumerable: true, get: function () { return project_router_helpers_1.deriveFallbackShortSummary; } });
33
- Object.defineProperty(exports, "lightlyStructurePrompt", { enumerable: true, get: function () { return project_router_helpers_1.lightlyStructurePrompt; } });
34
- Object.defineProperty(exports, "formatDescriptionWithCriteria", { enumerable: true, get: function () { return project_router_helpers_1.formatDescriptionWithCriteria; } });
35
- Object.defineProperty(exports, "resolveDefaultSpecModel", { enumerable: true, get: function () { return project_router_helpers_1.resolveDefaultSpecModel; } });
34
+ var project_router_helpers_2 = require("./project-router-helpers");
35
+ Object.defineProperty(exports, "stripSpecMetadataSections", { enumerable: true, get: function () { return project_router_helpers_2.stripSpecMetadataSections; } });
36
+ Object.defineProperty(exports, "extractShortSummary", { enumerable: true, get: function () { return project_router_helpers_2.extractShortSummary; } });
37
+ Object.defineProperty(exports, "deriveFallbackShortSummary", { enumerable: true, get: function () { return project_router_helpers_2.deriveFallbackShortSummary; } });
38
+ Object.defineProperty(exports, "lightlyStructurePrompt", { enumerable: true, get: function () { return project_router_helpers_2.lightlyStructurePrompt; } });
39
+ Object.defineProperty(exports, "formatDescriptionWithCriteria", { enumerable: true, get: function () { return project_router_helpers_2.formatDescriptionWithCriteria; } });
40
+ Object.defineProperty(exports, "resolveDefaultSpecModel", { enumerable: true, get: function () { return project_router_helpers_2.resolveDefaultSpecModel; } });
36
41
  function createProjectRouter(registry) {
37
42
  const router = (0, express_1.Router)({ mergeParams: true });
38
43
  // Middleware: resolve project from :projectId param
@@ -139,6 +144,22 @@ function createProjectRouter(registry) {
139
144
  });
140
145
  codeRouter(req, res, next);
141
146
  });
147
+ // Project Builder sidebar re-entry (add-project-builder D5): read the
148
+ // workspace-resident blueprint pair's source of truth. 404 when the project
149
+ // has no blueprint (every non-Builder project) — the sidebar entry hides.
150
+ router.get('/:projectId/blueprint', (req, res) => {
151
+ const project = ctx(req).project;
152
+ const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
153
+ const workspace = exec.relocated && exec.workspaceDir
154
+ ? exec.workspaceDir
155
+ : (project.slug ? (0, workspace_manager_1.workspacePathFor)(project.slug) : null);
156
+ const blueprint = workspace ? (0, blueprint_render_1.readBlueprint)(workspace) : null;
157
+ if (!blueprint) {
158
+ res.status(404).json({ error: 'no blueprint for this project' });
159
+ return;
160
+ }
161
+ res.json({ blueprint });
162
+ });
142
163
  // Relocate-artifacts gate (single chokepoint for ALL project-router ticket
143
164
  // I/O): relocated ⇒ the registry entry's ticketsPath (workspace); legacy ⇒
144
165
  // resolveTicketStoragePath (preserves integration-contract.json custom
@@ -148,6 +169,108 @@ function createProjectRouter(registry) {
148
169
  const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
149
170
  return exec.relocated ? exec.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(project.path);
150
171
  };
172
+ // Milestone batch commit (add-project-builder D7): insert one blueprint
173
+ // milestone's generated specs as `M<n>`-labeled todo tickets, flip that
174
+ // milestone to `committed`, and re-render the blueprint pair. Jira-connected
175
+ // projects ride the existing materializer on the store mutation unchanged.
176
+ router.post('/:projectId/blueprint/commit-milestone', (req, res) => {
177
+ const projectCtx = ctx(req);
178
+ const project = projectCtx.project;
179
+ const body = (req.body ?? {});
180
+ const milestoneId = typeof body.milestoneId === 'string' && /^m[0-9]{1,3}$/i.test(body.milestoneId)
181
+ ? body.milestoneId.toLowerCase()
182
+ : null;
183
+ if (!milestoneId) {
184
+ res.status(400).json({ error: 'milestoneId (e.g. "m2") is required' });
185
+ return;
186
+ }
187
+ const rawSpecs = Array.isArray(body.specs) ? body.specs : [];
188
+ if (rawSpecs.length === 0) {
189
+ res.status(400).json({ error: 'specs must contain at least one titled spec' });
190
+ return;
191
+ }
192
+ const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
193
+ const workspace = exec.relocated && exec.workspaceDir
194
+ ? exec.workspaceDir
195
+ : (project.slug ? (0, workspace_manager_1.workspacePathFor)(project.slug) : null);
196
+ const blueprint = workspace ? (0, blueprint_render_1.readBlueprint)(workspace) : null;
197
+ if (!workspace || !blueprint) {
198
+ res.status(404).json({ error: 'no blueprint for this project' });
199
+ return;
200
+ }
201
+ const milestone = blueprint.milestones.find((m) => m.id === milestoneId);
202
+ if (!milestone) {
203
+ res.status(404).json({ error: `milestone ${milestoneId} not found in the blueprint` });
204
+ return;
205
+ }
206
+ if (milestone.status !== 'planned') {
207
+ res.status(409).json({ error: `milestone ${milestoneId} is already ${milestone.status}` });
208
+ return;
209
+ }
210
+ const label = milestoneId.toUpperCase();
211
+ const quality = (0, blueprint_spec_quality_1.analyzeBuilderSpecBatch)({ specsComplete: body.specsComplete, specs: rawSpecs }, { milestoneLabel: label, minSpecs: 1, maxSpecs: 10, requireScaffold: false });
212
+ if (!quality.valid) {
213
+ res.status(400).json({ error: 'milestone_spec_quality_invalid', detail: (0, blueprint_spec_quality_1.firstBuilderSpecQualityDetail)(quality) });
214
+ return;
215
+ }
216
+ const coerced = (0, blueprint_draft_parser_1.coerceBlueprint)({ blueprintVersion: 1, specsComplete: true, m1Specs: rawSpecs });
217
+ const specs = coerced?.m1Specs ?? [];
218
+ try {
219
+ const created = [];
220
+ (0, ticket_store_1.mutateStore)(ticketPath(req), (store) => {
221
+ const indexToId = new Map();
222
+ specs.forEach((spec, index) => {
223
+ const id = store.next_id;
224
+ store.next_id += 1;
225
+ const now = new Date().toISOString();
226
+ const labels = spec.labels.includes(label) ? spec.labels : [...spec.labels, label];
227
+ const prerequisite = spec.dependsOnIndex;
228
+ const prerequisites = prerequisite !== undefined && indexToId.has(prerequisite)
229
+ ? [indexToId.get(prerequisite)]
230
+ : [];
231
+ const ticket = {
232
+ id,
233
+ title: spec.title,
234
+ description: (0, project_router_helpers_1.formatDescriptionWithCriteria)(spec.description, spec.acceptanceCriteria),
235
+ status: 'todo',
236
+ priority: spec.priority,
237
+ labels,
238
+ assignee: null,
239
+ prerequisites,
240
+ metadata: {},
241
+ origin_conversation_id: null,
242
+ is_epic: false,
243
+ parent_epic_id: null,
244
+ execution_order: null,
245
+ short_summary: spec.shortSummary,
246
+ created_at: now,
247
+ updated_at: now,
248
+ created_by: 'project-builder',
249
+ source: 'project-builder',
250
+ };
251
+ store.tickets[String(id)] = ticket;
252
+ created.push(ticket);
253
+ indexToId.set(index, id);
254
+ });
255
+ });
256
+ milestone.status = 'committed';
257
+ milestone.ticketIds = created.map((t) => t.id);
258
+ (0, blueprint_render_1.writeBlueprintPair)(workspace, blueprint);
259
+ for (const ticket of created) {
260
+ projectCtx.broadcast({
261
+ type: 'ticket_created',
262
+ ticket: ticket,
263
+ projectId: project.id,
264
+ timestamp: new Date().toISOString(),
265
+ });
266
+ }
267
+ res.status(201).json({ insertedIds: created.map((t) => t.id), milestone: milestone.id });
268
+ }
269
+ catch (err) {
270
+ console.error('[project-router] commit-milestone error:', err);
271
+ res.status(500).json({ error: 'Failed to commit the milestone' });
272
+ }
273
+ });
151
274
  const deps = { router, registry, ctx, ticketPath };
152
275
  (0, project_router_jobs_1.registerJobsRoutes)(deps);
153
276
  (0, project_router_spending_1.registerSpendingRoutes)(deps);
@@ -71,8 +71,14 @@ function commonFlagsFor(opts) {
71
71
  return [
72
72
  // No approval bypass is necessary when no tool can be invoked. Keeping it
73
73
  // off also makes the pure-output boundary fail closed if Claude ever
74
- // changes how it interprets the sentinel.
75
- ...(toolPolicy === 'none' ? [] : ['--dangerously-skip-permissions']),
74
+ // changes how it interprets the sentinel. Read-only work uses Claude's
75
+ // native plan mode: Read/Grep/Glob stay usable without granting the
76
+ // blanket permission bypass carried by normal autonomous turns.
77
+ ...(toolPolicy === 'default'
78
+ ? ['--dangerously-skip-permissions']
79
+ : toolPolicy === 'read-only'
80
+ ? ['--permission-mode', 'plan', '--safe-mode']
81
+ : []),
76
82
  ...toolArgs,
77
83
  '--output-format', 'stream-json',
78
84
  '--verbose',
@@ -33,6 +33,9 @@ const WHICH_CMD = process.platform === 'win32' ? 'where' : 'which';
33
33
  const CODEX_MIN_VERSION = '0.128.0';
34
34
  exports._CODEX_MIN_VERSION = CODEX_MIN_VERSION;
35
35
  const CODEX_MODELS = [
36
+ { value: 'gpt-5.6-sol', label: 'GPT-5.6 Sol' },
37
+ { value: 'gpt-5.6-terra', label: 'GPT-5.6 Terra' },
38
+ { value: 'gpt-5.6-luna', label: 'GPT-5.6 Luna' },
36
39
  { value: 'gpt-5.5', label: 'GPT-5.5', default: true },
37
40
  { value: 'gpt-5.4', label: 'GPT-5.4' },
38
41
  { value: 'gpt-5.4-mini', label: 'GPT-5.4 Mini' },
@@ -41,11 +44,19 @@ const CODEX_MODELS = [
41
44
  const SANDBOX_FLAGS = ['--sandbox', 'workspace-write'];
42
45
  const RAIL_SANDBOX_FLAGS = ['--sandbox', 'danger-full-access'];
43
46
  // `codex exec resume` does NOT accept `--sandbox` (the flag only exists on
44
- // `codex exec`); pass the policy as a `-c` config override instead so the
45
- // resumed session honours workspace-write even when the per-project
46
- // `.codex/config.toml` isn't on the spawn cwd (e.g. explore-cwd).
47
- const SANDBOX_RESUME_FLAGS = ['-c', 'sandbox_mode="workspace-write"'];
47
+ // `codex exec`); `resumeSandboxFlags` passes the selected policy as a `-c`
48
+ // config override instead, even when the per-project `.codex/config.toml`
49
+ // isn't on the spawn cwd (e.g. explore-cwd).
48
50
  const SKIP_GIT_CHECK = '--skip-git-repo-check';
51
+ function sandboxFlags(opts, rail = false) {
52
+ if (opts.toolPolicy === 'read-only')
53
+ return ['--sandbox', 'read-only'];
54
+ return rail ? [...RAIL_SANDBOX_FLAGS] : [...SANDBOX_FLAGS];
55
+ }
56
+ function resumeSandboxFlags(opts) {
57
+ const mode = opts.toolPolicy === 'read-only' ? 'read-only' : 'workspace-write';
58
+ return ['-c', `sandbox_mode="${mode}"`];
59
+ }
49
60
  /** Fold system prompt into the user prompt for providers without --system-prompt. */
50
61
  function fold(systemPrompt, prompt) {
51
62
  if (!systemPrompt)
@@ -76,7 +87,7 @@ function buildCodexArgs(action, opts) {
76
87
  // ("quiero hacer un tetris"), the long system text dominates the prompt
77
88
  // and codex responds to the system instructions instead of the user.
78
89
  // Trust AGENTS.md and pass only the user prompt.
79
- args.push('exec', '--json', ...SANDBOX_FLAGS, SKIP_GIT_CHECK);
90
+ args.push('exec', '--json', ...sandboxFlags(opts), SKIP_GIT_CHECK);
80
91
  args.push(opts.prompt);
81
92
  args.push('--model', opts.model);
82
93
  // Native reasoning effort (codex 0.139+). String value is quoted to match
@@ -95,7 +106,7 @@ function buildCodexArgs(action, opts) {
95
106
  case 'agent-refine':
96
107
  case 'auto-title':
97
108
  case 'setup-enrich': {
98
- args.push('exec', '--json', ...SANDBOX_FLAGS, SKIP_GIT_CHECK);
109
+ args.push('exec', '--json', ...sandboxFlags(opts), SKIP_GIT_CHECK);
99
110
  args.push(fold(opts.systemPrompt, opts.prompt));
100
111
  args.push('--model', opts.model);
101
112
  // Native reasoning effort (codex 0.139+). String value is quoted to match
@@ -113,7 +124,7 @@ function buildCodexArgs(action, opts) {
113
124
  // See chat-turn note: AGENTS.md in explore-cwd carries the Explore
114
125
  // framing; the per-turn argv must stay user-text-only so codex doesn't
115
126
  // mistake the system prompt for the user request.
116
- args.push('exec', 'resume', '--json', ...SANDBOX_RESUME_FLAGS, SKIP_GIT_CHECK);
127
+ args.push('exec', 'resume', '--json', ...resumeSandboxFlags(opts), SKIP_GIT_CHECK);
117
128
  args.push(opts.sessionId);
118
129
  args.push(opts.prompt);
119
130
  args.push('--model', opts.model);
@@ -133,7 +144,7 @@ function buildCodexArgs(action, opts) {
133
144
  if (!opts.sessionId) {
134
145
  throw new Error(`${action} requires sessionId`);
135
146
  }
136
- args.push('exec', 'resume', '--json', ...SANDBOX_RESUME_FLAGS, SKIP_GIT_CHECK);
147
+ args.push('exec', 'resume', '--json', ...resumeSandboxFlags(opts), SKIP_GIT_CHECK);
137
148
  args.push(opts.sessionId);
138
149
  args.push(fold(opts.systemPrompt, opts.prompt));
139
150
  args.push('--model', opts.model);
@@ -157,7 +168,7 @@ function buildCodexArgs(action, opts) {
157
168
  // On Windows, Codex's workspace-write sandbox can fail before the first
158
169
  // shell command with `windows sandbox: spawn setup refresh`; full access
159
170
  // matches the existing fully-autonomous rail contract.
160
- args.push('exec', '--json', ...RAIL_SANDBOX_FLAGS, SKIP_GIT_CHECK);
171
+ args.push('exec', '--json', ...sandboxFlags(opts, true), SKIP_GIT_CHECK);
161
172
  args.push(fold(opts.systemPrompt, opts.prompt));
162
173
  args.push('--model', opts.model);
163
174
  // Native reasoning effort (codex 0.139+). String value is quoted to match
@@ -333,7 +344,9 @@ exports.codexAdapter = {
333
344
  profileEnvSupport: true,
334
345
  systemPromptArg: false,
335
346
  supportsReasoningEffort: true,
336
- reasoningEfforts: ['minimal', 'low', 'medium', 'high'], // codex model_reasoning_effort values
347
+ // codex model_reasoning_effort values. xhigh/max ship with GPT-5.6 (all
348
+ // tiers); ultra is Sol-only upstream — codex validates the combo itself.
349
+ reasoningEfforts: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max', 'ultra'],
337
350
  supportsImageInput: true, // codex-cli `-i/--image <FILE>...` (verified 0.141)
338
351
  },
339
352
  modelCatalog: () => CODEX_MODELS,
@@ -44,10 +44,19 @@ const GEMINI_MODELS = [
44
44
  { value: 'gemini-2.5-flash-lite', label: 'Gemini 2.5 Flash Lite' },
45
45
  ];
46
46
  const STREAM_JSON_FLAGS = ['--output-format', 'stream-json'];
47
- // Auto-approve tool calls so headless turns never block on an approval prompt.
48
- // Parity with codex's workspace-write/full-access sandbox; the non-destructive
49
- // stance for Explore is carried by GEMINI.md + the system prompt, not a sandbox.
47
+ // Default autonomous turns auto-approve tool calls so they never block on a
48
+ // headless approval prompt. `approvalFlags` replaces this with native plan mode
49
+ // for explicitly read-only work; the two modes are never combined.
50
50
  const YOLO_FLAG = '--yolo';
51
+ function approvalFlags(opts) {
52
+ // Gemini CLI has no Codex-style selectable filesystem sandbox mode. Its
53
+ // verified native read-only boundary is approval-mode=plan (documented by
54
+ // the CLI itself as "plan (read-only mode)"). Crucially, never combine it
55
+ // with --yolo, which would auto-approve every action.
56
+ return opts.toolPolicy === 'read-only'
57
+ ? ['--approval-mode', 'plan']
58
+ : [YOLO_FLAG];
59
+ }
51
60
  /** Fold system prompt into the user prompt for providers without --system-prompt. */
52
61
  function fold(systemPrompt, prompt) {
53
62
  if (!systemPrompt)
@@ -63,12 +72,12 @@ function buildGeminiArgs(action, opts) {
63
72
  // short user message ("quiero hacer un tetris") makes the model answer the
64
73
  // system instructions instead of the user — so pass user text only and
65
74
  // trust GEMINI.md, exactly like the codex adapter trusts AGENTS.md.
66
- return ['-p', opts.prompt, ...model, ...STREAM_JSON_FLAGS, YOLO_FLAG, ...(opts.extraArgs ?? [])];
75
+ return ['-p', opts.prompt, ...model, ...STREAM_JSON_FLAGS, ...approvalFlags(opts), ...(opts.extraArgs ?? [])];
67
76
  }
68
77
  case 'chat-resume': {
69
78
  if (!opts.sessionId)
70
79
  throw new Error(`${action} requires sessionId`);
71
- return ['-p', opts.prompt, ...model, ...STREAM_JSON_FLAGS, '--resume', opts.sessionId, YOLO_FLAG, ...(opts.extraArgs ?? [])];
80
+ return ['-p', opts.prompt, ...model, ...STREAM_JSON_FLAGS, '--resume', opts.sessionId, ...approvalFlags(opts), ...(opts.extraArgs ?? [])];
72
81
  }
73
82
  case 'chat-stream': {
74
83
  // Gemini has no persistent-stdin multi-turn transport; the Explore
@@ -81,12 +90,12 @@ function buildGeminiArgs(action, opts) {
81
90
  case 'auto-title':
82
91
  case 'setup-enrich':
83
92
  case 'rail-job': {
84
- return ['-p', fold(opts.systemPrompt, opts.prompt), ...model, ...STREAM_JSON_FLAGS, YOLO_FLAG, ...(opts.extraArgs ?? [])];
93
+ return ['-p', fold(opts.systemPrompt, opts.prompt), ...model, ...STREAM_JSON_FLAGS, ...approvalFlags(opts), ...(opts.extraArgs ?? [])];
85
94
  }
86
95
  case 'setup-enrich-resume': {
87
96
  if (!opts.sessionId)
88
97
  throw new Error(`${action} requires sessionId`);
89
- return ['-p', fold(opts.systemPrompt, opts.prompt), ...model, ...STREAM_JSON_FLAGS, '--resume', opts.sessionId, YOLO_FLAG, ...(opts.extraArgs ?? [])];
98
+ return ['-p', fold(opts.systemPrompt, opts.prompt), ...model, ...STREAM_JSON_FLAGS, '--resume', opts.sessionId, ...approvalFlags(opts), ...(opts.extraArgs ?? [])];
90
99
  }
91
100
  }
92
101
  }
@@ -408,10 +408,18 @@ async function launchIsolatedRail(input, io = {}) {
408
408
  // overlay their repo's own untracked on-disk entries instead. Resolved ONCE
409
409
  // per launch; a resolution failure degrades to the legacy (repo) source.
410
410
  let overlaySourceRoot = baseRepo;
411
+ let overlayFallbackRoots;
411
412
  try {
412
413
  const exec = resolveExecution({ slug, path: baseRepo });
413
- if (exec.relocated && exec.workspaceDir)
414
+ if (exec.relocated && exec.workspaceDir) {
414
415
  overlaySourceRoot = exec.workspaceDir;
416
+ // The repo as FALLBACK root: repo-resident untracked carve-outs
417
+ // (OpenSpec's `/opsx:*` commands, `openspec-*` skills, user extras)
418
+ // are absent from both the checkout (untracked) and the workspace
419
+ // (the framework ships only `specrails/`), so without this the claude
420
+ // CLI reports `Unknown command: /opsx:ff` inside isolated rails.
421
+ overlayFallbackRoots = [baseRepo];
422
+ }
415
423
  }
416
424
  catch { /* legacy fallback */ }
417
425
  let overlayProviderDir = '.claude';
@@ -668,6 +676,7 @@ async function launchIsolatedRail(input, io = {}) {
668
676
  const res = overlay({
669
677
  worktreePath: handle.worktreePath,
670
678
  sourceRoot: overlaySourceRoot,
679
+ fallbackSourceRoots: overlayFallbackRoots,
671
680
  providerDir: overlayProviderDir,
672
681
  instructionsFilename: overlayInstructions,
673
682
  });
@@ -847,6 +856,7 @@ async function launchIsolatedRail(input, io = {}) {
847
856
  a.overlayCleanupEvidence = (0, worktree_overlay_1.revalidateOverlayCleanupEvidence)({
848
857
  worktreePath: a.handle.worktreePath,
849
858
  sourceRoot: overlaySourceRoot,
859
+ fallbackSourceRoots: overlayFallbackRoots,
850
860
  providerDir: overlayProviderDir,
851
861
  instructionsFilename: overlayInstructions,
852
862
  }, a.overlayCleanupEvidence);
@@ -26,8 +26,8 @@ const crypto_1 = require("crypto");
26
26
  const artifact_registry_1 = require("./artifact-registry");
27
27
  const install_config_path_1 = require("./install-config-path");
28
28
  const bundled_core_1 = require("./bundled-core");
29
- const bundled_openspec_1 = require("./bundled-openspec");
30
29
  const path_resolver_1 = require("./path-resolver");
30
+ const offline_assemble_1 = require("./offline-assemble");
31
31
  const framework_manager_1 = require("./framework-manager");
32
32
  const workspace_resolution_1 = require("./workspace-resolution");
33
33
  /**
@@ -90,58 +90,10 @@ function spawnCoreInit(args, cwd) {
90
90
  stdio: ['ignore', 'pipe', 'pipe'],
91
91
  });
92
92
  }
93
- /**
94
- * Spawn the BUNDLED specrails-core `init` (offline framework + assemble; openspec
95
- * is the only network step). Runs `node <bundled-cli> init <args>` with
96
- * `SPECRAILS_CORE_SCRIPT_DIR` pointed at the bundle so core's template/command
97
- * sources resolve from the app bundle, NOT a global install or npm registry. The
98
- * framework was already materialized by FrameworkManager.materialize() (idempotent
99
- * — core's ensureFramework skips re-materialization), so this call only assembles
100
- * the workspace by symlink + runs openspec init.
101
- *
102
- * Returns null when no bundled core is present (caller falls back to spawnCoreInit).
103
- */
104
- function spawnBundledCoreInit(args, cwd) {
105
- const cli = (0, bundled_core_1.getBundledCoreCli)();
106
- const coreRoot = (0, bundled_core_1.getBundledCoreRoot)();
107
- if (!cli || !coreRoot)
108
- return null;
109
- const fullArgs = [cli, 'init', ...args];
110
- // Force RELOCATION (core installs in-repo by default; the desktop keeps the
111
- // user's repo pristine + manages the spawn cwd). See spawnCoreInit.
112
- const env = { ...(0, win_spawn_1.windowsSpawnEnv)(), SPECRAILS_CORE_SCRIPT_DIR: coreRoot, SPECRAILS_RELOCATE: '1' };
113
- // Bundled openspec (offline) — the LAST network step of project-add. When the
114
- // app ships @fission-ai/openspec, point specrails-core's `installOpenSpecProject`
115
- // at the bundled CLI and the SAME node we run the bundled core with. Tauri
116
- // strips exec bits from bundled resources and the openspec CLI is a node script,
117
- // so it MUST be invoked as `node <cli> init …` — hence we set BOTH the BIN and
118
- // the NODE env (specrails-core's buildOpenSpecInvocation form 1). When absent we
119
- // leave them unset → core falls back to `npx @fission-ai/openspec` (still works
120
- // online). Bundled core + bundled openspec ⇒ project-add is FULLY OFFLINE.
121
- const openspecCli = (0, bundled_openspec_1.getBundledOpenspecCli)();
122
- if (openspecCli) {
123
- env.SPECRAILS_OPENSPEC_BIN = openspecCli;
124
- // Core runs openspec as `<node> <cli> init …`. SPECRAILS_OPENSPEC_NODE MUST be
125
- // a REAL node executable — NOT process.execPath (the packaged `specrails-server`
126
- // pkg binary), which cannot run openspec's ESM CLI and made `openspec init`
127
- // exit with code -1 (→ core throws InstallerError code 50, setup fails). Prefer
128
- // the bundled Node; fall back to `node` on PATH (resolveStartupPath prepends the
129
- // bundled node bin in desktop mode, so PATH `node` is the bundled node too).
130
- env.SPECRAILS_OPENSPEC_NODE = (0, path_resolver_1.resolveBundledNodeExe)() ?? 'node';
131
- }
132
- // Run the core CLI with the REAL bundled node — NOT process.execPath, which in
133
- // the packaged app is the `specrails-server` pkg binary and would re-launch the
134
- // server instead of running cli.js (same trap as SPECRAILS_OPENSPEC_NODE above).
135
- // Fall back to process.execPath only in dev/tests where it IS node.
136
- const nodeBin = (0, path_resolver_1.resolveBundledNodeExe)() ?? process.execPath;
137
- console.log(`[SetupManager] spawning BUNDLED core: ${nodeBin} ${fullArgs.join(' ')} (cwd=${cwd})` +
138
- (openspecCli ? ' [bundled openspec: offline]' : ' [openspec: npx fallback]'));
139
- return (0, win_spawn_1.spawnCli)(nodeBin, fullArgs, {
140
- cwd,
141
- env,
142
- stdio: ['ignore', 'pipe', 'pipe'],
143
- });
144
- }
93
+ // The bundled-core `init` spawn primitive is shared with the Project Builder's
94
+ // orchestrated commit (extract-don't-fork, add-project-builder D4) — the wizard
95
+ // keeps its streaming/phase shell and npx fallback around it.
96
+ // (Imported: spawnBundledCoreInit from ./offline-assemble.)
145
97
  // H19: hard cap on the runtime probe. `npx --yes --prefer-online
146
98
  // <CORE_PACKAGE_SPEC> version` does a network round-trip to the npm
147
99
  // registry, and spawnSync blocks the single event loop — without a timeout a
@@ -867,7 +819,7 @@ class SetupManager {
867
819
  }
868
820
  // Bundled core (offline, node <cli> init) when available, else legacy npx.
869
821
  const child = useBundledCore
870
- ? spawnBundledCoreInit(initArgs, projectPath)
822
+ ? (0, offline_assemble_1.spawnBundledCoreInit)(initArgs, projectPath)
871
823
  : spawnCoreInit(initArgs, projectPath);
872
824
  this._installProcesses.set(projectId, child);
873
825
  this._installLogBuffer.set(projectId, diagHeader);
@@ -11,6 +11,9 @@ exports.CLAUDE_MODELS = [
11
11
  { value: 'haiku', label: 'Claude Haiku' },
12
12
  ];
13
13
  exports.CODEX_MODELS = [
14
+ { value: 'gpt-5.6-sol', label: 'GPT-5.6 Sol' },
15
+ { value: 'gpt-5.6-terra', label: 'GPT-5.6 Terra' },
16
+ { value: 'gpt-5.6-luna', label: 'GPT-5.6 Luna' },
14
17
  { value: 'gpt-5.5', label: 'GPT-5.5' },
15
18
  { value: 'gpt-5.4', label: 'GPT-5.4' },
16
19
  { value: 'gpt-5.4-mini', label: 'GPT-5.4 Mini' },
@@ -99,7 +99,9 @@ function ensureFrameworkAgents(workspaceDir, providerDir, home) {
99
99
  }
100
100
  /** The framework subtrees `assemble` installs as a single DIR-symlink into
101
101
  * `current/<provider>/` (unlike agents, which are per-file). `/specrails:*`
102
- * and `/opsx:*` slash commands live under `commands/`. */
102
+ * slash commands live under `commands/`. (`/opsx:*` is NOT framework — the
103
+ * external OpenSpec binary installs it into the REPO's provider dir; isolated
104
+ * rails receive it via the worktree overlay's repo fallback root.) */
103
105
  const DIR_LINKED_SUBTREES = ['commands', 'skills', 'rules'];
104
106
  /**
105
107
  * Windows repair for the DIR-symlinked framework subtrees (`commands`, `skills`,