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
|
@@ -20,6 +20,7 @@ const jira_materializer_1 = require("./jira-materializer");
|
|
|
20
20
|
const jira_issue_fields_1 = require("./jira-issue-fields");
|
|
21
21
|
const jira_status_resolver_1 = require("./jira-status-resolver");
|
|
22
22
|
const jira_db_1 = require("./jira-db");
|
|
23
|
+
const workspace_resolution_1 = require("../workspace-resolution");
|
|
23
24
|
const POLL_INTERVAL_MS = 60_000;
|
|
24
25
|
const DRAIN_INTERVAL_MS = 10_000;
|
|
25
26
|
const POLL_OVERLAP_MS = 2 * 60_000;
|
|
@@ -54,6 +55,17 @@ class JiraSyncManager {
|
|
|
54
55
|
if (opts.startTimers !== false)
|
|
55
56
|
this.start();
|
|
56
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* Relocate-artifacts gate: the directory whose `.specrails/{local-tickets,
|
|
60
|
+
* backlog-config}.json` Jira sync reads/writes. Relocated ⇒ the workspace dir
|
|
61
|
+
* (the path-deriving helpers join `.specrails/...` under it); legacy ⇒
|
|
62
|
+
* project.path (byte-identical for existing projects). All Jira artifact I/O
|
|
63
|
+
* funnels through here.
|
|
64
|
+
*/
|
|
65
|
+
_artifactRoot() {
|
|
66
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ path: this.projectPath });
|
|
67
|
+
return exec.relocated && exec.workspaceDir ? exec.workspaceDir : this.projectPath;
|
|
68
|
+
}
|
|
57
69
|
/** Suppress the file-watcher echo for a local write we just made. */
|
|
58
70
|
notifyLocalWrite(revision) {
|
|
59
71
|
try {
|
|
@@ -185,7 +197,7 @@ class JiraSyncManager {
|
|
|
185
197
|
if (input.discardStatus !== undefined) {
|
|
186
198
|
(0, jira_db_1.setDiscardStatus)(this.db, this.projectId, input.discardStatus);
|
|
187
199
|
}
|
|
188
|
-
(0, jira_backlog_config_1.writeJiraBacklogConfig)(this.
|
|
200
|
+
(0, jira_backlog_config_1.writeJiraBacklogConfig)(this._artifactRoot());
|
|
189
201
|
// Discover the sprint custom-field id (best-effort) so sprint capture works
|
|
190
202
|
// from the first poll. Non-fatal — the poll re-discovers if this fails.
|
|
191
203
|
try {
|
|
@@ -248,11 +260,11 @@ class JiraSyncManager {
|
|
|
248
260
|
setEnabled(enabled) {
|
|
249
261
|
(0, jira_db_1.setConnectionEnabled)(this.db, this.projectId, enabled);
|
|
250
262
|
if (enabled) {
|
|
251
|
-
(0, jira_backlog_config_1.writeJiraBacklogConfig)(this.
|
|
263
|
+
(0, jira_backlog_config_1.writeJiraBacklogConfig)(this._artifactRoot());
|
|
252
264
|
this.start();
|
|
253
265
|
}
|
|
254
266
|
else {
|
|
255
|
-
(0, jira_backlog_config_1.writeLocalBacklogConfig)(this.
|
|
267
|
+
(0, jira_backlog_config_1.writeLocalBacklogConfig)(this._artifactRoot());
|
|
256
268
|
}
|
|
257
269
|
}
|
|
258
270
|
/** Configure (or clear) the status a discarded spec is moved to. */
|
|
@@ -289,7 +301,7 @@ class JiraSyncManager {
|
|
|
289
301
|
/** Remove the connection entirely and restore local backlog config. */
|
|
290
302
|
disconnect() {
|
|
291
303
|
(0, jira_db_1.deleteConnection)(this.db, this.projectId);
|
|
292
|
-
(0, jira_backlog_config_1.writeLocalBacklogConfig)(this.
|
|
304
|
+
(0, jira_backlog_config_1.writeLocalBacklogConfig)(this._artifactRoot());
|
|
293
305
|
this.stop();
|
|
294
306
|
}
|
|
295
307
|
// ─── Create a spec in Jira (Add Spec when source = Jira) ───────────────────
|
|
@@ -323,11 +335,11 @@ class JiraSyncManager {
|
|
|
323
335
|
const issue = full.ok
|
|
324
336
|
? full.data
|
|
325
337
|
: { id: created.data.id, key: created.data.key, fields: { summary: input.title, labels: input.labels ?? [] } };
|
|
326
|
-
const r = (0, jira_materializer_1.upsertIssuesIntoStore)(this.db, this.
|
|
338
|
+
const r = (0, jira_materializer_1.upsertIssuesIntoStore)(this.db, this._artifactRoot(), conn, [issue], new Set());
|
|
327
339
|
if (r.wrote)
|
|
328
340
|
this.notifyLocalWrite(r.revision);
|
|
329
341
|
const localId = r.changedLocalIds[0];
|
|
330
|
-
const t = readTicket(this.
|
|
342
|
+
const t = readTicket(this._artifactRoot(), localId);
|
|
331
343
|
if (t)
|
|
332
344
|
this.broadcast({ type: 'ticket_created', ticket: t, projectId: this.projectId, timestamp: t.updated_at });
|
|
333
345
|
return { ok: true, localId, jiraKey: created.data.key };
|
|
@@ -351,7 +363,7 @@ class JiraSyncManager {
|
|
|
351
363
|
const client = this.buildClient();
|
|
352
364
|
if (!client)
|
|
353
365
|
return { ok: false, error: 'no jira credentials' };
|
|
354
|
-
const file = (0, ticket_store_1.resolveTicketStoragePath)(this.
|
|
366
|
+
const file = (0, ticket_store_1.resolveTicketStoragePath)(this._artifactRoot());
|
|
355
367
|
const ticket = (0, ticket_store_1.readStore)(file).tickets[String(localId)];
|
|
356
368
|
if (!ticket)
|
|
357
369
|
return { ok: false, error: 'ticket not found' };
|
|
@@ -449,7 +461,7 @@ class JiraSyncManager {
|
|
|
449
461
|
}
|
|
450
462
|
const issues = res.data.issues ?? [];
|
|
451
463
|
if (issues.length > 0) {
|
|
452
|
-
const r = (0, jira_materializer_1.upsertIssuesIntoStore)(this.db, this.
|
|
464
|
+
const r = (0, jira_materializer_1.upsertIssuesIntoStore)(this.db, this._artifactRoot(), conn, issues, frozen);
|
|
453
465
|
// Suppress the watcher echo for our own write (avoids the full-board
|
|
454
466
|
// refresh flicker). When nothing changed, `wrote` is false and we also
|
|
455
467
|
// skip the granular broadcasts below — the board stays perfectly still.
|
|
@@ -459,7 +471,7 @@ class JiraSyncManager {
|
|
|
459
471
|
if (r.maxUpdatedMs > maxUpdated)
|
|
460
472
|
maxUpdated = r.maxUpdatedMs;
|
|
461
473
|
for (const localId of r.changedLocalIds) {
|
|
462
|
-
const t = readTicket(this.
|
|
474
|
+
const t = readTicket(this._artifactRoot(), localId);
|
|
463
475
|
if (t)
|
|
464
476
|
this.broadcast({ type: 'ticket_updated', ticket: t, projectId: this.projectId, timestamp: t.updated_at });
|
|
465
477
|
}
|
|
@@ -826,7 +838,7 @@ class JiraSyncManager {
|
|
|
826
838
|
if (localIds.length === 0)
|
|
827
839
|
return;
|
|
828
840
|
try {
|
|
829
|
-
const file = (0, ticket_store_1.resolveTicketStoragePath)(this.
|
|
841
|
+
const file = (0, ticket_store_1.resolveTicketStoragePath)(this._artifactRoot());
|
|
830
842
|
const ids = new Set(localIds.map(String));
|
|
831
843
|
const now = new Date().toISOString();
|
|
832
844
|
const store = (0, ticket_store_1.mutateStore)(file, (s) => {
|
|
@@ -885,7 +897,7 @@ class JiraSyncManager {
|
|
|
885
897
|
const metaRes = await client.getFieldsFull();
|
|
886
898
|
const fieldMeta = metaRes.ok ? metaRes.data : [];
|
|
887
899
|
// 3) Materialized ticket flags suppress already-shown info (no extra HTTP).
|
|
888
|
-
const ticket = (0, ticket_store_1.readStore)((0, ticket_store_1.resolveTicketStoragePath)(this.
|
|
900
|
+
const ticket = (0, ticket_store_1.readStore)((0, ticket_store_1.resolveTicketStoragePath)(this._artifactRoot())).tickets[String(localId)];
|
|
889
901
|
const fields = (0, jira_issue_fields_1.formatIssueFields)({
|
|
890
902
|
fields: issueRes.data.fields,
|
|
891
903
|
fieldMeta,
|
|
@@ -0,0 +1,153 @@
|
|
|
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.OPENSPEC_NPX_SPEC = void 0;
|
|
7
|
+
exports.openspecShimDir = openspecShimDir;
|
|
8
|
+
exports.ensureOpenspecShim = ensureOpenspecShim;
|
|
9
|
+
exports.prependShimToPath = prependShimToPath;
|
|
10
|
+
exports.removeOpenspecShim = removeOpenspecShim;
|
|
11
|
+
const fs_1 = __importDefault(require("fs"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const bundled_openspec_1 = require("./bundled-openspec");
|
|
15
|
+
/**
|
|
16
|
+
* openspec PATH shim — the robustness backstop for relocated Claude rails.
|
|
17
|
+
*
|
|
18
|
+
* THE PROBLEM. When a project's artifacts are RELOCATED to the home workspace,
|
|
19
|
+
* the rail's `claude` child is spawned with `cwd = <workspace>`. The repo's
|
|
20
|
+
* `openspec/` tree (changes, specs, .git) lives in the REPO, not the workspace.
|
|
21
|
+
* specrails-core's stage-3 templates wrap repo-resident `openspec <verb>` calls
|
|
22
|
+
* in `(cd "${SPECRAILS_REPO_DIR:-.}" && openspec …)` so they land in the repo —
|
|
23
|
+
* but a BARE `openspec …` that a skill, sub-agent, or an un-wrapped template
|
|
24
|
+
* line emits would run from the workspace cwd, where there is no OpenSpec
|
|
25
|
+
* project → "Error: not an OpenSpec project".
|
|
26
|
+
*
|
|
27
|
+
* THE FIX. For a relocated rail we materialize a per-job shim directory and
|
|
28
|
+
* PREPEND it to the spawn `PATH`. It contains an `openspec` executable (POSIX
|
|
29
|
+
* `sh` script + a Windows `openspec.cmd`) that unconditionally does:
|
|
30
|
+
*
|
|
31
|
+
* cd "$SPECRAILS_REPO_DIR" && exec <real openspec> "$@"
|
|
32
|
+
*
|
|
33
|
+
* so EVERY `openspec` invocation — wrapped OR bare, skill- OR template-driven —
|
|
34
|
+
* resolves against the repo. The "real openspec" is the bundled openspec
|
|
35
|
+
* (`node <cli>`) when present, else the legacy `npx @fission-ai/openspec@<pin>`.
|
|
36
|
+
*
|
|
37
|
+
* EXISTENCE-GATED. Only injected for a RELOCATED rail. Legacy (in-repo) rails
|
|
38
|
+
* spawn from the repo cwd where a bare `openspec` already resolves correctly, so
|
|
39
|
+
* the shim is never created and `PATH` is byte-identical to today.
|
|
40
|
+
*/
|
|
41
|
+
/** Pinned openspec npm spec for the `npx` fallback when no bundled openspec is
|
|
42
|
+
* present. Matches specrails-core's `pinned-versions.json` openspec pin and the
|
|
43
|
+
* desktop OPENSPEC_BUNDLE_VERSION in the release workflow. */
|
|
44
|
+
exports.OPENSPEC_NPX_SPEC = '@fission-ai/openspec@1.4.1';
|
|
45
|
+
/** `~/.specrails/projects/<slug>/openspec-shim/<jobId>` — the per-job shim dir. */
|
|
46
|
+
function openspecShimDir(slug, jobId, home = os_1.default.homedir()) {
|
|
47
|
+
return path_1.default.join(home, '.specrails', 'projects', slug, 'openspec-shim', jobId);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build the command the shim should `exec` to run the REAL openspec, as an argv
|
|
51
|
+
* array already shell-quoted into a single string. Prefers the bundled openspec
|
|
52
|
+
* (`node <cli>`); falls back to `npx -y <pinned openspec>`.
|
|
53
|
+
*/
|
|
54
|
+
function realOpenspecInvocation() {
|
|
55
|
+
const cli = (0, bundled_openspec_1.getBundledOpenspecCli)();
|
|
56
|
+
if (cli) {
|
|
57
|
+
// node "<cli>" — invoke the bundled openspec as a node script (Tauri strips
|
|
58
|
+
// exec bits from bundled resources, so it can't be run as a binary).
|
|
59
|
+
return {
|
|
60
|
+
posix: `node ${shq(cli)}`,
|
|
61
|
+
windows: `node ${winq(cli)}`,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Legacy fallback: npx resolves + caches openspec. `-y` skips the install
|
|
65
|
+
// confirmation prompt (rails are headless).
|
|
66
|
+
return {
|
|
67
|
+
posix: `npx -y ${shq(exports.OPENSPEC_NPX_SPEC)}`,
|
|
68
|
+
windows: `npx -y ${winq(exports.OPENSPEC_NPX_SPEC)}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** POSIX single-quote a string for embedding in an `sh` script. */
|
|
72
|
+
function shq(s) {
|
|
73
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
74
|
+
}
|
|
75
|
+
/** Windows double-quote a string for a `.cmd` script. */
|
|
76
|
+
function winq(s) {
|
|
77
|
+
return `"${s.replace(/"/g, '""')}"`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Materialize the per-job openspec shim directory and return its absolute path
|
|
81
|
+
* (to be PREPENDED to the spawn PATH). Writes both an `openspec` POSIX script
|
|
82
|
+
* (chmod 755 inside a 700 dir) and an `openspec.cmd` for Windows. The shim cd's
|
|
83
|
+
* into `$SPECRAILS_REPO_DIR` (defaulting to `.` so a missing var degrades to the
|
|
84
|
+
* cwd) before exec'ing the real openspec, so the repo's OpenSpec project is
|
|
85
|
+
* always the one operated on.
|
|
86
|
+
*
|
|
87
|
+
* Returns null when the shim could not be created (best-effort — a failure must
|
|
88
|
+
* never block a spawn; the rail still runs, just without the shim's safety net).
|
|
89
|
+
*/
|
|
90
|
+
function ensureOpenspecShim(slug, jobId, home = os_1.default.homedir()) {
|
|
91
|
+
try {
|
|
92
|
+
const dir = openspecShimDir(slug, jobId, home);
|
|
93
|
+
fs_1.default.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
94
|
+
try {
|
|
95
|
+
fs_1.default.chmodSync(dir, 0o700);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
/* chmod may be unsupported (Windows) — non-fatal */
|
|
99
|
+
}
|
|
100
|
+
const { posix, windows } = realOpenspecInvocation();
|
|
101
|
+
// POSIX shim: `openspec` (no extension). `${SPECRAILS_REPO_DIR:-.}` makes a
|
|
102
|
+
// missing var degrade to the current dir instead of an unbound-var error.
|
|
103
|
+
const posixScript = '#!/bin/sh\n' +
|
|
104
|
+
'# Auto-generated by specrails-desktop (relocated-rail openspec shim).\n' +
|
|
105
|
+
'# Re-points every bare `openspec` call at the repo working tree.\n' +
|
|
106
|
+
'cd "${SPECRAILS_REPO_DIR:-.}" || exit 1\n' +
|
|
107
|
+
`exec ${posix} "$@"\n`;
|
|
108
|
+
const posixPath = path_1.default.join(dir, 'openspec');
|
|
109
|
+
fs_1.default.writeFileSync(posixPath, posixScript, { mode: 0o755 });
|
|
110
|
+
try {
|
|
111
|
+
fs_1.default.chmodSync(posixPath, 0o755);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* non-fatal */
|
|
115
|
+
}
|
|
116
|
+
// Windows shim: `openspec.cmd`. cmd.exe resolves `openspec` to this on PATH.
|
|
117
|
+
const winScript = '@echo off\r\n' +
|
|
118
|
+
'rem Auto-generated by specrails-desktop (relocated-rail openspec shim).\r\n' +
|
|
119
|
+
'if defined SPECRAILS_REPO_DIR cd /d "%SPECRAILS_REPO_DIR%"\r\n' +
|
|
120
|
+
`${windows} %*\r\n`;
|
|
121
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'openspec.cmd'), winScript);
|
|
122
|
+
return dir;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Prepend `shimDir` to a PATH string using the platform delimiter, de-duping a
|
|
130
|
+
* leading entry that already equals `shimDir`. Returns the original `pathValue`
|
|
131
|
+
* unchanged when `shimDir` is null/empty.
|
|
132
|
+
*/
|
|
133
|
+
function prependShimToPath(pathValue, shimDir) {
|
|
134
|
+
const base = pathValue ?? '';
|
|
135
|
+
if (!shimDir)
|
|
136
|
+
return base;
|
|
137
|
+
const sep = path_1.default.delimiter;
|
|
138
|
+
const segments = base.split(sep).filter((s) => s.length > 0);
|
|
139
|
+
if (segments[0] === shimDir)
|
|
140
|
+
return base;
|
|
141
|
+
return [shimDir, ...segments].join(sep);
|
|
142
|
+
}
|
|
143
|
+
/** Remove a per-job shim dir (best-effort cleanup). No-op when absent. */
|
|
144
|
+
function removeOpenspecShim(slug, jobId, home = os_1.default.homedir()) {
|
|
145
|
+
try {
|
|
146
|
+
const dir = openspecShimDir(slug, jobId, home);
|
|
147
|
+
if (fs_1.default.existsSync(dir))
|
|
148
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
/* best-effort */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -7,6 +7,17 @@ const manager_1 = require("./plugins/manager");
|
|
|
7
7
|
const prereq_installer_1 = require("./plugins/prereq-installer");
|
|
8
8
|
const path_resolver_1 = require("./path-resolver");
|
|
9
9
|
const claude_approval_1 = require("./plugins/claude-approval");
|
|
10
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
11
|
+
/**
|
|
12
|
+
* Relocate-artifacts gate: the dir the PluginManager mutates (`.mcp.json` +
|
|
13
|
+
* `.specrails/plugins/` state + `.claude/agents/custom-*.md`). Relocated ⇒ the
|
|
14
|
+
* workspace (so the relocated CLI, spawned with cwd=workspace, loads the
|
|
15
|
+
* `.mcp.json` the manager wrote); legacy ⇒ project.path (byte-identical).
|
|
16
|
+
*/
|
|
17
|
+
function pluginRoot(project) {
|
|
18
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
19
|
+
return exec.relocated && exec.workspaceDir ? exec.workspaceDir : project.path;
|
|
20
|
+
}
|
|
10
21
|
function pluginsSectionEnabled() {
|
|
11
22
|
return process.env.SPECRAILS_PLUGINS_SECTION !== 'false';
|
|
12
23
|
}
|
|
@@ -46,7 +57,7 @@ function createPluginsRouter() {
|
|
|
46
57
|
router.get('/', async (req, res) => {
|
|
47
58
|
try {
|
|
48
59
|
const { project } = ctx(req);
|
|
49
|
-
const list = await (0, manager_1.getPluginManager)().listAvailable(project
|
|
60
|
+
const list = await (0, manager_1.getPluginManager)().listAvailable(pluginRoot(project), project.provider);
|
|
50
61
|
res.json({ plugins: list });
|
|
51
62
|
}
|
|
52
63
|
catch (err) {
|
|
@@ -57,7 +68,7 @@ function createPluginsRouter() {
|
|
|
57
68
|
router.get('/:name/preview-install', async (req, res) => {
|
|
58
69
|
try {
|
|
59
70
|
const { project } = ctx(req);
|
|
60
|
-
const result = await (0, manager_1.getPluginManager)().previewInstall(project
|
|
71
|
+
const result = await (0, manager_1.getPluginManager)().previewInstall(pluginRoot(project), project.id, req.params.name);
|
|
61
72
|
res.json(result);
|
|
62
73
|
}
|
|
63
74
|
catch (err) {
|
|
@@ -68,7 +79,7 @@ function createPluginsRouter() {
|
|
|
68
79
|
router.post('/:name/install', async (req, res) => {
|
|
69
80
|
try {
|
|
70
81
|
const { project, broadcast } = ctx(req);
|
|
71
|
-
await (0, manager_1.getPluginManager)().install(project
|
|
82
|
+
await (0, manager_1.getPluginManager)().install(pluginRoot(project), project.id, req.params.name, broadcast, project.provider);
|
|
72
83
|
res.status(200).json({ ok: true });
|
|
73
84
|
}
|
|
74
85
|
catch (err) {
|
|
@@ -79,7 +90,7 @@ function createPluginsRouter() {
|
|
|
79
90
|
router.delete('/:name', async (req, res) => {
|
|
80
91
|
try {
|
|
81
92
|
const { project, broadcast } = ctx(req);
|
|
82
|
-
await (0, manager_1.getPluginManager)().uninstall(project
|
|
93
|
+
await (0, manager_1.getPluginManager)().uninstall(pluginRoot(project), project.id, req.params.name, broadcast, project.provider);
|
|
83
94
|
res.status(200).json({ ok: true });
|
|
84
95
|
}
|
|
85
96
|
catch (err) {
|
|
@@ -171,7 +182,7 @@ function createPluginsRouter() {
|
|
|
171
182
|
router.post('/:name/activate', async (req, res) => {
|
|
172
183
|
try {
|
|
173
184
|
const { project, broadcast } = ctx(req);
|
|
174
|
-
await (0, manager_1.getPluginManager)().setActive(project
|
|
185
|
+
await (0, manager_1.getPluginManager)().setActive(pluginRoot(project), project.id, req.params.name, true, broadcast, project.provider);
|
|
175
186
|
res.json({ ok: true });
|
|
176
187
|
}
|
|
177
188
|
catch (err) {
|
|
@@ -182,7 +193,7 @@ function createPluginsRouter() {
|
|
|
182
193
|
router.post('/:name/deactivate', async (req, res) => {
|
|
183
194
|
try {
|
|
184
195
|
const { project, broadcast } = ctx(req);
|
|
185
|
-
await (0, manager_1.getPluginManager)().setActive(project
|
|
196
|
+
await (0, manager_1.getPluginManager)().setActive(pluginRoot(project), project.id, req.params.name, false, broadcast, project.provider);
|
|
186
197
|
res.json({ ok: true });
|
|
187
198
|
}
|
|
188
199
|
catch (err) {
|
|
@@ -193,7 +204,7 @@ function createPluginsRouter() {
|
|
|
193
204
|
router.post('/:name/update', async (req, res) => {
|
|
194
205
|
try {
|
|
195
206
|
const { project, broadcast } = ctx(req);
|
|
196
|
-
await (0, manager_1.getPluginManager)().updateMcpEntry(project
|
|
207
|
+
await (0, manager_1.getPluginManager)().updateMcpEntry(pluginRoot(project), project.id, req.params.name, broadcast, project.provider);
|
|
197
208
|
res.json({ ok: true });
|
|
198
209
|
}
|
|
199
210
|
catch (err) {
|
|
@@ -204,7 +215,7 @@ function createPluginsRouter() {
|
|
|
204
215
|
router.get('/:name/health', async (req, res) => {
|
|
205
216
|
try {
|
|
206
217
|
const { project, broadcast } = ctx(req);
|
|
207
|
-
const result = await (0, manager_1.getPluginManager)().verify(project
|
|
218
|
+
const result = await (0, manager_1.getPluginManager)().verify(pluginRoot(project), project.id, req.params.name, broadcast);
|
|
208
219
|
res.json(result);
|
|
209
220
|
}
|
|
210
221
|
catch (err) {
|
|
@@ -12,6 +12,27 @@ const agent_refine_db_1 = require("./agent-refine-db");
|
|
|
12
12
|
const agent_refine_manager_1 = require("./agent-refine-manager");
|
|
13
13
|
const providers_1 = require("./providers");
|
|
14
14
|
const profile_manager_1 = require("./profile-manager");
|
|
15
|
+
const workspace_resolution_1 = require("./workspace-resolution");
|
|
16
|
+
/**
|
|
17
|
+
* Relocate-artifacts gate: the dir whose `.specrails/{profiles,specrails-version}`
|
|
18
|
+
* AND whose `.claude/agents/**` catalog the profiles surface reads/writes.
|
|
19
|
+
* Relocated ⇒ the workspace dir (where core assembled the agents catalog +
|
|
20
|
+
* `.specrails/profiles`); legacy ⇒ project.path (byte-identical to today).
|
|
21
|
+
*
|
|
22
|
+
* The `.claude/agents/**` catalog MUST follow this same root: when relocated,
|
|
23
|
+
* core materializes the agents into `<workspace>/.claude/agents`, so a profiles
|
|
24
|
+
* catalog read/write rooted at `project.path` would (a) read a stale/absent repo
|
|
25
|
+
* copy and (b) write custom-* agents into the repo — violating repo-immutability
|
|
26
|
+
* and never reaching the workspace the rails actually load from.
|
|
27
|
+
*/
|
|
28
|
+
function specRoot(project) {
|
|
29
|
+
const exec = (0, workspace_resolution_1.resolveProjectExecution)({ slug: project.slug, path: project.path });
|
|
30
|
+
return exec.relocated && exec.workspaceDir ? exec.workspaceDir : project.path;
|
|
31
|
+
}
|
|
32
|
+
/** The agents-catalog directory for the relocate-aware root (`<root>/.claude/agents`). */
|
|
33
|
+
function agentsCatalogDir(project) {
|
|
34
|
+
return path_1.default.join(specRoot(project), '.claude', 'agents');
|
|
35
|
+
}
|
|
15
36
|
const AGENTS_SECTION_ENABLED = process.env.SPECRAILS_AGENTS_SECTION !== 'false';
|
|
16
37
|
function handleError(res, err) {
|
|
17
38
|
if (err instanceof profile_manager_1.ProfileValidationError) {
|
|
@@ -48,7 +69,7 @@ function createProfilesRouter() {
|
|
|
48
69
|
router.post('/migrate-from-settings', (req, res) => {
|
|
49
70
|
try {
|
|
50
71
|
const { project, broadcast } = ctx(req);
|
|
51
|
-
const agentsDir =
|
|
72
|
+
const agentsDir = agentsCatalogDir(project);
|
|
52
73
|
if (!fs_1.default.existsSync(agentsDir)) {
|
|
53
74
|
res.status(400).json({ error: 'no .claude/agents/ directory found' });
|
|
54
75
|
return;
|
|
@@ -129,7 +150,7 @@ function createProfilesRouter() {
|
|
|
129
150
|
],
|
|
130
151
|
};
|
|
131
152
|
try {
|
|
132
|
-
(0, profile_manager_1.createProfile)(project
|
|
153
|
+
(0, profile_manager_1.createProfile)(specRoot(project), profile, provider);
|
|
133
154
|
}
|
|
134
155
|
catch (err) {
|
|
135
156
|
if (err instanceof profile_manager_1.ProfileConflictError) {
|
|
@@ -189,8 +210,9 @@ function createProfilesRouter() {
|
|
|
189
210
|
router.get('/core-version', (req, res) => {
|
|
190
211
|
try {
|
|
191
212
|
const { project } = ctx(req);
|
|
213
|
+
const root = specRoot(project);
|
|
192
214
|
const candidates = [
|
|
193
|
-
path_1.default.join(
|
|
215
|
+
path_1.default.join(root, '.specrails', 'specrails-version'),
|
|
194
216
|
path_1.default.join(project.path, '.specrails-version'),
|
|
195
217
|
];
|
|
196
218
|
let version = null;
|
|
@@ -230,7 +252,7 @@ function createProfilesRouter() {
|
|
|
230
252
|
router.get('/catalog', (req, res) => {
|
|
231
253
|
try {
|
|
232
254
|
const { project } = ctx(req);
|
|
233
|
-
const dir =
|
|
255
|
+
const dir = agentsCatalogDir(project);
|
|
234
256
|
if (!fs_1.default.existsSync(dir)) {
|
|
235
257
|
res.json({ agents: [] });
|
|
236
258
|
return;
|
|
@@ -305,7 +327,7 @@ function createProfilesRouter() {
|
|
|
305
327
|
res.status(400).json({ error: 'invalid agent id' });
|
|
306
328
|
return;
|
|
307
329
|
}
|
|
308
|
-
const file = path_1.default.join(project
|
|
330
|
+
const file = path_1.default.join(agentsCatalogDir(project), `${agentId}.md`);
|
|
309
331
|
if (!fs_1.default.existsSync(file)) {
|
|
310
332
|
res.status(404).json({ error: 'agent not found' });
|
|
311
333
|
return;
|
|
@@ -333,7 +355,7 @@ function createProfilesRouter() {
|
|
|
333
355
|
res.status(400).json({ error: 'body is required' });
|
|
334
356
|
return;
|
|
335
357
|
}
|
|
336
|
-
const agentsDir =
|
|
358
|
+
const agentsDir = agentsCatalogDir(project);
|
|
337
359
|
fs_1.default.mkdirSync(agentsDir, { recursive: true });
|
|
338
360
|
const file = path_1.default.join(agentsDir, `${id}.md`);
|
|
339
361
|
if (fs_1.default.existsSync(file)) {
|
|
@@ -366,7 +388,7 @@ function createProfilesRouter() {
|
|
|
366
388
|
res.status(400).json({ error: 'body is required' });
|
|
367
389
|
return;
|
|
368
390
|
}
|
|
369
|
-
const file = path_1.default.join(project
|
|
391
|
+
const file = path_1.default.join(agentsCatalogDir(project), `${agentId}.md`);
|
|
370
392
|
if (!fs_1.default.existsSync(file)) {
|
|
371
393
|
res.status(404).json({ error: 'agent not found' });
|
|
372
394
|
return;
|
|
@@ -394,7 +416,7 @@ function createProfilesRouter() {
|
|
|
394
416
|
res.status(403).json({ error: 'only custom-* agents can be deleted' });
|
|
395
417
|
return;
|
|
396
418
|
}
|
|
397
|
-
const file = path_1.default.join(project
|
|
419
|
+
const file = path_1.default.join(agentsCatalogDir(project), `${agentId}.md`);
|
|
398
420
|
if (!fs_1.default.existsSync(file)) {
|
|
399
421
|
res.status(404).json({ error: 'agent not found' });
|
|
400
422
|
return;
|
|
@@ -647,7 +669,7 @@ function createProfilesRouter() {
|
|
|
647
669
|
router.get('/', (req, res) => {
|
|
648
670
|
try {
|
|
649
671
|
const { project } = ctx(req);
|
|
650
|
-
res.json({ profiles: (0, profile_manager_1.listProfiles)(project
|
|
672
|
+
res.json({ profiles: (0, profile_manager_1.listProfiles)(specRoot(project)) });
|
|
651
673
|
}
|
|
652
674
|
catch (err) {
|
|
653
675
|
handleError(res, err);
|
|
@@ -658,7 +680,7 @@ function createProfilesRouter() {
|
|
|
658
680
|
try {
|
|
659
681
|
const { project } = ctx(req);
|
|
660
682
|
const explicit = typeof req.query.profile === 'string' ? req.query.profile : undefined;
|
|
661
|
-
const resolved = (0, profile_manager_1.resolveProfile)(project
|
|
683
|
+
const resolved = (0, profile_manager_1.resolveProfile)(specRoot(project), explicit, project.provider ?? 'claude');
|
|
662
684
|
if (!resolved) {
|
|
663
685
|
res.json({ resolved: null });
|
|
664
686
|
return;
|
|
@@ -674,7 +696,7 @@ function createProfilesRouter() {
|
|
|
674
696
|
try {
|
|
675
697
|
const { project, broadcast } = ctx(req);
|
|
676
698
|
const body = req.body;
|
|
677
|
-
(0, profile_manager_1.createProfile)(project
|
|
699
|
+
(0, profile_manager_1.createProfile)(specRoot(project), body, project.provider ?? 'claude');
|
|
678
700
|
broadcast({ type: 'profile.changed', projectId: project.id, name: body.name });
|
|
679
701
|
res.status(201).json({ profile: body });
|
|
680
702
|
}
|
|
@@ -691,7 +713,7 @@ function createProfilesRouter() {
|
|
|
691
713
|
res.status(400).json({ error: "body field 'name' is required" });
|
|
692
714
|
return;
|
|
693
715
|
}
|
|
694
|
-
const copy = (0, profile_manager_1.duplicateProfile)(project
|
|
716
|
+
const copy = (0, profile_manager_1.duplicateProfile)(specRoot(project), req.params.name, newName, project.provider ?? 'claude');
|
|
695
717
|
broadcast({ type: 'profile.changed', projectId: project.id, name: newName });
|
|
696
718
|
res.status(201).json({ profile: copy });
|
|
697
719
|
}
|
|
@@ -708,7 +730,7 @@ function createProfilesRouter() {
|
|
|
708
730
|
res.status(400).json({ error: "body field 'name' is required" });
|
|
709
731
|
return;
|
|
710
732
|
}
|
|
711
|
-
const renamed = (0, profile_manager_1.renameProfile)(project
|
|
733
|
+
const renamed = (0, profile_manager_1.renameProfile)(specRoot(project), req.params.name, newName, project.provider ?? 'claude');
|
|
712
734
|
broadcast({ type: 'profile.changed', projectId: project.id, name: newName });
|
|
713
735
|
res.json({ profile: renamed });
|
|
714
736
|
}
|
|
@@ -720,7 +742,7 @@ function createProfilesRouter() {
|
|
|
720
742
|
router.get('/:name', (req, res) => {
|
|
721
743
|
try {
|
|
722
744
|
const { project } = ctx(req);
|
|
723
|
-
res.json({ profile: (0, profile_manager_1.getProfile)(project
|
|
745
|
+
res.json({ profile: (0, profile_manager_1.getProfile)(specRoot(project), req.params.name, project.provider ?? 'claude') });
|
|
724
746
|
}
|
|
725
747
|
catch (err) {
|
|
726
748
|
handleError(res, err);
|
|
@@ -735,7 +757,7 @@ function createProfilesRouter() {
|
|
|
735
757
|
res.status(400).json({ error: "body.name must match path parameter (use /rename to change name)" });
|
|
736
758
|
return;
|
|
737
759
|
}
|
|
738
|
-
(0, profile_manager_1.updateProfile)(project
|
|
760
|
+
(0, profile_manager_1.updateProfile)(specRoot(project), body, project.provider ?? 'claude');
|
|
739
761
|
broadcast({ type: 'profile.changed', projectId: project.id, name: body.name });
|
|
740
762
|
res.json({ profile: body });
|
|
741
763
|
}
|
|
@@ -747,7 +769,7 @@ function createProfilesRouter() {
|
|
|
747
769
|
router.delete('/:name', (req, res) => {
|
|
748
770
|
try {
|
|
749
771
|
const { project, broadcast } = ctx(req);
|
|
750
|
-
(0, profile_manager_1.deleteProfile)(project
|
|
772
|
+
(0, profile_manager_1.deleteProfile)(specRoot(project), req.params.name);
|
|
751
773
|
broadcast({ type: 'profile.changed', projectId: project.id, name: req.params.name, deleted: true });
|
|
752
774
|
res.json({ ok: true });
|
|
753
775
|
}
|