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
|
@@ -28,6 +28,10 @@ const interactive_job_session_1 = require("./interactive-job-session");
|
|
|
28
28
|
const attachment_manager_1 = require("./attachment-manager");
|
|
29
29
|
const ticket_store_1 = require("./ticket-store");
|
|
30
30
|
const binary_probe_1 = require("./binary-probe");
|
|
31
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
32
|
+
const framework_manager_1 = require("./framework-manager");
|
|
33
|
+
const openspec_shim_1 = require("./openspec-shim");
|
|
34
|
+
const artifact_registry_1 = require("./artifact-registry");
|
|
31
35
|
// ─── Telemetry env helpers ────────────────────────────────────────────────────
|
|
32
36
|
/** Build the OTEL environment variable block for a spawned claude process.
|
|
33
37
|
* Extracted as a pure function so it is unit-testable without a full spawn. */
|
|
@@ -156,6 +160,13 @@ class QueueManager {
|
|
|
156
160
|
/** Pre-spawn working-tree snapshot refs keyed by jobId — read at exit time
|
|
157
161
|
* by the Code-Explorer provenance hook. Cleared on job exit. */
|
|
158
162
|
_snapshotRefs;
|
|
163
|
+
/** Per-job resolved execution context (relocate-artifacts gate), captured at
|
|
164
|
+
* spawn time so `_onJobExit`'s provenance hook uses the SAME repoDir the
|
|
165
|
+
* snapshot used (= project.path, never the workspace). Cleared on job exit. */
|
|
166
|
+
_jobExecution;
|
|
167
|
+
/** Per-job openspec PATH shim dir (relocated claude rails only). Cleaned up on
|
|
168
|
+
* job exit. In-memory map of jobId → shim dir. */
|
|
169
|
+
_openspecShims = new Map();
|
|
159
170
|
/** Pending per-job interactive flag keyed by jobId — read at spawn time.
|
|
160
171
|
* In-memory only (mirrors _jobModelSelection). */
|
|
161
172
|
_jobInteractiveSelection;
|
|
@@ -190,6 +201,7 @@ class QueueManager {
|
|
|
190
201
|
this._jobProviderSelection = new Map();
|
|
191
202
|
this._jobModelSelection = new Map();
|
|
192
203
|
this._snapshotRefs = new Map();
|
|
204
|
+
this._jobExecution = new Map();
|
|
193
205
|
this._jobInteractiveSelection = new Map();
|
|
194
206
|
this._interactiveSessions = new Map();
|
|
195
207
|
const envTimeout = process.env.WM_ZOMBIE_TIMEOUT_MS !== undefined
|
|
@@ -261,6 +273,8 @@ class QueueManager {
|
|
|
261
273
|
this._interactiveSessions.clear();
|
|
262
274
|
// Release any per-job provenance snapshots so teardown leaves no map entries.
|
|
263
275
|
this._snapshotRefs.clear();
|
|
276
|
+
this._jobExecution.clear();
|
|
277
|
+
this._openspecShims.clear();
|
|
264
278
|
// Drop the DB reference last so any in-flight 'close' callback sees null
|
|
265
279
|
// and skips all DB work via the existing `if (this._db)` guards.
|
|
266
280
|
this._db = null;
|
|
@@ -522,7 +536,7 @@ class QueueManager {
|
|
|
522
536
|
if (ticketIds.length === 0)
|
|
523
537
|
return '';
|
|
524
538
|
try {
|
|
525
|
-
const store = (0, ticket_store_1.readStore)(
|
|
539
|
+
const store = (0, ticket_store_1.readStore)(this._resolveTicketsPath());
|
|
526
540
|
const sections = [];
|
|
527
541
|
for (const ticketId of ticketIds) {
|
|
528
542
|
const storeAttachmentIds = new Set((store.tickets[String(ticketId)]?.attachments ?? []).map((attachment) => attachment.id));
|
|
@@ -563,7 +577,7 @@ class QueueManager {
|
|
|
563
577
|
const specs = [];
|
|
564
578
|
if (this._cwd) {
|
|
565
579
|
try {
|
|
566
|
-
const store = (0, ticket_store_1.readStore)(
|
|
580
|
+
const store = (0, ticket_store_1.readStore)(this._resolveTicketsPath());
|
|
567
581
|
for (const ticketId of ticketIds) {
|
|
568
582
|
const ticket = store.tickets[String(ticketId)];
|
|
569
583
|
if (!ticket)
|
|
@@ -634,6 +648,46 @@ class QueueManager {
|
|
|
634
648
|
}
|
|
635
649
|
return this._adapter;
|
|
636
650
|
}
|
|
651
|
+
/**
|
|
652
|
+
* Resolve the relocate-artifacts execution context for this manager's project.
|
|
653
|
+
* The gate: relocated only when a registry entry exists AND core has populated
|
|
654
|
+
* the workspace; otherwise legacy (cwd = project.path, empty env) — preserving
|
|
655
|
+
* byte-identical behaviour for every existing in-repo project. Falls back to a
|
|
656
|
+
* legacy resolution rooted at `this._cwd` when slug/cwd are unavailable
|
|
657
|
+
* (non-Super contexts / tests that construct the manager without a slug).
|
|
658
|
+
*/
|
|
659
|
+
_resolveExecution() {
|
|
660
|
+
const repoDir = this._cwd ?? process.cwd();
|
|
661
|
+
if (!this._projectSlug || !this._cwd) {
|
|
662
|
+
return {
|
|
663
|
+
relocated: false,
|
|
664
|
+
cwd: repoDir,
|
|
665
|
+
repoDir,
|
|
666
|
+
workspaceDir: null,
|
|
667
|
+
ticketsPath: path_1.default.join(repoDir, '.specrails', 'local-tickets.json'),
|
|
668
|
+
backlogConfigPath: path_1.default.join(repoDir, '.specrails', 'backlog-config.json'),
|
|
669
|
+
profilesDir: path_1.default.join(repoDir, '.specrails', 'profiles'),
|
|
670
|
+
pluginsStateDir: path_1.default.join(repoDir, '.specrails', 'plugins'),
|
|
671
|
+
fileSummariesDir: path_1.default.join(repoDir, '.specrails', 'file-summaries'),
|
|
672
|
+
specrailsDir: path_1.default.join(repoDir, '.specrails'),
|
|
673
|
+
stateDir: path_1.default.join(repoDir, '.claude'),
|
|
674
|
+
env: {},
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
return (0, workspace_resolution_1.resolveProjectExecution)({ slug: this._projectSlug, path: this._cwd });
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Resolve the local-tickets.json path honouring the relocate-artifacts gate.
|
|
681
|
+
* Relocated ⇒ the registry entry's ticketsPath (workspace). Legacy ⇒
|
|
682
|
+
* `resolveTicketStoragePath(this._cwd)` which preserves the
|
|
683
|
+
* integration-contract.json custom-storagePath behaviour for existing repos.
|
|
684
|
+
*/
|
|
685
|
+
_resolveTicketsPath() {
|
|
686
|
+
const exec = this._resolveExecution();
|
|
687
|
+
if (exec.relocated)
|
|
688
|
+
return exec.ticketsPath;
|
|
689
|
+
return (0, ticket_store_1.resolveTicketStoragePath)(this._cwd ?? process.cwd());
|
|
690
|
+
}
|
|
637
691
|
/**
|
|
638
692
|
* Spawn an interactive ultracode session. The job row is created with the
|
|
639
693
|
* `interactive` flag set; the resident child runs the first turn (the
|
|
@@ -687,6 +741,13 @@ class QueueManager {
|
|
|
687
741
|
// Interactive jobs skip provenance, but a defensive delete keeps the map
|
|
688
742
|
// clean if a snapshot was ever recorded for this id.
|
|
689
743
|
this._snapshotRefs.delete(jobId);
|
|
744
|
+
// Clean up the per-job openspec PATH shim (relocated claude rails only).
|
|
745
|
+
const shim = this._openspecShims.get(jobId);
|
|
746
|
+
if (shim) {
|
|
747
|
+
this._openspecShims.delete(jobId);
|
|
748
|
+
if (this._projectSlug)
|
|
749
|
+
(0, openspec_shim_1.removeOpenspecShim)(this._projectSlug, jobId, (0, artifact_registry_1.resolveHome)());
|
|
750
|
+
}
|
|
690
751
|
if (this._disposed)
|
|
691
752
|
return;
|
|
692
753
|
const job = this._jobs.get(jobId);
|
|
@@ -785,6 +846,13 @@ class QueueManager {
|
|
|
785
846
|
// primary; everything in this spawn (binary, argv, model, profile, OTEL,
|
|
786
847
|
// plugins, result parsing, ai_invocations.provider) flows from `adapter`.
|
|
787
848
|
const adapter = this._resolveJobAdapter(jobId);
|
|
849
|
+
// Relocate-artifacts gate: resolve cwd/repoDir/env for this spawn. Legacy
|
|
850
|
+
// projects get cwd = project.path + empty env (byte-identical to today);
|
|
851
|
+
// relocated projects get cwd = workspace + SPECRAILS_REPO_DIR. Captured per
|
|
852
|
+
// job so the post-exit provenance hook uses the SAME repoDir.
|
|
853
|
+
const execution = this._resolveExecution();
|
|
854
|
+
this._jobExecution.set(jobId, execution);
|
|
855
|
+
const spawnCwd = execution.cwd;
|
|
788
856
|
job.status = 'running';
|
|
789
857
|
job.startedAt = new Date().toISOString();
|
|
790
858
|
job.queuePosition = null;
|
|
@@ -875,10 +943,17 @@ class QueueManager {
|
|
|
875
943
|
: adapter.id === 'claude' && this._db
|
|
876
944
|
? (0, db_1.getProjectSettings)(this._db).orchestratorModel
|
|
877
945
|
: (this._resolvedModel ?? adapter.defaultModel());
|
|
946
|
+
// Relocate-artifacts: when relocated, claude is spawned from the workspace
|
|
947
|
+
// so add `--add-dir <repoDir>` so its tools can still reach repo files by
|
|
948
|
+
// absolute path. (gemini/codex get env-only tweaks at spawn time below.)
|
|
949
|
+
const railExtraArgs = execution.relocated && adapter.id === 'claude'
|
|
950
|
+
? ['--add-dir', execution.repoDir]
|
|
951
|
+
: undefined;
|
|
878
952
|
const args = adapter.buildArgs('rail-job', {
|
|
879
953
|
prompt: railPrompt,
|
|
880
954
|
systemPrompt: systemAppend || undefined,
|
|
881
955
|
model: railModel,
|
|
956
|
+
extraArgs: railExtraArgs,
|
|
882
957
|
});
|
|
883
958
|
// Resolve agent profile (if any) and snapshot per-job before spawn.
|
|
884
959
|
// Super mode only (projectId + projectSlug + cwd all present).
|
|
@@ -894,11 +969,13 @@ class QueueManager {
|
|
|
894
969
|
try {
|
|
895
970
|
const selection = this._jobProfileSelection.get(jobId); // undefined|null|string
|
|
896
971
|
this._jobProfileSelection.delete(jobId);
|
|
897
|
-
|
|
972
|
+
// When relocated, core + `.specrails/profiles` live in the workspace
|
|
973
|
+
// (execution.cwd); legacy reads from the repo (execution.cwd === repo).
|
|
974
|
+
const coreSupports = projectSupportsProfiles(execution.cwd);
|
|
898
975
|
if (selection !== null && coreSupports) {
|
|
899
976
|
// selection is string (explicit) or undefined (default resolution)
|
|
900
977
|
const { resolveProfile, snapshotForJob, persistJobProfile, } = require('./profile-manager');
|
|
901
|
-
const resolved = resolveProfile(
|
|
978
|
+
const resolved = resolveProfile(execution.cwd, selection ?? undefined, adapter.id);
|
|
902
979
|
if (resolved) {
|
|
903
980
|
profileSnapshotPath = snapshotForJob(this._projectSlug, jobId, resolved);
|
|
904
981
|
profileName = resolved.name;
|
|
@@ -921,12 +998,22 @@ class QueueManager {
|
|
|
921
998
|
// gets signals synthesised by the codex-otel-bridge attached below.
|
|
922
999
|
let spawnEnv = process.env;
|
|
923
1000
|
const telemetryEnabled = !!(this._projectId && this._db && (0, db_1.getProjectSettings)(this._db).pipelineTelemetryEnabled);
|
|
1001
|
+
// Resolve the framework version ONCE at spawn time — `framework/current`
|
|
1002
|
+
// is read from `~/.specrails/framework/current`. A concurrent atomic swap
|
|
1003
|
+
// (FrameworkManager.swapCurrent) does not disturb this job: we captured the
|
|
1004
|
+
// version here and the per-job snapshot resolved its handles from this
|
|
1005
|
+
// value. GATED on `execution.relocated`: a LEGACY (in-repo) job does NOT
|
|
1006
|
+
// assemble from `framework/current`, so stamping it with a sibling project's
|
|
1007
|
+
// materialized framework version would be wrong telemetry. Null otherwise.
|
|
1008
|
+
const frameworkVersion = execution.relocated ? (0, framework_manager_1.readCurrentFrameworkVersion)() : null;
|
|
924
1009
|
if (telemetryEnabled && adapter.capabilities.nativeOtelEnv && this._projectId) {
|
|
925
1010
|
const extra = {};
|
|
926
1011
|
if (profileName)
|
|
927
1012
|
extra['specrails.profile_name'] = profileName;
|
|
928
1013
|
if (profileName)
|
|
929
1014
|
extra['specrails.profile_schema_version'] = '1';
|
|
1015
|
+
if (frameworkVersion)
|
|
1016
|
+
extra['specrails.framework_version'] = frameworkVersion;
|
|
930
1017
|
spawnEnv = {
|
|
931
1018
|
...process.env,
|
|
932
1019
|
...buildTelemetryEnv(jobId, this._projectId, this._desktopPort, extra),
|
|
@@ -952,7 +1039,8 @@ class QueueManager {
|
|
|
952
1039
|
if (adapter.mcpRegistration === 'project-json' && this._projectId && this._projectSlug && this._cwd) {
|
|
953
1040
|
try {
|
|
954
1041
|
const { resolvePluginsForSpawn, snapshotPluginsForJob } = require('./plugins/rail-integration');
|
|
955
|
-
|
|
1042
|
+
// Relocated ⇒ `.mcp.json`/plugin state live in the workspace (execution.cwd).
|
|
1043
|
+
const resolution = await resolvePluginsForSpawn(execution.cwd, this._projectId, jobId);
|
|
956
1044
|
pluginActive = resolution.active;
|
|
957
1045
|
pluginDegraded = resolution.degraded;
|
|
958
1046
|
if (pluginActive.length > 0 || pluginDegraded.length > 0) {
|
|
@@ -987,6 +1075,14 @@ class QueueManager {
|
|
|
987
1075
|
const settings = (0, db_1.getProjectSettings)(this._db);
|
|
988
1076
|
if (settings.pipelineTelemetryEnabled && (pluginActive.length > 0 || pluginDegraded.length > 0)) {
|
|
989
1077
|
const extra = {};
|
|
1078
|
+
// Re-thread the resolved framework version (this block rebuilds the
|
|
1079
|
+
// whole telemetry env, so it must carry the same attr as the first one).
|
|
1080
|
+
if (frameworkVersion)
|
|
1081
|
+
extra['specrails.framework_version'] = frameworkVersion;
|
|
1082
|
+
if (profileName)
|
|
1083
|
+
extra['specrails.profile_name'] = profileName;
|
|
1084
|
+
if (profileName)
|
|
1085
|
+
extra['specrails.profile_schema_version'] = '1';
|
|
990
1086
|
if (pluginActive.length > 0) {
|
|
991
1087
|
extra['specrails.plugins.active'] = JSON.stringify(pluginActive.map((p) => p.name));
|
|
992
1088
|
extra['specrails.plugins.versions'] = JSON.stringify(Object.fromEntries(pluginActive.map((p) => [p.name, p.version])));
|
|
@@ -1004,13 +1100,38 @@ class QueueManager {
|
|
|
1004
1100
|
// uses this to pre-acknowledge the project's custom subagents so they load
|
|
1005
1101
|
// in `gemini -p` mode (else invoke_agent reports "Subagent not found" and the
|
|
1006
1102
|
// orchestrator silently falls back to a generic agent). No-op for claude/codex.
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1103
|
+
// Runs in the SPAWN cwd (workspace when relocated) — gemini acks the
|
|
1104
|
+
// project's subagents where it will actually discover them.
|
|
1105
|
+
try {
|
|
1106
|
+
adapter.prepareHeadlessSpawn?.(spawnCwd);
|
|
1107
|
+
}
|
|
1108
|
+
catch (err) {
|
|
1109
|
+
/* c8 ignore next -- best-effort prep; a failure is non-fatal */
|
|
1110
|
+
console.warn(`[queue-manager] headless-spawn prep failed: ${err.message}`);
|
|
1111
|
+
}
|
|
1112
|
+
// ─── Relocate-artifacts spawn env ──────────────────────────────────────
|
|
1113
|
+
// Merge SPECRAILS_REPO_DIR (+ workspace/tickets/state/etc.) so stage-3
|
|
1114
|
+
// `${SPECRAILS_REPO_DIR:-.}` re-pointing drives source/openspec/git I/O back
|
|
1115
|
+
// into the repo. Per provider: gemini trusts the workspace cwd; codex gets
|
|
1116
|
+
// NO CODEX_HOME override (all-or-nothing incl. auth → breaks the rail) and
|
|
1117
|
+
// relies on cwd-based discovery from the workspace. Legacy ⇒ empty env, no-op.
|
|
1118
|
+
if (execution.relocated) {
|
|
1119
|
+
spawnEnv = { ...spawnEnv, ...execution.env };
|
|
1120
|
+
if (adapter.id === 'gemini') {
|
|
1121
|
+
spawnEnv = { ...spawnEnv, GEMINI_CLI_TRUST_WORKSPACE: 'true' };
|
|
1010
1122
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1123
|
+
// openspec PATH shim (claude rails only): prepend a per-job shim dir that
|
|
1124
|
+
// re-points every BARE `openspec` call at the repo working tree, so a
|
|
1125
|
+
// skill- or un-wrapped-template-driven `openspec <verb>` from the workspace
|
|
1126
|
+
// cwd still operates on the repo's OpenSpec project (see openspec-shim.ts).
|
|
1127
|
+
// claude is the only adapter that runs the openspec-backed sr-* rails;
|
|
1128
|
+
// gemini/codex skill scaffolds carry their own repo-dir wrapping.
|
|
1129
|
+
if (adapter.id === 'claude' && this._projectSlug) {
|
|
1130
|
+
const shimDir = (0, openspec_shim_1.ensureOpenspecShim)(this._projectSlug, jobId, (0, artifact_registry_1.resolveHome)());
|
|
1131
|
+
if (shimDir) {
|
|
1132
|
+
this._openspecShims.set(jobId, shimDir);
|
|
1133
|
+
spawnEnv = { ...spawnEnv, PATH: (0, openspec_shim_1.prependShimToPath)(spawnEnv.PATH, shimDir) };
|
|
1134
|
+
}
|
|
1014
1135
|
}
|
|
1015
1136
|
}
|
|
1016
1137
|
// ─── Interactive ultracode branch ──────────────────────────────────────
|
|
@@ -1028,16 +1149,20 @@ class QueueManager {
|
|
|
1028
1149
|
prompt: '',
|
|
1029
1150
|
systemPrompt: systemAppend || undefined,
|
|
1030
1151
|
model: railModel,
|
|
1152
|
+
extraArgs: railExtraArgs,
|
|
1031
1153
|
});
|
|
1032
|
-
this._startInteractiveJob(jobId, job, adapter, { binary, args: interactiveArgs, cwd:
|
|
1154
|
+
this._startInteractiveJob(jobId, job, adapter, { binary, args: interactiveArgs, cwd: spawnCwd, env: spawnEnv }, railPrompt);
|
|
1033
1155
|
return;
|
|
1034
1156
|
}
|
|
1035
1157
|
// Code-Explorer pre-spawn snapshot. Captures the working-tree state via
|
|
1036
1158
|
// `git stash create --include-untracked` so the post-exit hook can diff
|
|
1037
1159
|
// against it. Gated by SPECRAILS_CODE_EXPLORER — when off, no-op.
|
|
1038
|
-
|
|
1160
|
+
// CRITICAL: snapshot the REPO working tree (execution.repoDir), never the
|
|
1161
|
+
// workspace — else a relocated job would diff an empty workspace and silently
|
|
1162
|
+
// record zero "touched by AI" files.
|
|
1163
|
+
if ((0, feature_flags_1.isCodeExplorerEnabled)()) {
|
|
1039
1164
|
try {
|
|
1040
|
-
const snap = (0, file_provenance_1.snapshotWorkingTree)(
|
|
1165
|
+
const snap = (0, file_provenance_1.snapshotWorkingTree)(execution.repoDir);
|
|
1041
1166
|
this._snapshotRefs.set(jobId, snap);
|
|
1042
1167
|
}
|
|
1043
1168
|
catch (err) {
|
|
@@ -1048,7 +1173,7 @@ class QueueManager {
|
|
|
1048
1173
|
const child = (0, cli_prompt_1.spawnAiCli)(binary, args, {
|
|
1049
1174
|
env: spawnEnv,
|
|
1050
1175
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1051
|
-
cwd:
|
|
1176
|
+
cwd: spawnCwd,
|
|
1052
1177
|
});
|
|
1053
1178
|
this._activeProcess = child;
|
|
1054
1179
|
this._activeJobId = jobId;
|
|
@@ -1237,6 +1362,12 @@ class QueueManager {
|
|
|
1237
1362
|
// stash commit it references is dangling and git-GC'd on its own).
|
|
1238
1363
|
const snapshot = this._snapshotRefs.get(jobId);
|
|
1239
1364
|
this._snapshotRefs.delete(jobId);
|
|
1365
|
+
// Relocate-artifacts: the repo dir this job snapshotted against (= repoDir,
|
|
1366
|
+
// never the workspace). Falls back to this._cwd for jobs spawned before this
|
|
1367
|
+
// map existed (e.g. restored-from-db) so provenance still targets the repo.
|
|
1368
|
+
const jobExecution = this._jobExecution.get(jobId);
|
|
1369
|
+
this._jobExecution.delete(jobId);
|
|
1370
|
+
const provenanceRepoDir = jobExecution?.repoDir ?? this._cwd;
|
|
1240
1371
|
// A3: release the active slot for THIS job before any early return, so a
|
|
1241
1372
|
// disposed/unknown-job exit can never leave the slot reserved (which would
|
|
1242
1373
|
// wedge the queue). Guarded by identity in case a stale exit fires late.
|
|
@@ -1341,11 +1472,11 @@ class QueueManager {
|
|
|
1341
1472
|
// the pre-spawn snapshot and inserts one row per touched path. Gated by
|
|
1342
1473
|
// SPECRAILS_CODE_EXPLORER (re-checked at each completion so the flag can
|
|
1343
1474
|
// be flipped off mid-session without leaving partial writes).
|
|
1344
|
-
if ((0, feature_flags_1.isCodeExplorerEnabled)() &&
|
|
1475
|
+
if ((0, feature_flags_1.isCodeExplorerEnabled)() && provenanceRepoDir && this._projectId) {
|
|
1345
1476
|
const ref = snapshot?.ref ?? '';
|
|
1346
1477
|
try {
|
|
1347
|
-
const diff = (0, file_provenance_1.diffAgainstSnapshot)(
|
|
1348
|
-
const patches = (0, file_provenance_1.collectDiffPatches)(
|
|
1478
|
+
const diff = (0, file_provenance_1.diffAgainstSnapshot)(provenanceRepoDir, ref, snapshot?.untracked, snapshot?.headSha);
|
|
1479
|
+
const patches = (0, file_provenance_1.collectDiffPatches)(provenanceRepoDir, ref, diff, snapshot?.headSha);
|
|
1349
1480
|
if (diff.length > 50) {
|
|
1350
1481
|
console.warn(`[provenance.large_job] job=${jobId} files=${diff.length}`);
|
|
1351
1482
|
}
|
|
@@ -20,6 +20,11 @@ const win_spawn_1 = require("./util/win-spawn");
|
|
|
20
20
|
const setup_prerequisites_1 = require("./setup-prerequisites");
|
|
21
21
|
const core_package_1 = require("./core-package");
|
|
22
22
|
const providers_1 = require("./providers");
|
|
23
|
+
const artifact_registry_1 = require("./artifact-registry");
|
|
24
|
+
const install_config_path_1 = require("./install-config-path");
|
|
25
|
+
const bundled_core_1 = require("./bundled-core");
|
|
26
|
+
const bundled_openspec_1 = require("./bundled-openspec");
|
|
27
|
+
const framework_manager_1 = require("./framework-manager");
|
|
23
28
|
/**
|
|
24
29
|
* specrails-core's installer (Node-native from v4.2.0 onward, bash
|
|
25
30
|
* prior) always scaffolds into `.claude/` regardless of which AI
|
|
@@ -75,6 +80,45 @@ function spawnCoreInit(args, cwd) {
|
|
|
75
80
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
76
81
|
});
|
|
77
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Spawn the BUNDLED specrails-core `init` (offline framework + assemble; openspec
|
|
85
|
+
* is the only network step). Runs `node <bundled-cli> init <args>` with
|
|
86
|
+
* `SPECRAILS_CORE_SCRIPT_DIR` pointed at the bundle so core's template/command
|
|
87
|
+
* sources resolve from the app bundle, NOT a global install or npm registry. The
|
|
88
|
+
* framework was already materialized by FrameworkManager.materialize() (idempotent
|
|
89
|
+
* — core's ensureFramework skips re-materialization), so this call only assembles
|
|
90
|
+
* the workspace by symlink + runs openspec init.
|
|
91
|
+
*
|
|
92
|
+
* Returns null when no bundled core is present (caller falls back to spawnCoreInit).
|
|
93
|
+
*/
|
|
94
|
+
function spawnBundledCoreInit(args, cwd) {
|
|
95
|
+
const cli = (0, bundled_core_1.getBundledCoreCli)();
|
|
96
|
+
const coreRoot = (0, bundled_core_1.getBundledCoreRoot)();
|
|
97
|
+
if (!cli || !coreRoot)
|
|
98
|
+
return null;
|
|
99
|
+
const fullArgs = [cli, 'init', ...args];
|
|
100
|
+
const env = { ...process.env, SPECRAILS_CORE_SCRIPT_DIR: coreRoot };
|
|
101
|
+
// Bundled openspec (offline) — the LAST network step of project-add. When the
|
|
102
|
+
// app ships @fission-ai/openspec, point specrails-core's `installOpenSpecProject`
|
|
103
|
+
// at the bundled CLI and the SAME node we run the bundled core with. Tauri
|
|
104
|
+
// strips exec bits from bundled resources and the openspec CLI is a node script,
|
|
105
|
+
// so it MUST be invoked as `node <cli> init …` — hence we set BOTH the BIN and
|
|
106
|
+
// the NODE env (specrails-core's buildOpenSpecInvocation form 1). When absent we
|
|
107
|
+
// leave them unset → core falls back to `npx @fission-ai/openspec` (still works
|
|
108
|
+
// online). Bundled core + bundled openspec ⇒ project-add is FULLY OFFLINE.
|
|
109
|
+
const openspecCli = (0, bundled_openspec_1.getBundledOpenspecCli)();
|
|
110
|
+
if (openspecCli) {
|
|
111
|
+
env.SPECRAILS_OPENSPEC_BIN = openspecCli;
|
|
112
|
+
env.SPECRAILS_OPENSPEC_NODE = process.execPath;
|
|
113
|
+
}
|
|
114
|
+
console.log(`[SetupManager] spawning BUNDLED core: ${process.execPath} ${fullArgs.join(' ')} (cwd=${cwd})` +
|
|
115
|
+
(openspecCli ? ' [bundled openspec: offline]' : ' [openspec: npx fallback]'));
|
|
116
|
+
return (0, win_spawn_1.spawnCli)(process.execPath, fullArgs, {
|
|
117
|
+
cwd,
|
|
118
|
+
env,
|
|
119
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
78
122
|
// H19: hard cap on the runtime probe. `npx --yes --prefer-online
|
|
79
123
|
// <CORE_PACKAGE_SPEC> version` does a network round-trip to the npm
|
|
80
124
|
// registry, and spawnSync blocks the single event loop — without a timeout a
|
|
@@ -120,8 +164,8 @@ function writeSpawnInstallConfig(projectId, yamlText) {
|
|
|
120
164
|
(0, fs_1.writeFileSync)(tempPath, yamlText, 'utf-8');
|
|
121
165
|
return tempPath;
|
|
122
166
|
}
|
|
123
|
-
function readInstallConfig(
|
|
124
|
-
const configPath = (0,
|
|
167
|
+
function readInstallConfig(project) {
|
|
168
|
+
const configPath = (0, install_config_path_1.installConfigPath)(project);
|
|
125
169
|
try {
|
|
126
170
|
const text = (0, fs_1.readFileSync)(configPath, 'utf-8');
|
|
127
171
|
const tierMatch = text.match(/^tier:\s*(\w+)/m);
|
|
@@ -591,14 +635,33 @@ class SetupManager {
|
|
|
591
635
|
this._projectNames = new Map();
|
|
592
636
|
}
|
|
593
637
|
// ─── Full Install: TUI installer (npx specrails-core) ────────────────────────
|
|
594
|
-
startInstall(projectId, projectPath) {
|
|
638
|
+
startInstall(projectId, projectPath, projectSlug) {
|
|
595
639
|
if (this._installProcesses.has(projectId)) {
|
|
596
640
|
console.warn(`[SetupManager] install already running for ${projectId}`);
|
|
597
641
|
return;
|
|
598
642
|
}
|
|
599
|
-
|
|
643
|
+
// Relocate-artifacts: ensure the shared registry entry exists (slug
|
|
644
|
+
// agreement) BEFORE core's `init` runs, so core resolves its workspace from
|
|
645
|
+
// the registry and installs the relocated artifacts under desktop's slug.
|
|
646
|
+
// addProject already mirrors the entry; this is belt-and-suspenders for the
|
|
647
|
+
// (rare) case where the project was registered before the mirror existed.
|
|
648
|
+
// Wrapped so a registry write failure never blocks install (core then
|
|
649
|
+
// installs in-repo and the project simply stays legacy — gate-safe).
|
|
650
|
+
if (projectSlug) {
|
|
651
|
+
try {
|
|
652
|
+
(0, artifact_registry_1.mirrorProjectEntry)({ repoPath: projectPath, slug: projectSlug, desktopProjectId: projectId });
|
|
653
|
+
}
|
|
654
|
+
catch (err) {
|
|
655
|
+
console.warn(`[SetupManager] registry mirror before install failed (non-fatal): ${err.message}`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// Relocate-artifacts: the install config lives in the per-project HOME dir
|
|
659
|
+
// (NOT the repo). `--from-config` accepts any path, so the spawn below points
|
|
660
|
+
// core at the relocated location.
|
|
661
|
+
const installProject = { slug: projectSlug, path: projectPath };
|
|
662
|
+
const configPath = (0, install_config_path_1.installConfigPath)(installProject);
|
|
600
663
|
const hasConfig = (0, fs_1.existsSync)(configPath);
|
|
601
|
-
const parsedConfig = hasConfig ? readInstallConfig(
|
|
664
|
+
const parsedConfig = hasConfig ? readInstallConfig(installProject) : null;
|
|
602
665
|
const tier = parsedConfig?.tier ?? 'full';
|
|
603
666
|
this._projectTiers.set(projectId, tier);
|
|
604
667
|
// Pull provider out of the just-written install-config.yaml so the
|
|
@@ -628,24 +691,54 @@ class SetupManager {
|
|
|
628
691
|
});
|
|
629
692
|
return;
|
|
630
693
|
}
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
694
|
+
// ─── Bundled-core fast path (offline framework) ──────────────────────────
|
|
695
|
+
// When the app ships specrails-core, materialize the versioned framework
|
|
696
|
+
// ONCE (instant/offline) and run the BUNDLED core `init` (offline framework
|
|
697
|
+
// assemble by symlink; openspec init is the only remaining network step) —
|
|
698
|
+
// NO `npx specrails-core` round-trip. When NO bundled core is present we fall
|
|
699
|
+
// through to the legacy npx probe + spawn, byte-identical to today.
|
|
700
|
+
const useBundledCore = (0, bundled_core_1.getBundledCoreCli)() !== null;
|
|
701
|
+
if (useBundledCore) {
|
|
702
|
+
// Materialize the framework for the selected provider (idempotent). The
|
|
703
|
+
// subsequent bundled `init` finds it already present (ensureFramework
|
|
704
|
+
// skips) and just assembles the workspace by symlink. Best-effort: a
|
|
705
|
+
// materialize failure is surfaced but the bundled init also re-runs
|
|
706
|
+
// ensureFramework, so it self-heals.
|
|
707
|
+
const provider = this._projectProviders.get(projectId) ?? 'claude';
|
|
708
|
+
try {
|
|
709
|
+
const fm = new framework_manager_1.FrameworkManager({
|
|
710
|
+
broadcast: (msg) => this._broadcast(msg),
|
|
711
|
+
});
|
|
712
|
+
const mat = fm.materialize((0, bundled_core_1.getBundledCoreVersion)() ?? undefined, [provider]);
|
|
713
|
+
if (mat.ran && mat.errors.length > 0) {
|
|
714
|
+
console.warn(`[SetupManager] framework materialize had errors: ${mat.errors.map((e) => `${e.provider}: ${e.message}`).join('; ')}`);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
catch (err) {
|
|
718
|
+
console.warn(`[SetupManager] framework materialize threw (non-fatal): ${err.message}`);
|
|
719
|
+
}
|
|
639
720
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
721
|
+
else {
|
|
722
|
+
// Legacy path: probe the npx-resolved core runtime (network) before spawn.
|
|
723
|
+
const probe = probeCoreRuntimeVersion(projectPath);
|
|
724
|
+
if (!probe.ok) {
|
|
725
|
+
this._broadcast({
|
|
726
|
+
type: 'setup_error',
|
|
727
|
+
projectId,
|
|
728
|
+
error: `Failed to verify specrails-core runtime before install: ${probe.error ?? 'unknown error'}`,
|
|
729
|
+
});
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
console.log(`[SetupManager] core runtime probe: ${probe.bin} -> ${probe.version}`);
|
|
733
|
+
const probeCmp = compareSemver(probe.version, MIN_NODE_NATIVE_CORE_VERSION);
|
|
734
|
+
if (probeCmp !== null && probeCmp < 0) {
|
|
735
|
+
this._broadcast({
|
|
736
|
+
type: 'setup_error',
|
|
737
|
+
projectId,
|
|
738
|
+
error: `Resolved specrails-core@${probe.version} is legacy; expected Node-native >= ${MIN_NODE_NATIVE_CORE_VERSION}.`,
|
|
739
|
+
});
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
649
742
|
}
|
|
650
743
|
let spawnConfigPath = null;
|
|
651
744
|
if (hasConfig) {
|
|
@@ -659,7 +752,10 @@ class SetupManager {
|
|
|
659
752
|
const initArgs = hasConfig
|
|
660
753
|
? ['--yes', '--from-config', spawnConfigPath ?? configPath]
|
|
661
754
|
: ['--yes', '--root-dir', projectPath];
|
|
662
|
-
|
|
755
|
+
// Bundled core (offline, node <cli> init) when available, else legacy npx.
|
|
756
|
+
const child = useBundledCore
|
|
757
|
+
? spawnBundledCoreInit(initArgs, projectPath)
|
|
758
|
+
: spawnCoreInit(initArgs, projectPath);
|
|
663
759
|
this._installProcesses.set(projectId, child);
|
|
664
760
|
this._installLogBuffer.set(projectId, []);
|
|
665
761
|
// spawnCoreInit uses shell:false on POSIX, so a spawn failure emits 'error'
|
|
@@ -738,7 +834,7 @@ class SetupManager {
|
|
|
738
834
|
this._broadcast({
|
|
739
835
|
type: 'setup_error',
|
|
740
836
|
projectId,
|
|
741
|
-
error: formatBufferedInstallError(
|
|
837
|
+
error: formatBufferedInstallError(`${useBundledCore ? 'bundled specrails-core' : 'npx specrails-core'} exited with code ${code ?? 'unknown'}`, logBuffer),
|
|
742
838
|
});
|
|
743
839
|
}
|
|
744
840
|
});
|
|
@@ -769,7 +865,12 @@ class SetupManager {
|
|
|
769
865
|
catch (err) {
|
|
770
866
|
console.warn(`[SetupManager] Failed to pre-create enrich directories: ${err}`);
|
|
771
867
|
}
|
|
772
|
-
|
|
868
|
+
// Relocate-artifacts: the install config lives in the per-project HOME dir.
|
|
869
|
+
// The slug was allocated at addProject and mirrored into the shared registry
|
|
870
|
+
// before/at install time, so resolve it from there (legacy full-flow path —
|
|
871
|
+
// not exercised by the app's quick flow).
|
|
872
|
+
const enrichSlug = (0, artifact_registry_1.resolveArtifacts)(projectPath).entry?.slug;
|
|
873
|
+
const configPath = (0, install_config_path_1.installConfigPath)({ slug: enrichSlug, path: projectPath });
|
|
773
874
|
const hasConfig = (0, fs_1.existsSync)(configPath);
|
|
774
875
|
const enrichCmd = hasConfig ? '/specrails:enrich --from-config' : '/specrails:enrich';
|
|
775
876
|
this._spawnSetupWithAdapter(projectId, projectPath, {
|
|
@@ -1148,16 +1249,17 @@ class SetupManager {
|
|
|
1148
1249
|
getInstallTier(projectId) {
|
|
1149
1250
|
return this._projectTiers.get(projectId);
|
|
1150
1251
|
}
|
|
1151
|
-
getSummary(
|
|
1152
|
-
const config = readInstallConfig(
|
|
1252
|
+
getSummary(project) {
|
|
1253
|
+
const config = readInstallConfig(project);
|
|
1153
1254
|
const tier = config?.tier ?? 'quick';
|
|
1154
1255
|
// Provider is authoritative from install-config.yaml when present; we
|
|
1155
1256
|
// do NOT fall back to filesystem heuristics because both `.codex/` and
|
|
1156
1257
|
// `.claude/` can legitimately coexist (e.g. a project that's been
|
|
1157
1258
|
// re-init'd) and a generic `existsSync` probe would mis-route.
|
|
1259
|
+
// Relocate-artifacts: the config lives in the per-project HOME dir.
|
|
1158
1260
|
let provider = 'claude';
|
|
1159
1261
|
try {
|
|
1160
|
-
const text = (0, fs_1.readFileSync)((0,
|
|
1262
|
+
const text = (0, fs_1.readFileSync)((0, install_config_path_1.installConfigPath)(project), 'utf-8');
|
|
1161
1263
|
const m = text.match(/^provider:\s*(\w+)/m);
|
|
1162
1264
|
if (m && m[1] && (0, providers_1.hasAdapter)(m[1]))
|
|
1163
1265
|
provider = m[1];
|
|
@@ -1165,7 +1267,7 @@ class SetupManager {
|
|
|
1165
1267
|
catch {
|
|
1166
1268
|
// Missing install-config — stay on claude default.
|
|
1167
1269
|
}
|
|
1168
|
-
return computeSummary(
|
|
1270
|
+
return computeSummary(project.path, tier, provider);
|
|
1169
1271
|
}
|
|
1170
1272
|
}
|
|
1171
1273
|
exports.SetupManager = SetupManager;
|
|
@@ -57,10 +57,17 @@ const node_readline_1 = require("node:readline");
|
|
|
57
57
|
const cli_prompt_1 = require("./util/cli-prompt");
|
|
58
58
|
const explore_smash_1 = require("./explore-smash");
|
|
59
59
|
const explore_cwd_manager_1 = require("./explore-cwd-manager");
|
|
60
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
60
61
|
const ai_invocations_1 = require("./ai-invocations");
|
|
61
62
|
const result_event_1 = require("./result-event");
|
|
62
63
|
const ticket_store_1 = require("./ticket-store");
|
|
63
64
|
const ticket_store_2 = require("./ticket-store");
|
|
65
|
+
/** Resolve the tickets-store path for a SMASH run: the gated `ticketsPath` when
|
|
66
|
+
* the caller threaded it (relocation-aware), else the legacy repo-relative
|
|
67
|
+
* derivation. */
|
|
68
|
+
function smashTicketsPath(deps) {
|
|
69
|
+
return deps.ticketsPath ?? (0, ticket_store_2.resolveTicketStoragePath)(deps.projectPath);
|
|
70
|
+
}
|
|
64
71
|
const SMASH_TIMEOUT_MS_SIMPLE = 60_000;
|
|
65
72
|
const SMASH_TIMEOUT_MS_FULL = 900_000; // 15 min — super-spec mode, no rush
|
|
66
73
|
const SMASH_MAX_TURNS_SIMPLE = 1;
|
|
@@ -117,10 +124,17 @@ function prepareSmashSpawn(deps, ticket) {
|
|
|
117
124
|
const systemPrompt = (0, explore_smash_1.buildSmashSystemPrompt)(mode);
|
|
118
125
|
const userPrompt = `${ticket.title}\n\n${ticket.description}`;
|
|
119
126
|
let cwd;
|
|
127
|
+
let env;
|
|
120
128
|
// Full mode needs access to the project tree (so Read/Grep/Glob hit the
|
|
121
129
|
// real repo). Simple mode keeps the app-managed dir for a clean scope.
|
|
122
130
|
if (mode === 'full') {
|
|
123
|
-
|
|
131
|
+
// Relocate-artifacts: when relocated, spawn from the workspace (so the CLI
|
|
132
|
+
// discovers the relocated agents/profiles + reaches the repo via the
|
|
133
|
+
// `./project` symlink + SPECRAILS_REPO_DIR). Legacy ⇒ cwd = project.path,
|
|
134
|
+
// empty env (byte-identical).
|
|
135
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: deps.projectSlug, path: deps.projectPath });
|
|
136
|
+
cwd = exec.cwd;
|
|
137
|
+
env = exec.relocated ? exec.env : undefined;
|
|
124
138
|
}
|
|
125
139
|
else {
|
|
126
140
|
try {
|
|
@@ -135,7 +149,7 @@ function prepareSmashSpawn(deps, ticket) {
|
|
|
135
149
|
}
|
|
136
150
|
}
|
|
137
151
|
const model = deps.model ?? 'sonnet';
|
|
138
|
-
return { args: buildSmashArgs(model, systemPrompt, userPrompt, mode), cwd, systemPrompt, userPrompt, mode };
|
|
152
|
+
return { args: buildSmashArgs(model, systemPrompt, userPrompt, mode), cwd, env, systemPrompt, userPrompt, mode };
|
|
139
153
|
}
|
|
140
154
|
/**
|
|
141
155
|
* Read stream-json output from a child process. Exported for tests.
|
|
@@ -444,7 +458,7 @@ async function runSmash(deps, ticketId) {
|
|
|
444
458
|
_smashInFlight.add(inFlightKey);
|
|
445
459
|
try {
|
|
446
460
|
// Pre-flight: read store and check eligibility.
|
|
447
|
-
const filePath = (
|
|
461
|
+
const filePath = smashTicketsPath(deps);
|
|
448
462
|
let ticket;
|
|
449
463
|
try {
|
|
450
464
|
const { readStore } = await Promise.resolve().then(() => __importStar(require('./ticket-store')));
|
|
@@ -469,16 +483,17 @@ async function runSmash(deps, ticketId) {
|
|
|
469
483
|
ticketTitle: ticket.title,
|
|
470
484
|
timestamp: startedAt,
|
|
471
485
|
});
|
|
472
|
-
const { args, cwd } = prepareSmashSpawn({
|
|
486
|
+
const { args, cwd, env: spawnEnv } = prepareSmashSpawn({
|
|
473
487
|
projectSlug: deps.projectSlug,
|
|
474
488
|
projectPath: deps.projectPath,
|
|
475
489
|
projectName: deps.projectName,
|
|
476
490
|
model: deps.model,
|
|
491
|
+
mode,
|
|
477
492
|
}, ticket);
|
|
478
493
|
let child;
|
|
479
494
|
try {
|
|
480
495
|
child = spawn('claude', args, {
|
|
481
|
-
env: process.env,
|
|
496
|
+
env: spawnEnv ? { ...process.env, ...spawnEnv } : process.env,
|
|
482
497
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
483
498
|
cwd,
|
|
484
499
|
});
|
|
@@ -625,7 +640,7 @@ async function runSmashUndo(deps, ticketId, smashedAt) {
|
|
|
625
640
|
return { ok: false, deletedChildren: [], reason: 'disabled' };
|
|
626
641
|
}
|
|
627
642
|
const now = deps.now ?? (() => new Date());
|
|
628
|
-
const filePath = (
|
|
643
|
+
const filePath = smashTicketsPath(deps);
|
|
629
644
|
const finishedAt = now().toISOString();
|
|
630
645
|
let undone;
|
|
631
646
|
try {
|