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
|
@@ -9,28 +9,126 @@
|
|
|
9
9
|
* Exit code: 0 on green, 1 if any errors. Warnings are surfaced but
|
|
10
10
|
* don't fail the command.
|
|
11
11
|
*/
|
|
12
|
-
const path = require(
|
|
13
|
-
const fs = require(
|
|
12
|
+
const path = require("path");
|
|
13
|
+
const fs = require("fs");
|
|
14
14
|
|
|
15
|
-
const { discoverPlugins } = require(
|
|
15
|
+
const { discoverPlugins } = require("../../runtime/plugins/registry.js");
|
|
16
16
|
const {
|
|
17
17
|
validatePluginSet,
|
|
18
18
|
hasErrors: pluginHasErrors,
|
|
19
|
-
} = require(
|
|
20
|
-
const {
|
|
21
|
-
|
|
22
|
-
} = require(
|
|
23
|
-
const {
|
|
24
|
-
buildHookManifest,
|
|
25
|
-
} = require('../../runtime/hooks/aggregator.js');
|
|
26
|
-
const { resolvePlugins } = require('../../runtime/plugins/resolver.js');
|
|
19
|
+
} = require("../../runtime/plugins/validator.js");
|
|
20
|
+
const { loadHookManifest } = require("../../runtime/hooks/manifest-loader.js");
|
|
21
|
+
const { buildHookManifest } = require("../../runtime/hooks/aggregator.js");
|
|
22
|
+
const { resolvePlugins } = require("../../runtime/plugins/resolver.js");
|
|
27
23
|
const {
|
|
28
24
|
validateSkillsAtRoot,
|
|
29
25
|
validateSkill,
|
|
30
26
|
loadSkill,
|
|
31
27
|
detectKeywordCollisions,
|
|
32
28
|
hasErrors: skillHasErrors,
|
|
33
|
-
} = require(
|
|
29
|
+
} = require("../../runtime/skills/validator.js");
|
|
30
|
+
const { loadConfig } = require("../../runtime/config/loader.js");
|
|
31
|
+
const { IDE_CAPABILITIES } = require("../../runtime/ide/capabilities.js");
|
|
32
|
+
const {
|
|
33
|
+
MANAGED_EVENTS,
|
|
34
|
+
LEGACY_MANAGED_EVENTS,
|
|
35
|
+
HOOK_COMMAND_MARKER,
|
|
36
|
+
isAgileflowEntry,
|
|
37
|
+
} = require("../../runtime/ide/claude-code-settings.js");
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* v3-era directories under `.agileflow/` that v4 never writes.
|
|
41
|
+
* Detection-only — actual removal is the follow-up `--fix` work.
|
|
42
|
+
*/
|
|
43
|
+
const LEGACY_AGILEFLOW_SUBDIRS = [
|
|
44
|
+
"agents",
|
|
45
|
+
"base-prompts",
|
|
46
|
+
"cache",
|
|
47
|
+
"commands",
|
|
48
|
+
"config",
|
|
49
|
+
"council",
|
|
50
|
+
"experts",
|
|
51
|
+
"hooks",
|
|
52
|
+
"knowledge",
|
|
53
|
+
"lib",
|
|
54
|
+
"mixins",
|
|
55
|
+
"profiles",
|
|
56
|
+
"scripts",
|
|
57
|
+
"snippets",
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/** v3-era files at `.agileflow/` root that v4 doesn't write. */
|
|
61
|
+
const LEGACY_AGILEFLOW_FILES = ["CHANGELOG.md", "config.yaml"];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* v3-era directories under `.claude/` that v4 doesn't populate.
|
|
65
|
+
* `agents/` and `commands/` are intentionally NOT here — v4 still
|
|
66
|
+
* mirrors plugin slash-commands and subagents into them via
|
|
67
|
+
* claude-code-content.js. Only flagged if they contain agileflow-*
|
|
68
|
+
* entries (see section C below).
|
|
69
|
+
*/
|
|
70
|
+
const LEGACY_CLAUDE_SUBDIRS = ["hooks", "plans"];
|
|
71
|
+
|
|
72
|
+
/** Narrow a value to a plain object (not array, not null, not primitive). */
|
|
73
|
+
function isPlainObject(v) {
|
|
74
|
+
return v != null && typeof v === "object" && !Array.isArray(v);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Read JSON file, returning null on any failure (missing, malformed,
|
|
79
|
+
* permission denied). The detector treats unreadable config as "skip
|
|
80
|
+
* this section" rather than crashing.
|
|
81
|
+
*/
|
|
82
|
+
function readJSONSafe(p) {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** readdirSync wrapped to return [] on permission-denied / vanished dirs. */
|
|
91
|
+
function readdirSafe(p) {
|
|
92
|
+
try {
|
|
93
|
+
return fs.readdirSync(p);
|
|
94
|
+
} catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Expand env vars and ~ in a hook command's script path, then check if
|
|
101
|
+
* that file exists. Returns the resolved path if it's broken (file
|
|
102
|
+
* referenced but doesn't exist), or null otherwise (no path, or path
|
|
103
|
+
* exists, or path is opaque).
|
|
104
|
+
*
|
|
105
|
+
* Recognizes paths ending in common script extensions; conservative on
|
|
106
|
+
* purpose so we don't false-positive on shell builtins or commands
|
|
107
|
+
* that just happen to have a slash.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} command
|
|
110
|
+
* @param {string} cwd
|
|
111
|
+
* @returns {string|null}
|
|
112
|
+
*/
|
|
113
|
+
function brokenScriptInCommand(command, cwd) {
|
|
114
|
+
if (typeof command !== "string") return null;
|
|
115
|
+
// Skip our own dispatcher — handled by other detection kinds.
|
|
116
|
+
if (command.includes(HOOK_COMMAND_MARKER)) return null;
|
|
117
|
+
// Extract path-like tokens with a known script extension.
|
|
118
|
+
const tokens = command.match(
|
|
119
|
+
/[\w$~/.\-\\]+\.(?:js|sh|py|ts|mjs|cjs|bash|zsh|rb)\b/g,
|
|
120
|
+
);
|
|
121
|
+
if (!tokens) return null;
|
|
122
|
+
for (const tok of tokens) {
|
|
123
|
+
let p = tok
|
|
124
|
+
.replace(/\$CLAUDE_PROJECT_DIR/g, cwd)
|
|
125
|
+
.replace(/\$HOME/g, require("os").homedir())
|
|
126
|
+
.replace(/^~(?=\/|$)/, require("os").homedir());
|
|
127
|
+
if (!path.isAbsolute(p)) p = path.join(cwd, p);
|
|
128
|
+
if (!fs.existsSync(p)) return p;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
34
132
|
|
|
35
133
|
/**
|
|
36
134
|
* Walk `content/plugins/<plugin>/skills/<skill>/SKILL.md` for every
|
|
@@ -43,7 +141,7 @@ async function validateBundledSkills() {
|
|
|
43
141
|
/** @type {Array<import('../../runtime/skills/validator.js').SkillManifest>} */
|
|
44
142
|
const allSkills = [];
|
|
45
143
|
for (const p of plugins) {
|
|
46
|
-
const root = path.join(p.dir,
|
|
144
|
+
const root = path.join(p.dir, "skills");
|
|
47
145
|
const r = await validateSkillsAtRoot(root);
|
|
48
146
|
issues.push(...r.issues);
|
|
49
147
|
allSkills.push(...r.skills);
|
|
@@ -65,7 +163,9 @@ async function validateAggregatedHookManifest() {
|
|
|
65
163
|
const plugins = discoverPlugins();
|
|
66
164
|
const { ordered } = resolvePlugins(plugins, []);
|
|
67
165
|
const manifestObj = buildHookManifest(ordered);
|
|
68
|
-
const {
|
|
166
|
+
const {
|
|
167
|
+
normalizeManifest,
|
|
168
|
+
} = require("../../runtime/hooks/manifest-loader.js");
|
|
69
169
|
normalizeManifest(manifestObj);
|
|
70
170
|
} catch (err) {
|
|
71
171
|
errors.push(err.message);
|
|
@@ -77,7 +177,7 @@ async function validateAggregatedHookManifest() {
|
|
|
77
177
|
* Validate a project's installed hook manifest file (if present).
|
|
78
178
|
*/
|
|
79
179
|
async function validateInstalledManifest(cwd) {
|
|
80
|
-
const manifestPath = path.join(cwd,
|
|
180
|
+
const manifestPath = path.join(cwd, ".agileflow", "hook-manifest.yaml");
|
|
81
181
|
if (!fs.existsSync(manifestPath)) return [];
|
|
82
182
|
/** @type {string[]} */
|
|
83
183
|
const errors = [];
|
|
@@ -101,54 +201,739 @@ function printSection(title, issues) {
|
|
|
101
201
|
console.log(`\n${title}`);
|
|
102
202
|
if (issues.length === 0) {
|
|
103
203
|
// eslint-disable-next-line no-console
|
|
104
|
-
console.log(
|
|
204
|
+
console.log(" ok");
|
|
105
205
|
return 0;
|
|
106
206
|
}
|
|
107
207
|
let errorCount = 0;
|
|
108
208
|
for (const issue of issues) {
|
|
109
|
-
const sev = issue.severity ||
|
|
110
|
-
if (sev ===
|
|
111
|
-
const label = sev ===
|
|
112
|
-
const id = issue.skillId || issue.pluginId ||
|
|
113
|
-
const idPrefix = id ? `[${id}] ` :
|
|
209
|
+
const sev = issue.severity || "error";
|
|
210
|
+
if (sev === "error") errorCount += 1;
|
|
211
|
+
const label = sev === "error" ? "ERROR" : "WARN ";
|
|
212
|
+
const id = issue.skillId || issue.pluginId || "";
|
|
213
|
+
const idPrefix = id ? `[${id}] ` : "";
|
|
114
214
|
// eslint-disable-next-line no-console
|
|
115
215
|
console.log(` ${label} ${idPrefix}${issue.message}`);
|
|
116
216
|
}
|
|
117
217
|
return errorCount;
|
|
118
218
|
}
|
|
119
219
|
|
|
120
|
-
|
|
220
|
+
/**
|
|
221
|
+
* Check installation health in the current project directory.
|
|
222
|
+
* Returns issues describing missing/misconfigured IDE setup.
|
|
223
|
+
*/
|
|
224
|
+
async function checkInstallHealth(cwd) {
|
|
225
|
+
const issues = [];
|
|
226
|
+
const configPath = path.join(cwd, "agileflow.config.json");
|
|
227
|
+
|
|
228
|
+
if (!fs.existsSync(configPath)) {
|
|
229
|
+
issues.push({
|
|
230
|
+
severity: "warn",
|
|
231
|
+
message:
|
|
232
|
+
"agileflow.config.json not found — run `agileflow setup` to configure",
|
|
233
|
+
});
|
|
234
|
+
return issues;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let cfg;
|
|
238
|
+
try {
|
|
239
|
+
cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
240
|
+
} catch {
|
|
241
|
+
issues.push({
|
|
242
|
+
severity: "error",
|
|
243
|
+
message: "agileflow.config.json is not valid JSON",
|
|
244
|
+
});
|
|
245
|
+
return issues;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const targets = cfg?.ide?.targets;
|
|
249
|
+
if (!Array.isArray(targets) || targets.length === 0) {
|
|
250
|
+
issues.push({
|
|
251
|
+
severity: "warn",
|
|
252
|
+
message: "No IDE targets configured in agileflow.config.json",
|
|
253
|
+
});
|
|
254
|
+
return issues;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const ide of targets) {
|
|
258
|
+
const caps = IDE_CAPABILITIES[ide];
|
|
259
|
+
if (!caps) {
|
|
260
|
+
issues.push({
|
|
261
|
+
severity: "warn",
|
|
262
|
+
message: `Unknown IDE target "${ide}" in config`,
|
|
263
|
+
});
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Check skills directory exists and has at least one SKILL.md
|
|
268
|
+
if (caps.skills) {
|
|
269
|
+
const skillsDir = path.join(cwd, caps.skillsDir);
|
|
270
|
+
if (!fs.existsSync(skillsDir)) {
|
|
271
|
+
issues.push({
|
|
272
|
+
severity: "warn",
|
|
273
|
+
message: `[${ide}] Skills not installed — ${caps.skillsDir}/ missing. Run \`agileflow setup\``,
|
|
274
|
+
});
|
|
275
|
+
} else {
|
|
276
|
+
const skillFiles = fs.readdirSync(skillsDir).filter((f) => {
|
|
277
|
+
const skillPath = path.join(skillsDir, f, "SKILL.md");
|
|
278
|
+
return fs.existsSync(skillPath);
|
|
279
|
+
});
|
|
280
|
+
if (skillFiles.length === 0) {
|
|
281
|
+
issues.push({
|
|
282
|
+
severity: "error",
|
|
283
|
+
message: `[${ide}] Skills directory exists but contains no SKILL.md files`,
|
|
284
|
+
});
|
|
285
|
+
} else {
|
|
286
|
+
issues.push({
|
|
287
|
+
severity: "info",
|
|
288
|
+
message: `[${ide}] ${skillFiles.length} skill(s) installed in ${caps.skillsDir}/`,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Check hooks wired in IDE settings
|
|
295
|
+
if (caps.hooks && caps.settingsFile) {
|
|
296
|
+
const settingsPath = path.join(cwd, caps.settingsFile);
|
|
297
|
+
if (!fs.existsSync(settingsPath)) {
|
|
298
|
+
issues.push({
|
|
299
|
+
severity: "warn",
|
|
300
|
+
message: `[${ide}] Settings file not found at ${caps.settingsFile} — hooks may not fire`,
|
|
301
|
+
});
|
|
302
|
+
} else {
|
|
303
|
+
try {
|
|
304
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
305
|
+
const hasHooks =
|
|
306
|
+
settings?.hooks && Object.keys(settings.hooks).length > 0;
|
|
307
|
+
if (!hasHooks) {
|
|
308
|
+
issues.push({
|
|
309
|
+
severity: "warn",
|
|
310
|
+
message: `[${ide}] No hooks found in ${caps.settingsFile} — SessionStart injection unavailable`,
|
|
311
|
+
});
|
|
312
|
+
} else {
|
|
313
|
+
const hookEvents = Object.keys(settings.hooks);
|
|
314
|
+
issues.push({
|
|
315
|
+
severity: "info",
|
|
316
|
+
message: `[${ide}] Hooks wired: ${hookEvents
|
|
317
|
+
.filter((e) => !LEGACY_MANAGED_EVENTS.has(e))
|
|
318
|
+
.join(", ")}`,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
} catch {
|
|
322
|
+
issues.push({
|
|
323
|
+
severity: "warn",
|
|
324
|
+
message: `[${ide}] Could not parse ${caps.settingsFile}`,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return issues;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Build the shared input bundle every detector / fixer needs. Reading
|
|
336
|
+
* once here avoids the duplicate settings.json parse the old monolith
|
|
337
|
+
* did (once for hook-event detection, once for broken-command).
|
|
338
|
+
*
|
|
339
|
+
* @param {string} cwd
|
|
340
|
+
* @returns {{
|
|
341
|
+
* cwd: string,
|
|
342
|
+
* settingsPath: string,
|
|
343
|
+
* settings: any,
|
|
344
|
+
* agileflowDir: string,
|
|
345
|
+
* claudeDir: string,
|
|
346
|
+
* configPath: string,
|
|
347
|
+
* config: any,
|
|
348
|
+
* manifestPath: string,
|
|
349
|
+
* }}
|
|
350
|
+
*/
|
|
351
|
+
function buildContext(cwd) {
|
|
352
|
+
const settingsPath = path.join(cwd, ".claude", "settings.json");
|
|
353
|
+
const configPath = path.join(cwd, "agileflow.config.json");
|
|
354
|
+
return {
|
|
355
|
+
cwd,
|
|
356
|
+
settingsPath,
|
|
357
|
+
settings: fs.existsSync(settingsPath) ? readJSONSafe(settingsPath) : null,
|
|
358
|
+
agileflowDir: path.join(cwd, ".agileflow"),
|
|
359
|
+
claudeDir: path.join(cwd, ".claude"),
|
|
360
|
+
configPath,
|
|
361
|
+
config: fs.existsSync(configPath) ? readJSONSafe(configPath) : null,
|
|
362
|
+
manifestPath: path.join(cwd, ".agileflow", "hook-manifest.yaml"),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Walk `.claude/settings.json` hooks, coerce malformed shapes, and yield
|
|
368
|
+
* `[event, entries]` pairs every hook-related detector / fixer iterates.
|
|
369
|
+
* Returns [] when settings or hooks are missing / wrong-shape.
|
|
370
|
+
*
|
|
371
|
+
* @param {any} settings
|
|
372
|
+
* @returns {Array<[string, Array<any>]>}
|
|
373
|
+
*/
|
|
374
|
+
function iterHookEvents(settings) {
|
|
375
|
+
if (!settings || !isPlainObject(settings.hooks)) return [];
|
|
376
|
+
return Object.keys(settings.hooks).map((event) => {
|
|
377
|
+
const raw = settings.hooks[event];
|
|
378
|
+
let entries;
|
|
379
|
+
if (Array.isArray(raw)) entries = raw;
|
|
380
|
+
else if (isPlainObject(raw)) entries = [raw];
|
|
381
|
+
else entries = [];
|
|
382
|
+
return [event, entries];
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** (A) Legacy / orphan hook events — AgileFlow entries in events we no longer manage. */
|
|
387
|
+
function detectLegacyHookEvents(ctx) {
|
|
388
|
+
const issues = [];
|
|
389
|
+
for (const [event, entries] of iterHookEvents(ctx.settings)) {
|
|
390
|
+
const ours = entries.filter(isAgileflowEntry);
|
|
391
|
+
if (ours.length === 0) continue;
|
|
392
|
+
if (LEGACY_MANAGED_EVENTS.has(event)) {
|
|
393
|
+
issues.push({
|
|
394
|
+
severity: "warn",
|
|
395
|
+
kind: "legacy-hook-event",
|
|
396
|
+
path: `${ctx.settingsPath}#hooks.${event}`,
|
|
397
|
+
message: `Legacy hook event "${event}" in .claude/settings.json — AgileFlow used to write this, no longer does. Safe to remove ${ours.length} entry/entries.`,
|
|
398
|
+
});
|
|
399
|
+
} else if (!MANAGED_EVENTS.has(event)) {
|
|
400
|
+
issues.push({
|
|
401
|
+
severity: "warn",
|
|
402
|
+
kind: "orphan-hook-event",
|
|
403
|
+
path: `${ctx.settingsPath}#hooks.${event}`,
|
|
404
|
+
message: `Unknown event "${event}" in .claude/settings.json contains \`${HOOK_COMMAND_MARKER}\` command — not a current AgileFlow event. Probably from a much older install.`,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return issues;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** (F) Broken hook commands — entries whose `command` references a missing file. */
|
|
412
|
+
function detectBrokenHookCommands(ctx) {
|
|
413
|
+
const issues = [];
|
|
414
|
+
for (const [event, entries] of iterHookEvents(ctx.settings)) {
|
|
415
|
+
for (const entry of entries) {
|
|
416
|
+
if (!entry || !Array.isArray(entry.hooks)) continue;
|
|
417
|
+
for (const h of entry.hooks) {
|
|
418
|
+
if (!h || h.type !== "command" || typeof h.command !== "string") {
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const missingPath = brokenScriptInCommand(h.command, ctx.cwd);
|
|
422
|
+
if (!missingPath) continue;
|
|
423
|
+
issues.push({
|
|
424
|
+
severity: "error",
|
|
425
|
+
kind: "broken-hook-command",
|
|
426
|
+
path: `${ctx.settingsPath}#hooks.${event}`,
|
|
427
|
+
event,
|
|
428
|
+
command: h.command,
|
|
429
|
+
missingPath,
|
|
430
|
+
message: `Hook in .claude/settings.json#hooks.${event} references missing file: ${missingPath} (command: \`${h.command}\`)`,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
return issues;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** (B) v3-era directories and files under `.agileflow/`. */
|
|
439
|
+
function detectLegacyAgileflowPaths(ctx) {
|
|
440
|
+
const issues = [];
|
|
441
|
+
if (!fs.existsSync(ctx.agileflowDir)) return issues;
|
|
442
|
+
for (const name of LEGACY_AGILEFLOW_SUBDIRS) {
|
|
443
|
+
const p = path.join(ctx.agileflowDir, name);
|
|
444
|
+
if (fs.existsSync(p)) {
|
|
445
|
+
issues.push({
|
|
446
|
+
severity: "warn",
|
|
447
|
+
kind: "legacy-agileflow-subdir",
|
|
448
|
+
path: p,
|
|
449
|
+
message: `v3 directory \`.agileflow/${name}/\` present — v4 (skills-first) doesn't use this. Safe to delete unless you've kept custom content.`,
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
for (const name of LEGACY_AGILEFLOW_FILES) {
|
|
454
|
+
const p = path.join(ctx.agileflowDir, name);
|
|
455
|
+
if (fs.existsSync(p)) {
|
|
456
|
+
issues.push({
|
|
457
|
+
severity: "warn",
|
|
458
|
+
kind: "legacy-agileflow-file",
|
|
459
|
+
path: p,
|
|
460
|
+
message: `v3 file \`.agileflow/${name}\` present — not written by v4.`,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return issues;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** (C) v3-era directories under `.claude/` — flagged only if they hold agileflow-* entries. */
|
|
468
|
+
function detectLegacyClaudeSubdirs(ctx) {
|
|
469
|
+
const issues = [];
|
|
470
|
+
if (!fs.existsSync(ctx.claudeDir)) return issues;
|
|
471
|
+
for (const name of LEGACY_CLAUDE_SUBDIRS) {
|
|
472
|
+
const p = path.join(ctx.claudeDir, name);
|
|
473
|
+
if (!fs.existsSync(p)) continue;
|
|
474
|
+
const agileflowOwned = readdirSafe(p).filter(
|
|
475
|
+
(e) => e.startsWith("agileflow") || e.startsWith("AgileFlow"),
|
|
476
|
+
);
|
|
477
|
+
if (agileflowOwned.length === 0) continue;
|
|
478
|
+
issues.push({
|
|
479
|
+
severity: "warn",
|
|
480
|
+
kind: "legacy-claude-subdir",
|
|
481
|
+
path: p,
|
|
482
|
+
message: `v3 \`.claude/${name}/\` contains ${agileflowOwned.length} AgileFlow item(s) — v4 ships skills only; this dir isn't used.`,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
return issues;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* (D) Broken hook-manifest script references. YAML parse failures fall
|
|
490
|
+
* through silently — they're reported separately by
|
|
491
|
+
* `validateInstalledManifest()`, so we just skip the walk on parse fail.
|
|
492
|
+
*/
|
|
493
|
+
async function detectBrokenHookScripts(ctx) {
|
|
494
|
+
const issues = [];
|
|
495
|
+
if (!fs.existsSync(ctx.manifestPath)) return issues;
|
|
496
|
+
/** @type {{ hooks?: Array<{id: string, event: string, script: string}> } | null} */
|
|
497
|
+
let manifest = null;
|
|
498
|
+
try {
|
|
499
|
+
manifest = await loadHookManifest(ctx.manifestPath);
|
|
500
|
+
} catch {
|
|
501
|
+
return issues;
|
|
502
|
+
}
|
|
503
|
+
if (!manifest || !Array.isArray(manifest.hooks)) return issues;
|
|
504
|
+
for (const h of manifest.hooks) {
|
|
505
|
+
if (!h || typeof h.script !== "string") continue;
|
|
506
|
+
const scriptPath = path.isAbsolute(h.script)
|
|
507
|
+
? h.script
|
|
508
|
+
: path.join(ctx.cwd, h.script);
|
|
509
|
+
if (!fs.existsSync(scriptPath)) {
|
|
510
|
+
issues.push({
|
|
511
|
+
severity: "error",
|
|
512
|
+
kind: "broken-hook-script",
|
|
513
|
+
path: scriptPath,
|
|
514
|
+
message: `Hook "${h.id}" (${h.event}) points at missing script ${h.script}`,
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return issues;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** (E) Orphan skill dirs — installed agileflow-* skills not claimed by an enabled plugin. */
|
|
522
|
+
async function detectOrphanSkillDirs(ctx) {
|
|
523
|
+
const issues = [];
|
|
524
|
+
// Guard against malformed plugins fields: string, array, primitive.
|
|
525
|
+
// Object.entries on a string returns char-indexed entries; on an
|
|
526
|
+
// array it returns numeric-indexed ones — both produce garbage.
|
|
527
|
+
if (!ctx.config || !isPlainObject(ctx.config.plugins)) return issues;
|
|
528
|
+
const enabled = Object.entries(ctx.config.plugins)
|
|
529
|
+
.filter(([, v]) => v && v.enabled !== false)
|
|
530
|
+
.map(([k]) => k);
|
|
531
|
+
// De-dupe: don't push "core" if config already has it enabled.
|
|
532
|
+
if (!enabled.includes("core")) enabled.push("core");
|
|
533
|
+
/** @type {Set<string>} */
|
|
534
|
+
const expectedSkillIds = new Set();
|
|
535
|
+
for (const p of discoverPlugins()) {
|
|
536
|
+
if (!enabled.includes(p.id)) continue;
|
|
537
|
+
const skillsRoot = path.join(p.dir, "skills");
|
|
538
|
+
if (!fs.existsSync(skillsRoot)) continue;
|
|
539
|
+
for (const entry of readdirSafe(skillsRoot)) {
|
|
540
|
+
const skillFile = path.join(skillsRoot, entry, "SKILL.md");
|
|
541
|
+
if (!fs.existsSync(skillFile)) continue;
|
|
542
|
+
// loadSkill returns { skillId, frontmatter, body, ... } or throws.
|
|
543
|
+
// Either way, claim the dir name so we don't false-positive on a
|
|
544
|
+
// bundled skill.
|
|
545
|
+
let id = entry;
|
|
546
|
+
try {
|
|
547
|
+
const s = await loadSkill(skillFile);
|
|
548
|
+
id = (s && s.frontmatter && s.frontmatter.name) || s.skillId || entry;
|
|
549
|
+
} catch {
|
|
550
|
+
// unparseable bundled skill — still treat dir as owned
|
|
551
|
+
}
|
|
552
|
+
expectedSkillIds.add(id);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
const skillsDir = path.join(ctx.claudeDir, "skills");
|
|
556
|
+
if (!fs.existsSync(skillsDir)) return issues;
|
|
557
|
+
for (const entry of readdirSafe(skillsDir)) {
|
|
558
|
+
if (!entry.startsWith("agileflow")) continue;
|
|
559
|
+
if (expectedSkillIds.has(entry)) continue;
|
|
560
|
+
issues.push({
|
|
561
|
+
severity: "warn",
|
|
562
|
+
kind: "orphan-skill-dir",
|
|
563
|
+
path: path.join(skillsDir, entry),
|
|
564
|
+
message: `Orphan skill \`.claude/skills/${entry}/\` — not owned by any enabled plugin. Likely from a disabled or removed plugin.`,
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
return issues;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Parse a `<settingsPath>#hooks.<event>` issue.path. Returns null if
|
|
572
|
+
* the marker is missing.
|
|
573
|
+
*
|
|
574
|
+
* @param {string} issuePath
|
|
575
|
+
* @returns {{settingsPath: string, event: string} | null}
|
|
576
|
+
*/
|
|
577
|
+
function parseHookEventPath(issuePath) {
|
|
578
|
+
const hashIdx = (issuePath || "").lastIndexOf("#hooks.");
|
|
579
|
+
if (hashIdx < 0) return null;
|
|
580
|
+
return {
|
|
581
|
+
settingsPath: issuePath.slice(0, hashIdx),
|
|
582
|
+
event: issuePath.slice(hashIdx + "#hooks.".length),
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Persist a filtered hooks object back to settings.json, deleting the
|
|
588
|
+
* event entirely if no entries remain and dropping the hooks key if
|
|
589
|
+
* empty. Returns the relative .claude/settings.json#hooks.<event>
|
|
590
|
+
* string for use in success messages.
|
|
591
|
+
*/
|
|
592
|
+
function writeFilteredHooks(settings, settingsPath, event, kept) {
|
|
593
|
+
if (kept.length === 0) delete settings.hooks[event];
|
|
594
|
+
else settings.hooks[event] = kept;
|
|
595
|
+
if (Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
596
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Fix: strip AgileFlow entries from a hook event, preserving user entries. */
|
|
600
|
+
function fixHookEventEntry(issue) {
|
|
601
|
+
const parsed = parseHookEventPath(issue.path);
|
|
602
|
+
if (!parsed) {
|
|
603
|
+
return {
|
|
604
|
+
ok: false,
|
|
605
|
+
message: "Malformed issue.path; expected #hooks.<event>",
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
const { settingsPath, event } = parsed;
|
|
609
|
+
const settings = readJSONSafe(settingsPath);
|
|
610
|
+
if (!settings || !isPlainObject(settings.hooks)) {
|
|
611
|
+
return { ok: false, message: `No hooks object in ${settingsPath}` };
|
|
612
|
+
}
|
|
613
|
+
const raw = settings.hooks[event];
|
|
614
|
+
const entries = Array.isArray(raw) ? raw : isPlainObject(raw) ? [raw] : [];
|
|
615
|
+
const userEntries = entries.filter((e) => !isAgileflowEntry(e));
|
|
616
|
+
writeFilteredHooks(settings, settingsPath, event, userEntries);
|
|
617
|
+
return {
|
|
618
|
+
ok: true,
|
|
619
|
+
message: `Removed AgileFlow entries from .claude/settings.json#hooks.${event}`,
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/** Fix: recursively remove a directory artifact. */
|
|
624
|
+
function fixDirectoryRemoval(issue, ctx) {
|
|
625
|
+
if (!issue.path || !fs.existsSync(issue.path)) {
|
|
626
|
+
return { ok: false, message: `Path missing: ${issue.path}` };
|
|
627
|
+
}
|
|
628
|
+
fs.rmSync(issue.path, { recursive: true, force: true });
|
|
629
|
+
return { ok: true, message: `Removed ${path.relative(ctx.cwd, issue.path)}` };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/** Fix: unlink a file artifact. */
|
|
633
|
+
function fixFileRemoval(issue, ctx) {
|
|
634
|
+
if (!issue.path || !fs.existsSync(issue.path)) {
|
|
635
|
+
return { ok: false, message: `Path missing: ${issue.path}` };
|
|
636
|
+
}
|
|
637
|
+
fs.unlinkSync(issue.path);
|
|
638
|
+
return { ok: true, message: `Removed ${path.relative(ctx.cwd, issue.path)}` };
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/**
|
|
642
|
+
* Fix: delete agileflow-* children of a legacy .claude/ subdir, leaving
|
|
643
|
+
* user files alone. Empty parent dir is cleaned up best-effort.
|
|
644
|
+
*/
|
|
645
|
+
function fixClaudeSubdirSelective(issue, ctx) {
|
|
646
|
+
if (!issue.path || !fs.existsSync(issue.path)) {
|
|
647
|
+
return { ok: false, message: `Path missing: ${issue.path}` };
|
|
648
|
+
}
|
|
649
|
+
let removed = 0;
|
|
650
|
+
for (const entry of readdirSafe(issue.path)) {
|
|
651
|
+
if (!entry.startsWith("agileflow") && !entry.startsWith("AgileFlow"))
|
|
652
|
+
continue;
|
|
653
|
+
const p = path.join(issue.path, entry);
|
|
654
|
+
fs.rmSync(p, { recursive: true, force: true });
|
|
655
|
+
removed += 1;
|
|
656
|
+
}
|
|
657
|
+
// Empty-dir cleanup — fail silently if dir has other content.
|
|
658
|
+
try {
|
|
659
|
+
fs.rmdirSync(issue.path);
|
|
660
|
+
} catch {
|
|
661
|
+
/* dir not empty (user files present); leave it */
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
ok: true,
|
|
665
|
+
message: `Removed ${removed} AgileFlow item(s) from ${path.relative(ctx.cwd, issue.path)}`,
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** Fix: broken hook script — not auto-fixable; the script file itself is gone. */
|
|
670
|
+
function fixBrokenHookScriptStub() {
|
|
671
|
+
return {
|
|
672
|
+
ok: false,
|
|
673
|
+
message:
|
|
674
|
+
"Cannot auto-fix — the script file is gone. Run `agileflow update` to reinstall plugin scripts.",
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Fix: strip a hook entry whose command matches a missing path, preserving siblings. */
|
|
679
|
+
function fixBrokenHookCommand(issue) {
|
|
680
|
+
const parsed = parseHookEventPath(issue.path);
|
|
681
|
+
if (!parsed || !issue.command) {
|
|
682
|
+
return {
|
|
683
|
+
ok: false,
|
|
684
|
+
message: "Malformed issue — expected path and command",
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
const { settingsPath, event } = parsed;
|
|
688
|
+
const settings = readJSONSafe(settingsPath);
|
|
689
|
+
if (!settings || !isPlainObject(settings.hooks)) {
|
|
690
|
+
return { ok: false, message: `No hooks object in ${settingsPath}` };
|
|
691
|
+
}
|
|
692
|
+
const raw = settings.hooks[event];
|
|
693
|
+
const entries = Array.isArray(raw) ? raw : isPlainObject(raw) ? [raw] : [];
|
|
694
|
+
const kept = entries.filter((e) => {
|
|
695
|
+
if (!e || !Array.isArray(e.hooks)) return true;
|
|
696
|
+
// Drop entry if ANY of its hooks matches the broken command.
|
|
697
|
+
return !e.hooks.some(
|
|
698
|
+
(h) => h && h.type === "command" && h.command === issue.command,
|
|
699
|
+
);
|
|
700
|
+
});
|
|
701
|
+
writeFilteredHooks(settings, settingsPath, event, kept);
|
|
702
|
+
return {
|
|
703
|
+
ok: true,
|
|
704
|
+
message: `Removed broken hook from .claude/settings.json#hooks.${event} (command: ${issue.command})`,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Registry of stale-artifact handlers. Adding a new kind: append one
|
|
710
|
+
* entry with a detect function returning issues and a fix function
|
|
711
|
+
* returning {ok, message}. `handles` lists every issue.kind this entry
|
|
712
|
+
* accepts (most singular; a pair when one fix covers two kinds).
|
|
713
|
+
*/
|
|
714
|
+
const STALE_DETECTORS = [
|
|
715
|
+
{
|
|
716
|
+
detect: detectLegacyHookEvents,
|
|
717
|
+
fix: fixHookEventEntry,
|
|
718
|
+
handles: new Set(["legacy-hook-event", "orphan-hook-event"]),
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
detect: detectBrokenHookCommands,
|
|
722
|
+
fix: fixBrokenHookCommand,
|
|
723
|
+
handles: new Set(["broken-hook-command"]),
|
|
724
|
+
},
|
|
725
|
+
{
|
|
726
|
+
detect: detectLegacyAgileflowPaths,
|
|
727
|
+
// Split between two fixers based on issue.kind; the runner picks
|
|
728
|
+
// by handles, so we declare them separately below for clarity.
|
|
729
|
+
fix: (issue, ctx) =>
|
|
730
|
+
issue.kind === "legacy-agileflow-file"
|
|
731
|
+
? fixFileRemoval(issue, ctx)
|
|
732
|
+
: fixDirectoryRemoval(issue, ctx),
|
|
733
|
+
handles: new Set(["legacy-agileflow-subdir", "legacy-agileflow-file"]),
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
detect: detectLegacyClaudeSubdirs,
|
|
737
|
+
fix: fixClaudeSubdirSelective,
|
|
738
|
+
handles: new Set(["legacy-claude-subdir"]),
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
detect: detectBrokenHookScripts,
|
|
742
|
+
fix: fixBrokenHookScriptStub,
|
|
743
|
+
handles: new Set(["broken-hook-script"]),
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
detect: detectOrphanSkillDirs,
|
|
747
|
+
fix: fixDirectoryRemoval,
|
|
748
|
+
handles: new Set(["orphan-skill-dir"]),
|
|
749
|
+
},
|
|
750
|
+
];
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Detect stale AgileFlow artifacts left over from older versions, plugin
|
|
754
|
+
* renames, or aborted installs. Each issue identifies a path or hook
|
|
755
|
+
* entry that update wouldn't currently sweep.
|
|
756
|
+
*
|
|
757
|
+
* Diagnose-only: never modifies anything. Always returns an array — IO
|
|
758
|
+
* failures are caught and either yield an issue or are skipped (never
|
|
759
|
+
* thrown to the caller).
|
|
760
|
+
*
|
|
761
|
+
* Issue shape: { severity: "warn"|"error", kind: string, path?: string, message: string }
|
|
762
|
+
*
|
|
763
|
+
* @param {string} cwd
|
|
764
|
+
* @returns {Promise<Array<{severity: string, kind: string, path?: string, message: string}>>}
|
|
765
|
+
*/
|
|
766
|
+
async function checkStaleArtifacts(cwd) {
|
|
767
|
+
const ctx = buildContext(cwd);
|
|
768
|
+
const issues = [];
|
|
769
|
+
for (const entry of STALE_DETECTORS) {
|
|
770
|
+
issues.push(...(await entry.detect(ctx)));
|
|
771
|
+
}
|
|
772
|
+
return issues;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Apply a single stale-artifact fix. Hook-event entries are stripped
|
|
777
|
+
* from settings.json (preserving user entries in that event); fs
|
|
778
|
+
* artifacts are removed. Broken hook scripts cannot be auto-fixed —
|
|
779
|
+
* the script file itself is gone.
|
|
780
|
+
*
|
|
781
|
+
* @param {{kind: string, path?: string, message: string}} issue
|
|
782
|
+
* @param {string} cwd
|
|
783
|
+
* @returns {{ok: boolean, message: string}}
|
|
784
|
+
*/
|
|
785
|
+
function applyStaleFix(issue, cwd) {
|
|
786
|
+
const entry = STALE_DETECTORS.find((d) => d.handles.has(issue.kind));
|
|
787
|
+
if (!entry) {
|
|
788
|
+
return { ok: false, message: `Unknown issue kind: ${issue.kind}` };
|
|
789
|
+
}
|
|
790
|
+
return entry.fix(issue, buildContext(cwd));
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* `agileflow doctor --fix` body. Detects stale artifacts (same as
|
|
795
|
+
* `doctor`), then either previews removal (`--fix` alone) or executes
|
|
796
|
+
* (`--fix --yes`). Returns counts for testing.
|
|
797
|
+
*
|
|
798
|
+
* @param {string} cwd
|
|
799
|
+
* @param {{yes?: boolean, log?: (msg: string) => void}} [opts]
|
|
800
|
+
* @returns {Promise<{detected: number, fixed: number, failed: number, dryRun: boolean}>}
|
|
801
|
+
*/
|
|
802
|
+
async function doctorFix(cwd, opts = {}) {
|
|
803
|
+
const log = opts.log || ((m) => console.log(m)); // eslint-disable-line no-console
|
|
804
|
+
const issues = await checkStaleArtifacts(cwd);
|
|
805
|
+
if (issues.length === 0) {
|
|
806
|
+
log("Stale artifacts: ok — nothing to fix.");
|
|
807
|
+
return { detected: 0, fixed: 0, failed: 0, dryRun: !opts.yes };
|
|
808
|
+
}
|
|
809
|
+
if (!opts.yes) {
|
|
810
|
+
log(
|
|
811
|
+
`\n${issues.length} stale artifact(s) — dry-run preview (use --yes to actually remove):\n`,
|
|
812
|
+
);
|
|
813
|
+
for (const issue of issues) {
|
|
814
|
+
log(` • [${issue.kind}] ${issue.message}`);
|
|
815
|
+
if (issue.path) log(` ${issue.path}`);
|
|
816
|
+
}
|
|
817
|
+
log("\n Re-run with `agileflow doctor --fix --yes` to apply.");
|
|
818
|
+
return { detected: issues.length, fixed: 0, failed: 0, dryRun: true };
|
|
819
|
+
}
|
|
820
|
+
let fixed = 0;
|
|
821
|
+
let failed = 0;
|
|
822
|
+
log(`\nApplying fixes for ${issues.length} stale artifact(s):\n`);
|
|
823
|
+
for (const issue of issues) {
|
|
824
|
+
// Guard against synchronous throws from fs.rmSync / unlinkSync on
|
|
825
|
+
// EPERM/EBUSY/race-deleted paths — otherwise a mid-loop throw
|
|
826
|
+
// bypasses the final summary log line.
|
|
827
|
+
try {
|
|
828
|
+
const r = applyStaleFix(issue, cwd);
|
|
829
|
+
if (r.ok) {
|
|
830
|
+
fixed += 1;
|
|
831
|
+
log(` ✓ ${r.message}`);
|
|
832
|
+
} else {
|
|
833
|
+
failed += 1;
|
|
834
|
+
log(` ✗ [${issue.kind}] ${r.message}`);
|
|
835
|
+
}
|
|
836
|
+
} catch (err) {
|
|
837
|
+
failed += 1;
|
|
838
|
+
log(` ✗ [${issue.kind}] threw: ${err.message}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
log(`\n ${fixed} fixed, ${failed} skipped/failed.`);
|
|
842
|
+
return { detected: issues.length, fixed, failed, dryRun: false };
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
async function doctor(opts = {}) {
|
|
846
|
+
if (opts && opts.fix) {
|
|
847
|
+
const cwd = process.cwd();
|
|
848
|
+
const r = await doctorFix(cwd, { yes: !!opts.yes });
|
|
849
|
+
if (r.failed > 0) process.exit(1);
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
121
852
|
const cwd = process.cwd();
|
|
122
853
|
let totalErrors = 0;
|
|
123
854
|
|
|
124
855
|
// 1. Plugin manifests (every bundled plugin.yaml).
|
|
125
856
|
const plugins = discoverPlugins();
|
|
126
857
|
const pluginIssues = validatePluginSet(plugins);
|
|
127
|
-
totalErrors += printSection(
|
|
858
|
+
totalErrors += printSection("Plugin manifests:", pluginIssues);
|
|
128
859
|
|
|
129
860
|
// 2. Skills (every SKILL.md across every bundled plugin).
|
|
130
861
|
const { issues: skillIssues } = await validateBundledSkills();
|
|
131
|
-
totalErrors += printSection(
|
|
862
|
+
totalErrors += printSection(
|
|
863
|
+
"Skills:",
|
|
864
|
+
skillIssues.filter(
|
|
865
|
+
(i) =>
|
|
866
|
+
(i.severity !== "warn" && i.severity !== "warning") ||
|
|
867
|
+
!i.message.includes("_learnings"),
|
|
868
|
+
),
|
|
869
|
+
);
|
|
132
870
|
|
|
133
871
|
// 3. Aggregated hook manifest (what install would write).
|
|
134
872
|
const aggrErrors = await validateAggregatedHookManifest();
|
|
135
873
|
totalErrors += printSection(
|
|
136
|
-
|
|
137
|
-
aggrErrors.map((m) => ({ severity:
|
|
874
|
+
"Hook manifest (aggregated):",
|
|
875
|
+
aggrErrors.map((m) => ({ severity: "error", message: m })),
|
|
138
876
|
);
|
|
139
877
|
|
|
140
878
|
// 4. Installed hook manifest in this cwd (if any).
|
|
141
879
|
const installedErrors = await validateInstalledManifest(cwd);
|
|
142
880
|
totalErrors += printSection(
|
|
143
|
-
|
|
144
|
-
installedErrors.map((m) => ({ severity:
|
|
881
|
+
"Hook manifest (installed):",
|
|
882
|
+
installedErrors.map((m) => ({ severity: "error", message: m })),
|
|
145
883
|
);
|
|
146
884
|
|
|
885
|
+
// 5. Stale artifacts from older versions / aborted installs.
|
|
886
|
+
const staleIssues = await checkStaleArtifacts(cwd);
|
|
887
|
+
// eslint-disable-next-line no-console
|
|
888
|
+
console.log("\nStale artifacts:");
|
|
889
|
+
if (staleIssues.length === 0) {
|
|
890
|
+
// eslint-disable-next-line no-console
|
|
891
|
+
console.log(" ok");
|
|
892
|
+
} else {
|
|
893
|
+
for (const issue of staleIssues) {
|
|
894
|
+
const label = issue.severity === "error" ? "ERROR" : "WARN ";
|
|
895
|
+
if (issue.severity === "error") totalErrors += 1;
|
|
896
|
+
// eslint-disable-next-line no-console
|
|
897
|
+
console.log(` ${label} ${issue.message}`);
|
|
898
|
+
if (issue.path) {
|
|
899
|
+
// eslint-disable-next-line no-console
|
|
900
|
+
console.log(` ${issue.path}`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
// eslint-disable-next-line no-console
|
|
904
|
+
console.log(
|
|
905
|
+
`\n ${staleIssues.length} stale artifact(s). Run \`agileflow doctor --fix\` to preview removal, or \`--fix --yes\` to apply.`,
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
// 6. Installation health — skills + hooks in the current project.
|
|
910
|
+
const installIssues = await checkInstallHealth(cwd);
|
|
911
|
+
// eslint-disable-next-line no-console
|
|
912
|
+
console.log("\nInstallation health:");
|
|
913
|
+
if (installIssues.length === 0) {
|
|
914
|
+
// eslint-disable-next-line no-console
|
|
915
|
+
console.log(" ok");
|
|
916
|
+
} else {
|
|
917
|
+
for (const issue of installIssues) {
|
|
918
|
+
if (issue.severity === "info") {
|
|
919
|
+
// eslint-disable-next-line no-console
|
|
920
|
+
console.log(` ✓ ${issue.message}`);
|
|
921
|
+
} else if (issue.severity === "warn") {
|
|
922
|
+
// eslint-disable-next-line no-console
|
|
923
|
+
console.log(` WARN ${issue.message}`);
|
|
924
|
+
} else {
|
|
925
|
+
totalErrors += 1;
|
|
926
|
+
// eslint-disable-next-line no-console
|
|
927
|
+
console.log(` ERROR ${issue.message}`);
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
147
932
|
// eslint-disable-next-line no-console
|
|
148
|
-
console.log(
|
|
933
|
+
console.log("");
|
|
149
934
|
if (totalErrors === 0) {
|
|
150
935
|
// eslint-disable-next-line no-console
|
|
151
|
-
console.log(
|
|
936
|
+
console.log("✓ doctor: all checks passed");
|
|
152
937
|
return;
|
|
153
938
|
}
|
|
154
939
|
// eslint-disable-next-line no-console
|
|
@@ -157,3 +942,6 @@ async function doctor() {
|
|
|
157
942
|
}
|
|
158
943
|
|
|
159
944
|
module.exports = doctor;
|
|
945
|
+
module.exports.checkStaleArtifacts = checkStaleArtifacts;
|
|
946
|
+
module.exports.applyStaleFix = applyStaleFix;
|
|
947
|
+
module.exports.doctorFix = doctorFix;
|