specrails-desktop 2.8.0 → 2.9.1
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.
- package/README.md +23 -19
- package/client/dist/assets/{ActivityFeedPage-LKqd18-G.js → ActivityFeedPage-DpQzYMBz.js} +1 -1
- package/client/dist/assets/{AgentsPage-Cb-b-6Ot.js → AgentsPage-29fCY8qV.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-HVxQQ1wy.js → AnalyticsPage-BwGtS6Hf.js} +1 -1
- package/client/dist/assets/{BarChart-BOyHB0dw.js → BarChart-CTR97DVC.js} +1 -1
- package/client/dist/assets/{CodePage-DnOnwKGB.js → CodePage-yAAxKasA.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-D2auU39x.js → DesktopAnalyticsPage-BdK_XpsD.js} +1 -1
- package/client/dist/assets/{DocsDialog-CTuDX3GK.js → DocsDialog-BaE0cLlL.js} +2 -2
- package/client/dist/assets/{DocsPage-DRyMmu0Z.js → DocsPage-c1FgZX8_.js} +2 -2
- package/client/dist/assets/{ExportDropdown-DO-GGiMh.js → ExportDropdown-lPv_yDen.js} +1 -1
- package/client/dist/assets/{IntegrationsPage-BhbO4jFT.js → IntegrationsPage-DOpxRe7G.js} +1 -1
- package/client/dist/assets/{JobDetailPage-DJooEg1s.js → JobDetailPage-5ExzXY-F.js} +1 -1
- package/client/dist/assets/{JobsPage-BbaC-YOg.js → JobsPage-iW7WuPAc.js} +1 -1
- package/client/dist/assets/{dist-js-Xc2lRKp2.js → dist-js-A8aSaLng.js} +1 -1
- package/client/dist/assets/{dist-js-CiIVMsx3.js → dist-js-CD_m3Xj5.js} +1 -1
- package/client/dist/assets/index-D6BaYRRU.css +2 -0
- package/client/dist/assets/{index-DK214dak.js → index-DRhFPNAv.js} +44 -44
- package/client/dist/assets/{integrations-2C7MkGT0.js → integrations-7YyTBuU9.js} +1 -1
- package/client/dist/assets/{integrations-CX4p_bij.js → integrations-B9CEpNF0.js} +1 -1
- package/client/dist/assets/{integrations-C2jQtv-s.js → integrations-BlvAdewo.js} +1 -1
- package/client/dist/assets/{integrations-eQPHAYsE.js → integrations-Bw8UM9Xd.js} +1 -1
- package/client/dist/assets/{integrations-BDC670cg.js → integrations-C5SxNKnG.js} +1 -1
- package/client/dist/assets/{integrations-BqUmRUef.js → integrations-CJQKMmdW.js} +1 -1
- package/client/dist/assets/{integrations-CB98NeH5.js → integrations-DWz1eU_K.js} +1 -1
- package/client/dist/assets/{integrations-_SuVeQIG.js → integrations-DiPR8Fzp.js} +1 -1
- package/client/dist/assets/{lib-Bo5s6xpe.js → lib-1vkTuLY7.js} +1 -1
- package/client/dist/assets/setup-B6egeeTM.js +1 -0
- package/client/dist/assets/setup-BHroXlke.js +1 -0
- package/client/dist/assets/setup-BIXsWUp1.js +1 -0
- package/client/dist/assets/setup-BJRdg1iE.js +1 -0
- package/client/dist/assets/setup-C0rVGnCy.js +1 -0
- package/client/dist/assets/setup-Cpu17hJv.js +1 -0
- package/client/dist/assets/setup-D-1r0uSx.js +1 -0
- package/client/dist/assets/setup-Dn2-veYO.js +1 -0
- package/client/dist/assets/{tickets-9kdPXInd.js → tickets-CG_mo-Bg.js} +1 -1
- package/client/dist/assets/{tickets-n23kDqJT.js → tickets-CVJQ-vRm.js} +1 -1
- package/client/dist/assets/{tickets-tGx5AR5b.js → tickets-D5MSAPe_.js} +1 -1
- package/client/dist/assets/{tickets-1UIGf_oA.js → tickets-DBV3wgQZ.js} +1 -1
- package/client/dist/assets/{tickets-DNmXcAwu.js → tickets-Q0_pONEh.js} +1 -1
- package/client/dist/assets/{tickets-C6pwZwt4.js → tickets-RZ0LyeQe.js} +1 -1
- package/client/dist/assets/{tickets-DAjtxAVb.js → tickets-d1A6EOHa.js} +1 -1
- package/client/dist/assets/{tickets-0rM0lIXd.js → tickets-r4-oNV0R.js} +1 -1
- package/client/dist/assets/{useProjectCache-DVNypkmR.js → useProjectCache-CSi2xHri.js} +1 -1
- package/client/dist/index.html +5 -5
- package/docs/README.md +5 -2
- package/docs/agy-cli-provider-study.md +78 -0
- package/docs/cli.md +23 -4
- package/docs/codex.md +116 -58
- package/docs/creating-specs.md +19 -5
- package/docs/customizing.md +27 -6
- package/docs/gemini.md +225 -73
- package/docs/getting-started.md +18 -9
- package/docs/guide/de/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/de/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/de/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/de/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/de/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/de/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/de/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/de/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/de/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/de/insights/3-code-explorer.md +50 -0
- package/docs/guide/de/integrations/1-ai-providers.md +64 -0
- package/docs/guide/de/integrations/2-plugins.md +44 -0
- package/docs/guide/de/integrations/3-jira-integration.md +71 -0
- package/docs/guide/de/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/de/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/de/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/de/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/de/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/de/settings/1-themes.md +37 -0
- package/docs/guide/de/settings/2-language.md +39 -0
- package/docs/guide/de/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/de/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/de/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/de/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/de/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/de/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/en/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/en/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/en/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/en/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/en/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/en/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/en/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/en/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/en/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/en/insights/3-code-explorer.md +50 -0
- package/docs/guide/en/integrations/1-ai-providers.md +64 -0
- package/docs/guide/en/integrations/2-plugins.md +44 -0
- package/docs/guide/en/integrations/3-jira-integration.md +71 -0
- package/docs/guide/en/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/en/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/en/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/en/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/en/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/en/settings/1-themes.md +37 -0
- package/docs/guide/en/settings/2-language.md +39 -0
- package/docs/guide/en/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/en/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/en/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/en/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/en/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/en/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/es/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/es/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/es/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/es/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/es/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/es/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/es/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/es/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/es/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/es/insights/3-code-explorer.md +50 -0
- package/docs/guide/es/integrations/1-ai-providers.md +64 -0
- package/docs/guide/es/integrations/2-plugins.md +44 -0
- package/docs/guide/es/integrations/3-jira-integration.md +71 -0
- package/docs/guide/es/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/es/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/es/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/es/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/es/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/es/settings/1-themes.md +37 -0
- package/docs/guide/es/settings/2-language.md +39 -0
- package/docs/guide/es/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/es/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/es/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/es/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/es/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/es/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/fr/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/fr/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/fr/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/fr/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/fr/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/fr/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/fr/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/fr/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/fr/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/fr/insights/3-code-explorer.md +50 -0
- package/docs/guide/fr/integrations/1-ai-providers.md +64 -0
- package/docs/guide/fr/integrations/2-plugins.md +44 -0
- package/docs/guide/fr/integrations/3-jira-integration.md +71 -0
- package/docs/guide/fr/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/fr/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/fr/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/fr/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/fr/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/fr/settings/1-themes.md +37 -0
- package/docs/guide/fr/settings/2-language.md +39 -0
- package/docs/guide/fr/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/fr/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/fr/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/fr/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/fr/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/fr/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/it/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/it/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/it/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/it/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/it/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/it/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/it/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/it/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/it/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/it/insights/3-code-explorer.md +50 -0
- package/docs/guide/it/integrations/1-ai-providers.md +64 -0
- package/docs/guide/it/integrations/2-plugins.md +44 -0
- package/docs/guide/it/integrations/3-jira-integration.md +71 -0
- package/docs/guide/it/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/it/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/it/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/it/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/it/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/it/settings/1-themes.md +37 -0
- package/docs/guide/it/settings/2-language.md +39 -0
- package/docs/guide/it/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/it/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/it/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/it/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/it/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/it/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/ja/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/ja/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/ja/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/ja/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/ja/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/ja/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/ja/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/ja/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/ja/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/ja/insights/3-code-explorer.md +50 -0
- package/docs/guide/ja/integrations/1-ai-providers.md +64 -0
- package/docs/guide/ja/integrations/2-plugins.md +44 -0
- package/docs/guide/ja/integrations/3-jira-integration.md +71 -0
- package/docs/guide/ja/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/ja/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/ja/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/ja/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/ja/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/ja/settings/1-themes.md +37 -0
- package/docs/guide/ja/settings/2-language.md +39 -0
- package/docs/guide/ja/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/ja/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/ja/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/ja/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/ja/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/ja/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/pt/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/pt/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/pt/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/pt/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/pt/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/pt/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/pt/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/pt/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/pt/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/pt/insights/3-code-explorer.md +50 -0
- package/docs/guide/pt/integrations/1-ai-providers.md +64 -0
- package/docs/guide/pt/integrations/2-plugins.md +44 -0
- package/docs/guide/pt/integrations/3-jira-integration.md +71 -0
- package/docs/guide/pt/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/pt/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/pt/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/pt/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/pt/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/pt/settings/1-themes.md +37 -0
- package/docs/guide/pt/settings/2-language.md +39 -0
- package/docs/guide/pt/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/pt/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/pt/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/pt/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/pt/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/pt/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/guide/zh/agents/1-meet-the-agents.md +38 -0
- package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +45 -0
- package/docs/guide/zh/agents/3-customizing-models-per-agent.md +60 -0
- package/docs/guide/zh/agents/4-custom-agents-catalog.md +43 -0
- package/docs/guide/zh/getting-started/1-what-is-specrails.md +49 -0
- package/docs/guide/zh/getting-started/2-installing-and-first-run.md +42 -0
- package/docs/guide/zh/getting-started/3-adding-your-first-project.md +58 -0
- package/docs/guide/zh/getting-started/4-the-dashboard-tour.md +53 -0
- package/docs/guide/zh/insights/1-analytics-and-cost-tracking.md +78 -0
- package/docs/guide/zh/insights/2-the-integrated-terminal.md +46 -0
- package/docs/guide/zh/insights/3-code-explorer.md +50 -0
- package/docs/guide/zh/integrations/1-ai-providers.md +64 -0
- package/docs/guide/zh/integrations/2-plugins.md +44 -0
- package/docs/guide/zh/integrations/3-jira-integration.md +71 -0
- package/docs/guide/zh/integrations/4-mobile-companion.md +38 -0
- package/docs/guide/zh/pipeline/1-rails-and-jobs.md +94 -0
- package/docs/guide/zh/pipeline/2-the-job-detail-view.md +90 -0
- package/docs/guide/zh/pipeline/3-batch-implement-and-multi-feature.md +78 -0
- package/docs/guide/zh/pipeline/4-picking-an-engine-per-rail.md +60 -0
- package/docs/guide/zh/settings/1-themes.md +37 -0
- package/docs/guide/zh/settings/2-language.md +39 -0
- package/docs/guide/zh/settings/3-pipeline-telemetry-and-diagnostics.md +46 -0
- package/docs/guide/zh/settings/4-where-your-data-lives.md +48 -0
- package/docs/guide/zh/specs/1-specs-and-the-backlog.md +52 -0
- package/docs/guide/zh/specs/2-add-spec-quick-mode.md +45 -0
- package/docs/guide/zh/specs/3-add-spec-explore-mode.md +68 -0
- package/docs/guide/zh/specs/4-drafts-and-contract-layer.md +81 -0
- package/docs/internals/README.md +1 -1
- package/docs/internals/adding-a-provider.md +192 -59
- package/docs/internals/api-reference.md +130 -21
- package/docs/internals/architecture.md +22 -9
- package/docs/internals/bundled-framework-build-plan.md +264 -0
- package/docs/internals/configuration.md +33 -8
- package/docs/internals/global-artifacts-alignment-contract.md +486 -0
- package/docs/internals/global-artifacts-relocation-evaluation.md +294 -0
- package/docs/internals/operations-runbook.md +16 -5
- package/docs/internals/profiles.md +42 -14
- package/docs/platforms/macos.md +27 -8
- package/docs/platforms/windows.md +20 -6
- package/docs/running-pipelines.md +17 -9
- package/docs/terminal.md +9 -3
- package/docs/tracking-cost.md +17 -11
- package/package.json +1 -1
- package/server/dist/agent-refine-manager.js +20 -5
- package/server/dist/artifact-registry.js +468 -0
- package/server/dist/attachment-manager.js +5 -8
- package/server/dist/browser-capture-manager.js +4 -4
- package/server/dist/bundled-core.js +72 -0
- package/server/dist/bundled-openspec.js +58 -0
- package/server/dist/chat-manager.js +42 -5
- package/server/dist/code-explorer-router.js +10 -7
- package/server/dist/config.js +7 -2
- package/server/dist/context-budget.js +17 -6
- package/server/dist/context-scope.js +6 -2
- package/server/dist/contract-refine-runner.js +31 -9
- package/server/dist/desktop-router.js +24 -1
- package/server/dist/docs-router.js +210 -132
- package/server/dist/file-summary-manager.js +41 -16
- package/server/dist/framework-manager.js +248 -0
- package/server/dist/framework-migration.js +308 -0
- package/server/dist/index.js +30 -0
- package/server/dist/install-config-path.js +73 -0
- package/server/dist/jira/jira-sync-manager.js +23 -11
- package/server/dist/openspec-shim.js +153 -0
- package/server/dist/plugins-router.js +19 -8
- package/server/dist/profiles-router.js +38 -16
- package/server/dist/project-registry.js +101 -3
- package/server/dist/project-router-chat.js +1 -1
- package/server/dist/project-router-helpers.js +25 -12
- package/server/dist/project-router-jobs.js +3 -3
- package/server/dist/project-router-settings.js +8 -6
- package/server/dist/project-router-setup.js +27 -10
- package/server/dist/project-router-spending.js +6 -1
- package/server/dist/project-router-tickets.js +30 -10
- package/server/dist/project-router.js +16 -1
- package/server/dist/queue-manager.js +149 -18
- package/server/dist/setup-manager.js +131 -29
- package/server/dist/smash-runner.js +21 -6
- package/server/dist/ticket-store.js +6 -2
- package/server/dist/ticket-watcher.js +5 -1
- package/server/dist/vitest-setup.js +25 -0
- package/server/dist/workspace-manager.js +199 -0
- package/server/dist/workspace-resolution.js +147 -0
- package/client/dist/assets/index-DgKfQFcf.css +0 -2
- package/client/dist/assets/setup-BIIkb-_K.js +0 -1
- package/client/dist/assets/setup-BeQxu9kD.js +0 -1
- package/client/dist/assets/setup-CPa6GnlI.js +0 -1
- package/client/dist/assets/setup-CZl4OEJx.js +0 -1
- package/client/dist/assets/setup-ChpodNfn.js +0 -1
- package/client/dist/assets/setup-D_fjJH6u.js +0 -1
- package/client/dist/assets/setup-YzD8DX4O.js +0 -1
- package/client/dist/assets/setup-fRpDozmq.js +0 -1
- package/docs/adding-a-provider.md +0 -107
|
@@ -23,6 +23,9 @@ const ticket_watcher_1 = require("./ticket-watcher");
|
|
|
23
23
|
const terminal_manager_1 = require("./terminal-manager");
|
|
24
24
|
const browser_capture_manager_1 = require("./browser-capture-manager");
|
|
25
25
|
const explore_cwd_manager_1 = require("./explore-cwd-manager");
|
|
26
|
+
const artifact_registry_1 = require("./artifact-registry");
|
|
27
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
28
|
+
const workspace_manager_1 = require("./workspace-manager");
|
|
26
29
|
const ticket_store_1 = require("./ticket-store");
|
|
27
30
|
const jira_sync_manager_1 = require("./jira/jira-sync-manager");
|
|
28
31
|
const rails_store_1 = require("./rails-store");
|
|
@@ -51,6 +54,22 @@ class ProjectRegistry {
|
|
|
51
54
|
}
|
|
52
55
|
loadAll() {
|
|
53
56
|
const projects = (0, desktop_db_1.listProjects)(this._desktopDb);
|
|
57
|
+
// Self-heal the shared artifact registry: project one entry per desktop
|
|
58
|
+
// project, leaving non-desktop (core-standalone) entries untouched. Wrapped
|
|
59
|
+
// so a registry write failure never blocks app startup — a missing entry is
|
|
60
|
+
// recreated on the next addProject/reconcile.
|
|
61
|
+
try {
|
|
62
|
+
(0, artifact_registry_1.reconcileFromProjects)(projects.map((p) => ({
|
|
63
|
+
repoPath: p.path,
|
|
64
|
+
slug: p.slug,
|
|
65
|
+
providers: p.providers,
|
|
66
|
+
primaryProvider: p.provider,
|
|
67
|
+
desktopProjectId: p.id,
|
|
68
|
+
})));
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
console.error('[project-registry] registry reconcile failed (non-fatal):', err);
|
|
72
|
+
}
|
|
54
73
|
for (const project of projects) {
|
|
55
74
|
try {
|
|
56
75
|
this._loadProjectContext(project);
|
|
@@ -71,11 +90,48 @@ class ProjectRegistry {
|
|
|
71
90
|
listFailedProjects() {
|
|
72
91
|
return Array.from(this._failedProjects.values());
|
|
73
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* The deduped union of providers across every registered project (from the
|
|
95
|
+
* desktop DB, so it includes projects that failed to load a per-project DB).
|
|
96
|
+
* Used by the framework boot `versionCheck` to decide which providerDirs to
|
|
97
|
+
* materialize. Defaults to `['claude']` when there are no projects yet (so a
|
|
98
|
+
* fresh install still materializes the claude framework for the first add).
|
|
99
|
+
*/
|
|
100
|
+
installedProvidersUnion() {
|
|
101
|
+
const set = new Set();
|
|
102
|
+
for (const p of (0, desktop_db_1.listProjects)(this._desktopDb)) {
|
|
103
|
+
const list = p.providers && p.providers.length > 0 ? p.providers : [p.provider];
|
|
104
|
+
for (const prov of list) {
|
|
105
|
+
if (prov && prov.length > 0)
|
|
106
|
+
set.add(prov);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return set.size > 0 ? Array.from(set) : ['claude'];
|
|
110
|
+
}
|
|
74
111
|
addProject(opts) {
|
|
75
112
|
const row = (0, desktop_db_1.addProject)(this._desktopDb, opts);
|
|
113
|
+
// Mirror the new project into the shared artifact registry so specrails-core
|
|
114
|
+
// resolves its relocated artifacts. Wrapped so a registry write failure never
|
|
115
|
+
// breaks project creation — the startup reconcile will recreate the entry.
|
|
116
|
+
try {
|
|
117
|
+
(0, artifact_registry_1.mirrorProjectEntry)({
|
|
118
|
+
repoPath: row.path,
|
|
119
|
+
slug: row.slug,
|
|
120
|
+
providers: row.providers,
|
|
121
|
+
primaryProvider: row.provider,
|
|
122
|
+
desktopProjectId: row.id,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
console.error('[project-registry] registry mirror failed (non-fatal):', err);
|
|
127
|
+
}
|
|
76
128
|
return this._loadProjectContext(row);
|
|
77
129
|
}
|
|
78
130
|
removeProject(id) {
|
|
131
|
+
// Resolve the repo path BEFORE the DB row is deleted below so we can drop the
|
|
132
|
+
// shared artifact-registry entry. Prefer the live context, fall back to the
|
|
133
|
+
// desktop DB row (project may be registered-but-not-loaded, e.g. M9 failure).
|
|
134
|
+
const repoPath = this._contexts.get(id)?.project.path ?? (0, desktop_db_1.getProject)(this._desktopDb, id)?.path;
|
|
79
135
|
const ctx = this._contexts.get(id);
|
|
80
136
|
if (ctx) {
|
|
81
137
|
// Tear down spawners BEFORE closing the DB. QueueManager.shutdown() drops
|
|
@@ -163,6 +219,38 @@ class ProjectRegistry {
|
|
|
163
219
|
catch { /* ignore — non-fatal */ }
|
|
164
220
|
this._contexts.delete(id);
|
|
165
221
|
}
|
|
222
|
+
// Drop the relocated WORKSPACE for an adopted project whose REGISTRY slug
|
|
223
|
+
// differs from the desktop slug. The per-project data-dir rm above only
|
|
224
|
+
// removes `projects/<desktop-slug>`; an adopted repo's workspace lives under
|
|
225
|
+
// `projects/<registry-slug>/workspace` and would otherwise leak. Resolve the
|
|
226
|
+
// registry entry (BEFORE removeRegistryEntry deletes it) and, when relocated,
|
|
227
|
+
// remove the workspace under the registry slug. Best-effort — never blocks
|
|
228
|
+
// removal. (When the slugs match, the dir is already gone; removeWorkspace
|
|
229
|
+
// no-ops on a missing dir.)
|
|
230
|
+
if (repoPath) {
|
|
231
|
+
try {
|
|
232
|
+
const art = (0, artifact_registry_1.resolveArtifacts)(repoPath);
|
|
233
|
+
if (!art.isLegacy && art.entry?.slug) {
|
|
234
|
+
// Use the SAME home the registry/workspace live under (== os.homedir()
|
|
235
|
+
// in production; overridable via SPECRAILS_REGISTRY_HOME in tests) so
|
|
236
|
+
// the workspace dir resolves identically to the registry entry.
|
|
237
|
+
(0, workspace_manager_1.removeWorkspace)(art.entry.slug, (0, artifact_registry_1.resolveHome)());
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (err) {
|
|
241
|
+
console.error('[project-registry] workspace remove failed (non-fatal):', err);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// Drop the shared artifact-registry entry for this repo. Wrapped so a
|
|
245
|
+
// registry write failure never blocks project removal.
|
|
246
|
+
if (repoPath) {
|
|
247
|
+
try {
|
|
248
|
+
(0, artifact_registry_1.removeRegistryEntry)(repoPath);
|
|
249
|
+
}
|
|
250
|
+
catch (err) {
|
|
251
|
+
console.error('[project-registry] registry remove failed (non-fatal):', err);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
166
254
|
(0, desktop_db_1.removeProject)(this._desktopDb, id);
|
|
167
255
|
}
|
|
168
256
|
getContext(id) {
|
|
@@ -319,7 +407,12 @@ class ProjectRegistry {
|
|
|
319
407
|
if (completedTicketIds.length > 0 &&
|
|
320
408
|
(status === 'completed' || status === 'failed' || status === 'canceled' || status === 'zombie_terminated')) {
|
|
321
409
|
try {
|
|
322
|
-
|
|
410
|
+
// Relocate-artifacts gate: write the job outcome to the workspace
|
|
411
|
+
// ticket store when relocated, else the repo-relative store (legacy).
|
|
412
|
+
const outcomeExec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
413
|
+
const ticketFile = outcomeExec.relocated
|
|
414
|
+
? outcomeExec.ticketsPath
|
|
415
|
+
: (0, ticket_store_1.resolveTicketStoragePath)(project.path);
|
|
323
416
|
const now = new Date().toISOString();
|
|
324
417
|
let changedIds = [];
|
|
325
418
|
const store = (0, ticket_store_1.mutateStore)(ticketFile, (s) => {
|
|
@@ -453,9 +546,14 @@ class ProjectRegistry {
|
|
|
453
546
|
// persistent recursive watcher per project, the source of the fd leak that
|
|
454
547
|
// broke terminals. The watcher is now attached lazily on the first
|
|
455
548
|
// code-explorer request (see code-explorer-router.ts).
|
|
456
|
-
// Load commands for this project
|
|
549
|
+
// Load commands for this project. Relocate-artifacts: sr/specrails commands
|
|
550
|
+
// are materialized into the WORKSPACE when the project is relocated; the repo
|
|
551
|
+
// has none, so scanning project.path would find nothing. Resolve the gate so
|
|
552
|
+
// command discovery reads the same tree the rails load (legacy ⇒ repo).
|
|
457
553
|
try {
|
|
458
|
-
const
|
|
554
|
+
const cmdExec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
555
|
+
const commandsRoot = cmdExec.relocated && cmdExec.workspaceDir ? cmdExec.workspaceDir : project.path;
|
|
556
|
+
const config = (0, config_1.getConfig)(project.path, db, project.name, commandsRoot);
|
|
459
557
|
queueManager.setCommands(config.commands);
|
|
460
558
|
}
|
|
461
559
|
catch {
|
|
@@ -32,7 +32,7 @@ function registerChatRoutes(deps) {
|
|
|
32
32
|
const rawModel = req.body?.model;
|
|
33
33
|
let model;
|
|
34
34
|
if (rawModel === undefined || rawModel === null || rawModel === '') {
|
|
35
|
-
model = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, provider });
|
|
35
|
+
model = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, slug: project.slug, provider });
|
|
36
36
|
}
|
|
37
37
|
else if ((0, spec_models_1.isValidModelForProvider)(rawModel, provider)) {
|
|
38
38
|
model = rawModel;
|
|
@@ -21,6 +21,8 @@ const path_1 = __importDefault(require("path"));
|
|
|
21
21
|
const core_package_1 = require("./core-package");
|
|
22
22
|
const spec_models_1 = require("./spec-models");
|
|
23
23
|
const ticket_store_1 = require("./ticket-store");
|
|
24
|
+
const install_config_path_1 = require("./install-config-path");
|
|
25
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
24
26
|
const TERMINAL_PANEL_ENABLED = process.env.SPECRAILS_TERMINAL_PANEL !== 'false';
|
|
25
27
|
exports.TERMINAL_PANEL_ENABLED = TERMINAL_PANEL_ENABLED;
|
|
26
28
|
// ─── YAML helpers ─────────────────────────────────────────────────────────────
|
|
@@ -55,12 +57,23 @@ function serializeInstallConfigYaml(config) {
|
|
|
55
57
|
// ─── Agent model helpers ──────────────────────────────────────────────────────
|
|
56
58
|
const VALID_MODEL_ALIASES = ['sonnet', 'opus', 'haiku'];
|
|
57
59
|
exports.VALID_MODEL_ALIASES = VALID_MODEL_ALIASES;
|
|
60
|
+
/**
|
|
61
|
+
* Relocate-artifacts: where the agent `.claude/agents/*.md` files live. When the
|
|
62
|
+
* project is relocated AND the workspace is populated by core, the agents are
|
|
63
|
+
* materialized into `<workspace>/.claude/agents` (same reasoning the profiles
|
|
64
|
+
* router uses for profiles). Legacy ⇒ `<project>/.claude/agents` (byte-identical).
|
|
65
|
+
*/
|
|
66
|
+
function resolveAgentsDir(project) {
|
|
67
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
68
|
+
const root = exec.relocated && exec.workspaceDir ? exec.workspaceDir : project.path;
|
|
69
|
+
return path_1.default.join(root, '.claude', 'agents');
|
|
70
|
+
}
|
|
58
71
|
/**
|
|
59
72
|
* Read installed agents from `.claude/agents/*.md` (top-level only, no subdirs).
|
|
60
73
|
* Extracts the `model:` field from YAML frontmatter.
|
|
61
74
|
*/
|
|
62
|
-
function readAgentModels(
|
|
63
|
-
const agentsDir =
|
|
75
|
+
function readAgentModels(project) {
|
|
76
|
+
const agentsDir = resolveAgentsDir(project);
|
|
64
77
|
if (!fs_1.default.existsSync(agentsDir))
|
|
65
78
|
return [];
|
|
66
79
|
let entries;
|
|
@@ -97,12 +110,12 @@ function readAgentModels(projectPath) {
|
|
|
97
110
|
return agents;
|
|
98
111
|
}
|
|
99
112
|
/**
|
|
100
|
-
* Read
|
|
101
|
-
* `.claude/agents/*.md` frontmatter to match the config's
|
|
102
|
-
* No-op if the config file does not exist.
|
|
113
|
+
* Read the install-config.yaml (per-project HOME dir) and patch the `model:`
|
|
114
|
+
* line in each `.claude/agents/*.md` frontmatter to match the config's
|
|
115
|
+
* defaults/overrides. No-op if the config file does not exist.
|
|
103
116
|
*/
|
|
104
|
-
function applyModelConfig(
|
|
105
|
-
const configPath =
|
|
117
|
+
function applyModelConfig(project) {
|
|
118
|
+
const configPath = (0, install_config_path_1.installConfigPath)(project);
|
|
106
119
|
if (!fs_1.default.existsSync(configPath))
|
|
107
120
|
return;
|
|
108
121
|
let configText;
|
|
@@ -127,7 +140,7 @@ function applyModelConfig(projectPath) {
|
|
|
127
140
|
overrides[m[1]] = m[2];
|
|
128
141
|
}
|
|
129
142
|
}
|
|
130
|
-
const agentsDir =
|
|
143
|
+
const agentsDir = resolveAgentsDir(project);
|
|
131
144
|
if (!fs_1.default.existsSync(agentsDir))
|
|
132
145
|
return;
|
|
133
146
|
let entries;
|
|
@@ -244,16 +257,16 @@ function formatDescriptionWithCriteria(body, criteria) {
|
|
|
244
257
|
* Resolve the default model used by Add Spec for a project.
|
|
245
258
|
*
|
|
246
259
|
* Order:
|
|
247
|
-
* 1. `models.defaults.model` from
|
|
248
|
-
* if it parses AND is in the provider allow-list.
|
|
260
|
+
* 1. `models.defaults.model` from the install-config.yaml (per-project HOME
|
|
261
|
+
* dir), if it parses AND is in the provider allow-list.
|
|
249
262
|
* 2. Provider default from `PROVIDER_DEFAULT_MODEL` (`sonnet` / `gpt-5.5`).
|
|
250
263
|
*
|
|
251
264
|
* Logs a warning when the configured value exists but is not valid for the
|
|
252
265
|
* project's provider.
|
|
253
266
|
*/
|
|
254
267
|
function resolveDefaultSpecModel(args) {
|
|
255
|
-
const { projectPath, provider } = args;
|
|
256
|
-
const configPath =
|
|
268
|
+
const { projectPath, slug, provider } = args;
|
|
269
|
+
const configPath = (0, install_config_path_1.installConfigPath)({ slug, path: projectPath });
|
|
257
270
|
if (!fs_1.default.existsSync(configPath))
|
|
258
271
|
return (0, spec_models_1.getProviderDefault)(provider);
|
|
259
272
|
let configText;
|
|
@@ -96,7 +96,7 @@ function registerJobsRoutes(deps) {
|
|
|
96
96
|
// response also lists every installed provider so the Add Spec modal can
|
|
97
97
|
// render its AI Engine selector without a second round-trip.
|
|
98
98
|
const provider = (0, provider_selection_1.resolveProvider)(project, typeof req.query.provider === 'string' ? req.query.provider : undefined);
|
|
99
|
-
const model = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, provider });
|
|
99
|
+
const model = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, slug: project.slug, provider });
|
|
100
100
|
const allowed = (0, spec_models_1.getModelsForProvider)(provider);
|
|
101
101
|
res.json({ model, provider, allowed, providers: project.providers });
|
|
102
102
|
});
|
|
@@ -389,13 +389,13 @@ function registerJobsRoutes(deps) {
|
|
|
389
389
|
skip_reason: inMemory.skipReason,
|
|
390
390
|
};
|
|
391
391
|
const phaseDefinitions = queueManager.phasesForCommand(synthetic.command);
|
|
392
|
-
const tickets = (0, ticket_store_1.resolveTicketsFromCommand)(project.path, synthetic.command);
|
|
392
|
+
const tickets = (0, ticket_store_1.resolveTicketsFromCommand)(project.path, synthetic.command, ticketPath(req));
|
|
393
393
|
res.json({ job: { ...synthetic, hasTelemetry: false, tickets }, events: [], phaseDefinitions });
|
|
394
394
|
return;
|
|
395
395
|
}
|
|
396
396
|
const events = (0, db_1.getJobEvents)(db, jobId);
|
|
397
397
|
const phaseDefinitions = queueManager.phasesForCommand(job.command);
|
|
398
|
-
const tickets = (0, ticket_store_1.resolveTicketsFromCommand)(project.path, job.command);
|
|
398
|
+
const tickets = (0, ticket_store_1.resolveTicketsFromCommand)(project.path, job.command, ticketPath(req));
|
|
399
399
|
const annotated = { ...job, hasTelemetry: (0, db_1.hasJobTelemetry)(db, jobId), tickets };
|
|
400
400
|
res.json({ job: annotated, events, phaseDefinitions });
|
|
401
401
|
});
|
|
@@ -15,6 +15,7 @@ const context_scope_1 = require("./context-scope");
|
|
|
15
15
|
const terminal_settings_1 = require("./terminal-settings");
|
|
16
16
|
const terminal_marks_store_1 = require("./terminal-marks-store");
|
|
17
17
|
const project_router_helpers_1 = require("./project-router-helpers");
|
|
18
|
+
const install_config_path_1 = require("./install-config-path");
|
|
18
19
|
function registerSettingsRoutes(deps) {
|
|
19
20
|
const { router, registry, ctx, ticketPath } = deps;
|
|
20
21
|
// ─── Project settings (pipeline telemetry) ───────────────────────────────────
|
|
@@ -112,7 +113,7 @@ function registerSettingsRoutes(deps) {
|
|
|
112
113
|
// ─── Agent models ────────────────────────────────────────────────────────────
|
|
113
114
|
router.get('/:projectId/agent-models', (req, res) => {
|
|
114
115
|
const { project } = ctx(req);
|
|
115
|
-
const agents = (0, project_router_helpers_1.readAgentModels)(project
|
|
116
|
+
const agents = (0, project_router_helpers_1.readAgentModels)(project);
|
|
116
117
|
res.json({ agents });
|
|
117
118
|
});
|
|
118
119
|
router.patch('/:projectId/agent-models', (req, res) => {
|
|
@@ -138,8 +139,9 @@ function registerSettingsRoutes(deps) {
|
|
|
138
139
|
}
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
// Relocate-artifacts: the install config lives in the per-project HOME dir,
|
|
143
|
+
// NEVER `<project>/.specrails` (which would leak into the user's repo).
|
|
144
|
+
const configPath = (0, install_config_path_1.installConfigPath)(project);
|
|
143
145
|
// Read existing config or build default shape
|
|
144
146
|
let existingConfig = {
|
|
145
147
|
version: 1,
|
|
@@ -206,11 +208,11 @@ function registerSettingsRoutes(deps) {
|
|
|
206
208
|
}
|
|
207
209
|
existingConfig.models = mergedModels;
|
|
208
210
|
try {
|
|
209
|
-
fs_1.default.mkdirSync(
|
|
211
|
+
fs_1.default.mkdirSync(path_1.default.dirname(configPath), { recursive: true });
|
|
210
212
|
const yaml = (0, project_router_helpers_1.serializeInstallConfigYaml)(existingConfig);
|
|
211
213
|
fs_1.default.writeFileSync(configPath, yaml, 'utf-8');
|
|
212
|
-
(0, project_router_helpers_1.applyModelConfig)(project
|
|
213
|
-
const agents = (0, project_router_helpers_1.readAgentModels)(project
|
|
214
|
+
(0, project_router_helpers_1.applyModelConfig)(project);
|
|
215
|
+
const agents = (0, project_router_helpers_1.readAgentModels)(project);
|
|
214
216
|
res.json({ agents });
|
|
215
217
|
}
|
|
216
218
|
catch (err) {
|
|
@@ -15,6 +15,8 @@ const queue_manager_1 = require("./queue-manager");
|
|
|
15
15
|
const command_resolver_1 = require("./command-resolver");
|
|
16
16
|
const changes_reader_1 = require("./changes-reader");
|
|
17
17
|
const project_router_helpers_1 = require("./project-router-helpers");
|
|
18
|
+
const install_config_path_1 = require("./install-config-path");
|
|
19
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
18
20
|
function registerSetupRoutes(deps) {
|
|
19
21
|
const { router, registry, ctx, ticketPath } = deps;
|
|
20
22
|
// ─── Install-config route ─────────────────────────────────────────────────────
|
|
@@ -25,10 +27,12 @@ function registerSetupRoutes(deps) {
|
|
|
25
27
|
res.status(400).json({ error: 'Request body must be a config object' });
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
// Relocate-artifacts: write to the per-project app-managed HOME dir, NEVER
|
|
31
|
+
// `<project>/.specrails` — so a fresh setup creates ZERO `.specrails` in the
|
|
32
|
+
// user's repo. See server/install-config-path.ts.
|
|
33
|
+
const configPath = (0, install_config_path_1.installConfigPath)(project);
|
|
30
34
|
try {
|
|
31
|
-
fs_1.default.mkdirSync(
|
|
35
|
+
fs_1.default.mkdirSync(path_1.default.dirname(configPath), { recursive: true });
|
|
32
36
|
const yaml = (0, project_router_helpers_1.serializeInstallConfigYaml)(config);
|
|
33
37
|
fs_1.default.writeFileSync(configPath, yaml, 'utf-8');
|
|
34
38
|
res.json({ ok: true, path: configPath });
|
|
@@ -45,7 +49,7 @@ function registerSetupRoutes(deps) {
|
|
|
45
49
|
return;
|
|
46
50
|
}
|
|
47
51
|
res.status(202).json({ ok: true });
|
|
48
|
-
setupManager.startInstall(project.id, project.path);
|
|
52
|
+
setupManager.startInstall(project.id, project.path, project.slug);
|
|
49
53
|
});
|
|
50
54
|
router.post('/:projectId/enrich/start', (req, res) => {
|
|
51
55
|
const { project, setupManager } = ctx(req);
|
|
@@ -115,7 +119,7 @@ function registerSetupRoutes(deps) {
|
|
|
115
119
|
tier: setupManager.getInstallTier(project.id) ?? null,
|
|
116
120
|
savedSessionId: savedSessionId ?? null,
|
|
117
121
|
logLines: setupManager.getInstallLog(project.id),
|
|
118
|
-
summary: setupManager.getSummary(project
|
|
122
|
+
summary: setupManager.getSummary(project),
|
|
119
123
|
});
|
|
120
124
|
});
|
|
121
125
|
router.post('/:projectId/setup/abort', (req, res) => {
|
|
@@ -428,11 +432,17 @@ function registerSetupRoutes(deps) {
|
|
|
428
432
|
});
|
|
429
433
|
// ─── Integration contract ──────────────────────────────────────────────────
|
|
430
434
|
const DEFAULT_TICKET_CAPABILITIES = ['crud', 'labels', 'status', 'priorities', 'dependencies'];
|
|
431
|
-
const DEFAULT_TICKET_STORAGE_PATH = '.specrails/local-tickets.json';
|
|
432
435
|
// GET /:projectId/integration-contract — Return the project's integration contract with ticketProvider
|
|
433
436
|
router.get('/:projectId/integration-contract', (req, res) => {
|
|
434
|
-
const
|
|
435
|
-
const
|
|
437
|
+
const project = ctx(req).project;
|
|
438
|
+
const projectPath = project.path;
|
|
439
|
+
// Relocate-artifacts: the integration-contract.json + tickets store live in
|
|
440
|
+
// the workspace when relocated. Mirror the gated ticketPath() so this
|
|
441
|
+
// informational endpoint reports the path the relocated CLI actually loads,
|
|
442
|
+
// not a stale repo path. Legacy projects are byte-identical.
|
|
443
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: projectPath });
|
|
444
|
+
const contractRoot = exec.relocated && exec.workspaceDir ? exec.workspaceDir : projectPath;
|
|
445
|
+
const contractFile = path_1.default.join(contractRoot, '.claude', 'integration-contract.json');
|
|
436
446
|
let rawContract = {};
|
|
437
447
|
let source = 'default';
|
|
438
448
|
if (fs_1.default.existsSync(contractFile)) {
|
|
@@ -445,10 +455,17 @@ function registerSetupRoutes(deps) {
|
|
|
445
455
|
}
|
|
446
456
|
}
|
|
447
457
|
const rawProvider = rawContract.ticketProvider;
|
|
448
|
-
|
|
458
|
+
// When relocated, the tickets store is the registry entry's ticketsPath (an
|
|
459
|
+
// absolute workspace path); a custom storagePath from the contract is still
|
|
460
|
+
// resolved relative to the contract root. Legacy ⇒ ticketPath() (which
|
|
461
|
+
// honours the contract's custom storagePath).
|
|
462
|
+
const storagePath = rawProvider?.storagePath;
|
|
463
|
+
const resolvedStorage = storagePath
|
|
464
|
+
? path_1.default.resolve(contractRoot, storagePath)
|
|
465
|
+
: ticketPath(req);
|
|
449
466
|
const ticketProvider = {
|
|
450
467
|
type: rawProvider?.type ?? 'local',
|
|
451
|
-
storagePath:
|
|
468
|
+
storagePath: resolvedStorage,
|
|
452
469
|
capabilities: rawProvider?.capabilities ?? DEFAULT_TICKET_CAPABILITIES,
|
|
453
470
|
};
|
|
454
471
|
res.json({ ticketProvider, source });
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerSpendingRoutes = registerSpendingRoutes;
|
|
4
4
|
const queue_manager_1 = require("./queue-manager");
|
|
5
5
|
const config_1 = require("./config");
|
|
6
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
6
7
|
const ai_invocations_1 = require("./ai-invocations");
|
|
7
8
|
const spending_1 = require("./spending");
|
|
8
9
|
const ticket_store_1 = require("./ticket-store");
|
|
@@ -205,7 +206,11 @@ function registerSpendingRoutes(deps) {
|
|
|
205
206
|
router.get('/:projectId/config', (req, res) => {
|
|
206
207
|
const { project, db } = ctx(req);
|
|
207
208
|
try {
|
|
208
|
-
|
|
209
|
+
// Relocate-artifacts: commands are materialized into the workspace when
|
|
210
|
+
// relocated; resolve the gate so the /config commands list is non-empty.
|
|
211
|
+
const cfgExec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
212
|
+
const commandsRoot = cfgExec.relocated && cfgExec.workspaceDir ? cfgExec.workspaceDir : project.path;
|
|
213
|
+
const config = (0, config_1.getConfig)(project.path, db, project.name, commandsRoot);
|
|
209
214
|
const dailyBudgetRaw = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.daily_budget_usd'`).get()?.value;
|
|
210
215
|
const dailyBudgetUsd = dailyBudgetRaw != null ? parseFloat(dailyBudgetRaw) : null;
|
|
211
216
|
const zombieTimeoutRaw = db.prepare(`SELECT value FROM queue_state WHERE key = 'config.zombie_timeout_ms'`).get()?.value;
|
|
@@ -58,6 +58,7 @@ const explore_draft_title_1 = require("./explore-draft-title");
|
|
|
58
58
|
const cli_prompt_1 = require("./util/cli-prompt");
|
|
59
59
|
const readline_1 = require("readline");
|
|
60
60
|
const tree_kill_1 = __importDefault(require("tree-kill"));
|
|
61
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
61
62
|
const multer_1 = __importDefault(require("multer"));
|
|
62
63
|
const attachment_manager_1 = require("./attachment-manager");
|
|
63
64
|
const project_router_helpers_1 = require("./project-router-helpers");
|
|
@@ -160,7 +161,7 @@ function registerTicketsRoutes(deps) {
|
|
|
160
161
|
const rawModel = req.body?.model;
|
|
161
162
|
let resolvedModel;
|
|
162
163
|
if (rawModel === undefined || rawModel === null || rawModel === '') {
|
|
163
|
-
resolvedModel = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, provider });
|
|
164
|
+
resolvedModel = (0, project_router_helpers_1.resolveDefaultSpecModel)({ projectPath: project.path, slug: project.slug, provider });
|
|
164
165
|
}
|
|
165
166
|
else if ((0, spec_models_1.isValidModelForProvider)(rawModel, provider)) {
|
|
166
167
|
resolvedModel = rawModel;
|
|
@@ -218,7 +219,12 @@ function registerTicketsRoutes(deps) {
|
|
|
218
219
|
};
|
|
219
220
|
// Persist Quick mode Contract Refine choice (per-project last value).
|
|
220
221
|
(0, db_1.setQuickContractRefineLast)(ctx(req).db, quickContractRefine);
|
|
221
|
-
|
|
222
|
+
// Relocate-artifacts: tickets read from the workspace when relocated.
|
|
223
|
+
const quickSpecRoot = (() => {
|
|
224
|
+
const e = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
225
|
+
return e.relocated && e.workspaceDir ? e.workspaceDir : project.path;
|
|
226
|
+
})();
|
|
227
|
+
const specsPrefix = (0, context_scope_1.buildScopedSystemPromptPrefix)(quickScope, project.path, quickSpecRoot);
|
|
222
228
|
const codebaseRule = quickScope.full
|
|
223
229
|
? `- You MAY use Read, Grep, and Glob to inspect the project codebase. Bash is not available.`
|
|
224
230
|
: hasAttachments
|
|
@@ -284,13 +290,22 @@ function registerTicketsRoutes(deps) {
|
|
|
284
290
|
loadUserEnv: provider === 'claude' && quickScope.userMcp,
|
|
285
291
|
});
|
|
286
292
|
const binary = adapter.binary;
|
|
293
|
+
// Relocate-artifacts gate: spawn from the workspace + SPECRAILS_REPO_DIR when
|
|
294
|
+
// relocated, else cwd = project.path + process.env (byte-identical legacy).
|
|
295
|
+
const specGenExec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
296
|
+
let specGenEnv = process.env;
|
|
297
|
+
if (specGenExec.relocated) {
|
|
298
|
+
specGenEnv = { ...process.env, ...specGenExec.env };
|
|
299
|
+
if (adapter.id === 'gemini')
|
|
300
|
+
specGenEnv = { ...specGenEnv, GEMINI_CLI_TRUST_WORKSPACE: 'true' };
|
|
301
|
+
}
|
|
287
302
|
// spawnAiCli reroutes multi-line argv values through stdin on Windows;
|
|
288
303
|
// POSIX argv path unchanged.
|
|
289
|
-
console.log(`[project-router] spec-gen spawn: ${binary} (cwd=${
|
|
304
|
+
console.log(`[project-router] spec-gen spawn: ${binary} (cwd=${specGenExec.cwd}, requestId=${requestId})`);
|
|
290
305
|
const child = (0, cli_prompt_1.spawnAiCli)(binary, args, {
|
|
291
|
-
env:
|
|
306
|
+
env: specGenEnv,
|
|
292
307
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
293
|
-
cwd:
|
|
308
|
+
cwd: specGenExec.cwd,
|
|
294
309
|
});
|
|
295
310
|
// Watchdog: unlike ai-edit, generate-spec keeps no cancellable handle, so a
|
|
296
311
|
// hung CLI (network stall, model never emitting a terminating event) would
|
|
@@ -511,7 +526,7 @@ function registerTicketsRoutes(deps) {
|
|
|
511
526
|
slug: project.slug,
|
|
512
527
|
pendingId: pendingSpecId,
|
|
513
528
|
realTicketId: created.id,
|
|
514
|
-
|
|
529
|
+
ticketStorePath: ticketPath(req),
|
|
515
530
|
});
|
|
516
531
|
if (migrated.length > 0) {
|
|
517
532
|
created.attachments = migrated;
|
|
@@ -894,7 +909,7 @@ function registerTicketsRoutes(deps) {
|
|
|
894
909
|
slug: project.slug,
|
|
895
910
|
pendingId: pendingSpecId,
|
|
896
911
|
realTicketId: created.id,
|
|
897
|
-
|
|
912
|
+
ticketStorePath: ticketPath(req),
|
|
898
913
|
});
|
|
899
914
|
if (migrated.length > 0) {
|
|
900
915
|
created.attachments = migrated;
|
|
@@ -1040,7 +1055,7 @@ function registerTicketsRoutes(deps) {
|
|
|
1040
1055
|
slug: project.slug,
|
|
1041
1056
|
pendingId: pendingSpecId,
|
|
1042
1057
|
realTicketId: created.id,
|
|
1043
|
-
|
|
1058
|
+
ticketStorePath: ticketPath(req),
|
|
1044
1059
|
});
|
|
1045
1060
|
if (migrated.length > 0) {
|
|
1046
1061
|
created.attachments = migrated;
|
|
@@ -1147,6 +1162,10 @@ function registerTicketsRoutes(deps) {
|
|
|
1147
1162
|
projectSlug: project.slug,
|
|
1148
1163
|
projectPath: project.path,
|
|
1149
1164
|
projectName: project.name,
|
|
1165
|
+
// Relocate-artifacts: pass the gated tickets-store path so SMASH
|
|
1166
|
+
// reads/writes the same store the rails load (workspace when
|
|
1167
|
+
// relocated), not a stale repo copy.
|
|
1168
|
+
ticketsPath: filePath,
|
|
1150
1169
|
broadcast: broadcast,
|
|
1151
1170
|
mode,
|
|
1152
1171
|
model,
|
|
@@ -1184,6 +1203,7 @@ function registerTicketsRoutes(deps) {
|
|
|
1184
1203
|
projectSlug: project.slug,
|
|
1185
1204
|
projectPath: project.path,
|
|
1186
1205
|
projectName: project.name,
|
|
1206
|
+
ticketsPath: ticketPath(req),
|
|
1187
1207
|
broadcast: broadcast,
|
|
1188
1208
|
}, ticketId, smashedAt);
|
|
1189
1209
|
if (!result.ok) {
|
|
@@ -1768,7 +1788,7 @@ function registerTicketsRoutes(deps) {
|
|
|
1768
1788
|
const attachment = await attachment_manager_1.attachmentManager.upload({
|
|
1769
1789
|
slug: ctx(req).project.slug,
|
|
1770
1790
|
ticketKey: parsed.key,
|
|
1771
|
-
|
|
1791
|
+
ticketStorePath: parsed.isPending ? null : ticketPath(req),
|
|
1772
1792
|
file: {
|
|
1773
1793
|
buffer: file.buffer,
|
|
1774
1794
|
originalname: file.originalname,
|
|
@@ -1829,7 +1849,7 @@ function registerTicketsRoutes(deps) {
|
|
|
1829
1849
|
slug: ctx(req).project.slug,
|
|
1830
1850
|
ticketKey: parsed.key,
|
|
1831
1851
|
attachmentId,
|
|
1832
|
-
|
|
1852
|
+
ticketStorePath: parsed.isPending ? null : ticketPath(req),
|
|
1833
1853
|
});
|
|
1834
1854
|
if (!ok) {
|
|
1835
1855
|
res.status(404).json({ error: 'Attachment not found' });
|
|
@@ -10,6 +10,7 @@ const plugins_router_1 = require("./plugins-router");
|
|
|
10
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
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
13
14
|
const project_router_jobs_1 = require("./project-router-jobs");
|
|
14
15
|
const project_router_spending_1 = require("./project-router-spending");
|
|
15
16
|
const project_router_chat_1 = require("./project-router-chat");
|
|
@@ -88,10 +89,24 @@ function createProjectRouter(registry) {
|
|
|
88
89
|
projectId: projectCtx.project.id,
|
|
89
90
|
broadcast: projectCtx.broadcast,
|
|
90
91
|
fileSummaryManager: projectCtx.fileSummaryManager,
|
|
92
|
+
// Relocate-artifacts: summary JSON OUTPUTS live in the workspace when
|
|
93
|
+
// relocated (source tree still read from project.path). Resolved per-call.
|
|
94
|
+
resolveSummaryRoot: () => {
|
|
95
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: projectCtx.project.slug, path: projectCtx.project.path });
|
|
96
|
+
return exec.relocated && exec.workspaceDir ? exec.workspaceDir : projectCtx.project.path;
|
|
97
|
+
},
|
|
91
98
|
}));
|
|
92
99
|
codeRouter(req, res, next);
|
|
93
100
|
});
|
|
94
|
-
|
|
101
|
+
// Relocate-artifacts gate (single chokepoint for ALL project-router ticket
|
|
102
|
+
// I/O): relocated ⇒ the registry entry's ticketsPath (workspace); legacy ⇒
|
|
103
|
+
// resolveTicketStoragePath (preserves integration-contract.json custom
|
|
104
|
+
// storagePath). Existing in-repo projects are byte-identical.
|
|
105
|
+
const ticketPath = (req) => {
|
|
106
|
+
const project = ctx(req).project;
|
|
107
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
108
|
+
return exec.relocated ? exec.ticketsPath : (0, ticket_store_1.resolveTicketStoragePath)(project.path);
|
|
109
|
+
};
|
|
95
110
|
const deps = { router, registry, ctx, ticketPath };
|
|
96
111
|
(0, project_router_jobs_1.registerJobsRoutes)(deps);
|
|
97
112
|
(0, project_router_spending_1.registerSpendingRoutes)(deps);
|