agileflow 4.0.0-alpha.2 → 4.0.0-alpha.21
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/CHANGELOG.md +51 -0
- package/content/plugins/accessibility/plugin.yaml +14 -0
- package/content/plugins/accessibility/skills/agileflow-accessibility/SKILL.md +392 -0
- package/content/plugins/accessibility/skills/agileflow-accessibility/references/aria-patterns.md +528 -0
- package/content/plugins/accessibility/skills/agileflow-accessibility/references/testing-checklist.md +457 -0
- package/content/plugins/accessibility/skills/agileflow-accessibility/references/wcag-guide.md +683 -0
- package/content/plugins/accessibility/skills/agileflow-accessibility/workflows/audit-page.md +310 -0
- package/content/plugins/accessibility/skills/agileflow-accessibility/workflows/implement-accessible-component.md +479 -0
- package/content/plugins/ads/agents/ads-audit-budget.md +185 -0
- package/content/plugins/ads/agents/ads-audit-compliance.md +171 -0
- package/content/plugins/ads/agents/ads-audit-creative.md +168 -0
- package/content/plugins/ads/agents/ads-audit-google.md +227 -0
- package/content/plugins/ads/agents/ads-audit-meta.md +184 -0
- package/content/plugins/ads/agents/ads-audit-tracking.md +205 -0
- package/content/plugins/ads/agents/ads-consensus.md +410 -0
- package/content/plugins/ads/agents/ads-generate.md +152 -0
- package/content/plugins/ads/agents/ads-performance-tracker.md +212 -0
- package/content/plugins/ads/plugin.yaml +23 -4
- package/content/plugins/ads/skills/agileflow-ads/SKILL.md +218 -0
- package/content/plugins/ads/skills/agileflow-ads/references/ad-copy-formula-guide.md +131 -0
- package/content/plugins/ads/skills/agileflow-ads/references/audience-targeting-guide.md +137 -0
- package/content/plugins/ads/skills/agileflow-ads/references/bid-strategy-guide.md +115 -0
- package/content/plugins/ads/skills/agileflow-ads/references/platform-benchmarks.md +100 -0
- package/content/plugins/ads/skills/agileflow-ads/workflows/audit.md +118 -0
- package/content/plugins/ads/skills/agileflow-ads/workflows/generate.md +84 -0
- package/content/plugins/audit/agents/a11y-analyzer-aria.md +173 -0
- package/content/plugins/audit/agents/a11y-analyzer-forms.md +173 -0
- package/content/plugins/audit/agents/a11y-analyzer-keyboard.md +183 -0
- package/content/plugins/audit/agents/a11y-analyzer-semantic.md +169 -0
- package/content/plugins/audit/agents/a11y-analyzer-visual.md +172 -0
- package/content/plugins/audit/agents/a11y-consensus.md +249 -0
- package/content/plugins/audit/agents/accessibility.md +558 -0
- package/content/plugins/audit/agents/api-quality-analyzer-conventions.md +156 -0
- package/content/plugins/audit/agents/api-quality-analyzer-docs.md +184 -0
- package/content/plugins/audit/agents/api-quality-analyzer-errors.md +191 -0
- package/content/plugins/audit/agents/api-quality-analyzer-pagination.md +179 -0
- package/content/plugins/audit/agents/api-quality-analyzer-versioning.md +150 -0
- package/content/plugins/audit/agents/api-quality-consensus.md +217 -0
- package/content/plugins/audit/agents/api-validator.md +191 -0
- package/content/plugins/audit/agents/arch-analyzer-circular.md +156 -0
- package/content/plugins/audit/agents/arch-analyzer-complexity.md +193 -0
- package/content/plugins/audit/agents/arch-analyzer-coupling.md +152 -0
- package/content/plugins/audit/agents/arch-analyzer-layering.md +160 -0
- package/content/plugins/audit/agents/arch-analyzer-patterns.md +210 -0
- package/content/plugins/audit/agents/arch-consensus.md +228 -0
- package/content/plugins/audit/agents/browser-qa.md +342 -0
- package/content/plugins/audit/agents/code-reviewer.md +298 -0
- package/content/plugins/audit/agents/completeness-analyzer-api.md +199 -0
- package/content/plugins/audit/agents/completeness-analyzer-conditional.md +211 -0
- package/content/plugins/audit/agents/completeness-analyzer-handlers.md +166 -0
- package/content/plugins/audit/agents/completeness-analyzer-imports.md +165 -0
- package/content/plugins/audit/agents/completeness-analyzer-routes.md +190 -0
- package/content/plugins/audit/agents/completeness-analyzer-state.md +196 -0
- package/content/plugins/audit/agents/completeness-analyzer-stubs.md +206 -0
- package/content/plugins/audit/agents/completeness-consensus.md +295 -0
- package/content/plugins/audit/agents/error-analyzer.md +213 -0
- package/content/plugins/audit/agents/flow-analyzer-authorization.md +182 -0
- package/content/plugins/audit/agents/flow-analyzer-discovery.md +174 -0
- package/content/plugins/audit/agents/flow-analyzer-errors.md +186 -0
- package/content/plugins/audit/agents/flow-analyzer-feedback.md +185 -0
- package/content/plugins/audit/agents/flow-analyzer-navigation.md +177 -0
- package/content/plugins/audit/agents/flow-analyzer-persistence.md +193 -0
- package/content/plugins/audit/agents/flow-analyzer-wiring.md +169 -0
- package/content/plugins/audit/agents/flow-consensus.md +237 -0
- package/content/plugins/audit/agents/legal-analyzer-a11y.md +114 -0
- package/content/plugins/audit/agents/legal-analyzer-ai.md +121 -0
- package/content/plugins/audit/agents/legal-analyzer-consumer.md +114 -0
- package/content/plugins/audit/agents/legal-analyzer-content.md +117 -0
- package/content/plugins/audit/agents/legal-analyzer-international.md +119 -0
- package/content/plugins/audit/agents/legal-analyzer-licensing.md +119 -0
- package/content/plugins/audit/agents/legal-analyzer-privacy.md +112 -0
- package/content/plugins/audit/agents/legal-analyzer-security.md +116 -0
- package/content/plugins/audit/agents/legal-analyzer-terms.md +115 -0
- package/content/plugins/audit/agents/legal-consensus.md +250 -0
- package/content/plugins/audit/agents/logic-analyzer-edge.md +179 -0
- package/content/plugins/audit/agents/logic-analyzer-flow.md +264 -0
- package/content/plugins/audit/agents/logic-analyzer-invariant.md +215 -0
- package/content/plugins/audit/agents/logic-analyzer-race.md +280 -0
- package/content/plugins/audit/agents/logic-analyzer-type.md +227 -0
- package/content/plugins/audit/agents/logic-consensus.md +259 -0
- package/content/plugins/audit/agents/perf-analyzer-assets.md +182 -0
- package/content/plugins/audit/agents/perf-analyzer-bundle.md +173 -0
- package/content/plugins/audit/agents/perf-analyzer-caching.md +170 -0
- package/content/plugins/audit/agents/perf-analyzer-compute.md +173 -0
- package/content/plugins/audit/agents/perf-analyzer-memory.md +193 -0
- package/content/plugins/audit/agents/perf-analyzer-network.md +165 -0
- package/content/plugins/audit/agents/perf-analyzer-queries.md +162 -0
- package/content/plugins/audit/agents/perf-analyzer-rendering.md +168 -0
- package/content/plugins/audit/agents/perf-consensus.md +287 -0
- package/content/plugins/audit/agents/qa.md +820 -0
- package/content/plugins/audit/agents/quality-analyzer-comments.md +159 -0
- package/content/plugins/audit/agents/quality-analyzer-duplication.md +184 -0
- package/content/plugins/audit/agents/quality-analyzer-naming.md +160 -0
- package/content/plugins/audit/agents/quality-consensus.md +241 -0
- package/content/plugins/audit/agents/schema-validator.md +473 -0
- package/content/plugins/audit/agents/security-analyzer-api.md +210 -0
- package/content/plugins/audit/agents/security-analyzer-auth.md +169 -0
- package/content/plugins/audit/agents/security-analyzer-authz.md +180 -0
- package/content/plugins/audit/agents/security-analyzer-deps.md +153 -0
- package/content/plugins/audit/agents/security-analyzer-infra.md +184 -0
- package/content/plugins/audit/agents/security-analyzer-injection.md +155 -0
- package/content/plugins/audit/agents/security-analyzer-input.md +201 -0
- package/content/plugins/audit/agents/security-analyzer-secrets.md +183 -0
- package/content/plugins/audit/agents/security-consensus.md +283 -0
- package/content/plugins/audit/agents/test-analyzer-assertions.md +188 -0
- package/content/plugins/audit/agents/test-analyzer-coverage.md +189 -0
- package/content/plugins/audit/agents/test-analyzer-fragility.md +193 -0
- package/content/plugins/audit/agents/test-analyzer-integration.md +161 -0
- package/content/plugins/audit/agents/test-analyzer-maintenance.md +180 -0
- package/content/plugins/audit/agents/test-analyzer-mocking.md +188 -0
- package/content/plugins/audit/agents/test-analyzer-patterns.md +196 -0
- package/content/plugins/audit/agents/test-analyzer-structure.md +184 -0
- package/content/plugins/audit/agents/test-consensus.md +301 -0
- package/content/plugins/audit/agents/testing.md +561 -0
- package/content/plugins/audit/agents/ui-validator.md +344 -0
- package/content/plugins/audit/plugin.yaml +186 -5
- package/content/plugins/audit/skills/agileflow-audit/SKILL.md +113 -0
- package/content/plugins/audit/skills/agileflow-audit/references/audit-depth-guide.md +151 -0
- package/content/plugins/audit/skills/agileflow-audit/references/dependency-risk-guide.md +139 -0
- package/content/plugins/audit/skills/agileflow-audit/references/owasp-top10.md +120 -0
- package/content/plugins/audit/skills/agileflow-audit/references/performance-budget-guide.md +143 -0
- package/content/plugins/audit/skills/agileflow-audit/references/wcag-criteria.md +117 -0
- package/content/plugins/audit/skills/agileflow-audit/workflows/run-audit.md +52 -0
- package/content/plugins/audit/skills/agileflow-audit/workflows/tdd.md +66 -0
- package/content/plugins/core/agents/adr-writer.md +521 -0
- package/content/plugins/core/agents/epic-planner.md +520 -0
- package/content/plugins/core/agents/mentor.md +709 -0
- package/content/plugins/core/agents/orchestrator.md +776 -0
- package/content/plugins/core/agents/team-coordinator.md +334 -0
- package/content/plugins/core/agents/team-lead.md +181 -0
- package/content/plugins/core/agents/workspace-orchestrator.md +146 -0
- package/content/plugins/core/hooks/context-loader.js +31 -4
- package/content/plugins/core/hooks/damage-control-bash.js +10 -2
- package/content/plugins/core/hooks/damage-control-edit.js +4 -1
- package/content/plugins/core/hooks/damage-control-patterns.yaml +1 -1
- package/content/plugins/core/hooks/damage-control-write.js +4 -1
- package/content/plugins/core/hooks/{pre-compact-state.js → post-compact-state.js} +25 -8
- package/content/plugins/core/hooks/preferences-injector.js +352 -0
- package/content/plugins/core/plugin.yaml +24 -28
- package/content/plugins/core/skills/agileflow-adr/SKILL.md +34 -8
- package/content/plugins/core/skills/agileflow-adr/references/madr-format-guide.md +86 -0
- package/content/plugins/core/skills/agileflow-adr/workflows/write-adr.md +57 -0
- package/content/plugins/core/skills/agileflow-babysit-mentor/SKILL.md +94 -27
- package/content/plugins/core/skills/agileflow-babysit-mentor/references/mentor-decision-guide.md +81 -0
- package/content/plugins/core/skills/agileflow-babysit-mentor/workflows/mentor-session.md +79 -0
- package/content/plugins/core/skills/agileflow-epic-planner/SKILL.md +37 -7
- package/content/plugins/core/skills/agileflow-epic-planner/references/epic-sizing-guide.md +81 -0
- package/content/plugins/core/skills/agileflow-epic-planner/workflows/plan-epic.md +55 -0
- package/content/plugins/core/skills/agileflow-status-updater/SKILL.md +36 -20
- package/content/plugins/core/skills/agileflow-status-updater/references/status-transitions.md +89 -0
- package/content/plugins/core/skills/agileflow-status-updater/workflows/update-status.md +56 -0
- package/content/plugins/core/skills/agileflow-story-writer/SKILL.md +39 -114
- package/content/plugins/core/skills/agileflow-story-writer/references/estimation-reference.md +36 -0
- package/content/plugins/core/skills/agileflow-story-writer/references/story-template.md +92 -0
- package/content/plugins/core/skills/agileflow-story-writer/workflows/write-story.md +138 -0
- package/content/plugins/council/agents/council-advocate.md +223 -0
- package/content/plugins/council/agents/council-analyst.md +278 -0
- package/content/plugins/council/agents/council-compounder.md +204 -0
- package/content/plugins/council/agents/council-contrarian.md +217 -0
- package/content/plugins/council/agents/council-moonshot.md +217 -0
- package/content/plugins/council/agents/council-optimist.md +185 -0
- package/content/plugins/council/agents/council-revenue.md +200 -0
- package/content/plugins/council/agents/council-technical.md +218 -0
- package/content/plugins/council/agents/multi-expert.md +334 -0
- package/content/plugins/council/plugin.yaml +23 -4
- package/content/plugins/council/skills/agileflow-council/SKILL.md +102 -0
- package/content/plugins/council/skills/agileflow-council/references/decision-log-template.md +109 -0
- package/content/plugins/council/skills/agileflow-council/references/perspective-guide.md +104 -0
- package/content/plugins/council/skills/agileflow-council/references/when-to-convene-guide.md +112 -0
- package/content/plugins/council/skills/agileflow-council/workflows/convene.md +73 -0
- package/content/plugins/council/skills/agileflow-council/workflows/multi-expert.md +75 -0
- package/content/plugins/database/plugin.yaml +14 -0
- package/content/plugins/database/skills/agileflow-database/SKILL.md +284 -0
- package/content/plugins/database/skills/agileflow-database/references/indexing-guide.md +313 -0
- package/content/plugins/database/skills/agileflow-database/references/migration-guide.md +328 -0
- package/content/plugins/database/skills/agileflow-database/references/schema-design-guide.md +467 -0
- package/content/plugins/database/skills/agileflow-database/workflows/design-schema.md +213 -0
- package/content/plugins/database/skills/agileflow-database/workflows/optimize-query.md +253 -0
- package/content/plugins/debugging/plugin.yaml +14 -0
- package/content/plugins/debugging/skills/agileflow-debug/SKILL.md +236 -0
- package/content/plugins/debugging/skills/agileflow-debug/references/common-patterns.md +350 -0
- package/content/plugins/debugging/skills/agileflow-debug/references/debugging-strategies.md +328 -0
- package/content/plugins/debugging/skills/agileflow-debug/workflows/debug-issue.md +187 -0
- package/content/plugins/debugging/skills/agileflow-debug/workflows/reproduce-bug.md +194 -0
- package/content/plugins/delivery/agents/ci.md +547 -0
- package/content/plugins/delivery/agents/devops.md +789 -0
- package/content/plugins/delivery/plugin.yaml +19 -0
- package/content/plugins/delivery/skills/agileflow-delivery/SKILL.md +111 -0
- package/content/plugins/delivery/skills/agileflow-delivery/references/changelog-format-guide.md +133 -0
- package/content/plugins/delivery/skills/agileflow-delivery/references/ci-pipeline-guide.md +158 -0
- package/content/plugins/delivery/skills/agileflow-delivery/references/pr-checklist-guide.md +133 -0
- package/content/plugins/delivery/skills/agileflow-delivery/references/release-checklist.md +142 -0
- package/content/plugins/delivery/skills/agileflow-delivery/workflows/changelog.md +72 -0
- package/content/plugins/delivery/skills/agileflow-delivery/workflows/deploy.md +74 -0
- package/content/plugins/delivery/skills/agileflow-delivery/workflows/pr.md +75 -0
- package/content/plugins/docs/agents/documentation.md +544 -0
- package/content/plugins/docs/agents/readme-updater.md +640 -0
- package/content/plugins/docs/plugin.yaml +19 -0
- package/content/plugins/docs/skills/agileflow-docs/SKILL.md +106 -0
- package/content/plugins/docs/skills/agileflow-docs/references/api-doc-template.md +167 -0
- package/content/plugins/docs/skills/agileflow-docs/references/doc-types-guide.md +141 -0
- package/content/plugins/docs/skills/agileflow-docs/references/readme-template.md +156 -0
- package/content/plugins/docs/skills/agileflow-docs/workflows/readme-sync.md +57 -0
- package/content/plugins/docs/skills/agileflow-docs/workflows/sync.md +64 -0
- package/content/plugins/engineering/agents/api.md +718 -0
- package/content/plugins/engineering/agents/codebase-query.md +285 -0
- package/content/plugins/engineering/agents/compliance.md +559 -0
- package/content/plugins/engineering/agents/database.md +644 -0
- package/content/plugins/engineering/agents/integrations.md +644 -0
- package/content/plugins/engineering/agents/mobile.md +552 -0
- package/content/plugins/engineering/agents/monitoring.md +585 -0
- package/content/plugins/engineering/agents/performance.md +529 -0
- package/content/plugins/engineering/agents/refactor.md +592 -0
- package/content/plugins/engineering/agents/security.md +524 -0
- package/content/plugins/engineering/agents/ui.md +1336 -0
- package/content/plugins/engineering/plugin.yaml +37 -0
- package/content/plugins/engineering/skills/agileflow-engineering/SKILL.md +127 -0
- package/content/plugins/engineering/skills/agileflow-engineering/references/code-review-guide.md +126 -0
- package/content/plugins/engineering/skills/agileflow-engineering/references/domain-routing-guide.md +89 -0
- package/content/plugins/engineering/skills/agileflow-engineering/references/refactoring-guide.md +136 -0
- package/content/plugins/engineering/skills/agileflow-engineering/workflows/diagnose.md +63 -0
- package/content/plugins/engineering/skills/agileflow-engineering/workflows/impact.md +60 -0
- package/content/plugins/ideation/agents/brainstorm-analyzer-features.md +179 -0
- package/content/plugins/ideation/agents/brainstorm-analyzer-growth.md +169 -0
- package/content/plugins/ideation/agents/brainstorm-analyzer-integration.md +181 -0
- package/content/plugins/ideation/agents/brainstorm-analyzer-market.md +150 -0
- package/content/plugins/ideation/agents/brainstorm-analyzer-ux.md +180 -0
- package/content/plugins/ideation/agents/brainstorm-consensus.md +245 -0
- package/content/plugins/ideation/agents/design.md +568 -0
- package/content/plugins/ideation/agents/product.md +582 -0
- package/content/plugins/ideation/plugin.yaml +31 -0
- package/content/plugins/ideation/skills/agileflow-ideation/SKILL.md +109 -0
- package/content/plugins/ideation/skills/agileflow-ideation/references/brainstorm-techniques.md +138 -0
- package/content/plugins/ideation/skills/agileflow-ideation/references/competitive-analysis-template.md +148 -0
- package/content/plugins/ideation/skills/agileflow-ideation/references/feature-prioritization-guide.md +147 -0
- package/content/plugins/ideation/skills/agileflow-ideation/references/user-story-patterns.md +152 -0
- package/content/plugins/ideation/skills/agileflow-ideation/workflows/features.md +65 -0
- package/content/plugins/ideation/skills/agileflow-ideation/workflows/ideate.md +54 -0
- package/content/plugins/migration/agents/datamigration.md +757 -0
- package/content/plugins/migration/plugin.yaml +17 -0
- package/content/plugins/migration/skills/agileflow-migration/SKILL.md +106 -0
- package/content/plugins/migration/skills/agileflow-migration/references/data-validation-checklist.md +154 -0
- package/content/plugins/migration/skills/agileflow-migration/references/migration-patterns.md +209 -0
- package/content/plugins/migration/skills/agileflow-migration/references/rollback-playbook.md +171 -0
- package/content/plugins/migration/skills/agileflow-migration/references/version-compatibility-matrix.md +155 -0
- package/content/plugins/migration/skills/agileflow-migration/workflows/plan.md +73 -0
- package/content/plugins/migration/skills/agileflow-migration/workflows/validate.md +71 -0
- package/content/plugins/performance/plugin.yaml +14 -0
- package/content/plugins/performance/skills/agileflow-performance/SKILL.md +224 -0
- package/content/plugins/performance/skills/agileflow-performance/references/optimization-patterns.md +554 -0
- package/content/plugins/performance/skills/agileflow-performance/references/profiling-guide.md +383 -0
- package/content/plugins/performance/skills/agileflow-performance/references/web-vitals-guide.md +360 -0
- package/content/plugins/performance/skills/agileflow-performance/workflows/improve-web-vitals.md +344 -0
- package/content/plugins/performance/skills/agileflow-performance/workflows/profile-and-fix.md +254 -0
- package/content/plugins/planning/agents/analytics.md +670 -0
- package/content/plugins/planning/agents/rlm-subcore.md +215 -0
- package/content/plugins/planning/plugin.yaml +19 -0
- package/content/plugins/planning/skills/agileflow-planning/SKILL.md +111 -0
- package/content/plugins/planning/skills/agileflow-planning/references/estimation-guide.md +114 -0
- package/content/plugins/planning/skills/agileflow-planning/references/rpi-workflow.md +119 -0
- package/content/plugins/planning/skills/agileflow-planning/references/sprint-planning-guide.md +145 -0
- package/content/plugins/planning/skills/agileflow-planning/workflows/impact.md +63 -0
- package/content/plugins/planning/skills/agileflow-planning/workflows/rpi.md +104 -0
- package/content/plugins/psychology/plugin.yaml +14 -0
- package/content/plugins/psychology/skills/agileflow-retention/SKILL.md +252 -0
- package/content/plugins/psychology/skills/agileflow-retention/references/competitor-analysis.md +240 -0
- package/content/plugins/psychology/skills/agileflow-retention/references/psychology-models.md +349 -0
- package/content/plugins/psychology/skills/agileflow-retention/references/retention-patterns.md +279 -0
- package/content/plugins/psychology/skills/agileflow-retention/workflows/design-retention-feature.md +287 -0
- package/content/plugins/psychology/skills/agileflow-retention/workflows/retention-audit.md +259 -0
- package/content/plugins/refactoring/plugin.yaml +14 -0
- package/content/plugins/refactoring/skills/agileflow-refactor/SKILL.md +235 -0
- package/content/plugins/refactoring/skills/agileflow-refactor/references/refactoring-patterns.md +405 -0
- package/content/plugins/refactoring/skills/agileflow-refactor/references/safety-checks.md +177 -0
- package/content/plugins/refactoring/skills/agileflow-refactor/workflows/extract-module.md +226 -0
- package/content/plugins/refactoring/skills/agileflow-refactor/workflows/safe-refactor.md +169 -0
- package/content/plugins/research/agents/research.md +503 -0
- package/content/plugins/research/plugin.yaml +17 -0
- package/content/plugins/research/skills/agileflow-research/SKILL.md +110 -0
- package/content/plugins/research/skills/agileflow-research/references/knowledge-decay-guide.md +121 -0
- package/content/plugins/research/skills/agileflow-research/references/research-prompt-guide.md +141 -0
- package/content/plugins/research/skills/agileflow-research/references/synthesis-template.md +154 -0
- package/content/plugins/research/skills/agileflow-research/workflows/analyze.md +60 -0
- package/content/plugins/research/skills/agileflow-research/workflows/ask.md +64 -0
- package/content/plugins/research/skills/agileflow-research/workflows/import.md +66 -0
- package/content/plugins/research/skills/agileflow-research/workflows/synthesize.md +66 -0
- package/content/plugins/reviews/plugin.yaml +14 -0
- package/content/plugins/reviews/skills/agileflow-pr-reviewer/SKILL.md +241 -0
- package/content/plugins/reviews/skills/agileflow-pr-reviewer/references/review-checklist.md +200 -0
- package/content/plugins/reviews/skills/agileflow-pr-reviewer/references/security-patterns.md +328 -0
- package/content/plugins/reviews/skills/agileflow-pr-reviewer/workflows/review-pr.md +153 -0
- package/content/plugins/reviews/skills/agileflow-pr-reviewer/workflows/security-review.md +177 -0
- package/content/plugins/seo/agents/seo-analyzer-content.md +169 -0
- package/content/plugins/seo/agents/seo-analyzer-images.md +198 -0
- package/content/plugins/seo/agents/seo-analyzer-performance.md +217 -0
- package/content/plugins/seo/agents/seo-analyzer-schema.md +184 -0
- package/content/plugins/seo/agents/seo-analyzer-sitemap.md +177 -0
- package/content/plugins/seo/agents/seo-analyzer-technical.md +151 -0
- package/content/plugins/seo/agents/seo-consensus.md +304 -0
- package/content/plugins/seo/plugin.yaml +19 -4
- package/content/plugins/seo/skills/agileflow-seo/SKILL.md +188 -0
- package/content/plugins/seo/skills/agileflow-seo/references/cwv-thresholds.md +110 -0
- package/content/plugins/seo/skills/agileflow-seo/references/eeat-framework.md +144 -0
- package/content/plugins/seo/skills/agileflow-seo/references/keyword-research-guide.md +125 -0
- package/content/plugins/seo/skills/agileflow-seo/references/schema-types.md +139 -0
- package/content/plugins/seo/skills/agileflow-seo/references/technical-seo-checklist.md +139 -0
- package/content/plugins/seo/skills/agileflow-seo/workflows/audit.md +98 -0
- package/content/plugins/seo/skills/agileflow-seo/workflows/page.md +118 -0
- package/content/plugins/testing/plugin.yaml +16 -0
- package/content/plugins/testing/skills/agileflow-test-writer/SKILL.md +260 -0
- package/content/plugins/testing/skills/agileflow-test-writer/references/coverage-targets.md +239 -0
- package/content/plugins/testing/skills/agileflow-test-writer/references/test-patterns.md +420 -0
- package/content/plugins/testing/skills/agileflow-test-writer/workflows/add-coverage.md +154 -0
- package/content/plugins/testing/skills/agileflow-test-writer/workflows/write-tests-from-ac.md +225 -0
- package/package.json +2 -2
- package/src/cli/commands/doctor.js +818 -30
- package/src/cli/commands/hook.js +17 -14
- package/src/cli/commands/launch.js +1454 -0
- package/src/cli/commands/learn.js +149 -0
- package/src/cli/commands/plugins.js +113 -0
- package/src/cli/commands/setup.js +455 -110
- package/src/cli/commands/skills.js +324 -0
- package/src/cli/commands/status.js +8 -10
- package/src/cli/commands/update.js +76 -15
- package/src/cli/index.js +90 -26
- package/src/cli/wizard/babysit-mode-picker.js +192 -0
- package/src/cli/wizard/behaviors-picker.js +208 -54
- package/src/cli/wizard/ide-picker.js +40 -28
- package/src/cli/wizard/install-scope-picker.js +57 -0
- package/src/cli/wizard/launch-alias-picker.js +50 -0
- package/src/cli/wizard/launch-cli-picker.js +129 -0
- package/src/cli/wizard/launch-tmux-picker.js +133 -0
- package/src/cli/wizard/learnings-picker.js +40 -0
- package/src/cli/wizard/plugin-picker.js +47 -16
- package/src/lib/brand.js +116 -0
- package/src/lib/errors.js +120 -0
- package/src/lib/path-check.js +39 -0
- package/src/runtime/config/defaults.js +22 -17
- package/src/runtime/config/loader.js +77 -8
- package/src/runtime/config/schema.json +43 -16
- package/src/runtime/config/writer.js +3 -1
- package/src/runtime/ide/babysit-skill.js +202 -0
- package/src/runtime/ide/capabilities.js +84 -29
- package/src/runtime/ide/claude-code-content.js +177 -0
- package/src/runtime/ide/claude-code-settings.js +67 -29
- package/src/runtime/ide/claude-code-skills.js +47 -32
- package/src/runtime/ide/codex-config.js +295 -0
- package/src/runtime/installer/install.js +252 -24
- package/src/runtime/launch/alias-installer.js +191 -0
- package/src/runtime/launch/cli-resume.js +244 -0
- package/src/runtime/launch/closed-windows.js +338 -0
- package/src/runtime/launch/defaults.js +66 -0
- package/src/runtime/launch/detect-clis.js +69 -0
- package/src/runtime/launch/doctor.js +464 -0
- package/src/runtime/launch/exec-wrapper.js +114 -0
- package/src/runtime/launch/parallel-session.js +247 -0
- package/src/runtime/launch/prefs.js +211 -0
- package/src/runtime/launch/project-prefs.js +234 -0
- package/src/runtime/launch/resolve-cli.js +56 -0
- package/src/runtime/launch/restore.js +152 -0
- package/src/runtime/launch/schema.json +75 -0
- package/src/runtime/launch/session-lifecycle.js +313 -0
- package/src/runtime/launch/session-registry.js +401 -0
- package/src/runtime/launch/spawn.js +103 -0
- package/src/runtime/launch/tabs.js +350 -0
- package/src/runtime/launch/tmux.js +764 -0
- package/src/runtime/launch/worktree.js +260 -0
- package/src/runtime/plugins/registry.js +16 -11
- package/src/runtime/plugins/validator.js +57 -43
- package/src/runtime/skills/learnings.js +308 -0
- package/content/plugins/core/hooks/babysit-mentor-injector.js +0 -55
- package/src/cli/wizard/personalization.js +0 -64
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spawn a parallel tmux session — same-dir or worktree-backed — and
|
|
3
|
+
* switch the user's existing client to it.
|
|
4
|
+
*
|
|
5
|
+
* Used by `agileflow launch new [name]`, which the default keybind
|
|
6
|
+
* preset binds to Alt+s (no name → same-dir) and Alt+n (prompts for a
|
|
7
|
+
* name → worktree). The caller has already verified we're inside a
|
|
8
|
+
* tmux client; this module assumes that and uses `switch-client` to
|
|
9
|
+
* swap rather than `attach-session`.
|
|
10
|
+
*
|
|
11
|
+
* No-name vs name flow:
|
|
12
|
+
* - No name: target dir is `process.cwd()`. New session uses
|
|
13
|
+
* `nextFreeSessionName(<cli>-<dir>, ...)` so it gets a `-2`, `-3`
|
|
14
|
+
* suffix when the canonical name is taken.
|
|
15
|
+
* - Name: `createWorktree({ name })` first, then the same fresh-name
|
|
16
|
+
* spawn against the worktree dir.
|
|
17
|
+
*/
|
|
18
|
+
const path = require("path");
|
|
19
|
+
const {
|
|
20
|
+
baseSessionName,
|
|
21
|
+
nextFreeSessionName,
|
|
22
|
+
sessionExists,
|
|
23
|
+
createSession,
|
|
24
|
+
applyKeybindPreset,
|
|
25
|
+
defaultRunner,
|
|
26
|
+
} = require("./tmux.js");
|
|
27
|
+
const { createWorktree, removeWorktree } = require("./worktree.js");
|
|
28
|
+
const { recordSession, forgetSession } = require("./session-registry.js");
|
|
29
|
+
const { resolveAgileflowBin } = require("./alias-installer.js");
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @typedef {Object} ParallelSpawnResult
|
|
33
|
+
* @property {string} sessionName - the new session's tmux name
|
|
34
|
+
* @property {string} cwd - the directory the session was created in (cwd or worktree path)
|
|
35
|
+
* @property {{ path: string, branch: string, base: string }} [worktree]
|
|
36
|
+
* - only set when `name` was supplied
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Build a typed error so callers can branch on `err.code`. Mirrors the
|
|
41
|
+
* pattern used elsewhere in the launch runtime.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} message
|
|
44
|
+
* @param {string} code
|
|
45
|
+
* @returns {Error}
|
|
46
|
+
*/
|
|
47
|
+
function makeSpawnError(message, code) {
|
|
48
|
+
const err = new Error(message);
|
|
49
|
+
/** @type {any} */ (err).code = code;
|
|
50
|
+
return err;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolve the directory for the new session. With `name`, create a
|
|
55
|
+
* worktree first and use its path. Pure logic so tests can verify the
|
|
56
|
+
* branching without spawning anything.
|
|
57
|
+
*
|
|
58
|
+
* @param {{
|
|
59
|
+
* name?: string,
|
|
60
|
+
* cwd: string,
|
|
61
|
+
* createWorktreeImpl?: typeof createWorktree,
|
|
62
|
+
* }} opts
|
|
63
|
+
* @returns {{ cwd: string, worktree?: { path: string, branch: string, base: string } }}
|
|
64
|
+
*/
|
|
65
|
+
function resolveSpawnDir(opts) {
|
|
66
|
+
if (!opts.name) {
|
|
67
|
+
return { cwd: opts.cwd };
|
|
68
|
+
}
|
|
69
|
+
const impl = opts.createWorktreeImpl || createWorktree;
|
|
70
|
+
const wt = impl({ name: opts.name });
|
|
71
|
+
return { cwd: wt.path, worktree: wt };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Spawn a new tmux session and switch the user's client to it.
|
|
76
|
+
*
|
|
77
|
+
* @param {{
|
|
78
|
+
* bin: string,
|
|
79
|
+
* name?: string,
|
|
80
|
+
* prefs: import("./defaults.js").LaunchPrefs,
|
|
81
|
+
* cwd?: string,
|
|
82
|
+
* runner?: ReturnType<typeof defaultRunner>,
|
|
83
|
+
* log?: (msg: string) => void,
|
|
84
|
+
* createWorktreeImpl?: typeof createWorktree,
|
|
85
|
+
* removeWorktreeImpl?: typeof removeWorktree,
|
|
86
|
+
* }} opts
|
|
87
|
+
* @returns {Promise<ParallelSpawnResult>}
|
|
88
|
+
*/
|
|
89
|
+
async function runParallelSpawn(opts) {
|
|
90
|
+
const runner = opts.runner || defaultRunner();
|
|
91
|
+
const removeWt = opts.removeWorktreeImpl || removeWorktree;
|
|
92
|
+
const log =
|
|
93
|
+
typeof opts.log === "function"
|
|
94
|
+
? opts.log
|
|
95
|
+
: (msg) => {
|
|
96
|
+
// eslint-disable-next-line no-console
|
|
97
|
+
console.error(msg);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const { cwd: targetCwd, worktree } = resolveSpawnDir({
|
|
101
|
+
name: opts.name,
|
|
102
|
+
cwd: opts.cwd || process.cwd(),
|
|
103
|
+
createWorktreeImpl: opts.createWorktreeImpl,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (worktree) {
|
|
107
|
+
log(
|
|
108
|
+
`agileflow launch: created worktree at ${worktree.path} on branch ${worktree.branch}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Once we've created the worktree we OWN it — if any of the tmux steps
|
|
113
|
+
// below fail, roll it back so the user isn't left with an orphan dir
|
|
114
|
+
// + branch they have to clean up by hand. The rollback helper itself
|
|
115
|
+
// is best-effort; on failure we surface a warning but still re-throw
|
|
116
|
+
// the original tmux error.
|
|
117
|
+
// Track the session name across the try/catch boundary so the
|
|
118
|
+
// rollback path can also forget the registry entry we added.
|
|
119
|
+
/** @type {string | null} */
|
|
120
|
+
let registeredName = null;
|
|
121
|
+
try {
|
|
122
|
+
const cliId = path.basename(opts.bin);
|
|
123
|
+
const base = baseSessionName(cliId, targetCwd);
|
|
124
|
+
// Always pick a fresh name. `new` semantics are "I want a parallel
|
|
125
|
+
// session" — never "reattach". `nextFreeSessionName` walks
|
|
126
|
+
// base, base-2, base-3, ... until it finds an unused slot.
|
|
127
|
+
const sessionName = nextFreeSessionName(base, (n) =>
|
|
128
|
+
sessionExists(n, runner),
|
|
129
|
+
);
|
|
130
|
+
registeredName = sessionName;
|
|
131
|
+
|
|
132
|
+
// Record the session in the cross-reboot registry BEFORE spawning
|
|
133
|
+
// so the __exec wrapper can find its entry. If the spawn fails we
|
|
134
|
+
// unrecord in the catch block below.
|
|
135
|
+
recordSession({
|
|
136
|
+
name: sessionName,
|
|
137
|
+
cli: cliId,
|
|
138
|
+
cwd: targetCwd,
|
|
139
|
+
uuid: null,
|
|
140
|
+
worktree: worktree
|
|
141
|
+
? { path: worktree.path, branch: worktree.branch, base: worktree.base }
|
|
142
|
+
: undefined,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const agileflowBin = resolveAgileflowBin();
|
|
146
|
+
const create = createSession(
|
|
147
|
+
{
|
|
148
|
+
name: sessionName,
|
|
149
|
+
bin: agileflowBin,
|
|
150
|
+
args: ["launch", "__exec", sessionName],
|
|
151
|
+
cwd: targetCwd,
|
|
152
|
+
statusPosition: opts.prefs.tmux.statusPosition,
|
|
153
|
+
},
|
|
154
|
+
runner,
|
|
155
|
+
);
|
|
156
|
+
if (create.status !== 0) {
|
|
157
|
+
if (create.error) throw create.error;
|
|
158
|
+
|
|
159
|
+
// Race recovery: in the same-dir (no-name) case, another
|
|
160
|
+
// `agileflow launch new` invocation could have grabbed our
|
|
161
|
+
// candidate name between the nextFreeSessionName probe and the
|
|
162
|
+
// new-session call. If the session is alive now, treat this as
|
|
163
|
+
// "user got what they wanted" and switch-client to it. We don't
|
|
164
|
+
// do this for the worktree path because the worktree dir is
|
|
165
|
+
// freshly created and unique, so the name collision shouldn't
|
|
166
|
+
// happen — if it does, something weirder is going on and the
|
|
167
|
+
// user should see the original error.
|
|
168
|
+
if (!opts.name && sessionExists(sessionName, runner)) {
|
|
169
|
+
log(
|
|
170
|
+
`agileflow launch: race-recovered, attaching to session ${sessionName}`,
|
|
171
|
+
);
|
|
172
|
+
} else {
|
|
173
|
+
const stderr = create.stderr.trim() || "tmux new-session failed";
|
|
174
|
+
throw makeSpawnError(`tmux: ${stderr}`, "ETMUX_CREATE");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Apply the user's keybind preset to the (server-wide) bindings table.
|
|
179
|
+
// Same call the engine makes on every launch, so the new session
|
|
180
|
+
// inherits the same Alt+q etc. as the parent.
|
|
181
|
+
if (opts.prefs.keybinds && opts.prefs.keybinds.preset) {
|
|
182
|
+
const result = applyKeybindPreset(opts.prefs.keybinds.preset, runner);
|
|
183
|
+
for (const f of result.failures) {
|
|
184
|
+
log(`agileflow launch: keybind skipped — ${f.hint}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Swap the user's tmux client to the new session. If switch-client
|
|
189
|
+
// fails the session is still alive — surface its name so the user
|
|
190
|
+
// can attach manually.
|
|
191
|
+
const sw = runner.runSync(["switch-client", "-t", sessionName]);
|
|
192
|
+
if (sw.status !== 0) {
|
|
193
|
+
throw makeSpawnError(
|
|
194
|
+
`switch-client failed: ${sw.stderr.trim() || "unknown error"}; ` +
|
|
195
|
+
`session "${sessionName}" is still running — \`tmux attach -t ${sessionName}\` to enter it.`,
|
|
196
|
+
"ETMUX_SWITCH",
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
log(`agileflow launch: switched to new session ${sessionName}`);
|
|
201
|
+
return { sessionName, cwd: targetCwd, worktree };
|
|
202
|
+
} catch (err) {
|
|
203
|
+
// Forget the registry entry we added — the spawn didn't succeed
|
|
204
|
+
// so there's nothing to restore later. Worst case: a tmux session
|
|
205
|
+
// exists but isn't in the registry (cosmetic; user can attach
|
|
206
|
+
// manually via `tmux attach -t <name>`).
|
|
207
|
+
if (registeredName) {
|
|
208
|
+
try {
|
|
209
|
+
forgetSession(registeredName);
|
|
210
|
+
} catch {
|
|
211
|
+
/* swallow */
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Rollback path. Worktree got created but a subsequent step (tmux
|
|
215
|
+
// create / keybind apply / switch-client) failed — remove the
|
|
216
|
+
// worktree dir + branch so the repo state matches the launch state
|
|
217
|
+
// (i.e., as if the user had never pressed Alt+n).
|
|
218
|
+
if (worktree) {
|
|
219
|
+
try {
|
|
220
|
+
const result = removeWt({
|
|
221
|
+
path: worktree.path,
|
|
222
|
+
branch: worktree.branch,
|
|
223
|
+
});
|
|
224
|
+
if (result.removed && result.branchRemoved) {
|
|
225
|
+
log(
|
|
226
|
+
`agileflow launch: rolled back worktree ${worktree.path} + branch ${worktree.branch}`,
|
|
227
|
+
);
|
|
228
|
+
} else {
|
|
229
|
+
log(
|
|
230
|
+
`agileflow launch: worktree rollback partial — manual cleanup may be needed (${result.stderr})`,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
} catch (rollbackErr) {
|
|
234
|
+
log(
|
|
235
|
+
`agileflow launch: rollback failed — leftover at ${worktree.path}: ${rollbackErr.message}`,
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
throw err;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
module.exports = {
|
|
244
|
+
runParallelSpawn,
|
|
245
|
+
resolveSpawnDir,
|
|
246
|
+
makeSpawnError,
|
|
247
|
+
};
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read / write `~/.agileflow/launch-prefs.json` — the first user-level
|
|
3
|
+
* config in v4.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors the atomic-write pattern from `runtime/config/writer.js`:
|
|
6
|
+
* render to a sibling temp file, then `fs.rename()` into place so a
|
|
7
|
+
* concurrent read either sees the old content or the new content,
|
|
8
|
+
* never a torn half-write.
|
|
9
|
+
*
|
|
10
|
+
* Validation here is *shape-light* on read: missing keys are filled
|
|
11
|
+
* from `defaultPrefs()`, unknown extras are dropped. The JSON schema
|
|
12
|
+
* (`schema.json`) is the authoritative contract for editor tooling.
|
|
13
|
+
*/
|
|
14
|
+
const fs = require("fs");
|
|
15
|
+
const os = require("os");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
|
|
18
|
+
const {
|
|
19
|
+
defaultPrefs,
|
|
20
|
+
KNOWN_CLI_IDS,
|
|
21
|
+
STATUS_POSITIONS,
|
|
22
|
+
KEYBIND_PRESETS,
|
|
23
|
+
} = require("./defaults.js");
|
|
24
|
+
|
|
25
|
+
const FILENAME = "launch-prefs.json";
|
|
26
|
+
const SCHEMA_REF = "./node_modules/agileflow/src/runtime/launch/schema.json";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {string} [home] - override for tests; defaults to os.homedir()
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
function prefsPath(home) {
|
|
33
|
+
const root = home || os.homedir();
|
|
34
|
+
return path.join(root, ".agileflow", FILENAME);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Merge a partial prefs object on top of defaults, sanitizing unknown
|
|
39
|
+
* enum values back to their defaults. Returns a fresh object — never
|
|
40
|
+
* mutates the input.
|
|
41
|
+
*
|
|
42
|
+
* @param {Partial<import('./defaults.js').LaunchPrefs>} [partial]
|
|
43
|
+
* @returns {import('./defaults.js').LaunchPrefs}
|
|
44
|
+
*/
|
|
45
|
+
function mergeWithDefaults(partial = {}) {
|
|
46
|
+
const base = defaultPrefs();
|
|
47
|
+
|
|
48
|
+
const cli = partial.cli || {};
|
|
49
|
+
const preferred = KNOWN_CLI_IDS.includes(cli.preferred)
|
|
50
|
+
? cli.preferred
|
|
51
|
+
: base.cli.preferred;
|
|
52
|
+
const fallbackOrder = Array.isArray(cli.fallbackOrder)
|
|
53
|
+
? cli.fallbackOrder.filter((id) => KNOWN_CLI_IDS.includes(id))
|
|
54
|
+
: base.cli.fallbackOrder;
|
|
55
|
+
|
|
56
|
+
const tmux = partial.tmux || {};
|
|
57
|
+
const tmuxEnabled =
|
|
58
|
+
typeof tmux.enabled === "boolean" ? tmux.enabled : base.tmux.enabled;
|
|
59
|
+
const statusPosition = STATUS_POSITIONS.includes(tmux.statusPosition)
|
|
60
|
+
? tmux.statusPosition
|
|
61
|
+
: base.tmux.statusPosition;
|
|
62
|
+
|
|
63
|
+
const keybinds = partial.keybinds || {};
|
|
64
|
+
const preset = KEYBIND_PRESETS.includes(keybinds.preset)
|
|
65
|
+
? keybinds.preset
|
|
66
|
+
: base.keybinds.preset;
|
|
67
|
+
|
|
68
|
+
const aliases = partial.aliases || {};
|
|
69
|
+
const afEnabled =
|
|
70
|
+
aliases.af && typeof aliases.af.enabled === "boolean"
|
|
71
|
+
? aliases.af.enabled
|
|
72
|
+
: base.aliases.af.enabled;
|
|
73
|
+
|
|
74
|
+
const pinned = Array.isArray(partial.pinned)
|
|
75
|
+
? partial.pinned.filter((s) => typeof s === "string")
|
|
76
|
+
: base.pinned;
|
|
77
|
+
|
|
78
|
+
// Invariant: `preferred` must appear in `fallbackOrder` (the latter is
|
|
79
|
+
// documented as "order to try if preferred is missing", which only
|
|
80
|
+
// makes sense if preferred itself is in the chain). Hand-edited prefs
|
|
81
|
+
// can violate this — we repair it here rather than at every read site.
|
|
82
|
+
let resolvedFallback = fallbackOrder.length
|
|
83
|
+
? fallbackOrder
|
|
84
|
+
: base.cli.fallbackOrder;
|
|
85
|
+
if (!resolvedFallback.includes(preferred)) {
|
|
86
|
+
resolvedFallback = [preferred, ...resolvedFallback];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
version: 1,
|
|
91
|
+
cli: {
|
|
92
|
+
preferred,
|
|
93
|
+
fallbackOrder: resolvedFallback,
|
|
94
|
+
},
|
|
95
|
+
tmux: { enabled: tmuxEnabled, statusPosition },
|
|
96
|
+
keybinds: { preset },
|
|
97
|
+
aliases: { af: { enabled: afEnabled } },
|
|
98
|
+
pinned,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Load prefs from disk. Returns defaults if the file is absent.
|
|
104
|
+
* Throws on invalid JSON so the caller can surface an actionable error.
|
|
105
|
+
*
|
|
106
|
+
* @param {string} [home]
|
|
107
|
+
* @returns {Promise<{
|
|
108
|
+
* prefs: import('./defaults.js').LaunchPrefs,
|
|
109
|
+
* source: 'file' | 'defaults',
|
|
110
|
+
* path: string,
|
|
111
|
+
* }>}
|
|
112
|
+
*/
|
|
113
|
+
async function loadPrefs(home) {
|
|
114
|
+
const file = prefsPath(home);
|
|
115
|
+
let raw;
|
|
116
|
+
try {
|
|
117
|
+
raw = await fs.promises.readFile(file, "utf8");
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (err && err.code === "ENOENT") {
|
|
120
|
+
return { prefs: defaultPrefs(), source: "defaults", path: file };
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let parsed;
|
|
126
|
+
try {
|
|
127
|
+
parsed = JSON.parse(raw);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
const wrapped = new Error(`could not parse ${file}: ${err.message}`);
|
|
130
|
+
wrapped.cause = err;
|
|
131
|
+
throw wrapped;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// JSON.parse can return null, primitives, or arrays — any of which
|
|
135
|
+
// would crash `mergeWithDefaults` at the first property access. Reject
|
|
136
|
+
// them with the same "could not parse" shape so the caller's error
|
|
137
|
+
// path is the only one users see.
|
|
138
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
139
|
+
const kind =
|
|
140
|
+
parsed === null
|
|
141
|
+
? "null"
|
|
142
|
+
: Array.isArray(parsed)
|
|
143
|
+
? "array"
|
|
144
|
+
: typeof parsed;
|
|
145
|
+
throw new Error(
|
|
146
|
+
`could not parse ${file}: expected a JSON object, got ${kind}`,
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return { prefs: mergeWithDefaults(parsed), source: "file", path: file };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Atomically write prefs to disk. Stamps `lastUpdated` with the current
|
|
155
|
+
* ISO timestamp. Ensures `~/.agileflow/` exists.
|
|
156
|
+
*
|
|
157
|
+
* @param {import('./defaults.js').LaunchPrefs} next
|
|
158
|
+
* @param {string} [home]
|
|
159
|
+
* @returns {Promise<string>} absolute path of the written file
|
|
160
|
+
*/
|
|
161
|
+
async function writePrefs(next, home) {
|
|
162
|
+
const merged = mergeWithDefaults(next);
|
|
163
|
+
const file = prefsPath(home);
|
|
164
|
+
const dir = path.dirname(file);
|
|
165
|
+
const tmp = path.join(dir, `.${FILENAME}.tmp-${process.pid}`);
|
|
166
|
+
|
|
167
|
+
const payload = {
|
|
168
|
+
$schema: SCHEMA_REF,
|
|
169
|
+
...merged,
|
|
170
|
+
lastUpdated: new Date().toISOString(),
|
|
171
|
+
};
|
|
172
|
+
const content = JSON.stringify(payload, null, 2) + "\n";
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
176
|
+
await fs.promises.writeFile(tmp, content, "utf8");
|
|
177
|
+
await fs.promises.rename(tmp, file);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
try {
|
|
180
|
+
await fs.promises.unlink(tmp);
|
|
181
|
+
} catch {
|
|
182
|
+
/* swallow */
|
|
183
|
+
}
|
|
184
|
+
throw err;
|
|
185
|
+
}
|
|
186
|
+
return file;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Whether a prefs file exists on disk.
|
|
191
|
+
* @param {string} [home]
|
|
192
|
+
* @returns {Promise<boolean>}
|
|
193
|
+
*/
|
|
194
|
+
async function prefsExist(home) {
|
|
195
|
+
try {
|
|
196
|
+
await fs.promises.access(prefsPath(home), fs.constants.F_OK);
|
|
197
|
+
return true;
|
|
198
|
+
} catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = {
|
|
204
|
+
FILENAME,
|
|
205
|
+
SCHEMA_REF,
|
|
206
|
+
prefsPath,
|
|
207
|
+
mergeWithDefaults,
|
|
208
|
+
loadPrefs,
|
|
209
|
+
writePrefs,
|
|
210
|
+
prefsExist,
|
|
211
|
+
};
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-project launch prefs cascade.
|
|
3
|
+
*
|
|
4
|
+
* Layering (lowest-priority first):
|
|
5
|
+
* 1. Built-in defaults from `defaults.js`
|
|
6
|
+
* 2. Global user prefs `~/.agileflow/launch-prefs.json`
|
|
7
|
+
* 3. Project prefs `<repo>/.agileflow/launch.json`
|
|
8
|
+
*
|
|
9
|
+
* The project file is searched by walking up from `cwd` until either:
|
|
10
|
+
* - we find a `.agileflow/launch.json`, or
|
|
11
|
+
* - we cross a `.git` boundary (the repo root — beyond it we're in a
|
|
12
|
+
* different project's territory), or
|
|
13
|
+
* - we hit the filesystem root.
|
|
14
|
+
*
|
|
15
|
+
* Per-key partial override: a project file can specify ONLY the keys it
|
|
16
|
+
* cares about. Unspecified keys fall through to the global file's value,
|
|
17
|
+
* which in turn falls through to defaults. This lets a repo flip just
|
|
18
|
+
* `tmux.enabled` without having to mirror the whole prefs shape.
|
|
19
|
+
*
|
|
20
|
+
* Project file shape mirrors `launch-prefs.json` minus `$schema` and
|
|
21
|
+
* `lastUpdated`. Hand-edited files are normal; unknown extras are
|
|
22
|
+
* dropped silently (same posture as the global loader).
|
|
23
|
+
*/
|
|
24
|
+
const fs = require("fs");
|
|
25
|
+
const path = require("path");
|
|
26
|
+
|
|
27
|
+
const { loadPrefs } = require("./prefs.js");
|
|
28
|
+
|
|
29
|
+
const PROJECT_DIR = ".agileflow";
|
|
30
|
+
const PROJECT_FILENAME = "launch.json";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Walk up from `cwd` looking for a `.agileflow/launch.json`. Stops at
|
|
34
|
+
* the filesystem root, or at the first directory containing a `.git`
|
|
35
|
+
* entry — beyond a repo boundary we shouldn't be inheriting another
|
|
36
|
+
* project's prefs.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} cwd
|
|
39
|
+
* @param {{
|
|
40
|
+
* existsSync?: (p: string) => boolean,
|
|
41
|
+
* statSync?: typeof fs.statSync,
|
|
42
|
+
* }} [opts]
|
|
43
|
+
* @returns {string | null}
|
|
44
|
+
*/
|
|
45
|
+
function findProjectPrefsFile(cwd, opts = {}) {
|
|
46
|
+
const existsSync = opts.existsSync || ((p) => fs.existsSync(p));
|
|
47
|
+
const statSync = opts.statSync || fs.statSync;
|
|
48
|
+
let dir = path.resolve(cwd);
|
|
49
|
+
// Cap the walk at 64 ancestors. Filesystems hit root long before that
|
|
50
|
+
// in practice; the cap exists so a pathological symlink loop can't
|
|
51
|
+
// stall us.
|
|
52
|
+
for (let i = 0; i < 64; i++) {
|
|
53
|
+
const candidate = path.join(dir, PROJECT_DIR, PROJECT_FILENAME);
|
|
54
|
+
if (existsSync(candidate)) {
|
|
55
|
+
try {
|
|
56
|
+
if (statSync(candidate).isFile()) return candidate;
|
|
57
|
+
} catch {
|
|
58
|
+
/* swallow — vanished between exists + stat */
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Repo boundary check happens AFTER the in-dir search so the repo
|
|
62
|
+
// root itself can host the project prefs (the common case).
|
|
63
|
+
if (existsSync(path.join(dir, ".git"))) return null;
|
|
64
|
+
const parent = path.dirname(dir);
|
|
65
|
+
if (parent === dir) return null; // hit filesystem root
|
|
66
|
+
dir = parent;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Read + parse a project prefs file. Returns null on missing file,
|
|
73
|
+
* malformed JSON, or non-object payload — same forgiving posture as
|
|
74
|
+
* `loadRegistry`. Surfacing a hard error here would block `launch` for
|
|
75
|
+
* what is almost always a manual-edit typo.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} file
|
|
78
|
+
* @param {{ readFileSync?: typeof fs.readFileSync }} [opts]
|
|
79
|
+
* @returns {Partial<import("./defaults.js").LaunchPrefs> | null}
|
|
80
|
+
*/
|
|
81
|
+
function readProjectPrefs(file, opts = {}) {
|
|
82
|
+
const readFileSync = opts.readFileSync || fs.readFileSync;
|
|
83
|
+
let raw;
|
|
84
|
+
try {
|
|
85
|
+
raw = readFileSync(file, "utf8");
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = JSON.parse(raw);
|
|
92
|
+
} catch {
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.error(
|
|
95
|
+
`agileflow launch: ${file} is not valid JSON — ignoring project prefs.`,
|
|
96
|
+
);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return /** @type {Partial<import("./defaults.js").LaunchPrefs>} */ (parsed);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Layer a partial prefs object over an already-merged base. Mirrors the
|
|
107
|
+
* shape and validation of `mergeWithDefaults` but uses `base` (the
|
|
108
|
+
* global-merged prefs) as the fallback instead of the built-in defaults.
|
|
109
|
+
*
|
|
110
|
+
* Per-key semantics:
|
|
111
|
+
* - cli, tmux, keybinds, aliases: partial sub-object override. Any
|
|
112
|
+
* leaf that the project omits inherits from `base`.
|
|
113
|
+
* - pinned: project array (when present) REPLACES base entirely —
|
|
114
|
+
* "pinned" is conceptually a list, not a deep-merge dictionary.
|
|
115
|
+
*
|
|
116
|
+
* Unknown enum values fall back to the corresponding `base` value (NOT
|
|
117
|
+
* defaults) so a typo in the project file degrades gracefully.
|
|
118
|
+
*
|
|
119
|
+
* @param {import("./defaults.js").LaunchPrefs} base
|
|
120
|
+
* @param {Partial<import("./defaults.js").LaunchPrefs>} partial
|
|
121
|
+
* @returns {import("./defaults.js").LaunchPrefs}
|
|
122
|
+
*/
|
|
123
|
+
function mergePartialOver(base, partial) {
|
|
124
|
+
const {
|
|
125
|
+
KNOWN_CLI_IDS,
|
|
126
|
+
STATUS_POSITIONS,
|
|
127
|
+
KEYBIND_PRESETS,
|
|
128
|
+
} = require("./defaults.js");
|
|
129
|
+
|
|
130
|
+
const cli = partial.cli || {};
|
|
131
|
+
const preferred = KNOWN_CLI_IDS.includes(cli.preferred)
|
|
132
|
+
? cli.preferred
|
|
133
|
+
: base.cli.preferred;
|
|
134
|
+
const fallbackOrder = Array.isArray(cli.fallbackOrder)
|
|
135
|
+
? cli.fallbackOrder.filter((id) => KNOWN_CLI_IDS.includes(id))
|
|
136
|
+
: base.cli.fallbackOrder;
|
|
137
|
+
|
|
138
|
+
const tmux = partial.tmux || {};
|
|
139
|
+
const tmuxEnabled =
|
|
140
|
+
typeof tmux.enabled === "boolean" ? tmux.enabled : base.tmux.enabled;
|
|
141
|
+
const statusPosition = STATUS_POSITIONS.includes(tmux.statusPosition)
|
|
142
|
+
? tmux.statusPosition
|
|
143
|
+
: base.tmux.statusPosition;
|
|
144
|
+
|
|
145
|
+
const keybinds = partial.keybinds || {};
|
|
146
|
+
const preset = KEYBIND_PRESETS.includes(keybinds.preset)
|
|
147
|
+
? keybinds.preset
|
|
148
|
+
: base.keybinds.preset;
|
|
149
|
+
|
|
150
|
+
const aliases = partial.aliases || {};
|
|
151
|
+
const afEnabled =
|
|
152
|
+
aliases.af && typeof aliases.af.enabled === "boolean"
|
|
153
|
+
? aliases.af.enabled
|
|
154
|
+
: base.aliases.af.enabled;
|
|
155
|
+
|
|
156
|
+
const pinned = Array.isArray(partial.pinned)
|
|
157
|
+
? partial.pinned.filter((s) => typeof s === "string")
|
|
158
|
+
: base.pinned;
|
|
159
|
+
|
|
160
|
+
// Same invariant as the global loader: preferred must be in fallbackOrder.
|
|
161
|
+
// Project-level override could break it (e.g., partial only sets
|
|
162
|
+
// cli.preferred and inherits a fallbackOrder that doesn't include it).
|
|
163
|
+
let resolvedFallback = fallbackOrder.length
|
|
164
|
+
? fallbackOrder
|
|
165
|
+
: base.cli.fallbackOrder;
|
|
166
|
+
if (!resolvedFallback.includes(preferred)) {
|
|
167
|
+
resolvedFallback = [preferred, ...resolvedFallback];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
version: /** @type {1} */ (1),
|
|
172
|
+
cli: { preferred, fallbackOrder: resolvedFallback },
|
|
173
|
+
tmux: { enabled: tmuxEnabled, statusPosition },
|
|
174
|
+
keybinds: { preset },
|
|
175
|
+
aliases: { af: { enabled: afEnabled } },
|
|
176
|
+
pinned,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Load prefs with the full cascade. The returned `sources` array records
|
|
182
|
+
* which files contributed (in increasing precedence), so the `launch
|
|
183
|
+
* where` subcommand can show the user exactly what's being layered.
|
|
184
|
+
*
|
|
185
|
+
* @param {{
|
|
186
|
+
* home?: string,
|
|
187
|
+
* cwd?: string,
|
|
188
|
+
* loadGlobalPrefs?: typeof loadPrefs,
|
|
189
|
+
* findProjectFile?: typeof findProjectPrefsFile,
|
|
190
|
+
* readProjectFile?: typeof readProjectPrefs,
|
|
191
|
+
* }} [opts]
|
|
192
|
+
* @returns {Promise<{
|
|
193
|
+
* prefs: import("./defaults.js").LaunchPrefs,
|
|
194
|
+
* sources: Array<{ layer: 'defaults' | 'global' | 'project', path?: string }>,
|
|
195
|
+
* }>}
|
|
196
|
+
*/
|
|
197
|
+
async function loadCascadedPrefs(opts = {}) {
|
|
198
|
+
const loadGlobal = opts.loadGlobalPrefs || loadPrefs;
|
|
199
|
+
const findProject = opts.findProjectFile || findProjectPrefsFile;
|
|
200
|
+
const readProject = opts.readProjectFile || readProjectPrefs;
|
|
201
|
+
const cwd = opts.cwd || process.cwd();
|
|
202
|
+
|
|
203
|
+
/** @type {Array<{ layer: 'defaults' | 'global' | 'project', path?: string }>} */
|
|
204
|
+
const sources = [{ layer: "defaults" }];
|
|
205
|
+
|
|
206
|
+
const globalLoaded = await loadGlobal(opts.home);
|
|
207
|
+
if (globalLoaded.source === "file") {
|
|
208
|
+
sources.push({ layer: "global", path: globalLoaded.path });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const projectFile = findProject(cwd);
|
|
212
|
+
if (!projectFile) {
|
|
213
|
+
return { prefs: globalLoaded.prefs, sources };
|
|
214
|
+
}
|
|
215
|
+
const projectPartial = readProject(projectFile);
|
|
216
|
+
if (!projectPartial) {
|
|
217
|
+
// File present but unreadable / malformed — already logged by
|
|
218
|
+
// readProjectPrefs. Treat as absent so launch keeps working.
|
|
219
|
+
return { prefs: globalLoaded.prefs, sources };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const merged = mergePartialOver(globalLoaded.prefs, projectPartial);
|
|
223
|
+
sources.push({ layer: "project", path: projectFile });
|
|
224
|
+
return { prefs: merged, sources };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
module.exports = {
|
|
228
|
+
PROJECT_DIR,
|
|
229
|
+
PROJECT_FILENAME,
|
|
230
|
+
findProjectPrefsFile,
|
|
231
|
+
readProjectPrefs,
|
|
232
|
+
mergePartialOver,
|
|
233
|
+
loadCascadedPrefs,
|
|
234
|
+
};
|