specrails-desktop 2.7.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -19
- package/client/dist/assets/{ActivityFeedPage-LKqd18-G.js → ActivityFeedPage-DNqnf1fZ.js} +1 -1
- package/client/dist/assets/{AgentsPage-Cb-b-6Ot.js → AgentsPage-vmNIEbGM.js} +1 -1
- package/client/dist/assets/{AnalyticsPage-HVxQQ1wy.js → AnalyticsPage-CdfN0ofZ.js} +1 -1
- package/client/dist/assets/{BarChart-BOyHB0dw.js → BarChart-CIkopHjl.js} +1 -1
- package/client/dist/assets/{CodePage-DnOnwKGB.js → CodePage-DDRNU5FN.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-D2auU39x.js → DesktopAnalyticsPage-Cl3sKKSG.js} +1 -1
- package/client/dist/assets/{DocsDialog-CTuDX3GK.js → DocsDialog-BGrBOfUr.js} +2 -2
- package/client/dist/assets/{DocsPage-DRyMmu0Z.js → DocsPage-CY-2SSzw.js} +2 -2
- package/client/dist/assets/{ExportDropdown-DO-GGiMh.js → ExportDropdown-BRHcvP0r.js} +1 -1
- package/client/dist/assets/{IntegrationsPage-BhbO4jFT.js → IntegrationsPage-nKdLB4Ub.js} +1 -1
- package/client/dist/assets/{JobDetailPage-DJooEg1s.js → JobDetailPage-Bf0A6WWQ.js} +1 -1
- package/client/dist/assets/{JobsPage-BbaC-YOg.js → JobsPage-Vg4nXPdL.js} +1 -1
- package/client/dist/assets/{dist-js-CiIVMsx3.js → dist-js-0i_klubI.js} +1 -1
- package/client/dist/assets/{dist-js-Xc2lRKp2.js → dist-js-CUs5GjwA.js} +1 -1
- package/client/dist/assets/{index-DK214dak.js → index-BXoHFtfG.js} +8 -8
- package/client/dist/assets/index-D6BaYRRU.css +2 -0
- 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-D6M_MvoC.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/{useProjectCache-DVNypkmR.js → useProjectCache-BeyBSNpD.js} +1 -1
- package/client/dist/index.html +4 -4
- 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 +39 -14
- 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/providers/gemini-adapter.js +4 -0
- package/server/dist/providers/gemini-agent-ack.js +65 -0
- package/server/dist/queue-manager.js +156 -12
- 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/util/stream-display.js +18 -3
- 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
|
@@ -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 {
|
|
@@ -340,11 +340,15 @@ function extractTicketIdsFromCommand(command) {
|
|
|
340
340
|
* resolve to `title: null`. Returns `[]` when the command has no ticket
|
|
341
341
|
* references.
|
|
342
342
|
*/
|
|
343
|
-
function resolveTicketsFromCommand(projectPath, command
|
|
343
|
+
function resolveTicketsFromCommand(projectPath, command,
|
|
344
|
+
/** Relocate-artifacts: when provided, read the store from this absolute path
|
|
345
|
+
* (the workspace ticket store) instead of `resolveTicketStoragePath(
|
|
346
|
+
* projectPath)`. Legacy callers omit it and behave byte-identically. */
|
|
347
|
+
ticketsPathOverride) {
|
|
344
348
|
const ids = extractTicketIdsFromCommand(command);
|
|
345
349
|
if (ids.length === 0)
|
|
346
350
|
return [];
|
|
347
|
-
const store = readStore(resolveTicketStoragePath(projectPath));
|
|
351
|
+
const store = readStore(ticketsPathOverride ?? resolveTicketStoragePath(projectPath));
|
|
348
352
|
return ids.map((id) => ({
|
|
349
353
|
id,
|
|
350
354
|
title: store.tickets[String(id)]?.title ?? null,
|
|
@@ -7,6 +7,7 @@ exports.TicketWatcher = void 0;
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const fs_1 = __importDefault(require("fs"));
|
|
9
9
|
const chokidar_1 = require("chokidar");
|
|
10
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
10
11
|
const TICKET_FILE = '.specrails/local-tickets.json';
|
|
11
12
|
const DEBOUNCE_MS = 150;
|
|
12
13
|
/**
|
|
@@ -33,7 +34,10 @@ class TicketWatcher {
|
|
|
33
34
|
start() {
|
|
34
35
|
if (this._closed)
|
|
35
36
|
return;
|
|
36
|
-
|
|
37
|
+
// Relocate-artifacts gate: watch the workspace ticket file when relocated,
|
|
38
|
+
// else the repo-relative file (byte-identical for existing projects).
|
|
39
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ path: this._projectPath });
|
|
40
|
+
const filePath = exec.relocated ? exec.ticketsPath : path_1.default.join(this._projectPath, TICKET_FILE);
|
|
37
41
|
// Seed initial revision so we can detect external changes
|
|
38
42
|
this._lastRevision = this._readRevision(filePath);
|
|
39
43
|
this._watcher = new chokidar_1.FSWatcher({
|
|
@@ -9,7 +9,8 @@ exports.extractDisplayText = extractDisplayText;
|
|
|
9
9
|
/**
|
|
10
10
|
* Map a parsed provider stream-json frame to the single display line the Job
|
|
11
11
|
* Detail log shows (or null when the frame carries no user-facing text).
|
|
12
|
-
* Handles
|
|
12
|
+
* Handles Claude `--output-format stream-json`, Codex `exec --json`, and Gemini
|
|
13
|
+
* `--output-format stream-json` frame shapes.
|
|
13
14
|
*/
|
|
14
15
|
function extractDisplayText(event) {
|
|
15
16
|
const type = event.type;
|
|
@@ -22,10 +23,24 @@ function extractDisplayText(event) {
|
|
|
22
23
|
return texts.join('') || null;
|
|
23
24
|
}
|
|
24
25
|
if (type === 'tool_use') {
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
// Claude emits `name`/`input`; Gemini stream-json uses `tool_name`/`parameters`.
|
|
27
|
+
// Tolerating both keeps a single tool_use branch across providers — without
|
|
28
|
+
// this fallback every Gemini tool call renders as `[tool: undefined] {}`.
|
|
29
|
+
const e = event;
|
|
30
|
+
const name = e.name ?? e.tool_name ?? '<unnamed>';
|
|
31
|
+
const input = JSON.stringify(e.input ?? e.parameters ?? {});
|
|
27
32
|
return `[tool: ${name}] ${input.slice(0, 120)}`;
|
|
28
33
|
}
|
|
34
|
+
if (type === 'message') {
|
|
35
|
+
// Gemini stream-json streams assistant text as `message` delta frames whose
|
|
36
|
+
// `content` is a plain string; the `role:"user"` echo carries no display
|
|
37
|
+
// value. Without this branch the Job Detail log drops all Gemini narration.
|
|
38
|
+
const e = event;
|
|
39
|
+
if (e.role !== 'assistant')
|
|
40
|
+
return null;
|
|
41
|
+
const text = e.content ?? '';
|
|
42
|
+
return text.length > 0 ? text : null;
|
|
43
|
+
}
|
|
29
44
|
if (type === 'tool_result' || type === 'system_prompt' || type === 'user' || type === 'system' || type === 'result') {
|
|
30
45
|
return null;
|
|
31
46
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
/**
|
|
10
|
+
* Test safety net: NEVER let a test write the relocation registry into the
|
|
11
|
+
* developer's real `$HOME/.specrails/registry.json`.
|
|
12
|
+
*
|
|
13
|
+
* `ProjectRegistry.loadAll()` runs `reconcileFromProjects`, and `addProject`
|
|
14
|
+
* runs `mirrorProjectEntry`; both resolve the registry path from
|
|
15
|
+
* `resolveHome()`, which falls back to `os.homedir()` when neither a `home`
|
|
16
|
+
* arg nor `SPECRAILS_REGISTRY_HOME` is set. A test that constructs a
|
|
17
|
+
* `ProjectRegistry` without pinning a tmp home would then pollute the real
|
|
18
|
+
* registry. This setup file (run once per test file by vitest `setupFiles`)
|
|
19
|
+
* points `SPECRAILS_REGISTRY_HOME` at a throwaway tmp dir unless the test has
|
|
20
|
+
* already set its own, so the real home is unreachable from tests by
|
|
21
|
+
* construction. Tests that need a specific home still override it per-test.
|
|
22
|
+
*/
|
|
23
|
+
if (!process.env.SPECRAILS_REGISTRY_HOME) {
|
|
24
|
+
process.env.SPECRAILS_REGISTRY_HOME = (0, fs_1.mkdtempSync)(path_1.default.join(os_1.default.tmpdir(), 'specrails-desktop-test-home-'));
|
|
25
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.workspacePathFor = workspacePathFor;
|
|
7
|
+
exports.ensureWorkspace = ensureWorkspace;
|
|
8
|
+
exports.assembleWorkspaceFramework = assembleWorkspaceFramework;
|
|
9
|
+
exports.removeWorkspace = removeWorkspace;
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const framework_manager_1 = require("./framework-manager");
|
|
14
|
+
const framework_migration_1 = require("./framework-migration");
|
|
15
|
+
/**
|
|
16
|
+
* WorkspaceManager — a reusable materializer for the per-project workspace dir
|
|
17
|
+
* under `~/.specrails/projects/<slug>/workspace`. This is the relocation target
|
|
18
|
+
* for a repo's artifacts (see `server/artifact-registry.ts` + the global-
|
|
19
|
+
* artifacts-alignment contract): the workspace holds the `./project` link back
|
|
20
|
+
* to the user's repo so tools spawned with the workspace as cwd can still reach
|
|
21
|
+
* the source.
|
|
22
|
+
*
|
|
23
|
+
* This generalizes the materialization logic of `explore-cwd-manager.ts`
|
|
24
|
+
* (the `ensureProjectLink` symlink/junction/fallback dance) without coupling to
|
|
25
|
+
* the Explore-specific embedded instructions file. Explore-cwd is intentionally
|
|
26
|
+
* left UNCHANGED (it keeps its own parallel copy); this module is the forward-
|
|
27
|
+
* looking home for workspace materialization.
|
|
28
|
+
*
|
|
29
|
+
* NOTE (this stage): nothing yet spawns from the workspace dir. This is the
|
|
30
|
+
* additive foundation — wiring spawn cwd/env to the workspace is a later stage.
|
|
31
|
+
*/
|
|
32
|
+
/** Base dir holding all per-project app data. Overridable for tests. */
|
|
33
|
+
function projectsBaseDir(home) {
|
|
34
|
+
return path_1.default.join(home ?? os_1.default.homedir(), '.specrails', 'projects');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Compute the workspace path for a project without touching the filesystem.
|
|
38
|
+
* Mirrors `artifact-registry.workspaceLayout(...).workspaceDir`.
|
|
39
|
+
*/
|
|
40
|
+
function workspacePathFor(slug, home) {
|
|
41
|
+
return path_1.default.join(projectsBaseDir(home), slug, 'workspace');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create or refresh the workspace dir for a project. Idempotent and cheap when
|
|
45
|
+
* already up-to-date. Creates the workspace directory and a `./project`
|
|
46
|
+
* symlink (junction on Windows) pointing at `projectPath`, with a
|
|
47
|
+
* `project-path.txt` fallback when symlink/junction creation fails. Returns the
|
|
48
|
+
* absolute workspace path.
|
|
49
|
+
*/
|
|
50
|
+
function ensureWorkspace(slug, projectPath, home) {
|
|
51
|
+
const ws = workspacePathFor(slug, home);
|
|
52
|
+
fs_1.default.mkdirSync(ws, { recursive: true });
|
|
53
|
+
ensureProjectLink(ws, projectPath);
|
|
54
|
+
return ws;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Ensure the workspace exists (with the `./project` link) AND assemble the
|
|
58
|
+
* framework into it by SYMLINK (offline) via the bundled specrails-core
|
|
59
|
+
* `assemble` subcommand. The providerDir static subtrees
|
|
60
|
+
* (`agents/`/`commands/`/`skills/`/`rules/`) become symlinks into
|
|
61
|
+
* `~/.specrails/framework/current/<provider>/`; `agent-memory/` stays a real
|
|
62
|
+
* writable dir (core's assemble handles that distinction).
|
|
63
|
+
*
|
|
64
|
+
* EXISTENCE-GATED: when no bundled core is present, `assembled` is false and the
|
|
65
|
+
* caller must fall back to the legacy `npx specrails-core init` assembly. The
|
|
66
|
+
* workspace + `./project` link are still ensured so the legacy path has a cwd.
|
|
67
|
+
*/
|
|
68
|
+
function assembleWorkspaceFramework(slug, projectPath, provider, opts = {}) {
|
|
69
|
+
const ws = ensureWorkspace(slug, projectPath, opts.home);
|
|
70
|
+
const fm = opts.framework ?? new framework_manager_1.FrameworkManager({ home: opts.home });
|
|
71
|
+
if (!fm.isAvailable()) {
|
|
72
|
+
return { assembled: false, workspace: ws };
|
|
73
|
+
}
|
|
74
|
+
// Lazy-on-first-touch migration: if this workspace still holds a per-workspace
|
|
75
|
+
// framework COPY (the pre-bundled relocate-core layout), convert it to symlinks
|
|
76
|
+
// into `framework/current` non-destructively BEFORE assembling. Idempotent +
|
|
77
|
+
// safe to run repeatedly; it no-ops once the workspace is symlinked. The
|
|
78
|
+
// migration re-enters this function with `_skipMigrate` to do the actual
|
|
79
|
+
// re-link, so we guard against infinite recursion here.
|
|
80
|
+
if (!opts._skipMigrate) {
|
|
81
|
+
try {
|
|
82
|
+
(0, framework_migration_1.migrateWorkspaceToSymlinks)(slug, projectPath, provider, {
|
|
83
|
+
home: opts.home,
|
|
84
|
+
framework: fm,
|
|
85
|
+
broadcast: opts.broadcast,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
/* migration is best-effort; assemble below still produces a usable layout */
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const res = fm.assembleWorkspace({
|
|
93
|
+
workspace: ws,
|
|
94
|
+
provider,
|
|
95
|
+
version: opts.version,
|
|
96
|
+
codeRoot: projectPath,
|
|
97
|
+
});
|
|
98
|
+
if (!res.ran)
|
|
99
|
+
return { assembled: false, workspace: ws };
|
|
100
|
+
return { assembled: true, workspace: ws, error: res.error };
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Recursively remove the workspace dir for a project. The `project`
|
|
104
|
+
* symlink/junction is unlinked explicitly (never followed) so the user's repo
|
|
105
|
+
* is never touched. No-op when the dir does not exist.
|
|
106
|
+
*/
|
|
107
|
+
function removeWorkspace(slug, home) {
|
|
108
|
+
const ws = workspacePathFor(slug, home);
|
|
109
|
+
if (!fs_1.default.existsSync(ws))
|
|
110
|
+
return;
|
|
111
|
+
const linkPath = path_1.default.join(ws, 'project');
|
|
112
|
+
try {
|
|
113
|
+
const st = fs_1.default.lstatSync(linkPath);
|
|
114
|
+
if (st.isSymbolicLink() || (process.platform === 'win32' && st.isDirectory())) {
|
|
115
|
+
// unlink works on POSIX symlinks; rmdir on Windows junctions
|
|
116
|
+
try {
|
|
117
|
+
fs_1.default.unlinkSync(linkPath);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
try {
|
|
121
|
+
fs_1.default.rmdirSync(linkPath);
|
|
122
|
+
}
|
|
123
|
+
catch { /* best-effort */ }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
/* link may not exist */
|
|
129
|
+
}
|
|
130
|
+
fs_1.default.rmSync(ws, { recursive: true, force: true });
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Ensure `<ws>/project` resolves to `<projectPath>` (symlink on POSIX, junction
|
|
134
|
+
* on Windows). Recreated when the existing target differs. On both symlink and
|
|
135
|
+
* junction failure, writes a `project-path.txt` fallback. This is the EXACT
|
|
136
|
+
* logic from `explore-cwd-manager.ts`'s private `ensureProjectLink`.
|
|
137
|
+
*/
|
|
138
|
+
function ensureProjectLink(cwd, projectPath) {
|
|
139
|
+
const linkPath = path_1.default.join(cwd, 'project');
|
|
140
|
+
const fallbackPath = path_1.default.join(cwd, 'project-path.txt');
|
|
141
|
+
let needsCreate = true;
|
|
142
|
+
try {
|
|
143
|
+
const st = fs_1.default.lstatSync(linkPath);
|
|
144
|
+
if (st.isSymbolicLink()) {
|
|
145
|
+
const current = fs_1.default.readlinkSync(linkPath);
|
|
146
|
+
if (path_1.default.resolve(cwd, current) === path_1.default.resolve(projectPath)) {
|
|
147
|
+
needsCreate = false;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
fs_1.default.unlinkSync(linkPath);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
// existing non-symlink (e.g. Windows junction or stale dir) — replace
|
|
155
|
+
try {
|
|
156
|
+
fs_1.default.unlinkSync(linkPath);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
try {
|
|
160
|
+
fs_1.default.rmdirSync(linkPath);
|
|
161
|
+
}
|
|
162
|
+
catch { /* best-effort */ }
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
/* link does not exist — fall through to create */
|
|
168
|
+
}
|
|
169
|
+
if (needsCreate) {
|
|
170
|
+
let created = false;
|
|
171
|
+
if (process.platform === 'win32') {
|
|
172
|
+
try {
|
|
173
|
+
fs_1.default.symlinkSync(projectPath, linkPath, 'junction');
|
|
174
|
+
created = true;
|
|
175
|
+
}
|
|
176
|
+
catch { /* fall through to plain symlink */ }
|
|
177
|
+
}
|
|
178
|
+
if (!created) {
|
|
179
|
+
try {
|
|
180
|
+
fs_1.default.symlinkSync(projectPath, linkPath);
|
|
181
|
+
created = true;
|
|
182
|
+
}
|
|
183
|
+
catch { /* fall through to text fallback */ }
|
|
184
|
+
}
|
|
185
|
+
if (!created) {
|
|
186
|
+
// Final fallback: write the absolute path so the model can use it.
|
|
187
|
+
fs_1.default.writeFileSync(fallbackPath, projectPath, 'utf-8');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// If we successfully created/verified the symlink, clean up any stale
|
|
192
|
+
// fallback file from a prior failed attempt.
|
|
193
|
+
if (fs_1.default.existsSync(fallbackPath)) {
|
|
194
|
+
try {
|
|
195
|
+
fs_1.default.unlinkSync(fallbackPath);
|
|
196
|
+
}
|
|
197
|
+
catch { /* ignore */ }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* workspace-resolution — the SINGLE gate for the relocate-artifacts-to-home
|
|
4
|
+
* feature (DESKTOP STAGE 2). Every spawner cwd/env decision and every relocated
|
|
5
|
+
* artifact-read site funnels through `resolveProjectExecution(...)`.
|
|
6
|
+
*
|
|
7
|
+
* THE GATE — a project is "relocated" iff BOTH hold:
|
|
8
|
+
* 1. a registry entry exists for the repo (`resolveArtifacts(...).isLegacy ===
|
|
9
|
+
* false`), AND
|
|
10
|
+
* 2. the workspace is actually populated by core — detected by the presence of
|
|
11
|
+
* `<workspace>/.specrails/specrails-version` (core installed there).
|
|
12
|
+
*
|
|
13
|
+
* When NOT relocated (every existing in-repo project, or a relocated registry
|
|
14
|
+
* entry whose workspace core has not yet populated) the resolution is
|
|
15
|
+
* BYTE-IDENTICAL to pre-stage-2 behaviour: cwd = project.path, repo-relative
|
|
16
|
+
* artifact paths, empty env. This is what keeps stage 2 regression-safe for
|
|
17
|
+
* existing users — only relocated projects diverge.
|
|
18
|
+
*
|
|
19
|
+
* When relocated:
|
|
20
|
+
* - cwd = the workspace dir (core discovers sr-* agents there),
|
|
21
|
+
* - repoDir = project.path (the user's repo; source/git/openspec stay here),
|
|
22
|
+
* - env = { SPECRAILS_REPO_DIR: project.path, ... } so stage-3
|
|
23
|
+
* `${SPECRAILS_REPO_DIR:-.}` re-pointing drives source/openspec/
|
|
24
|
+
* git I/O back into the repo,
|
|
25
|
+
* - artifact paths point at the workspace (from the registry entry),
|
|
26
|
+
* - the `<workspace>/project` symlink is (re)created before returning so tools
|
|
27
|
+
* spawned from the workspace can still reach the repo by relative path.
|
|
28
|
+
*
|
|
29
|
+
* The workspace path is ALWAYS read from the registry entry (an adopted repo's
|
|
30
|
+
* registry slug may differ from the desktop.sqlite slug); never recompute it
|
|
31
|
+
* from a desktop slug.
|
|
32
|
+
*/
|
|
33
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
34
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
35
|
+
};
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.isWorkspacePopulated = isWorkspacePopulated;
|
|
38
|
+
exports.resolveProjectExecution = resolveProjectExecution;
|
|
39
|
+
const fs_1 = __importDefault(require("fs"));
|
|
40
|
+
const path_1 = __importDefault(require("path"));
|
|
41
|
+
const artifact_registry_1 = require("./artifact-registry");
|
|
42
|
+
const workspace_manager_1 = require("./workspace-manager");
|
|
43
|
+
/** Marker file core writes into the workspace once it has installed there. The
|
|
44
|
+
* presence of this file is the "populated" half of the gate. */
|
|
45
|
+
const WORKSPACE_VERSION_MARKER = path_1.default.join('.specrails', 'specrails-version');
|
|
46
|
+
/** True when `<workspaceDir>/.specrails/specrails-version` exists (core has
|
|
47
|
+
* installed into the workspace). The legacy `.specrails-version` location is
|
|
48
|
+
* NOT a workspace marker (a bare workspace dir never has the dot-file form).
|
|
49
|
+
*
|
|
50
|
+
* Bundled-framework layout: `assemble` SYMLINKS the providerDir subtrees
|
|
51
|
+
* (`agents/` per-file, `commands/`/`skills/`/`rules/` whole-dir) into the
|
|
52
|
+
* workspace but writes the `.specrails/specrails-version` marker as a REAL file
|
|
53
|
+
* (via core's `writeManifestFiles`). So "populated" depends only on the real
|
|
54
|
+
* marker — it is agnostic to whether the providerDir is a real dir or a tree of
|
|
55
|
+
* symlinks into `framework/current/<provider>/`. `fs.existsSync` resolves the
|
|
56
|
+
* marker (and would also resolve it through a symlinked `.specrails`, though the
|
|
57
|
+
* manifest layer is never linked). */
|
|
58
|
+
function isWorkspacePopulated(workspaceDir) {
|
|
59
|
+
try {
|
|
60
|
+
return fs_1.default.existsSync(path_1.default.join(workspaceDir, WORKSPACE_VERSION_MARKER));
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* THE GATE. Resolve how a project's AI CLIs should be spawned and where its
|
|
68
|
+
* relocated artifacts live. Read-only with respect to the registry; the only
|
|
69
|
+
* side effect (when relocated) is (re)creating the workspace `./project` symlink
|
|
70
|
+
* via `ensureWorkspace`.
|
|
71
|
+
*/
|
|
72
|
+
function resolveProjectExecution(project, home) {
|
|
73
|
+
const repoDir = project.path;
|
|
74
|
+
let art;
|
|
75
|
+
try {
|
|
76
|
+
art = (0, artifact_registry_1.resolveArtifacts)(project.path, { allocate: false }, home);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// A registry read failure must never break a spawn — fall back to legacy.
|
|
80
|
+
return legacyExecution(repoDir);
|
|
81
|
+
}
|
|
82
|
+
// Gate part 1: no registry entry ⇒ legacy.
|
|
83
|
+
if (art.isLegacy) {
|
|
84
|
+
return legacyExecution(repoDir);
|
|
85
|
+
}
|
|
86
|
+
// Gate part 2: registry entry exists but the workspace is NOT yet populated by
|
|
87
|
+
// core ⇒ stay legacy (existing project that has a registry entry but whose
|
|
88
|
+
// core install still lives in-repo). This is what keeps every existing
|
|
89
|
+
// assertion valid: a registry entry alone never flips a project to relocated.
|
|
90
|
+
if (!isWorkspacePopulated(art.workspaceDir)) {
|
|
91
|
+
return legacyExecution(repoDir);
|
|
92
|
+
}
|
|
93
|
+
// Relocated. (Re)create the workspace `./project` symlink so tools spawned from
|
|
94
|
+
// the workspace can still reach the repo. ALWAYS use the REGISTRY entry's slug
|
|
95
|
+
// (an adopted repo's registry slug may differ from the desktop slug) so the
|
|
96
|
+
// symlink lands in the same workspace dir core populated. Best-effort — a
|
|
97
|
+
// symlink failure must not abort the spawn (ensureWorkspace falls back to
|
|
98
|
+
// project-path.txt internally).
|
|
99
|
+
const workspaceSlug = art.entry?.slug ?? project.slug;
|
|
100
|
+
try {
|
|
101
|
+
if (workspaceSlug)
|
|
102
|
+
(0, workspace_manager_1.ensureWorkspace)(workspaceSlug, repoDir, home);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
/* non-fatal — ensureWorkspace already has its own fallbacks */
|
|
106
|
+
}
|
|
107
|
+
const env = {
|
|
108
|
+
SPECRAILS_REPO_DIR: repoDir,
|
|
109
|
+
SPECRAILS_WORKSPACE_DIR: art.workspaceDir,
|
|
110
|
+
SPECRAILS_TICKETS_PATH: art.ticketsPath,
|
|
111
|
+
SPECRAILS_STATE_DIR: art.stateDir,
|
|
112
|
+
SPECRAILS_BACKLOG_CONFIG_PATH: art.backlogConfigPath,
|
|
113
|
+
SPECRAILS_PROFILES_DIR: art.profilesDir,
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
relocated: true,
|
|
117
|
+
cwd: art.workspaceDir,
|
|
118
|
+
repoDir,
|
|
119
|
+
workspaceDir: art.workspaceDir,
|
|
120
|
+
ticketsPath: art.ticketsPath,
|
|
121
|
+
backlogConfigPath: art.backlogConfigPath,
|
|
122
|
+
profilesDir: art.profilesDir,
|
|
123
|
+
pluginsStateDir: art.pluginsStateDir,
|
|
124
|
+
fileSummariesDir: art.fileSummariesDir,
|
|
125
|
+
specrailsDir: art.specrailsDir,
|
|
126
|
+
stateDir: art.stateDir,
|
|
127
|
+
env,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** Build the legacy (in-repo, byte-identical-to-today) execution. */
|
|
131
|
+
function legacyExecution(repoDir) {
|
|
132
|
+
const specrailsDir = path_1.default.join(repoDir, '.specrails');
|
|
133
|
+
return {
|
|
134
|
+
relocated: false,
|
|
135
|
+
cwd: repoDir,
|
|
136
|
+
repoDir,
|
|
137
|
+
workspaceDir: null,
|
|
138
|
+
ticketsPath: path_1.default.join(specrailsDir, 'local-tickets.json'),
|
|
139
|
+
backlogConfigPath: path_1.default.join(specrailsDir, 'backlog-config.json'),
|
|
140
|
+
profilesDir: path_1.default.join(specrailsDir, 'profiles'),
|
|
141
|
+
pluginsStateDir: path_1.default.join(specrailsDir, 'plugins'),
|
|
142
|
+
fileSummariesDir: path_1.default.join(specrailsDir, 'file-summaries'),
|
|
143
|
+
specrailsDir,
|
|
144
|
+
stateDir: path_1.default.join(repoDir, '.claude'),
|
|
145
|
+
env: {},
|
|
146
|
+
};
|
|
147
|
+
}
|