agileflow 3.4.3 → 4.0.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +235 -499
- package/README.md +22 -114
- package/bin/agileflow.js +15 -0
- package/bin/hooks/pre-bash.js +35 -0
- package/bin/hooks/pre-compact.js +34 -0
- package/bin/hooks/pre-edit.js +32 -0
- package/bin/hooks/pre-write.js +32 -0
- package/bin/hooks/session-start.js +42 -0
- package/bin/hooks/stop.js +34 -0
- package/content/plugins/ads/plugin.yaml +14 -0
- package/content/plugins/audit/plugin.yaml +14 -0
- package/content/plugins/core/hooks/session-welcome.js +19 -0
- package/content/plugins/core/plugin.yaml +34 -0
- package/content/plugins/core/skills/agileflow-adr/SKILL.md +179 -0
- package/content/plugins/core/skills/agileflow-babysit-mentor/SKILL.md +144 -0
- package/content/plugins/core/skills/agileflow-epic-planner/SKILL.md +179 -0
- package/content/plugins/core/skills/agileflow-status-updater/SKILL.md +132 -0
- package/content/plugins/core/skills/agileflow-story-writer/SKILL.md +200 -0
- package/content/plugins/council/plugin.yaml +14 -0
- package/content/plugins/seo/plugin.yaml +14 -0
- package/package.json +29 -49
- package/src/cli/commands/doctor.js +159 -0
- package/src/cli/commands/hook.js +80 -0
- package/src/cli/commands/setup.js +254 -0
- package/src/cli/commands/status.js +47 -0
- package/src/cli/commands/update.js +82 -0
- package/src/cli/index.js +73 -0
- package/src/cli/wizard/ide-picker.js +57 -0
- package/src/cli/wizard/personalization.js +64 -0
- package/src/cli/wizard/plugin-picker.js +106 -0
- package/src/lib/hash.js +41 -0
- package/src/runtime/config/defaults.js +45 -0
- package/src/runtime/config/loader.js +118 -0
- package/src/runtime/config/schema.json +76 -0
- package/src/runtime/config/writer.js +54 -0
- package/src/runtime/hooks/aggregator.js +133 -0
- package/src/runtime/hooks/chain.js +93 -0
- package/src/runtime/hooks/logger.js +68 -0
- package/src/runtime/hooks/manifest-loader.js +228 -0
- package/src/runtime/hooks/orchestrator.js +322 -0
- package/src/runtime/ide/capabilities.js +111 -0
- package/src/runtime/ide/claude-code-settings.js +234 -0
- package/src/runtime/ide/claude-code-skills.js +202 -0
- package/src/runtime/installer/file-index.js +112 -0
- package/src/runtime/installer/install.js +306 -0
- package/src/runtime/installer/stash.js +61 -0
- package/src/runtime/installer/sync-engine.js +205 -0
- package/src/runtime/plugins/registry.js +132 -0
- package/src/runtime/plugins/resolver.js +138 -0
- package/src/runtime/plugins/validator.js +196 -0
- package/src/runtime/skills/validator.js +335 -0
- package/lib/README.md +0 -178
- package/lib/api-routes.js +0 -625
- package/lib/api-server.js +0 -278
- package/lib/cache-provider.js +0 -155
- package/lib/codebase-indexer.js +0 -819
- package/lib/colors.generated.js +0 -117
- package/lib/colors.js +0 -341
- package/lib/consent.js +0 -232
- package/lib/content-sanitizer.js +0 -464
- package/lib/correlation.js +0 -277
- package/lib/drivers/claude-driver.ts +0 -312
- package/lib/drivers/codex-driver.ts +0 -464
- package/lib/drivers/driver-manager.ts +0 -159
- package/lib/drivers/gemini-driver.ts +0 -498
- package/lib/drivers/index.ts +0 -17
- package/lib/error-codes.js +0 -590
- package/lib/errors.js +0 -670
- package/lib/feature-flags.js +0 -171
- package/lib/feedback.js +0 -595
- package/lib/file-cache.js +0 -541
- package/lib/flag-detection.js +0 -344
- package/lib/format-error.js +0 -156
- package/lib/gate-runner.js +0 -282
- package/lib/generator-factory.js +0 -333
- package/lib/git-operations.js +0 -266
- package/lib/lazy-require.js +0 -59
- package/lib/lock-file.js +0 -144
- package/lib/logger.js +0 -106
- package/lib/merge-operations.js +0 -1006
- package/lib/path-resolver.js +0 -544
- package/lib/path-utils.js +0 -49
- package/lib/paths.js +0 -291
- package/lib/placeholder-registry.js +0 -822
- package/lib/process-executor.js +0 -214
- package/lib/progress.js +0 -334
- package/lib/protocol/driver.ts +0 -354
- package/lib/protocol/index.ts +0 -12
- package/lib/protocol/ir.ts +0 -271
- package/lib/registry-cache.js +0 -80
- package/lib/registry-di.js +0 -358
- package/lib/result-schema.js +0 -363
- package/lib/result.js +0 -210
- package/lib/session-display.js +0 -331
- package/lib/session-operations.js +0 -611
- package/lib/session-registry.js +0 -484
- package/lib/session-state-machine.js +0 -465
- package/lib/session-switching.js +0 -191
- package/lib/skill-loader.js +0 -213
- package/lib/smart-json-file.js +0 -682
- package/lib/state-machine.js +0 -286
- package/lib/table-formatter.js +0 -519
- package/lib/template-loader.js +0 -143
- package/lib/transient-status.js +0 -374
- package/lib/ui-manager.js +0 -612
- package/lib/validate-args.js +0 -213
- package/lib/validate-commands.js +0 -308
- package/lib/validate-names.js +0 -143
- package/lib/validate-paths.js +0 -434
- package/lib/validate.js +0 -134
- package/lib/worktree-operations.js +0 -201
- package/lib/yaml-utils.js +0 -164
- package/scripts/README.md +0 -267
- package/scripts/af +0 -34
- package/scripts/agent-loop.js +0 -879
- package/scripts/agileflow-configure.js +0 -368
- package/scripts/agileflow-statusline.sh +0 -857
- package/scripts/agileflow-welcome.js +0 -2246
- package/scripts/api-server-runner.js +0 -177
- package/scripts/archive-completed-stories.sh +0 -308
- package/scripts/auto-self-improve.js +0 -326
- package/scripts/automation-run-due.js +0 -128
- package/scripts/babysit-clear-restore.js +0 -154
- package/scripts/babysit-context-restore.js +0 -89
- package/scripts/backfill-ideation-status.js +0 -128
- package/scripts/batch-pmap-loop.js +0 -551
- package/scripts/check-sessions.js +0 -116
- package/scripts/check-update.js +0 -282
- package/scripts/ci-summary.js +0 -294
- package/scripts/claude-smart.sh +0 -85
- package/scripts/claude-tmux.sh +0 -737
- package/scripts/claude-watchdog.sh +0 -225
- package/scripts/clear-active-command.js +0 -48
- package/scripts/compress-status.sh +0 -116
- package/scripts/context-loader.js +0 -310
- package/scripts/damage-control/bash-tool-damage-control.js +0 -22
- package/scripts/damage-control/edit-tool-damage-control.js +0 -19
- package/scripts/damage-control/patterns.yaml +0 -227
- package/scripts/damage-control/write-tool-damage-control.js +0 -19
- package/scripts/damage-control-bash.js +0 -51
- package/scripts/damage-control-edit.js +0 -48
- package/scripts/damage-control-multi-agent.js +0 -231
- package/scripts/damage-control-write.js +0 -48
- package/scripts/dependency-check.js +0 -311
- package/scripts/document-repl.js +0 -793
- package/scripts/expertise-metrics.sh +0 -264
- package/scripts/generate-all.sh +0 -77
- package/scripts/generate-colors.js +0 -314
- package/scripts/generators/agent-registry.js +0 -183
- package/scripts/generators/command-registry.js +0 -166
- package/scripts/generators/index.js +0 -85
- package/scripts/generators/inject-babysit.js +0 -191
- package/scripts/generators/inject-help.js +0 -125
- package/scripts/generators/inject-readme.js +0 -166
- package/scripts/generators/skill-registry.js +0 -188
- package/scripts/get-env.js +0 -225
- package/scripts/init.sh +0 -76
- package/scripts/lib/README-portable-tasks.md +0 -424
- package/scripts/lib/ac-test-matcher.js +0 -452
- package/scripts/lib/audit-cleanup.js +0 -250
- package/scripts/lib/audit-registry.js +0 -340
- package/scripts/lib/automation-registry.js +0 -544
- package/scripts/lib/automation-runner.js +0 -476
- package/scripts/lib/browser-qa-evidence.js +0 -409
- package/scripts/lib/browser-qa-status.js +0 -192
- package/scripts/lib/bus-utils.js +0 -473
- package/scripts/lib/colors.generated.sh +0 -82
- package/scripts/lib/colors.sh +0 -46
- package/scripts/lib/command-prereqs.js +0 -280
- package/scripts/lib/concurrency-limiter.js +0 -511
- package/scripts/lib/configure-detect.js +0 -596
- package/scripts/lib/configure-features.js +0 -1927
- package/scripts/lib/configure-repair.js +0 -327
- package/scripts/lib/configure-utils.js +0 -114
- package/scripts/lib/context-formatter.js +0 -1158
- package/scripts/lib/context-loader.js +0 -840
- package/scripts/lib/counter.js +0 -103
- package/scripts/lib/damage-control-utils.js +0 -619
- package/scripts/lib/feature-catalog.js +0 -332
- package/scripts/lib/file-lock.js +0 -392
- package/scripts/lib/file-tracking.js +0 -735
- package/scripts/lib/frontmatter-parser.js +0 -133
- package/scripts/lib/gate-enforcer.js +0 -295
- package/scripts/lib/hook-metrics.js +0 -324
- package/scripts/lib/ideation-index.js +0 -1205
- package/scripts/lib/json-utils.sh +0 -162
- package/scripts/lib/lifecycle-detector.js +0 -125
- package/scripts/lib/model-profiles.js +0 -118
- package/scripts/lib/portable-tasks-cli.js +0 -274
- package/scripts/lib/portable-tasks.js +0 -479
- package/scripts/lib/process-cleanup.js +0 -527
- package/scripts/lib/quality-gates.js +0 -788
- package/scripts/lib/scale-detector.js +0 -396
- package/scripts/lib/sessionRegistry.js +0 -678
- package/scripts/lib/signal-detectors.js +0 -867
- package/scripts/lib/skill-catalog.js +0 -557
- package/scripts/lib/skill-recommender.js +0 -311
- package/scripts/lib/state-migrator.js +0 -353
- package/scripts/lib/status-task-bridge.js +0 -522
- package/scripts/lib/status-writer.js +0 -255
- package/scripts/lib/story-claiming.js +0 -704
- package/scripts/lib/story-state-machine.js +0 -437
- package/scripts/lib/sync-ideation-status.js +0 -291
- package/scripts/lib/task-registry-cache.js +0 -490
- package/scripts/lib/task-registry.js +0 -1191
- package/scripts/lib/task-sync.js +0 -230
- package/scripts/lib/tdd-phase-manager.js +0 -455
- package/scripts/lib/team-events.js +0 -510
- package/scripts/lib/tmux-audit-monitor.js +0 -612
- package/scripts/lib/tmux-group-colors.js +0 -113
- package/scripts/lib/tool-registry.yaml +0 -241
- package/scripts/lib/tool-shed.js +0 -441
- package/scripts/lib/validation-registry.js +0 -177
- package/scripts/messaging-bridge.js +0 -561
- package/scripts/migrate-ideation-index.js +0 -553
- package/scripts/native-team-observer.js +0 -219
- package/scripts/obtain-context.js +0 -272
- package/scripts/pre-push-check.sh +0 -46
- package/scripts/precompact-context.sh +0 -306
- package/scripts/query-codebase.js +0 -543
- package/scripts/ralph-loop.js +0 -1278
- package/scripts/resume-session.sh +0 -121
- package/scripts/screenshot-verifier.js +0 -215
- package/scripts/session-boundary.js +0 -138
- package/scripts/session-coordinator.sh +0 -232
- package/scripts/session-manager.js +0 -546
- package/scripts/smart-detect.js +0 -449
- package/scripts/spawn-audit-sessions.js +0 -877
- package/scripts/spawn-parallel.js +0 -751
- package/scripts/strip-ai-attribution.js +0 -63
- package/scripts/task-completed-gate.js +0 -237
- package/scripts/team-manager.js +0 -596
- package/scripts/team-status-display.js +0 -200
- package/scripts/teammate-idle-gate.js +0 -237
- package/scripts/test-session-boundary.js +0 -80
- package/scripts/tmux-close-windows.sh +0 -180
- package/scripts/tmux-restore-window.sh +0 -67
- package/scripts/tmux-save-closed-window.sh +0 -35
- package/scripts/tui/App.js +0 -151
- package/scripts/tui/Dashboard.js +0 -277
- package/scripts/tui/blessed/data/watcher.js +0 -180
- package/scripts/tui/blessed/index.js +0 -244
- package/scripts/tui/blessed/panels/output.js +0 -101
- package/scripts/tui/blessed/panels/sessions.js +0 -150
- package/scripts/tui/blessed/panels/trace.js +0 -97
- package/scripts/tui/blessed/ui/help.js +0 -77
- package/scripts/tui/blessed/ui/screen.js +0 -52
- package/scripts/tui/blessed/ui/statusbar.js +0 -47
- package/scripts/tui/blessed/ui/tabbar.js +0 -99
- package/scripts/tui/index.js +0 -70
- package/scripts/tui/lib/crashRecovery.js +0 -304
- package/scripts/tui/lib/eventStream.js +0 -309
- package/scripts/tui/lib/keyboard.js +0 -261
- package/scripts/tui/lib/loopControl.js +0 -371
- package/scripts/tui/panels/OutputPanel.js +0 -240
- package/scripts/tui/panels/SessionPanel.js +0 -170
- package/scripts/tui/panels/TracePanel.js +0 -298
- package/scripts/tui/simple-tui.js +0 -510
- package/scripts/validate-expertise.sh +0 -263
- package/scripts/validate-tokens.sh +0 -73
- package/scripts/validators/README.md +0 -143
- package/scripts/validators/component-validator.js +0 -239
- package/scripts/validators/json-schema-validator.js +0 -186
- package/scripts/validators/markdown-validator.js +0 -152
- package/scripts/validators/migration-validator.js +0 -129
- package/scripts/validators/security-validator.js +0 -380
- package/scripts/validators/story-format-validator.js +0 -197
- package/scripts/validators/test-result-validator.js +0 -114
- package/scripts/validators/workflow-validator.js +0 -247
- package/scripts/welcome-deferred.js +0 -437
- package/scripts/worktree-create.sh +0 -111
- package/src/core/agents/a11y-analyzer-aria.md +0 -155
- package/src/core/agents/a11y-analyzer-forms.md +0 -162
- package/src/core/agents/a11y-analyzer-keyboard.md +0 -175
- package/src/core/agents/a11y-analyzer-semantic.md +0 -153
- package/src/core/agents/a11y-analyzer-visual.md +0 -158
- package/src/core/agents/a11y-consensus.md +0 -248
- package/src/core/agents/accessibility.md +0 -515
- package/src/core/agents/adr-writer.md +0 -463
- package/src/core/agents/ads-audit-budget.md +0 -181
- package/src/core/agents/ads-audit-compliance.md +0 -169
- package/src/core/agents/ads-audit-creative.md +0 -164
- package/src/core/agents/ads-audit-google.md +0 -226
- package/src/core/agents/ads-audit-meta.md +0 -183
- package/src/core/agents/ads-audit-tracking.md +0 -197
- package/src/core/agents/ads-consensus.md +0 -396
- package/src/core/agents/ads-generate.md +0 -145
- package/src/core/agents/ads-performance-tracker.md +0 -197
- package/src/core/agents/analytics.md +0 -617
- package/src/core/agents/api-quality-analyzer-conventions.md +0 -148
- package/src/core/agents/api-quality-analyzer-docs.md +0 -176
- package/src/core/agents/api-quality-analyzer-errors.md +0 -183
- package/src/core/agents/api-quality-analyzer-pagination.md +0 -171
- package/src/core/agents/api-quality-analyzer-versioning.md +0 -143
- package/src/core/agents/api-quality-consensus.md +0 -214
- package/src/core/agents/api-validator.md +0 -183
- package/src/core/agents/api.md +0 -665
- package/src/core/agents/arch-analyzer-circular.md +0 -148
- package/src/core/agents/arch-analyzer-complexity.md +0 -171
- package/src/core/agents/arch-analyzer-coupling.md +0 -146
- package/src/core/agents/arch-analyzer-layering.md +0 -151
- package/src/core/agents/arch-analyzer-patterns.md +0 -162
- package/src/core/agents/arch-consensus.md +0 -227
- package/src/core/agents/brainstorm-analyzer-features.md +0 -169
- package/src/core/agents/brainstorm-analyzer-growth.md +0 -161
- package/src/core/agents/brainstorm-analyzer-integration.md +0 -172
- package/src/core/agents/brainstorm-analyzer-market.md +0 -147
- package/src/core/agents/brainstorm-analyzer-ux.md +0 -167
- package/src/core/agents/brainstorm-consensus.md +0 -237
- package/src/core/agents/browser-qa.md +0 -328
- package/src/core/agents/ci.md +0 -511
- package/src/core/agents/code-reviewer.md +0 -288
- package/src/core/agents/codebase-query.md +0 -266
- package/src/core/agents/completeness-analyzer-api.md +0 -190
- package/src/core/agents/completeness-analyzer-conditional.md +0 -201
- package/src/core/agents/completeness-analyzer-handlers.md +0 -159
- package/src/core/agents/completeness-analyzer-imports.md +0 -159
- package/src/core/agents/completeness-analyzer-routes.md +0 -182
- package/src/core/agents/completeness-analyzer-state.md +0 -188
- package/src/core/agents/completeness-analyzer-stubs.md +0 -198
- package/src/core/agents/completeness-consensus.md +0 -286
- package/src/core/agents/compliance.md +0 -509
- package/src/core/agents/council-advocate.md +0 -206
- package/src/core/agents/council-analyst.md +0 -252
- package/src/core/agents/council-optimist.md +0 -170
- package/src/core/agents/database.md +0 -601
- package/src/core/agents/datamigration.md +0 -699
- package/src/core/agents/design.md +0 -525
- package/src/core/agents/devops.md +0 -720
- package/src/core/agents/documentation.md +0 -504
- package/src/core/agents/epic-planner.md +0 -480
- package/src/core/agents/error-analyzer.md +0 -201
- package/src/core/agents/integrations.md +0 -603
- package/src/core/agents/legal-analyzer-a11y.md +0 -110
- package/src/core/agents/legal-analyzer-ai.md +0 -117
- package/src/core/agents/legal-analyzer-consumer.md +0 -108
- package/src/core/agents/legal-analyzer-content.md +0 -113
- package/src/core/agents/legal-analyzer-international.md +0 -115
- package/src/core/agents/legal-analyzer-licensing.md +0 -115
- package/src/core/agents/legal-analyzer-privacy.md +0 -108
- package/src/core/agents/legal-analyzer-security.md +0 -112
- package/src/core/agents/legal-analyzer-terms.md +0 -111
- package/src/core/agents/legal-consensus.md +0 -242
- package/src/core/agents/logic-analyzer-edge.md +0 -170
- package/src/core/agents/logic-analyzer-flow.md +0 -253
- package/src/core/agents/logic-analyzer-invariant.md +0 -206
- package/src/core/agents/logic-analyzer-race.md +0 -266
- package/src/core/agents/logic-analyzer-type.md +0 -217
- package/src/core/agents/logic-consensus.md +0 -253
- package/src/core/agents/mentor.md +0 -654
- package/src/core/agents/mobile.md +0 -501
- package/src/core/agents/monitoring.md +0 -537
- package/src/core/agents/multi-expert.md +0 -311
- package/src/core/agents/orchestrator.md +0 -749
- package/src/core/agents/perf-analyzer-assets.md +0 -174
- package/src/core/agents/perf-analyzer-bundle.md +0 -165
- package/src/core/agents/perf-analyzer-caching.md +0 -160
- package/src/core/agents/perf-analyzer-compute.md +0 -165
- package/src/core/agents/perf-analyzer-memory.md +0 -182
- package/src/core/agents/perf-analyzer-network.md +0 -157
- package/src/core/agents/perf-analyzer-queries.md +0 -155
- package/src/core/agents/perf-analyzer-rendering.md +0 -156
- package/src/core/agents/perf-consensus.md +0 -280
- package/src/core/agents/performance.md +0 -492
- package/src/core/agents/product.md +0 -535
- package/src/core/agents/qa.md +0 -765
- package/src/core/agents/readme-updater.md +0 -579
- package/src/core/agents/refactor.md +0 -558
- package/src/core/agents/research.md +0 -453
- package/src/core/agents/rlm-subcore.md +0 -207
- package/src/core/agents/schema-validator.md +0 -454
- package/src/core/agents/security-analyzer-api.md +0 -199
- package/src/core/agents/security-analyzer-auth.md +0 -160
- package/src/core/agents/security-analyzer-authz.md +0 -168
- package/src/core/agents/security-analyzer-deps.md +0 -147
- package/src/core/agents/security-analyzer-infra.md +0 -176
- package/src/core/agents/security-analyzer-injection.md +0 -148
- package/src/core/agents/security-analyzer-input.md +0 -191
- package/src/core/agents/security-analyzer-secrets.md +0 -175
- package/src/core/agents/security-consensus.md +0 -276
- package/src/core/agents/security.md +0 -486
- package/src/core/agents/seo-analyzer-content.md +0 -167
- package/src/core/agents/seo-analyzer-images.md +0 -187
- package/src/core/agents/seo-analyzer-performance.md +0 -206
- package/src/core/agents/seo-analyzer-schema.md +0 -176
- package/src/core/agents/seo-analyzer-sitemap.md +0 -172
- package/src/core/agents/seo-analyzer-technical.md +0 -144
- package/src/core/agents/seo-consensus.md +0 -289
- package/src/core/agents/team-coordinator.md +0 -333
- package/src/core/agents/team-lead.md +0 -171
- package/src/core/agents/test-analyzer-assertions.md +0 -181
- package/src/core/agents/test-analyzer-coverage.md +0 -183
- package/src/core/agents/test-analyzer-fragility.md +0 -185
- package/src/core/agents/test-analyzer-integration.md +0 -155
- package/src/core/agents/test-analyzer-maintenance.md +0 -173
- package/src/core/agents/test-analyzer-mocking.md +0 -178
- package/src/core/agents/test-analyzer-patterns.md +0 -189
- package/src/core/agents/test-analyzer-structure.md +0 -177
- package/src/core/agents/test-consensus.md +0 -294
- package/src/core/agents/testing.md +0 -527
- package/src/core/agents/ui-validator.md +0 -331
- package/src/core/agents/ui.md +0 -1227
- package/src/core/commands/adr/list.md +0 -191
- package/src/core/commands/adr/update.md +0 -258
- package/src/core/commands/adr/view.md +0 -274
- package/src/core/commands/adr.md +0 -394
- package/src/core/commands/ads/audit.md +0 -453
- package/src/core/commands/ads/budget.md +0 -97
- package/src/core/commands/ads/competitor.md +0 -112
- package/src/core/commands/ads/creative.md +0 -85
- package/src/core/commands/ads/generate.md +0 -238
- package/src/core/commands/ads/google.md +0 -112
- package/src/core/commands/ads/health.md +0 -327
- package/src/core/commands/ads/landing.md +0 -119
- package/src/core/commands/ads/linkedin.md +0 -112
- package/src/core/commands/ads/meta.md +0 -91
- package/src/core/commands/ads/microsoft.md +0 -115
- package/src/core/commands/ads/plan.md +0 -321
- package/src/core/commands/ads/test-plan.md +0 -317
- package/src/core/commands/ads/tiktok.md +0 -129
- package/src/core/commands/ads/track.md +0 -288
- package/src/core/commands/ads/youtube.md +0 -124
- package/src/core/commands/ads.md +0 -140
- package/src/core/commands/agent.md +0 -256
- package/src/core/commands/api.md +0 -267
- package/src/core/commands/assign.md +0 -369
- package/src/core/commands/audit.md +0 -531
- package/src/core/commands/auto.md +0 -556
- package/src/core/commands/automate.md +0 -415
- package/src/core/commands/babysit.md +0 -643
- package/src/core/commands/baseline.md +0 -743
- package/src/core/commands/batch.md +0 -551
- package/src/core/commands/blockers.md +0 -602
- package/src/core/commands/board.md +0 -509
- package/src/core/commands/browser-qa.md +0 -240
- package/src/core/commands/changelog.md +0 -582
- package/src/core/commands/choose.md +0 -430
- package/src/core/commands/ci.md +0 -330
- package/src/core/commands/code/accessibility.md +0 -363
- package/src/core/commands/code/api.md +0 -313
- package/src/core/commands/code/architecture.md +0 -313
- package/src/core/commands/code/completeness.md +0 -519
- package/src/core/commands/code/legal.md +0 -509
- package/src/core/commands/code/logic.md +0 -432
- package/src/core/commands/code/performance.md +0 -506
- package/src/core/commands/code/security.md +0 -509
- package/src/core/commands/code/test.md +0 -505
- package/src/core/commands/compress.md +0 -408
- package/src/core/commands/configure.md +0 -1159
- package/src/core/commands/context/export.md +0 -296
- package/src/core/commands/context/full.md +0 -353
- package/src/core/commands/context/note.md +0 -380
- package/src/core/commands/council.md +0 -592
- package/src/core/commands/debt.md +0 -491
- package/src/core/commands/deploy.md +0 -864
- package/src/core/commands/deps.md +0 -728
- package/src/core/commands/diagnose.md +0 -404
- package/src/core/commands/docs.md +0 -469
- package/src/core/commands/epic/edit.md +0 -213
- package/src/core/commands/epic/list.md +0 -190
- package/src/core/commands/epic/view.md +0 -267
- package/src/core/commands/epic.md +0 -477
- package/src/core/commands/export.md +0 -238
- package/src/core/commands/feedback.md +0 -603
- package/src/core/commands/handoff.md +0 -386
- package/src/core/commands/help.md +0 -194
- package/src/core/commands/ideate/brief.md +0 -363
- package/src/core/commands/ideate/discover.md +0 -399
- package/src/core/commands/ideate/features.md +0 -497
- package/src/core/commands/ideate/history.md +0 -403
- package/src/core/commands/ideate/new.md +0 -900
- package/src/core/commands/impact.md +0 -407
- package/src/core/commands/install.md +0 -529
- package/src/core/commands/learn/explain.md +0 -118
- package/src/core/commands/learn/glossary.md +0 -135
- package/src/core/commands/learn/patterns.md +0 -138
- package/src/core/commands/learn/tour.md +0 -126
- package/src/core/commands/maintain.md +0 -558
- package/src/core/commands/metrics.md +0 -844
- package/src/core/commands/migrate/codemods.md +0 -151
- package/src/core/commands/migrate/plan.md +0 -131
- package/src/core/commands/migrate/scan.md +0 -114
- package/src/core/commands/migrate/validate.md +0 -119
- package/src/core/commands/multi-expert.md +0 -447
- package/src/core/commands/packages.md +0 -535
- package/src/core/commands/pr.md +0 -337
- package/src/core/commands/readme-sync.md +0 -329
- package/src/core/commands/research/analyze.md +0 -798
- package/src/core/commands/research/ask.md +0 -864
- package/src/core/commands/research/import.md +0 -1025
- package/src/core/commands/research/list.md +0 -273
- package/src/core/commands/research/synthesize.md +0 -928
- package/src/core/commands/research/view.md +0 -323
- package/src/core/commands/retro.md +0 -795
- package/src/core/commands/review.md +0 -694
- package/src/core/commands/rlm.md +0 -446
- package/src/core/commands/roadmap/analyze.md +0 -400
- package/src/core/commands/rpi.md +0 -633
- package/src/core/commands/seo/audit.md +0 -444
- package/src/core/commands/seo/competitor.md +0 -174
- package/src/core/commands/seo/content.md +0 -107
- package/src/core/commands/seo/geo.md +0 -229
- package/src/core/commands/seo/hreflang.md +0 -140
- package/src/core/commands/seo/images.md +0 -96
- package/src/core/commands/seo/page.md +0 -198
- package/src/core/commands/seo/plan.md +0 -163
- package/src/core/commands/seo/programmatic.md +0 -131
- package/src/core/commands/seo/references/cwv-thresholds.md +0 -64
- package/src/core/commands/seo/references/eeat-framework.md +0 -110
- package/src/core/commands/seo/references/quality-gates.md +0 -91
- package/src/core/commands/seo/references/schema-types.md +0 -102
- package/src/core/commands/seo/schema.md +0 -183
- package/src/core/commands/seo/sitemap.md +0 -97
- package/src/core/commands/seo/technical.md +0 -100
- package/src/core/commands/seo.md +0 -107
- package/src/core/commands/session/cleanup.md +0 -452
- package/src/core/commands/session/end.md +0 -865
- package/src/core/commands/session/history.md +0 -293
- package/src/core/commands/session/init.md +0 -210
- package/src/core/commands/session/new.md +0 -827
- package/src/core/commands/session/resume.md +0 -291
- package/src/core/commands/session/spawn.md +0 -205
- package/src/core/commands/session/status.md +0 -274
- package/src/core/commands/skill/list.md +0 -139
- package/src/core/commands/skill/recommend.md +0 -216
- package/src/core/commands/sprint.md +0 -714
- package/src/core/commands/status/undo.md +0 -191
- package/src/core/commands/status.md +0 -423
- package/src/core/commands/story/edit.md +0 -204
- package/src/core/commands/story/list.md +0 -199
- package/src/core/commands/story/view.md +0 -312
- package/src/core/commands/story-validate.md +0 -491
- package/src/core/commands/story.md +0 -465
- package/src/core/commands/tdd-next.md +0 -238
- package/src/core/commands/tdd.md +0 -211
- package/src/core/commands/team/guide.md +0 -688
- package/src/core/commands/team/list.md +0 -59
- package/src/core/commands/team/start.md +0 -130
- package/src/core/commands/team/status.md +0 -66
- package/src/core/commands/team/stop.md +0 -78
- package/src/core/commands/template.md +0 -644
- package/src/core/commands/tests.md +0 -731
- package/src/core/commands/update.md +0 -591
- package/src/core/commands/validate-expertise.md +0 -305
- package/src/core/commands/velocity.md +0 -630
- package/src/core/commands/verify.md +0 -534
- package/src/core/commands/whats-new.md +0 -201
- package/src/core/commands/workflow.md +0 -449
- package/src/core/council/sessions/.gitkeep +0 -0
- package/src/core/council/shared_reasoning.template.md +0 -106
- package/src/core/experts/README.md +0 -236
- package/src/core/experts/_core-expertise.yaml +0 -105
- package/src/core/experts/accessibility/expertise.yaml +0 -115
- package/src/core/experts/accessibility/question.md +0 -41
- package/src/core/experts/accessibility/self-improve.md +0 -45
- package/src/core/experts/accessibility/workflow.md +0 -59
- package/src/core/experts/adr-writer/expertise.yaml +0 -138
- package/src/core/experts/adr-writer/question.md +0 -56
- package/src/core/experts/adr-writer/self-improve.md +0 -106
- package/src/core/experts/adr-writer/workflow.md +0 -184
- package/src/core/experts/analytics/expertise.yaml +0 -119
- package/src/core/experts/analytics/question.md +0 -74
- package/src/core/experts/analytics/self-improve.md +0 -163
- package/src/core/experts/analytics/workflow.md +0 -272
- package/src/core/experts/api/expertise.yaml +0 -124
- package/src/core/experts/api/question.md +0 -74
- package/src/core/experts/api/self-improve.md +0 -122
- package/src/core/experts/api/workflow.md +0 -248
- package/src/core/experts/ci/expertise.yaml +0 -106
- package/src/core/experts/ci/question.md +0 -69
- package/src/core/experts/ci/self-improve.md +0 -100
- package/src/core/experts/ci/workflow.md +0 -145
- package/src/core/experts/codebase-query/expertise.yaml +0 -121
- package/src/core/experts/codebase-query/question.md +0 -73
- package/src/core/experts/codebase-query/self-improve.md +0 -105
- package/src/core/experts/compliance/expertise.yaml +0 -101
- package/src/core/experts/compliance/question.md +0 -56
- package/src/core/experts/compliance/self-improve.md +0 -106
- package/src/core/experts/compliance/workflow.md +0 -184
- package/src/core/experts/database/expertise.yaml +0 -109
- package/src/core/experts/database/question.md +0 -74
- package/src/core/experts/database/self-improve.md +0 -121
- package/src/core/experts/database/workflow.md +0 -234
- package/src/core/experts/datamigration/expertise.yaml +0 -141
- package/src/core/experts/datamigration/question.md +0 -56
- package/src/core/experts/datamigration/self-improve.md +0 -106
- package/src/core/experts/datamigration/workflow.md +0 -184
- package/src/core/experts/design/expertise.yaml +0 -116
- package/src/core/experts/design/question.md +0 -56
- package/src/core/experts/design/self-improve.md +0 -106
- package/src/core/experts/design/workflow.md +0 -184
- package/src/core/experts/devops/expertise.yaml +0 -116
- package/src/core/experts/devops/question.md +0 -68
- package/src/core/experts/devops/self-improve.md +0 -102
- package/src/core/experts/devops/workflow.md +0 -142
- package/src/core/experts/documentation/expertise.yaml +0 -126
- package/src/core/experts/documentation/question.md +0 -41
- package/src/core/experts/documentation/self-improve.md +0 -45
- package/src/core/experts/documentation/workflow.md +0 -55
- package/src/core/experts/epic-planner/expertise.yaml +0 -144
- package/src/core/experts/epic-planner/question.md +0 -56
- package/src/core/experts/epic-planner/self-improve.md +0 -106
- package/src/core/experts/epic-planner/workflow.md +0 -184
- package/src/core/experts/integrations/expertise.yaml +0 -113
- package/src/core/experts/integrations/question.md +0 -74
- package/src/core/experts/integrations/self-improve.md +0 -151
- package/src/core/experts/integrations/workflow.md +0 -246
- package/src/core/experts/mentor/expertise.yaml +0 -125
- package/src/core/experts/mentor/question.md +0 -56
- package/src/core/experts/mentor/self-improve.md +0 -106
- package/src/core/experts/mentor/workflow.md +0 -184
- package/src/core/experts/mobile/expertise.yaml +0 -136
- package/src/core/experts/mobile/question.md +0 -72
- package/src/core/experts/mobile/self-improve.md +0 -140
- package/src/core/experts/mobile/workflow.md +0 -240
- package/src/core/experts/monitoring/expertise.yaml +0 -132
- package/src/core/experts/monitoring/question.md +0 -76
- package/src/core/experts/monitoring/self-improve.md +0 -150
- package/src/core/experts/monitoring/workflow.md +0 -264
- package/src/core/experts/performance/expertise.yaml +0 -68
- package/src/core/experts/performance/question.md +0 -41
- package/src/core/experts/performance/self-improve.md +0 -45
- package/src/core/experts/performance/workflow.md +0 -61
- package/src/core/experts/product/expertise.yaml +0 -143
- package/src/core/experts/product/question.md +0 -56
- package/src/core/experts/product/self-improve.md +0 -106
- package/src/core/experts/product/workflow.md +0 -184
- package/src/core/experts/qa/expertise.yaml +0 -110
- package/src/core/experts/qa/question.md +0 -56
- package/src/core/experts/qa/self-improve.md +0 -106
- package/src/core/experts/qa/workflow.md +0 -184
- package/src/core/experts/readme-updater/expertise.yaml +0 -141
- package/src/core/experts/readme-updater/question.md +0 -56
- package/src/core/experts/readme-updater/self-improve.md +0 -106
- package/src/core/experts/readme-updater/workflow.md +0 -184
- package/src/core/experts/refactor/expertise.yaml +0 -135
- package/src/core/experts/refactor/question.md +0 -41
- package/src/core/experts/refactor/self-improve.md +0 -45
- package/src/core/experts/refactor/workflow.md +0 -57
- package/src/core/experts/research/expertise.yaml +0 -143
- package/src/core/experts/research/question.md +0 -56
- package/src/core/experts/research/self-improve.md +0 -106
- package/src/core/experts/research/workflow.md +0 -184
- package/src/core/experts/security/expertise.yaml +0 -117
- package/src/core/experts/security/question.md +0 -77
- package/src/core/experts/security/self-improve.md +0 -102
- package/src/core/experts/security/workflow.md +0 -152
- package/src/core/experts/templates/expertise-template.yaml +0 -67
- package/src/core/experts/templates/question-template.md +0 -56
- package/src/core/experts/templates/self-improve-template.md +0 -106
- package/src/core/experts/templates/workflow-template.md +0 -184
- package/src/core/experts/testing/expertise.yaml +0 -112
- package/src/core/experts/testing/question.md +0 -68
- package/src/core/experts/testing/self-improve.md +0 -102
- package/src/core/experts/testing/workflow.md +0 -143
- package/src/core/experts/ui/expertise.yaml +0 -133
- package/src/core/experts/ui/question.md +0 -74
- package/src/core/experts/ui/self-improve.md +0 -122
- package/src/core/experts/ui/workflow.md +0 -262
- package/src/core/knowledge/ads/ad-audit-checklist-scoring.md +0 -424
- package/src/core/knowledge/ads/ad-optimization-logic.md +0 -590
- package/src/core/knowledge/ads/ad-technical-specifications.md +0 -385
- package/src/core/knowledge/ads/definitive-advertising-reference-2026.md +0 -506
- package/src/core/knowledge/ads/paid-advertising-research-2026.md +0 -445
- package/src/core/profiles/COMPARISON.md +0 -170
- package/src/core/profiles/README.md +0 -178
- package/src/core/profiles/claude-code.yaml +0 -111
- package/src/core/profiles/codex.yaml +0 -103
- package/src/core/profiles/cursor.yaml +0 -134
- package/src/core/profiles/examples.js +0 -250
- package/src/core/profiles/loader.js +0 -235
- package/src/core/profiles/windsurf.yaml +0 -159
- package/src/core/skills/_learnings/README.md +0 -91
- package/src/core/skills/_learnings/_template.yaml +0 -106
- package/src/core/skills/_learnings/code-review.yaml +0 -118
- package/src/core/skills/_learnings/commit.yaml +0 -69
- package/src/core/skills/_learnings/story-writer.yaml +0 -71
- package/src/core/teams/backend.json +0 -41
- package/src/core/teams/builder-validator.json +0 -51
- package/src/core/teams/code-review.json +0 -41
- package/src/core/teams/frontend.json +0 -41
- package/src/core/teams/fullstack.json +0 -41
- package/src/core/teams/logic-audit.json +0 -53
- package/src/core/teams/perf-audit.json +0 -71
- package/src/core/teams/qa.json +0 -41
- package/src/core/teams/security-audit.json +0 -71
- package/src/core/teams/solo.json +0 -35
- package/src/core/teams/test-audit.json +0 -71
- package/src/core/templates/CONTEXT.md.example +0 -49
- package/src/core/templates/README-template.md +0 -16
- package/src/core/templates/adr-template.md +0 -28
- package/src/core/templates/agent-coordination-pattern.md +0 -38
- package/src/core/templates/agent-profile-template.md +0 -51
- package/src/core/templates/agileflow-metadata.json +0 -150
- package/src/core/templates/browser-qa-spec.yaml +0 -94
- package/src/core/templates/ci-workflow.yml +0 -74
- package/src/core/templates/claude-settings.advanced.example.json +0 -75
- package/src/core/templates/claude-settings.example.json +0 -26
- package/src/core/templates/command-documentation.md +0 -187
- package/src/core/templates/command-prerequisites.yaml +0 -169
- package/src/core/templates/comms-note-template.md +0 -24
- package/src/core/templates/damage-control-patterns.yaml +0 -243
- package/src/core/templates/environment.json +0 -18
- package/src/core/templates/epic-template.md +0 -27
- package/src/core/templates/plan-template.md +0 -125
- package/src/core/templates/preserve-rules-common.md +0 -107
- package/src/core/templates/preserve-rules.json +0 -42
- package/src/core/templates/proactive-action-spec.md +0 -29
- package/src/core/templates/product-brief.md +0 -136
- package/src/core/templates/quality-gate-priorities.md +0 -34
- package/src/core/templates/research-template.md +0 -44
- package/src/core/templates/session-harness-protocol.md +0 -128
- package/src/core/templates/session-state.json +0 -56
- package/src/core/templates/story-lifecycle.md +0 -213
- package/src/core/templates/story-template.md +0 -92
- package/src/core/templates/tdd-test-template.js +0 -241
- package/src/core/templates/worktrees-guide.md +0 -231
- package/tools/agileflow-npx.js +0 -52
- package/tools/cli/agileflow-cli.js +0 -72
- package/tools/cli/commands/config.js +0 -285
- package/tools/cli/commands/doctor.js +0 -496
- package/tools/cli/commands/list.js +0 -385
- package/tools/cli/commands/session.js +0 -1176
- package/tools/cli/commands/setup.js +0 -255
- package/tools/cli/commands/status.js +0 -101
- package/tools/cli/commands/tui.js +0 -56
- package/tools/cli/commands/uninstall.js +0 -155
- package/tools/cli/commands/update.js +0 -299
- package/tools/cli/installers/core/installer.js +0 -892
- package/tools/cli/installers/ide/_base-ide.js +0 -518
- package/tools/cli/installers/ide/_interface.js +0 -238
- package/tools/cli/installers/ide/claude-code.js +0 -432
- package/tools/cli/installers/ide/codex.js +0 -426
- package/tools/cli/installers/ide/cursor.js +0 -217
- package/tools/cli/installers/ide/manager.js +0 -222
- package/tools/cli/installers/ide/windsurf.js +0 -282
- package/tools/cli/lib/command-context.js +0 -382
- package/tools/cli/lib/config-manager.js +0 -446
- package/tools/cli/lib/content-injector.js +0 -969
- package/tools/cli/lib/content-transformer.js +0 -496
- package/tools/cli/lib/docs-setup.js +0 -464
- package/tools/cli/lib/error-handler.js +0 -165
- package/tools/cli/lib/ide-error-factory.js +0 -421
- package/tools/cli/lib/ide-errors.js +0 -367
- package/tools/cli/lib/ide-generator.js +0 -357
- package/tools/cli/lib/ide-health-monitor.js +0 -364
- package/tools/cli/lib/ide-registry.js +0 -297
- package/tools/cli/lib/npm-utils.js +0 -103
- package/tools/cli/lib/self-update.js +0 -148
- package/tools/cli/lib/ui.js +0 -211
- package/tools/cli/lib/utils.js +0 -87
- package/tools/cli/lib/validation-middleware.js +0 -491
- package/tools/cli/lib/version-checker.js +0 -95
- package/tools/postinstall.js +0 -190
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync engine — SHA256-based conflict-aware file installer.
|
|
3
|
+
*
|
|
4
|
+
* Ported from v3 `packages/cli/tools/cli/installers/core/installer.js`
|
|
5
|
+
* lines 349-455 (`copyFileWithPolicy`). Same decision tree, rewritten in
|
|
6
|
+
* plain JS + JSDoc and split into testable pieces.
|
|
7
|
+
*
|
|
8
|
+
* The engine handles one file at a time and returns the action taken.
|
|
9
|
+
* Callers loop over plugin content and aggregate results into
|
|
10
|
+
* `FileOpsCounters`.
|
|
11
|
+
*
|
|
12
|
+
* Decision tree (same as v3):
|
|
13
|
+
*
|
|
14
|
+
* destExists?
|
|
15
|
+
* no → write; record fresh hash; CREATED
|
|
16
|
+
* yes → force?
|
|
17
|
+
* yes → overwrite; record fresh hash; UPDATED
|
|
18
|
+
* no → compute currentHash of dest
|
|
19
|
+
* protected flag in index?
|
|
20
|
+
* yes → currentHash == newHash?
|
|
21
|
+
* yes → clear protected; UNCHANGED (auto-converged)
|
|
22
|
+
* no → stash new; keep user file; PRESERVED
|
|
23
|
+
* no → index.sha256 == currentHash? (file untouched since last install)
|
|
24
|
+
* yes → currentHash != newHash? write new; record hash; UPDATED
|
|
25
|
+
* else: nothing to do; UNCHANGED
|
|
26
|
+
* no → (unknown baseline / locally modified)
|
|
27
|
+
* currentHash == newHash?
|
|
28
|
+
* yes → adopt new hash; UNCHANGED
|
|
29
|
+
* no → stash new; keep user file; PRESERVED
|
|
30
|
+
*
|
|
31
|
+
* The `fileIndex.files[relativePath]` entry is always updated so that
|
|
32
|
+
* the next sync run has an accurate baseline.
|
|
33
|
+
*/
|
|
34
|
+
const fs = require('fs');
|
|
35
|
+
const path = require('path');
|
|
36
|
+
|
|
37
|
+
const { sha256Hex, sha256File } = require('../../lib/hash.js');
|
|
38
|
+
const { writeStash } = require('./stash.js');
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @typedef {'created' | 'updated' | 'preserved' | 'unchanged'} SyncAction
|
|
42
|
+
*
|
|
43
|
+
* @typedef {Object} FileOpsCounters
|
|
44
|
+
* @property {number} created
|
|
45
|
+
* @property {number} updated
|
|
46
|
+
* @property {number} preserved
|
|
47
|
+
* @property {number} unchanged
|
|
48
|
+
* @property {number} stashed
|
|
49
|
+
* @property {number} removed
|
|
50
|
+
* @property {string|null} updatesPath
|
|
51
|
+
*
|
|
52
|
+
* @typedef {Object} SyncOptions
|
|
53
|
+
* @property {Buffer|string} content - bytes to write at `dest`
|
|
54
|
+
* @property {string} dest - absolute destination path
|
|
55
|
+
* @property {string} relativePath - posix-style path used as the fileIndex key
|
|
56
|
+
* @property {import('./file-index.js').FileIndex} fileIndex - mutated with new hashes
|
|
57
|
+
* @property {string} cfgDir - absolute path to .agileflow/_cfg/
|
|
58
|
+
* @property {string} timestamp - ISO stamp used as the stash bucket
|
|
59
|
+
* @property {boolean} [force=false] - if true, overwrite user modifications
|
|
60
|
+
* @property {FileOpsCounters} [ops] - optional counters; if omitted, caller loses aggregate info
|
|
61
|
+
*/
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* @returns {FileOpsCounters}
|
|
65
|
+
*/
|
|
66
|
+
function emptyCounters() {
|
|
67
|
+
return {
|
|
68
|
+
created: 0,
|
|
69
|
+
updated: 0,
|
|
70
|
+
preserved: 0,
|
|
71
|
+
unchanged: 0,
|
|
72
|
+
stashed: 0,
|
|
73
|
+
removed: 0,
|
|
74
|
+
updatesPath: null,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Write `content` to `dest` atomically: write a temp file in the same
|
|
80
|
+
* directory, then `rename` it into place. Crashes mid-`writeFile`
|
|
81
|
+
* cannot leave a truncated `dest` — readers always see either the old
|
|
82
|
+
* content or the new content, never a half-write.
|
|
83
|
+
*
|
|
84
|
+
* Temp filename uses pid + random suffix so two concurrent calls in
|
|
85
|
+
* the same process (rare but possible in tests / cluster setups) don't
|
|
86
|
+
* collide on each other's temp file.
|
|
87
|
+
*
|
|
88
|
+
* @param {Buffer|string} content
|
|
89
|
+
* @param {string} dest
|
|
90
|
+
*/
|
|
91
|
+
async function writeContent(content, dest) {
|
|
92
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true });
|
|
93
|
+
const tmp = `${dest}.tmp-${process.pid}-${Math.random().toString(36).slice(2, 10)}`;
|
|
94
|
+
try {
|
|
95
|
+
if (typeof content === 'string') {
|
|
96
|
+
await fs.promises.writeFile(tmp, content, 'utf8');
|
|
97
|
+
} else {
|
|
98
|
+
await fs.promises.writeFile(tmp, content);
|
|
99
|
+
}
|
|
100
|
+
await fs.promises.rename(tmp, dest);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
try {
|
|
103
|
+
await fs.promises.unlink(tmp);
|
|
104
|
+
} catch {
|
|
105
|
+
/* swallow */
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sync a single file. Returns the action taken and mutates `fileIndex`
|
|
113
|
+
* plus `ops` (if provided).
|
|
114
|
+
*
|
|
115
|
+
* @param {SyncOptions} options
|
|
116
|
+
* @returns {Promise<SyncAction>}
|
|
117
|
+
*/
|
|
118
|
+
async function syncFile(options) {
|
|
119
|
+
const {
|
|
120
|
+
content,
|
|
121
|
+
dest,
|
|
122
|
+
relativePath,
|
|
123
|
+
fileIndex,
|
|
124
|
+
cfgDir,
|
|
125
|
+
timestamp,
|
|
126
|
+
force = false,
|
|
127
|
+
ops,
|
|
128
|
+
} = options;
|
|
129
|
+
|
|
130
|
+
const newHash = sha256Hex(content);
|
|
131
|
+
const existing = fileIndex.files[relativePath];
|
|
132
|
+
const existingRecord =
|
|
133
|
+
existing && typeof existing === 'object' && typeof existing.sha256 === 'string'
|
|
134
|
+
? existing
|
|
135
|
+
: null;
|
|
136
|
+
|
|
137
|
+
const currentHash = await sha256File(dest);
|
|
138
|
+
const destExists = currentHash !== null;
|
|
139
|
+
|
|
140
|
+
if (!destExists) {
|
|
141
|
+
await writeContent(content, dest);
|
|
142
|
+
fileIndex.files[relativePath] = { sha256: newHash, protected: false };
|
|
143
|
+
if (ops) ops.created++;
|
|
144
|
+
return 'created';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (force) {
|
|
148
|
+
await writeContent(content, dest);
|
|
149
|
+
fileIndex.files[relativePath] = { sha256: newHash, protected: false };
|
|
150
|
+
if (ops) ops.updated++;
|
|
151
|
+
return 'updated';
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Respect a previously-protected file unless it has since converged
|
|
155
|
+
// with upstream (user's edits happen to match the new content — rare
|
|
156
|
+
// but worth auto-clearing the protected flag so future updates flow).
|
|
157
|
+
if (existingRecord && existingRecord.protected) {
|
|
158
|
+
if (currentHash === newHash) {
|
|
159
|
+
fileIndex.files[relativePath] = { sha256: newHash, protected: false };
|
|
160
|
+
if (ops) ops.unchanged++;
|
|
161
|
+
return 'unchanged';
|
|
162
|
+
}
|
|
163
|
+
const stashPath = await writeStash({ cfgDir, timestamp, relativePath, content });
|
|
164
|
+
fileIndex.files[relativePath] = { sha256: currentHash, protected: true };
|
|
165
|
+
if (ops) {
|
|
166
|
+
ops.preserved++;
|
|
167
|
+
ops.stashed++;
|
|
168
|
+
ops.updatesPath = path.dirname(stashPath);
|
|
169
|
+
}
|
|
170
|
+
return 'preserved';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Baseline path: user hasn't touched this file since the last install.
|
|
174
|
+
if (existingRecord && existingRecord.sha256 === currentHash) {
|
|
175
|
+
if (currentHash === newHash) {
|
|
176
|
+
// Nothing to do; record stays current.
|
|
177
|
+
fileIndex.files[relativePath] = { sha256: newHash, protected: false };
|
|
178
|
+
if (ops) ops.unchanged++;
|
|
179
|
+
return 'unchanged';
|
|
180
|
+
}
|
|
181
|
+
await writeContent(content, dest);
|
|
182
|
+
fileIndex.files[relativePath] = { sha256: newHash, protected: false };
|
|
183
|
+
if (ops) ops.updated++;
|
|
184
|
+
return 'updated';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// No baseline OR user-modified file. If the current content happens to
|
|
188
|
+
// match upstream, adopt the hash and move on; otherwise preserve+stash.
|
|
189
|
+
if (currentHash === newHash) {
|
|
190
|
+
fileIndex.files[relativePath] = { sha256: newHash, protected: false };
|
|
191
|
+
if (ops) ops.unchanged++;
|
|
192
|
+
return 'unchanged';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const stashPath = await writeStash({ cfgDir, timestamp, relativePath, content });
|
|
196
|
+
fileIndex.files[relativePath] = { sha256: currentHash, protected: true };
|
|
197
|
+
if (ops) {
|
|
198
|
+
ops.preserved++;
|
|
199
|
+
ops.stashed++;
|
|
200
|
+
ops.updatesPath = path.dirname(stashPath);
|
|
201
|
+
}
|
|
202
|
+
return 'preserved';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = { syncFile, emptyCounters };
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin registry — discovers plugin manifests under apps/cli/content/plugins/.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2a scope: discover + shallow-validate plugin.yaml. Full validation
|
|
5
|
+
* (depends resolution, cycle detection, command/skill cross-refs) lands
|
|
6
|
+
* with the full validator in Phase 2b.
|
|
7
|
+
*/
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const yaml = require('js-yaml');
|
|
11
|
+
|
|
12
|
+
const PLUGINS_DIR = path.join(__dirname, '..', '..', '..', 'content', 'plugins');
|
|
13
|
+
|
|
14
|
+
const REQUIRED_FIELDS = ['id', 'name', 'description', 'version'];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {Object} PluginManifest
|
|
18
|
+
* @property {string} id
|
|
19
|
+
* @property {string} name
|
|
20
|
+
* @property {string} description
|
|
21
|
+
* @property {string} version
|
|
22
|
+
* @property {boolean} [enabledByDefault]
|
|
23
|
+
* @property {boolean} [cannotDisable]
|
|
24
|
+
* @property {string[]} [depends]
|
|
25
|
+
* @property {{
|
|
26
|
+
* commands?: object[],
|
|
27
|
+
* skills?: object[],
|
|
28
|
+
* agents?: object[],
|
|
29
|
+
* hooks?: object[],
|
|
30
|
+
* templates?: object[],
|
|
31
|
+
* }} [provides]
|
|
32
|
+
* @property {string} dir - absolute path of the plugin directory (computed)
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse and validate a single plugin.yaml.
|
|
37
|
+
* @param {string} pluginDir
|
|
38
|
+
* @returns {PluginManifest}
|
|
39
|
+
*/
|
|
40
|
+
function loadPlugin(pluginDir) {
|
|
41
|
+
const manifestPath = path.join(pluginDir, 'plugin.yaml');
|
|
42
|
+
if (!fs.existsSync(manifestPath)) {
|
|
43
|
+
throw new Error(`Missing plugin.yaml at ${pluginDir}`);
|
|
44
|
+
}
|
|
45
|
+
let raw;
|
|
46
|
+
try {
|
|
47
|
+
raw = fs.readFileSync(manifestPath, 'utf8');
|
|
48
|
+
} catch (err) {
|
|
49
|
+
throw new Error(`Cannot read ${manifestPath}: ${err.message}`);
|
|
50
|
+
}
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = yaml.load(raw);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
throw new Error(`Invalid YAML in ${manifestPath}: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
58
|
+
throw new Error(`Empty or non-object plugin.yaml at ${manifestPath}`);
|
|
59
|
+
}
|
|
60
|
+
const missing = REQUIRED_FIELDS.filter((k) => parsed[k] == null);
|
|
61
|
+
if (missing.length) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Plugin ${path.basename(pluginDir)} missing required fields: ${missing.join(', ')}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
// `depends` is optional, but if present it MUST be an array — silently
|
|
67
|
+
// coercing `depends: "core"` to `[]` would drop the author's intent.
|
|
68
|
+
let depends = [];
|
|
69
|
+
if (parsed.depends != null) {
|
|
70
|
+
if (!Array.isArray(parsed.depends)) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Plugin ${path.basename(pluginDir)}: 'depends' must be an array of plugin ids (got ${typeof parsed.depends})`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
depends = parsed.depends;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
id: parsed.id,
|
|
79
|
+
name: parsed.name,
|
|
80
|
+
description: parsed.description,
|
|
81
|
+
version: parsed.version,
|
|
82
|
+
enabledByDefault: Boolean(parsed.enabledByDefault),
|
|
83
|
+
cannotDisable: Boolean(parsed.cannotDisable),
|
|
84
|
+
depends,
|
|
85
|
+
provides: parsed.provides || {
|
|
86
|
+
commands: [],
|
|
87
|
+
skills: [],
|
|
88
|
+
agents: [],
|
|
89
|
+
hooks: [],
|
|
90
|
+
templates: [],
|
|
91
|
+
},
|
|
92
|
+
dir: pluginDir,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Discover all plugins in the bundled content/plugins/ directory.
|
|
98
|
+
* @param {string} [root=PLUGINS_DIR]
|
|
99
|
+
* @returns {PluginManifest[]} sorted: required (cannotDisable) first, then alpha by id
|
|
100
|
+
*/
|
|
101
|
+
function discoverPlugins(root = PLUGINS_DIR) {
|
|
102
|
+
if (!fs.existsSync(root)) return [];
|
|
103
|
+
const entries = fs.readdirSync(root, { withFileTypes: true });
|
|
104
|
+
const plugins = entries
|
|
105
|
+
.filter((e) => e.isDirectory())
|
|
106
|
+
.map((e) => loadPlugin(path.join(root, e.name)));
|
|
107
|
+
plugins.sort((a, b) => {
|
|
108
|
+
if (a.cannotDisable !== b.cannotDisable) {
|
|
109
|
+
return a.cannotDisable ? -1 : 1;
|
|
110
|
+
}
|
|
111
|
+
return a.id.localeCompare(b.id);
|
|
112
|
+
});
|
|
113
|
+
const ids = new Set();
|
|
114
|
+
for (const p of plugins) {
|
|
115
|
+
if (ids.has(p.id)) {
|
|
116
|
+
throw new Error(`Duplicate plugin id: ${p.id}`);
|
|
117
|
+
}
|
|
118
|
+
ids.add(p.id);
|
|
119
|
+
}
|
|
120
|
+
return plugins;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @param {string} id
|
|
125
|
+
* @param {string} [root=PLUGINS_DIR]
|
|
126
|
+
* @returns {PluginManifest|null}
|
|
127
|
+
*/
|
|
128
|
+
function getPlugin(id, root = PLUGINS_DIR) {
|
|
129
|
+
return discoverPlugins(root).find((p) => p.id === id) || null;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = { discoverPlugins, getPlugin, loadPlugin, PLUGINS_DIR };
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin dependency resolver.
|
|
3
|
+
*
|
|
4
|
+
* Given a set of discovered plugins and the user's selection, computes:
|
|
5
|
+
* - the transitive closure of dependencies (auto-enable anything the
|
|
6
|
+
* user-selected plugins depend on),
|
|
7
|
+
* - a topological install order (dependencies before dependents),
|
|
8
|
+
* - cycle detection (throws with the offending cycle path).
|
|
9
|
+
*
|
|
10
|
+
* Plugins with `cannotDisable: true` (currently just `core`) are always
|
|
11
|
+
* included in the resolved set even if not in `userSelected`.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {import('./registry.js').PluginManifest} PluginManifest
|
|
16
|
+
*
|
|
17
|
+
* @typedef {Object} ResolveResult
|
|
18
|
+
* @property {PluginManifest[]} ordered
|
|
19
|
+
* Plugins in install order — dependencies appear before dependents.
|
|
20
|
+
* @property {string[]} autoEnabled
|
|
21
|
+
* Ids that were pulled in because something else depended on them
|
|
22
|
+
* (i.e. not in `userSelected` and not `cannotDisable`).
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const COLOR_WHITE = 0;
|
|
26
|
+
const COLOR_GRAY = 1;
|
|
27
|
+
const COLOR_BLACK = 2;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validate that every plugin's `depends` references resolve to a known
|
|
31
|
+
* id in the discovered set. Thrown errors quote both the dependent and
|
|
32
|
+
* the missing dependency.
|
|
33
|
+
*
|
|
34
|
+
* @param {PluginManifest[]} discovered
|
|
35
|
+
* @param {Map<string, PluginManifest>} byId
|
|
36
|
+
*/
|
|
37
|
+
function assertDependsExist(discovered, byId) {
|
|
38
|
+
for (const p of discovered) {
|
|
39
|
+
for (const dep of p.depends || []) {
|
|
40
|
+
if (typeof dep !== 'string' || !dep) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Plugin "${p.id}" has an invalid entry in 'depends': ${JSON.stringify(dep)}`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
if (!byId.has(dep)) {
|
|
46
|
+
throw new Error(
|
|
47
|
+
`Plugin "${p.id}" depends on unknown plugin "${dep}"`,
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {PluginManifest[]} discovered
|
|
56
|
+
* @param {Iterable<string>} userSelected - ids the user explicitly enabled
|
|
57
|
+
* @returns {ResolveResult}
|
|
58
|
+
*/
|
|
59
|
+
function resolvePlugins(discovered, userSelected) {
|
|
60
|
+
const byId = new Map(discovered.map((p) => [p.id, p]));
|
|
61
|
+
assertDependsExist(discovered, byId);
|
|
62
|
+
|
|
63
|
+
const userSet = new Set(userSelected);
|
|
64
|
+
// Reject unknown user-selected ids loudly so callers don't silently
|
|
65
|
+
// drop a typo. The wizard's pluginsFromCsv layer also surfaces this,
|
|
66
|
+
// but direct callers of resolvePlugins (CI, programmatic install)
|
|
67
|
+
// get the same protection here.
|
|
68
|
+
for (const id of userSet) {
|
|
69
|
+
if (!byId.has(id)) {
|
|
70
|
+
const known = [...byId.keys()].sort().join(', ');
|
|
71
|
+
throw new Error(
|
|
72
|
+
`User-selected plugin "${id}" was not discovered. Available: ${known || '(none)'}`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Initial target set: cannotDisable (core) + user-selected.
|
|
77
|
+
/** @type {Set<string>} */
|
|
78
|
+
const target = new Set();
|
|
79
|
+
for (const p of discovered) {
|
|
80
|
+
if (p.cannotDisable || userSet.has(p.id)) {
|
|
81
|
+
target.add(p.id);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Walk transitive dependencies to grow `target`.
|
|
86
|
+
const queue = [...target];
|
|
87
|
+
while (queue.length) {
|
|
88
|
+
const id = queue.shift();
|
|
89
|
+
const p = byId.get(id);
|
|
90
|
+
for (const dep of p.depends || []) {
|
|
91
|
+
if (!target.has(dep)) {
|
|
92
|
+
target.add(dep);
|
|
93
|
+
queue.push(dep);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// DFS topological sort with three-color cycle detection.
|
|
99
|
+
/** @type {Map<string, number>} */
|
|
100
|
+
const color = new Map();
|
|
101
|
+
/** @type {string[]} */
|
|
102
|
+
const order = [];
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} id
|
|
106
|
+
* @param {string[]} stack - the current DFS path; used for cycle reporting
|
|
107
|
+
*/
|
|
108
|
+
function visit(id, stack) {
|
|
109
|
+
const c = color.get(id) ?? COLOR_WHITE;
|
|
110
|
+
if (c === COLOR_BLACK) return;
|
|
111
|
+
if (c === COLOR_GRAY) {
|
|
112
|
+
const cycleStart = stack.indexOf(id);
|
|
113
|
+
const cycle = [...stack.slice(cycleStart), id].join(' -> ');
|
|
114
|
+
throw new Error(`Plugin dependency cycle detected: ${cycle}`);
|
|
115
|
+
}
|
|
116
|
+
color.set(id, COLOR_GRAY);
|
|
117
|
+
const p = byId.get(id);
|
|
118
|
+
const nextStack = [...stack, id];
|
|
119
|
+
for (const dep of p.depends || []) {
|
|
120
|
+
visit(dep, nextStack);
|
|
121
|
+
}
|
|
122
|
+
color.set(id, COLOR_BLACK);
|
|
123
|
+
order.push(id);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const id of target) {
|
|
127
|
+
visit(id, []);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const ordered = order.map((id) => byId.get(id));
|
|
131
|
+
const autoEnabled = [...target].filter((id) => {
|
|
132
|
+
const p = byId.get(id);
|
|
133
|
+
return !userSet.has(id) && !p.cannotDisable;
|
|
134
|
+
});
|
|
135
|
+
return { ordered, autoEnabled };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = { resolvePlugins };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict plugin.yaml validator.
|
|
3
|
+
*
|
|
4
|
+
* Layered on top of `registry.js::loadPlugin`, which already does
|
|
5
|
+
* lightweight YAML-parse + required-field checks. The strict validator
|
|
6
|
+
* adds quality / consistency rules that we want to enforce in CI and in
|
|
7
|
+
* the upcoming `agileflow doctor` command, but which we don't want to
|
|
8
|
+
* fail on at first-load (so a slightly nonconforming third-party plugin
|
|
9
|
+
* still partially loads for diagnostic purposes).
|
|
10
|
+
*
|
|
11
|
+
* Returns an array of `Issue` objects. Empty array means valid.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} Issue
|
|
16
|
+
* @property {'error' | 'warning'} severity
|
|
17
|
+
* @property {string} pluginId
|
|
18
|
+
* @property {string} message
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('./registry.js').PluginManifest} PluginManifest
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
|
|
24
|
+
// Permissive semver: MAJOR.MINOR.PATCH with optional -prerelease and +build.
|
|
25
|
+
const SEMVER_PATTERN =
|
|
26
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
27
|
+
|
|
28
|
+
/** @type {ReadonlySet<string>} */
|
|
29
|
+
const VALID_PROVIDES_KEYS = new Set([
|
|
30
|
+
'commands',
|
|
31
|
+
'skills',
|
|
32
|
+
'agents',
|
|
33
|
+
'hooks',
|
|
34
|
+
'templates',
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validate a single plugin manifest. Pure: no I/O.
|
|
39
|
+
*
|
|
40
|
+
* @param {PluginManifest} plugin
|
|
41
|
+
* @returns {Issue[]}
|
|
42
|
+
*/
|
|
43
|
+
function validatePlugin(plugin) {
|
|
44
|
+
/** @type {Issue[]} */
|
|
45
|
+
const issues = [];
|
|
46
|
+
const id = plugin && typeof plugin.id === 'string' ? plugin.id : '<unknown>';
|
|
47
|
+
|
|
48
|
+
const error = (msg) => issues.push({ severity: 'error', pluginId: id, message: msg });
|
|
49
|
+
const warn = (msg) => issues.push({ severity: 'warning', pluginId: id, message: msg });
|
|
50
|
+
|
|
51
|
+
if (!plugin || typeof plugin !== 'object') {
|
|
52
|
+
return [{ severity: 'error', pluginId: id, message: 'Plugin manifest is not an object.' }];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// id format
|
|
56
|
+
if (typeof plugin.id !== 'string') {
|
|
57
|
+
error('`id` must be a string.');
|
|
58
|
+
} else if (!ID_PATTERN.test(plugin.id)) {
|
|
59
|
+
error(
|
|
60
|
+
`\`id\` "${plugin.id}" must match /^[a-z0-9][a-z0-9-]{0,63}$/ (lowercase, kebab-case, max 64 chars).`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// name
|
|
65
|
+
if (typeof plugin.name !== 'string' || !plugin.name.trim()) {
|
|
66
|
+
error('`name` must be a non-empty string.');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// description
|
|
70
|
+
if (typeof plugin.description !== 'string' || !plugin.description.trim()) {
|
|
71
|
+
error('`description` must be a non-empty string.');
|
|
72
|
+
} else if (plugin.description.length < 16) {
|
|
73
|
+
warn('`description` is very short — aim for at least one meaningful sentence.');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// version
|
|
77
|
+
if (typeof plugin.version !== 'string') {
|
|
78
|
+
error('`version` must be a string (semver).');
|
|
79
|
+
} else if (!SEMVER_PATTERN.test(plugin.version)) {
|
|
80
|
+
error(`\`version\` "${plugin.version}" must be a valid semver (MAJOR.MINOR.PATCH).`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// booleans
|
|
84
|
+
if (typeof plugin.enabledByDefault !== 'boolean') {
|
|
85
|
+
error('`enabledByDefault` must be a boolean.');
|
|
86
|
+
}
|
|
87
|
+
if (typeof plugin.cannotDisable !== 'boolean') {
|
|
88
|
+
error('`cannotDisable` must be a boolean.');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// cannotDisable implies enabledByDefault
|
|
92
|
+
if (plugin.cannotDisable === true && plugin.enabledByDefault === false) {
|
|
93
|
+
error('A plugin with `cannotDisable: true` must also have `enabledByDefault: true`.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// depends
|
|
97
|
+
if (!Array.isArray(plugin.depends)) {
|
|
98
|
+
error('`depends` must be an array of plugin ids.');
|
|
99
|
+
} else {
|
|
100
|
+
for (const dep of plugin.depends) {
|
|
101
|
+
if (typeof dep !== 'string' || !ID_PATTERN.test(dep)) {
|
|
102
|
+
error(`\`depends\` entry ${JSON.stringify(dep)} must be a valid plugin id.`);
|
|
103
|
+
}
|
|
104
|
+
if (typeof dep === 'string' && dep === plugin.id) {
|
|
105
|
+
error('A plugin cannot depend on itself.');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
for (const dep of plugin.depends) {
|
|
110
|
+
if (typeof dep === 'string') {
|
|
111
|
+
if (seen.has(dep)) {
|
|
112
|
+
warn(`Duplicate dependency "${dep}" in \`depends\` (deduplicate).`);
|
|
113
|
+
}
|
|
114
|
+
seen.add(dep);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// provides — allowed shape
|
|
120
|
+
if (plugin.provides != null) {
|
|
121
|
+
if (typeof plugin.provides !== 'object' || Array.isArray(plugin.provides)) {
|
|
122
|
+
error('`provides` must be an object with arrays of commands/skills/agents/hooks/templates.');
|
|
123
|
+
} else {
|
|
124
|
+
for (const key of Object.keys(plugin.provides)) {
|
|
125
|
+
if (!VALID_PROVIDES_KEYS.has(key)) {
|
|
126
|
+
warn(`\`provides.${key}\` is not a recognized key — expected one of: ${[...VALID_PROVIDES_KEYS].join(', ')}.`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const key of VALID_PROVIDES_KEYS) {
|
|
130
|
+
const v = plugin.provides[key];
|
|
131
|
+
if (v != null && !Array.isArray(v)) {
|
|
132
|
+
error(`\`provides.${key}\` must be an array (got ${typeof v}).`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return issues;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Validate all plugins together, including cross-plugin invariants:
|
|
143
|
+
* - unique ids (registry already detects this, but we double-check)
|
|
144
|
+
* - depends references resolve to known ids
|
|
145
|
+
*
|
|
146
|
+
* @param {PluginManifest[]} plugins
|
|
147
|
+
* @returns {Issue[]}
|
|
148
|
+
*/
|
|
149
|
+
function validatePluginSet(plugins) {
|
|
150
|
+
/** @type {Issue[]} */
|
|
151
|
+
const issues = [];
|
|
152
|
+
for (const p of plugins) {
|
|
153
|
+
issues.push(...validatePlugin(p));
|
|
154
|
+
}
|
|
155
|
+
const ids = new Set();
|
|
156
|
+
for (const p of plugins) {
|
|
157
|
+
if (typeof p.id === 'string') {
|
|
158
|
+
if (ids.has(p.id)) {
|
|
159
|
+
issues.push({
|
|
160
|
+
severity: 'error',
|
|
161
|
+
pluginId: p.id,
|
|
162
|
+
message: `Duplicate plugin id "${p.id}".`,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
ids.add(p.id);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const p of plugins) {
|
|
169
|
+
for (const dep of p.depends || []) {
|
|
170
|
+
if (typeof dep === 'string' && !ids.has(dep)) {
|
|
171
|
+
issues.push({
|
|
172
|
+
severity: 'error',
|
|
173
|
+
pluginId: p.id,
|
|
174
|
+
message: `Depends on unknown plugin "${dep}".`,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return issues;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @param {Issue[]} issues
|
|
184
|
+
* @returns {boolean}
|
|
185
|
+
*/
|
|
186
|
+
function hasErrors(issues) {
|
|
187
|
+
return issues.some((i) => i.severity === 'error');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = {
|
|
191
|
+
validatePlugin,
|
|
192
|
+
validatePluginSet,
|
|
193
|
+
hasErrors,
|
|
194
|
+
ID_PATTERN,
|
|
195
|
+
SEMVER_PATTERN,
|
|
196
|
+
};
|