@polymorphism-tech/morph-spec 8.47.0 → 9.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +51 -43
- package/README.md +39 -38
- package/bin/morph-spec.js +41 -113
- package/bin/validate.js +1 -147
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +86 -21
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +1 -1
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +1 -1
- package/framework/commands/morph-preflight.md +2 -3
- package/framework/commands/morph-status.md +4 -4
- package/framework/docs/CLI.md +574 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +3 -17
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +15 -63
- package/framework/hooks/claude-code/core/gate-guard.js +9 -78
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +36 -2
- package/framework/hooks/claude-code/core/protect-spec-files.js +25 -297
- package/framework/hooks/claude-code/core/state-sync.js +15 -85
- package/framework/hooks/claude-code/core/telemetry-log.js +15 -96
- package/framework/hooks/claude-code/core/validate-completion.js +37 -147
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +23 -0
- package/framework/hooks/claude-code/core/worktree-context.js +15 -114
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +11 -76
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +168 -44
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +11 -43
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +59 -0
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +4 -0
- package/framework/hooks/shared/gate-question.js +12 -0
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +14 -72
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +99 -0
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +51 -247
- package/framework/hooks/shared/state-writer.js +9 -54
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -50
- package/framework/skills/README.md +3 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +149 -222
- package/framework/skills/core/morph-hotfix/SKILL.md +33 -42
- package/framework/skills/core/morph-machine/SKILL.md +2 -2
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +137 -299
- package/framework/skills/core/morph-review/SKILL.md +111 -136
- package/framework/skills/core/morph-standards/SKILL.md +27 -39
- package/framework/skills/core/morph-uiux/SKILL.md +94 -152
- package/framework/standards/STANDARDS.json +109 -1219
- package/framework/standards/STANDARDS.meta.json +1319 -0
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/evals-with-cache.md +1 -2
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +3 -3
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +7 -7
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +15 -179
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +18 -105
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +17 -111
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +53 -307
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +6 -35
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +8 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +65 -72
- package/src/commands/project/status.js +28 -121
- package/src/commands/project/update.js +31 -223
- package/src/commands/project/worktree.js +109 -883
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +65 -120
- package/src/commands/state/approve.js +87 -229
- package/src/commands/state/archive.js +23 -139
- package/src/commands/state/auto-approval.js +65 -0
- package/src/commands/state/create.js +49 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +105 -494
- package/src/commands/state/gate-check.js +74 -183
- package/src/commands/state/gate-decision.js +8 -42
- package/src/commands/state/handoff.js +4 -31
- package/src/commands/state/list.js +21 -91
- package/src/commands/state/report-check.js +134 -338
- package/src/commands/state/retype.js +12 -28
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +79 -19
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +52 -216
- package/src/commands/validation/verify.js +45 -160
- package/src/core/paths/output-schema.js +9 -155
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +2 -30
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +32 -395
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +21 -0
- package/src/core/work-types.js +10 -45
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -8
- package/src/dashboard/app/components/CommandPalette.jsx +4 -11
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +3 -9
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -133
- package/src/dashboard/app/pages/Features.jsx +9 -16
- package/src/dashboard/app/pages/Guide.jsx +2 -3
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -7
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +14 -83
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +1 -7
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +92 -113
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +4 -7
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +14 -13
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +2 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +8 -59
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +37 -112
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +11 -122
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +2 -1
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +17 -17
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +2 -6
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +8 -33
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/mutation/dotnet-introspection.js +281 -0
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +47 -13
- package/src/lib/plan/artifact-vocabulary.js +40 -0
- package/src/lib/plan/checks.js +208 -0
- package/src/lib/plan/plan-parser.js +204 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +204 -0
- package/src/lib/risk-detector.js +131 -274
- package/src/lib/specs/adr-index.js +38 -85
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +31 -0
- package/src/lib/specs/gfm-table.js +11 -0
- package/src/lib/specs/revocations.js +31 -102
- package/src/lib/specs/screen-checklist.js +56 -132
- package/src/lib/stack/dotnet-build-ignores.js +43 -0
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +260 -0
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +37 -0
- package/src/lib/standards/sections.js +47 -0
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +23 -179
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -313
- package/src/lib/tasks/test-runner.js +107 -447
- package/src/lib/telemetry/metrics.js +13 -103
- package/src/lib/telemetry/telemetry-reader.js +13 -238
- package/src/lib/telemetry/verify-event.js +4 -34
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +4 -26
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +24 -137
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +54 -156
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +152 -575
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -137
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +31 -126
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -138
- package/src/utils/agents-installer.js +92 -272
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -87
- package/src/utils/file-copier.js +32 -90
- package/src/utils/hooks-installer.js +26 -296
- package/src/utils/skills-installer.js +4 -102
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -57
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/commands/utils/index.js +0 -3
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/evaluator-report.js +0 -104
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/dotnet-test-introspection.js +0 -632
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,22 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* protocolo (`api`) por alias.
|
|
4
|
-
*
|
|
5
|
-
* As duas perguntas saem do standard `ai-agents/providers/model-registry.md`,
|
|
6
|
-
* não da memória de quem implementa:
|
|
7
|
-
*
|
|
8
|
-
* - **tarifa por alias** — um alias sem tarifa é um custo que ninguém
|
|
9
|
-
* consegue prever antes de a chamada ser paga. O caso medido no acervo é o
|
|
10
|
-
* GHLBrain, cujo mapa `Pricing.Models` é literalmente `{}`.
|
|
11
|
-
* - **`api` por alias** — o standard mede, sobre o pin, que
|
|
12
|
-
* `AgentApi.Responses = 0`, ou seja `default(AgentApi)` é Responses, e que
|
|
13
|
-
* `ModelRegistry.ReadApi` devolve `AgentApi.Responses` quando o campo está
|
|
14
|
-
* ausente. A ausência do campo, portanto, NÃO é defeito: é uma decisão
|
|
15
|
-
* tomada por omissão. O achado NOMEIA o default assumido e pede
|
|
16
|
-
* confirmação — nunca afirma defeito.
|
|
17
|
-
*
|
|
18
|
-
* Severidade: `warn` nas duas. Só `pin-drift` produz `error`.
|
|
19
|
-
*/
|
|
1
|
+
// Ausência de `api` não é defeito: o default medido sobre o pin é Responses, então o achado nomeia o
|
|
2
|
+
// default e pede confirmação. Severidade `warn`; só `pin-drift` produz `error`.
|
|
20
3
|
|
|
21
4
|
import { readFileSync } from 'node:fs';
|
|
22
5
|
import { existsSync } from 'node:fs';
|
|
@@ -24,22 +7,10 @@ import { isAbsolute, join, basename } from 'node:path';
|
|
|
24
7
|
import { collectProjectFiles } from './ai-project-scan.js';
|
|
25
8
|
import { jsoncToJson } from '../../../framework/hooks/shared/comment-stripper.js';
|
|
26
9
|
|
|
27
|
-
/** Limite de tamanho para um JSON candidato (bytes de texto) — evita ler dumps. */
|
|
28
10
|
const MAX_JSON_CHARS = 512 * 1024;
|
|
29
11
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
*
|
|
33
|
-
* Não é indulgência: é paridade com o leitor de produção. O exemplar canônico
|
|
34
|
-
* `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json`
|
|
35
|
-
* — que diz na primeira linha "copie para `config/model-registry.json` no
|
|
36
|
-
* projeto" — tem 20 linhas de `//`, e `ModelRegistry.FromJson` do kit declara
|
|
37
|
-
* `CommentHandling = JsonCommentHandling.Skip` + `AllowTrailingCommas = true`.
|
|
38
|
-
* Com `JSON.parse` cru, as duas checagens de registry saíam `skip` sobre a
|
|
39
|
-
* ÚNICA forma que existem para auditar — e `skip` não muda o código de saída,
|
|
40
|
-
* então o silêncio era total. A auditoria tem de enxergar a mesma forma que o
|
|
41
|
-
* leitor de produção enxerga.
|
|
42
|
-
*/
|
|
12
|
+
// JSONC por paridade com o leitor de produção (`ModelRegistry.FromJson` aceita comentário e vírgula
|
|
13
|
+
// sobrando): com `JSON.parse` cru o exemplar canônico saía `skip`, e `skip` não muda o código de saída.
|
|
43
14
|
function readJsonSafe(file) {
|
|
44
15
|
try {
|
|
45
16
|
const raw = readFileSync(file, 'utf8');
|
|
@@ -67,7 +38,7 @@ function asAliasSource(file, data) {
|
|
|
67
38
|
return { file, aliases: data.aliases, defaultAlias: data.defaultAlias, data };
|
|
68
39
|
}
|
|
69
40
|
|
|
70
|
-
/** A
|
|
41
|
+
/** A seção `Pricing` de um `appsettings*.json`. */
|
|
71
42
|
function asPricingSource(file, data) {
|
|
72
43
|
if (!isPlainObject(data)) return null;
|
|
73
44
|
const pricing = data.Pricing ?? data.pricing;
|
|
@@ -77,19 +48,8 @@ function asPricingSource(file, data) {
|
|
|
77
48
|
return { file, models };
|
|
78
49
|
}
|
|
79
50
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*
|
|
83
|
-
* Ordem, primeira ocorrência ganha (spec §3.5):
|
|
84
|
-
* 1. path declarado em `.morph/config/config.json → project.ai.modelRegistry`;
|
|
85
|
-
* 2. um `*.json` cujo objeto raiz tenha `defaultAlias` **e** um mapa `aliases`;
|
|
86
|
-
* 3. a seção `Pricing` de um `appsettings*.json`.
|
|
87
|
-
*
|
|
88
|
-
* @param {string} root - raiz do projeto
|
|
89
|
-
* @param {object|null} config - conteúdo de `.morph/config/config.json`, se houver
|
|
90
|
-
* @returns {null | { aliasSource: object|null, pricingSource: object|null }}
|
|
91
|
-
* `null` quando NENHUMA das três formas foi encontrada.
|
|
92
|
-
*/
|
|
51
|
+
// Primeira forma ganha: o path de `project.ai.modelRegistry`, um `*.json` com `defaultAlias` e `aliases`,
|
|
52
|
+
// ou a seção `Pricing` de um `appsettings*.json`. `null` sem nenhuma.
|
|
93
53
|
export function locateAiRegistry(root, config = null) {
|
|
94
54
|
let aliasSource = null;
|
|
95
55
|
let pricingSource = null;
|
|
@@ -132,7 +92,6 @@ export function locateAiRegistry(root, config = null) {
|
|
|
132
92
|
return { aliasSource, pricingSource };
|
|
133
93
|
}
|
|
134
94
|
|
|
135
|
-
/** Mapa de tarifas embutido no próprio registry de modelos, quando existir. */
|
|
136
95
|
function extractPricingFromRegistry(file, data) {
|
|
137
96
|
if (!isPlainObject(data)) return null;
|
|
138
97
|
const pricing = data.pricing ?? data.Pricing;
|
|
@@ -153,40 +112,14 @@ function aliasHasWaiver(def) {
|
|
|
153
112
|
return isPlainObject(def) && typeof def.pricingWaiver === 'string' && def.pricingWaiver.trim() !== '';
|
|
154
113
|
}
|
|
155
114
|
|
|
156
|
-
|
|
157
|
-
* O documento usa a forma **por alias** (`pricing` / `pricingWaiver` dentro de
|
|
158
|
-
* cada alias) em vez da forma de mapa no topo?
|
|
159
|
-
*
|
|
160
|
-
* Basta UM alias declarar qualquer uma das duas: um documento meio-preenchido
|
|
161
|
-
* ainda é um documento na forma v2, e os aliases que faltam é que são o achado.
|
|
162
|
-
*/
|
|
115
|
+
// Basta um alias declarar `pricing` ou `pricingWaiver`: documento meio-preenchido ainda é forma por alias.
|
|
163
116
|
export function usesPerAliasPricing(aliasSource) {
|
|
164
117
|
if (!aliasSource) return false;
|
|
165
118
|
return Object.values(aliasSource.aliases).some((d) => aliasHasPricing(d) || aliasHasWaiver(d));
|
|
166
119
|
}
|
|
167
120
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
*
|
|
171
|
-
* Duas formas, e a checagem cobra a que o documento usa:
|
|
172
|
-
*
|
|
173
|
-
* 1. **Por alias** (`model-registry.v2.json`, o exemplar que o framework manda
|
|
174
|
-
* copiar) — a REGRA DURA escrita no próprio exemplar e imposta por
|
|
175
|
-
* `ModelRegistry.ReadPricing`: *"todo alias declara `pricing` OU
|
|
176
|
-
* `pricingWaiver` — nunca os dois, nunca nenhum"*. Um `pricingWaiver` é uma
|
|
177
|
-
* tarifa **decidida**: a ausência está escrita, não esquecida, e acusá-la
|
|
178
|
-
* seria gritar em código correto.
|
|
179
|
-
* 2. **Mapa no topo** (`pricing` na raiz, ou a seção `Pricing.Models` de um
|
|
180
|
-
* `appsettings*.json` — a forma medida no GHLBrain, cujo mapa é
|
|
181
|
-
* literalmente `{}`).
|
|
182
|
-
*
|
|
183
|
-
* Os achados de MAPA (`pricing-map-missing` / `pricing-map-empty`) só existem
|
|
184
|
-
* na forma 2: num documento v2 não há mapa a faltar, e emitir o achado ali era
|
|
185
|
-
* o primeiro dos seis falsos positivos sobre o exemplar canônico.
|
|
186
|
-
*
|
|
187
|
-
* @param {ReturnType<typeof locateAiRegistry>} registry
|
|
188
|
-
* @returns {null | Array<{ id: string, severity: 'warn', alias?: string, message: string }>}
|
|
189
|
-
*/
|
|
121
|
+
// Por alias, todo alias declara `pricing` OU `pricingWaiver`, nunca os dois, nunca nenhum (um waiver é
|
|
122
|
+
// tarifa decidida). Os achados de mapa só existem na forma de mapa no topo.
|
|
190
123
|
export function auditModelPricing(registry) {
|
|
191
124
|
if (!registry) return null;
|
|
192
125
|
const { aliasSource, pricingSource } = registry;
|
|
@@ -253,19 +186,7 @@ export function auditModelPricing(registry) {
|
|
|
253
186
|
return findings;
|
|
254
187
|
}
|
|
255
188
|
|
|
256
|
-
/**
|
|
257
|
-
* Todo alias declara `api`?
|
|
258
|
-
*
|
|
259
|
-
* O default do standard é **`responses`**: medido sobre o pin, `AgentApi.Responses = 0`
|
|
260
|
-
* e `ModelRegistry.ReadApi` devolve Responses quando o campo está ausente. A mensagem
|
|
261
|
-
* nomeia esse default e PEDE CONFIRMAÇÃO — a ausência do campo é uma decisão por
|
|
262
|
-
* omissão, não um defeito. É a pergunta que o `ModelRegistry.cs` do ProspectPRO
|
|
263
|
-
* documenta ter custado um bug.
|
|
264
|
-
*
|
|
265
|
-
* @param {ReturnType<typeof locateAiRegistry>} registry
|
|
266
|
-
* @returns {null | Array<{ id: string, severity: 'warn', alias: string, message: string }>}
|
|
267
|
-
* `null` quando não há aliases para perguntar.
|
|
268
|
-
*/
|
|
189
|
+
/** `null` quando não há aliases para perguntar. */
|
|
269
190
|
export function auditApiPerAlias(registry) {
|
|
270
191
|
if (!registry || !registry.aliasSource) return null;
|
|
271
192
|
const findings = [];
|
package/src/lib/ci/ci-verify.js
CHANGED
|
@@ -18,7 +18,7 @@ export class CiUsageError extends Error {}
|
|
|
18
18
|
const NODE_KEYS = ['build', 'tests', 'validators', 'e2e'];
|
|
19
19
|
|
|
20
20
|
function isVerifyResult(result) {
|
|
21
|
-
return Boolean(result) && ['pass', 'fail'].includes(result.status)
|
|
21
|
+
return Boolean(result) && ['pass', 'fail'].includes(result.status)
|
|
22
22
|
&& NODE_KEYS.every((k) => typeof result.nodes?.[k]?.status === 'string');
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -47,18 +47,19 @@ function dispatchWorkflow(root, workflow, branch) {
|
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
50
|
+
// The last green stamp still proves the tree if nothing moved since it and nothing is uncommitted
|
|
51
|
+
// (treeMovedSince sees only commits). A null sha or HEAD would read as "not moved", so both are required.
|
|
52
|
+
export function reusableLocalStamp(root, stamp) {
|
|
53
|
+
if (stamp?.status !== 'pass' || !stamp.sha) return null;
|
|
54
|
+
const head = getHeadSha(root);
|
|
55
|
+
if (!head) return null;
|
|
56
|
+
const dirty = uncommittedCode(root);
|
|
57
|
+
if (dirty === null || dirty.length > 0) return null;
|
|
58
|
+
return treeMovedSince(root, stamp.sha, head) ? null : stamp;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Every way CI cannot answer is `mode: 'local'` with the reason: no CI is never a pause. Only the
|
|
62
|
+
// uploaded `verify --json` becomes a result, judged by the same stamp and rules as a local run.
|
|
62
63
|
export async function runCiVerify(root, featureName, config, { log = () => {}, waitMs = CI_WAIT_MS, wait = waitForRun } = {}) {
|
|
63
64
|
const avail = ciAvailability(root, featureName, config);
|
|
64
65
|
if (!avail.ok) return { mode: 'local', reason: avail.reason };
|
package/src/lib/ci/github-ci.js
CHANGED
|
@@ -21,14 +21,8 @@ function gh(cwd, args, timeout = 15000) {
|
|
|
21
21
|
return execFileSync(bin, [...prefix, ...args], { cwd, stdio: 'pipe', timeout }).toString();
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* publish its JSON, because only that artifact becomes a stamp. A green run of any other
|
|
27
|
-
* workflow (lint, deploy) proves nothing about the suite.
|
|
28
|
-
*
|
|
29
|
-
* @param {string} root
|
|
30
|
-
* @returns {string|null} workflow file name, as `gh run list --workflow` takes it
|
|
31
|
-
*/
|
|
24
|
+
// Must call `morph-spec verify` AND publish its JSON: only that artifact becomes a stamp, and a green
|
|
25
|
+
// lint or deploy run proves nothing about the suite.
|
|
32
26
|
export function findCiWorkflow(root) {
|
|
33
27
|
const dir = join(root, CI_WORKFLOW_DIR);
|
|
34
28
|
if (!existsSync(dir)) return null;
|
|
@@ -69,15 +63,7 @@ function ghAuthText(root) {
|
|
|
69
63
|
return `${res.stdout ?? ''}${res.stderr ?? ''}`;
|
|
70
64
|
}
|
|
71
65
|
|
|
72
|
-
/**
|
|
73
|
-
* Whether CI can answer for this feature, and why not. Never throws: every "no" is a reason the
|
|
74
|
-
* caller prints before falling back to the local verify.
|
|
75
|
-
*
|
|
76
|
-
* @param {string} root - the feature's tree
|
|
77
|
-
* @param {string} featureName
|
|
78
|
-
* @param {object|null} config - `.morph/config/config.json`
|
|
79
|
-
* @returns {{ ok: true, branch: string, https: boolean, scopes: string|null } | { ok: false, reason: string }}
|
|
80
|
-
*/
|
|
66
|
+
/** Never throws: every "no" is a reason the caller prints before falling back to the local verify. */
|
|
81
67
|
export function ciAvailability(root, featureName, config) {
|
|
82
68
|
if (config?.project?.ci === false) return { ok: false, reason: 'project.ci = false no config.json' };
|
|
83
69
|
const url = originUrl(root);
|
|
@@ -93,21 +79,14 @@ export function ciAvailability(root, featureName, config) {
|
|
|
93
79
|
return { ok: true, branch, https: /^https?:/i.test(url), scopes };
|
|
94
80
|
}
|
|
95
81
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
* rejects the whole push. Unknown scopes (fine-grained token, SSH remote) are not a refusal.
|
|
99
|
-
*/
|
|
82
|
+
// Creating a workflow file over HTTPS needs the token's `workflow` scope, or GitHub rejects the whole push.
|
|
83
|
+
// Unknown scopes (fine-grained token, SSH remote) are not a refusal.
|
|
100
84
|
export function workflowPushProblem({ https, scopes }) {
|
|
101
85
|
if (!https || scopes === null) return null;
|
|
102
86
|
return /\bworkflow\b/.test(scopes) ? null : 'o token do gh não tem o escopo "workflow" (gh auth refresh -s workflow)';
|
|
103
87
|
}
|
|
104
88
|
|
|
105
|
-
/**
|
|
106
|
-
* Paths CI would not see: uncommitted or untracked files outside `.morph/`, plus the config,
|
|
107
|
-
* which names the test commands.
|
|
108
|
-
*
|
|
109
|
-
* @returns {string[]|null} null when git did not answer
|
|
110
|
-
*/
|
|
89
|
+
/** Uncommitted or untracked files outside `.morph/`, plus the config; null when git did not answer. */
|
|
111
90
|
export function uncommittedCode(root) {
|
|
112
91
|
let out;
|
|
113
92
|
try {
|
|
@@ -138,10 +117,8 @@ export function listRuns(root, branch, workflow) {
|
|
|
138
117
|
}
|
|
139
118
|
}
|
|
140
119
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
* the evaluator and the archive commit only `.morph/`, and the workflow does not re-run for them.
|
|
144
|
-
*/
|
|
120
|
+
// `isCurrent` compares trees, not SHAs: the review agents and the archive commit only `.morph/`,
|
|
121
|
+
// and the workflow does not re-run for them.
|
|
145
122
|
export function pickRun(runs, isCurrent) {
|
|
146
123
|
return (runs || []).find((run) => !(run.status === 'completed' && NO_VERDICT.has(run.conclusion))
|
|
147
124
|
&& run.headSha && isCurrent(run.headSha)) ?? null;
|
|
@@ -160,12 +137,7 @@ export function downloadResult(root, runId) {
|
|
|
160
137
|
}
|
|
161
138
|
}
|
|
162
139
|
|
|
163
|
-
/**
|
|
164
|
-
* Polls until the run over the current tree completes. Never decides: the caller turns
|
|
165
|
-
* `timeout`/`no-run`/`unknown` into "no CI proof", never into red.
|
|
166
|
-
*
|
|
167
|
-
* @returns {Promise<{ state: 'completed'|'timeout'|'no-run'|'unknown', run?: object }>}
|
|
168
|
-
*/
|
|
140
|
+
/** Never decides: the caller turns `timeout`/`no-run`/`unknown` into "no CI proof", never into red. */
|
|
169
141
|
export async function waitForRun(root, { branch, workflow, isCurrent, timeoutMs, appearMs, pollMs = 15000,
|
|
170
142
|
sleep = (ms) => new Promise((r) => setTimeout(r, ms)), now = Date.now, onPending = () => {}, list = listRuns }) {
|
|
171
143
|
const start = now();
|
|
@@ -56,16 +56,8 @@ const step = (name, dir, run) => [
|
|
|
56
56
|
` run: ${run}`,
|
|
57
57
|
];
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
* pinned morph-spec, so the suite is whatever `project.tests` says, not a second definition here.
|
|
62
|
-
* `.morph/` is out of the trigger (bookkeeping commits would cancel the run over the code) except
|
|
63
|
-
* the config, which names the commands.
|
|
64
|
-
*
|
|
65
|
-
* @param {string} root
|
|
66
|
-
* @param {object|null} config
|
|
67
|
-
* @returns {string}
|
|
68
|
-
*/
|
|
59
|
+
// Calls the project's pinned morph-spec, so the suite is whatever `project.tests` says. `.morph/` is out of
|
|
60
|
+
// the trigger (bookkeeping commits would cancel the run over the code) except the config.
|
|
69
61
|
export function renderCiWorkflow(root, config) {
|
|
70
62
|
const packages = packageDirs(root, config);
|
|
71
63
|
const lines = [
|
|
@@ -1,24 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Claude Config Detector
|
|
3
|
-
*
|
|
4
|
-
* Scans .claude/ settings and plugins for MCP servers, hooks, skills, and commands.
|
|
5
|
-
* Used by mcp-setup and session-start hooks.
|
|
6
|
-
*
|
|
7
|
-
* @module detectors/claude-config-detector
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
2
|
import { join } from 'path';
|
|
12
3
|
import { homedir } from 'os';
|
|
13
4
|
|
|
14
|
-
|
|
15
|
-
* Plugin ids (`name@marketplace`) that are switched ON. User settings first, then
|
|
16
|
-
* the project's; a later file overrides an earlier one, and `false` turns one off.
|
|
17
|
-
*
|
|
18
|
-
* @param {string} targetPath - Root path of the project
|
|
19
|
-
* @param {string} [home] - Home directory holding `.claude/settings.json`
|
|
20
|
-
* @returns {string[]}
|
|
21
|
-
*/
|
|
5
|
+
// User settings first, then the project's: a later file overrides, and `false` turns a plugin off.
|
|
22
6
|
export function readEnabledPlugins(targetPath, home = homedir()) {
|
|
23
7
|
const merged = new Map();
|
|
24
8
|
const files = [
|
|
@@ -32,17 +16,11 @@ export function readEnabledPlugins(targetPath, home = homedir()) {
|
|
|
32
16
|
const map = JSON.parse(readFileSync(file, 'utf8').replace(/^/, '')).enabledPlugins;
|
|
33
17
|
if (!map || typeof map !== 'object') continue;
|
|
34
18
|
for (const [id, on] of Object.entries(map)) merged.set(id, on === true);
|
|
35
|
-
} catch {
|
|
19
|
+
} catch {}
|
|
36
20
|
}
|
|
37
21
|
return [...merged].filter(([, on]) => on).map(([id]) => id);
|
|
38
22
|
}
|
|
39
23
|
|
|
40
|
-
/**
|
|
41
|
-
* Detect Claude Code configuration for a project.
|
|
42
|
-
*
|
|
43
|
-
* @param {string} targetPath - Root path of the project
|
|
44
|
-
* @returns {{ plugins: string[], enabledPlugins: string[], mcpServers: Array<{name: string, config: Object}>, customSkills: string[], customCommands: string[], hooks: string[] }}
|
|
45
|
-
*/
|
|
46
24
|
export function detectClaudeConfig(targetPath) {
|
|
47
25
|
const result = {
|
|
48
26
|
plugins: [],
|
|
@@ -58,7 +36,6 @@ export function detectClaudeConfig(targetPath) {
|
|
|
58
36
|
const mcpJsonNames = new Set();
|
|
59
37
|
|
|
60
38
|
try {
|
|
61
|
-
// ── MCP Servers from .mcp.json (primary — Claude Code reads this) ───────
|
|
62
39
|
const mcpJsonPath = join(targetPath, '.mcp.json');
|
|
63
40
|
if (existsSync(mcpJsonPath)) {
|
|
64
41
|
try {
|
|
@@ -68,10 +45,9 @@ export function detectClaudeConfig(targetPath) {
|
|
|
68
45
|
result.mcpServers.push({ name, config });
|
|
69
46
|
mcpJsonNames.add(name);
|
|
70
47
|
}
|
|
71
|
-
} catch {
|
|
48
|
+
} catch {}
|
|
72
49
|
}
|
|
73
50
|
|
|
74
|
-
// ── MCP Servers + hooks from settings (legacy + hooks/permissions) ───────
|
|
75
51
|
const settingsPaths = [
|
|
76
52
|
join(targetPath, '.claude', 'settings.json'),
|
|
77
53
|
join(targetPath, '.claude', 'settings.local.json'),
|
|
@@ -81,19 +57,18 @@ export function detectClaudeConfig(targetPath) {
|
|
|
81
57
|
if (existsSync(sp)) {
|
|
82
58
|
try {
|
|
83
59
|
const settings = JSON.parse(readFileSync(sp, 'utf8'));
|
|
84
|
-
// Legacy
|
|
60
|
+
// Legacy entries: .mcp.json wins; among settings files, the later one wins.
|
|
85
61
|
const mcps = settings.mcpServers || {};
|
|
86
62
|
for (const [name, config] of Object.entries(mcps)) {
|
|
87
|
-
if (mcpJsonNames.has(name)) continue;
|
|
63
|
+
if (mcpJsonNames.has(name)) continue;
|
|
88
64
|
const idx = result.mcpServers.findIndex(m => m.name === name);
|
|
89
65
|
if (idx >= 0) {
|
|
90
|
-
result.mcpServers[idx] = { name, config };
|
|
66
|
+
result.mcpServers[idx] = { name, config };
|
|
91
67
|
} else {
|
|
92
68
|
result.mcpServers.push({ name, config });
|
|
93
69
|
}
|
|
94
70
|
}
|
|
95
71
|
|
|
96
|
-
// Collect hooks
|
|
97
72
|
if (settings.hooks) {
|
|
98
73
|
for (const [event, eventHooks] of Object.entries(settings.hooks)) {
|
|
99
74
|
for (const group of (Array.isArray(eventHooks) ? eventHooks : [])) {
|
|
@@ -103,60 +78,43 @@ export function detectClaudeConfig(targetPath) {
|
|
|
103
78
|
}
|
|
104
79
|
}
|
|
105
80
|
}
|
|
106
|
-
} catch {
|
|
81
|
+
} catch {}
|
|
107
82
|
}
|
|
108
83
|
}
|
|
109
84
|
|
|
110
|
-
// ── Plugins from global config ──────────────────────────────────────────
|
|
111
85
|
const pluginsPath = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
112
86
|
if (existsSync(pluginsPath)) {
|
|
113
87
|
try {
|
|
114
88
|
const pluginsData = JSON.parse(readFileSync(pluginsPath, 'utf8'));
|
|
115
89
|
const plugins = pluginsData.plugins || {};
|
|
116
90
|
result.plugins = Object.keys(plugins);
|
|
117
|
-
} catch {
|
|
91
|
+
} catch {}
|
|
118
92
|
}
|
|
119
93
|
|
|
120
|
-
// ── Custom skills ───────────────────────────────────────────────────────
|
|
121
94
|
const skillsPath = join(targetPath, '.claude', 'skills');
|
|
122
95
|
if (existsSync(skillsPath)) {
|
|
123
96
|
try {
|
|
124
97
|
result.customSkills = readdirSync(skillsPath).filter(f => !f.startsWith('.'));
|
|
125
|
-
} catch {
|
|
98
|
+
} catch {}
|
|
126
99
|
}
|
|
127
100
|
|
|
128
|
-
// ── Custom commands ─────────────────────────────────────────────────────
|
|
129
101
|
const commandsPath = join(targetPath, '.claude', 'commands');
|
|
130
102
|
if (existsSync(commandsPath)) {
|
|
131
103
|
try {
|
|
132
104
|
result.customCommands = readdirSync(commandsPath).filter(f => f.endsWith('.md'));
|
|
133
|
-
} catch {
|
|
105
|
+
} catch {}
|
|
134
106
|
}
|
|
135
107
|
} catch {
|
|
136
|
-
// Fail-safe
|
|
137
108
|
}
|
|
138
109
|
|
|
139
110
|
return result;
|
|
140
111
|
}
|
|
141
112
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
*
|
|
145
|
-
* `phaseMatrix` and `currentPhase` default rather than being required: the
|
|
146
|
-
* `init` caller passed only `mcpServers`, so `phaseMatrix[currentPhase]` threw
|
|
147
|
-
* a TypeError that `init`'s outer catch swallowed as
|
|
148
|
-
* "Claude config detection skipped" — taking `saveIntegrations` and
|
|
149
|
-
* `generateSettings` down with it, since both run after this line.
|
|
150
|
-
*
|
|
151
|
-
* @param {Array<{name: string}>} mcpServers - Detected MCP server list
|
|
152
|
-
* @param {Object} [phaseMatrix] - From framework/mcp/registry.json → phaseMatrix
|
|
153
|
-
* @param {string} [currentPhase] - Phase to look up; omit for every phase
|
|
154
|
-
* @returns {{ available: string[], recommended: string[], missing: string[] }}
|
|
155
|
-
*/
|
|
113
|
+
// `phaseMatrix`/`currentPhase` default: `init` passes only `mcpServers`, and a throw here was
|
|
114
|
+
// swallowed by its outer catch, skipping the steps after it.
|
|
156
115
|
export function mapMcpsToPhases(mcpServers, phaseMatrix = {}, currentPhase = null) {
|
|
157
116
|
const configuredNames = new Set((mcpServers || []).map(m => m.name.toLowerCase()));
|
|
158
|
-
// No phase asked for → the union across every phase, deduped.
|
|
159
|
-
// caller showing "which of my MCPs this framework uses" wants.
|
|
117
|
+
// No phase asked for → the union across every phase, deduped.
|
|
160
118
|
const recommended = currentPhase
|
|
161
119
|
? (phaseMatrix[currentPhase] || [])
|
|
162
120
|
: [...new Set(Object.values(phaseMatrix).flat())];
|
|
@@ -168,19 +126,6 @@ export function mapMcpsToPhases(mcpServers, phaseMatrix = {}, currentPhase = nul
|
|
|
168
126
|
};
|
|
169
127
|
}
|
|
170
128
|
|
|
171
|
-
/**
|
|
172
|
-
* The MCP-by-phase table `init` prints: every phase that recommends at least
|
|
173
|
-
* one of the MCPs this machine actually has configured.
|
|
174
|
-
*
|
|
175
|
-
* Separate from `mapMcpsToPhases` because the caller wants a per-phase
|
|
176
|
-
* breakdown, not one phase's slice — reaching for the wrong one is what
|
|
177
|
-
* produced `Object.entries()` over `{available, recommended, missing}` and a
|
|
178
|
-
* `.map(m => m.name)` over plain strings.
|
|
179
|
-
*
|
|
180
|
-
* @param {Array<{name: string}>} mcpServers
|
|
181
|
-
* @param {Object} phaseMatrix
|
|
182
|
-
* @returns {Array<{phase: string, available: string[]}>} phases with ≥1 match
|
|
183
|
-
*/
|
|
184
129
|
export function mcpAvailabilityByPhase(mcpServers, phaseMatrix = {}) {
|
|
185
130
|
return Object.keys(phaseMatrix)
|
|
186
131
|
.map(phase => ({
|
|
@@ -190,12 +135,6 @@ export function mcpAvailabilityByPhase(mcpServers, phaseMatrix = {}) {
|
|
|
190
135
|
.filter(({ available }) => available.length > 0);
|
|
191
136
|
}
|
|
192
137
|
|
|
193
|
-
/**
|
|
194
|
-
* Format a human-readable one-liner summary of detected config.
|
|
195
|
-
*
|
|
196
|
-
* @param {{ plugins: string[], mcpServers: Array<{name: string}> }} config
|
|
197
|
-
* @returns {string}
|
|
198
|
-
*/
|
|
199
138
|
export function formatConfigSummary(config) {
|
|
200
139
|
const parts = [];
|
|
201
140
|
if (config.mcpServers.length > 0) {
|