@polymorphism-tech/morph-spec 9.0.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 +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +4 -62
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +1 -1
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +12 -4
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +6 -17
- 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 +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- 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 +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +11 -67
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- 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/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- 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 +1 -42
- 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 +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- 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/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/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 +2 -2
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +2 -2
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +34 -12
- package/framework/standards/STANDARDS.meta.json +16 -2
- 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/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 +2 -2
- 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 +1 -1
- 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/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/docs/ui-mockups.md +4 -0
- 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 +17 -104
- 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 +5 -57
- 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.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +14 -96
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +2 -5
- package/src/commands/validation/validate-feature.js +5 -94
- package/src/commands/validation/verify.js +15 -98
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- 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 +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- 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 +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- 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 +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- 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 +2 -6
- 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 +4 -19
- 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 +4 -50
- 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 +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- 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 +1 -0
- 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 +3 -15
- 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 +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +5 -31
- package/src/lib/plan/plan-parser.js +1 -6
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +5 -23
- package/src/lib/risk-detector.js +39 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/validators/code-budget/code-budget-validator.js +3 -43
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- 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 +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +20 -125
- package/src/lib/validators/verify-runner.js +52 -358
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- 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 +13 -95
- 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 -147
- package/src/utils/agents-installer.js +28 -275
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,70 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* A propriedade que faz o `--dry-run` ser honesto está numa frase:
|
|
5
|
-
*
|
|
6
|
-
* > **o caminho real também emite.**
|
|
7
|
-
*
|
|
8
|
-
* Em modo normal cada verbo EXECUTA e registra; em `dryRun`, só registra. O
|
|
9
|
-
* resumo impresso ao fim de uma execução real sai de `list()`, não de
|
|
10
|
-
* contadores paralelos. A consequência é o ponto: um ponto de emissão
|
|
11
|
-
* esquecido some das DUAS saídas ao mesmo tempo, e o resumo de uma execução
|
|
12
|
-
* real fica visivelmente incompleto — o defeito aparece sem sequer usar o
|
|
13
|
-
* `--dry-run`.
|
|
14
|
-
*
|
|
15
|
-
* **Não há segunda travessia.** Nenhum filtro, walker ou lista de payload é
|
|
16
|
-
* reimplementado aqui. Os filtros que decidem o que instalar
|
|
17
|
-
* (`shouldInstallSkill`, `agentEligible`, `shouldInstall`) já são puros e
|
|
18
|
-
* continuam sendo os únicos. O anti-exemplar tem nome e mora neste
|
|
19
|
-
* repositório: `checkAgentsDrift` funciona como preditor porque re-deriva o
|
|
20
|
-
* conjunto elegível por conta própria e porque os dois lados chamam o mesmo
|
|
21
|
-
* renderer puro. Estender essa forma para 9 payloads + hooks + merge de
|
|
22
|
-
* settings + gitignore + índice do git é reimplementar o instalador inteiro e
|
|
23
|
-
* depender de disciplina humana para mantê-los sincronizados.
|
|
24
|
-
*
|
|
25
|
-
* **Os verbos são SÍNCRONOS de propósito.** Metade dos instaladores que eles
|
|
26
|
-
* atravessam (`installSkills`, `installAgents`, `installRulesForStack`) é
|
|
27
|
-
* síncrona; um coletor assíncrono obrigaria a tornar `installRulesForStack`
|
|
28
|
-
* async e a mudar a assinatura que `setup-infra`, `init` e os testes já usam.
|
|
29
|
-
*
|
|
30
|
-
* **O registro é a IDENTIDADE do efeito, nunca o conteúdo.** `writeFile` grava
|
|
31
|
-
* `{ verb, target }`, não os bytes: `installClaudeHooks` carimba um
|
|
32
|
-
* `generated_at` a cada execução, e comparar conteúdo faria o canário de
|
|
33
|
-
* igualdade dry × real reprovar por causa do relógio.
|
|
34
|
-
*/
|
|
1
|
+
// O caminho real também emite: o resumo de uma execução real sai de `list()`, então um ponto de
|
|
2
|
+
// emissão esquecido some das duas saídas. Registra-se a identidade do efeito, nunca o conteúdo:
|
|
3
|
+
// `installClaudeHooks` carimba `generated_at`, e comparar bytes reprovaria a igualdade dry × real.
|
|
35
4
|
|
|
36
5
|
import fs from 'fs-extra';
|
|
37
6
|
import { spawnSync } from 'node:child_process';
|
|
38
7
|
import { dirname } from 'node:path';
|
|
39
8
|
|
|
40
|
-
/**
|
|
41
|
-
* @typedef {Object} Effect
|
|
42
|
-
* @property {string} verb - ensureDir | remove | copyDir | copyFile | writeFile | writeJson | mergeJson | spawn | gitIndex
|
|
43
|
-
* @property {string} target - o caminho (ou comando) sobre o qual o efeito age
|
|
44
|
-
* @property {string} [from] - origem, para os verbos de cópia
|
|
45
|
-
* @property {string} [detail] - qualificador estável (argumentos do spawn, operação do git)
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Cria um coletor de efeitos.
|
|
50
|
-
*
|
|
51
|
-
* @param {{ dryRun?: boolean }} [options]
|
|
52
|
-
* @returns {{
|
|
53
|
-
* dryRun: boolean,
|
|
54
|
-
* ensureDir: (path: string) => void,
|
|
55
|
-
* remove: (path: string) => void,
|
|
56
|
-
* copyDir: (from: string, to: string) => void,
|
|
57
|
-
* copyFile: (from: string, to: string) => void,
|
|
58
|
-
* writeFile: (path: string, content: string) => void,
|
|
59
|
-
* writeJson: (path: string, data: object) => void,
|
|
60
|
-
* mergeJson: (path: string, patch: object) => void,
|
|
61
|
-
* spawn: (cmd: string, args: string[], options?: object) => object|null,
|
|
62
|
-
* gitIndex: (op: string, path: string, options?: object) => boolean,
|
|
63
|
-
* list: () => Effect[],
|
|
64
|
-
* }}
|
|
65
|
-
*/
|
|
66
9
|
export function createEffects({ dryRun = false } = {}) {
|
|
67
|
-
/** @type {Effect[]} */
|
|
68
10
|
const effects = [];
|
|
69
11
|
|
|
70
12
|
const record = (verb, target, extra = {}) => {
|
|
@@ -118,13 +60,7 @@ export function createEffects({ dryRun = false } = {}) {
|
|
|
118
60
|
}
|
|
119
61
|
},
|
|
120
62
|
|
|
121
|
-
|
|
122
|
-
* Leitura + fusão rasa + escrita, como UM efeito.
|
|
123
|
-
*
|
|
124
|
-
* A leitura acontece nos dois modos (ler não muda nada); só a escrita é
|
|
125
|
-
* suprimida em dry. É assim que `~/.claude/settings.json` — um arquivo do
|
|
126
|
-
* usuário, FORA do projeto — é atualizado sem que o dry o toque.
|
|
127
|
-
*/
|
|
63
|
+
// A leitura acontece nos dois modos; só a escrita é suprimida em dry.
|
|
128
64
|
mergeJson(path, patch) {
|
|
129
65
|
record('mergeJson', path);
|
|
130
66
|
if (dryRun) return;
|
|
@@ -138,21 +74,9 @@ export function createEffects({ dryRun = false } = {}) {
|
|
|
138
74
|
fs.writeJsonSync(path, { ...current, ...patch }, { spaces: 2 });
|
|
139
75
|
},
|
|
140
76
|
|
|
141
|
-
|
|
142
|
-
* @param {string} cmd
|
|
143
|
-
* @param {string[]} args
|
|
144
|
-
* @param {{ cwd?: string, dryRunArgs?: string[] }} [options]
|
|
145
|
-
* `dryRunArgs` é a variante SÓ-LEITURA do mesmo comando (por exemplo
|
|
146
|
-
* `--check`). Em dry ela é executada no lugar; sem ela, nada roda.
|
|
147
|
-
* O REGISTRO é o mesmo nos dois modos: o efeito previsto é o comando
|
|
148
|
-
* real, não a sua sonda.
|
|
149
|
-
*/
|
|
77
|
+
// `dryRunArgs`: variante só-leitura do comando, executada no lugar em dry; o registro é o comando real.
|
|
150
78
|
spawn(cmd, args = [], options = {}) {
|
|
151
|
-
// `args` viaja
|
|
152
|
-
// humano; quebrá-lo por espaço para descobrir onde o spawn escreve é o
|
|
153
|
-
// bug que um caminho como `R:\Polymorphism Tech\...` produz em silêncio,
|
|
154
|
-
// e que o relatório mostrou pondo um efeito do PACOTE da CLI no grupo
|
|
155
|
-
// "no projeto".
|
|
79
|
+
// `args` viaja além do `detail`: quebrar o `detail` por espaço erra caminhos com espaço.
|
|
156
80
|
record('spawn', cmd, { detail: args.join(' '), args: [...args] });
|
|
157
81
|
const { cwd, dryRunArgs } = options;
|
|
158
82
|
if (dryRun) {
|
|
@@ -162,11 +86,7 @@ export function createEffects({ dryRun = false } = {}) {
|
|
|
162
86
|
return spawnSync(cmd, args, { cwd, encoding: 'utf8' });
|
|
163
87
|
},
|
|
164
88
|
|
|
165
|
-
|
|
166
|
-
* Mutação do índice do git (`git rm --cached`, etc.). A SONDA que decide
|
|
167
|
-
* se há o que fazer fica fora daqui de propósito: sondar é ler, e ler
|
|
168
|
-
* acontece nos dois modos.
|
|
169
|
-
*/
|
|
89
|
+
// A sonda que decide se há o que fazer fica fora daqui: sondar é ler, e ler acontece nos dois modos.
|
|
170
90
|
gitIndex(op, path, options = {}) {
|
|
171
91
|
record('gitIndex', path, { detail: op });
|
|
172
92
|
if (dryRun) return true;
|
|
@@ -182,12 +102,7 @@ export function createEffects({ dryRun = false } = {}) {
|
|
|
182
102
|
};
|
|
183
103
|
}
|
|
184
104
|
|
|
185
|
-
|
|
186
|
-
* Coletor no-op-de-registro para chamadores que não passam um: executa
|
|
187
|
-
* normalmente e registra numa lista descartada. Existe para que assinaturas
|
|
188
|
-
* novas de `installSkills`/`installAgents`/`installRulesForStack` continuem
|
|
189
|
-
* funcionando para `setup-infra` e `init` sem mudança de comportamento.
|
|
190
|
-
*/
|
|
105
|
+
// Executa e registra numa lista descartada, para chamadores que não passam um coletor.
|
|
191
106
|
export function realEffects() {
|
|
192
107
|
return createEffects({ dryRun: false });
|
|
193
108
|
}
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP Installer
|
|
3
|
-
*
|
|
4
|
-
* Core logic for installing and configuring MCP servers during morph-spec init
|
|
5
|
-
* and via `morph-spec mcp setup`. Handles auto-installable (credential-free)
|
|
6
|
-
* MCPs, prerequisite checking, credential collection, and setup guidance.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
import { join } from 'path';
|
|
10
2
|
import { readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs';
|
|
11
3
|
import { execSync, spawn } from 'child_process';
|
|
@@ -13,10 +5,6 @@ import { homedir } from 'os';
|
|
|
13
5
|
import { installMcpServers } from '../../utils/claude-settings-manager.js';
|
|
14
6
|
import { mcpServerNames } from '../../utils/agents-installer.js';
|
|
15
7
|
|
|
16
|
-
/**
|
|
17
|
-
* Load the enhanced MCP registry (v3.0.0)
|
|
18
|
-
* @returns {Object} Registry with install blocks
|
|
19
|
-
*/
|
|
20
8
|
export function loadMcpRegistry() {
|
|
21
9
|
const registryPath = join(
|
|
22
10
|
import.meta.dirname, '..', '..', '..', 'framework', 'mcp', 'registry.json'
|
|
@@ -25,15 +13,7 @@ export function loadMcpRegistry() {
|
|
|
25
13
|
return JSON.parse(raw);
|
|
26
14
|
}
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
* Filter MCPs by stack relevance
|
|
30
|
-
* @param {Object} registry - Full MCP registry
|
|
31
|
-
* @param {string} stack - Detected project stack (e.g., 'nextjs-neon', 'blazor')
|
|
32
|
-
* @param {string[]} [integrations] - `project.integrations` (e.g., ['neon']). The
|
|
33
|
-
* detectors put a database or payment provider here, never in the stack string,
|
|
34
|
-
* so an MCP relevant to it (neon, asaas) is matched against this list too.
|
|
35
|
-
* @returns {Object} Filtered map of mcpName → mcpEntry
|
|
36
|
-
*/
|
|
16
|
+
// Detectors put a database or payment provider in `integrations`, never in the stack string.
|
|
37
17
|
export function filterByStack(registry, stack, integrations = []) {
|
|
38
18
|
const result = {};
|
|
39
19
|
const normalizedStack = (stack || '').toLowerCase();
|
|
@@ -53,14 +33,7 @@ export function filterByStack(registry, stack, integrations = []) {
|
|
|
53
33
|
return result;
|
|
54
34
|
}
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
* Match detected MCP servers against the registry
|
|
58
|
-
* @param {Array} existingServers - From detectClaudeConfig().mcpServers
|
|
59
|
-
* @param {Object} registry - MCP registry
|
|
60
|
-
* @param {string[]} [enabledPlugins] - From detectClaudeConfig().enabledPlugins. A plugin
|
|
61
|
-
* that ships the server already runs it, and writing it to `.mcp.json` too starts it twice.
|
|
62
|
-
* @returns {Object} Map of mcpName → { installed: true, source }
|
|
63
|
-
*/
|
|
36
|
+
// A plugin that ships the server already runs it: writing it to `.mcp.json` too starts it twice.
|
|
64
37
|
export function getInstalledMcps(existingServers, registry, enabledPlugins = []) {
|
|
65
38
|
const installed = {};
|
|
66
39
|
const registryNames = Object.keys(registry.mcps);
|
|
@@ -86,11 +59,6 @@ export function getInstalledMcps(existingServers, registry, enabledPlugins = [])
|
|
|
86
59
|
return installed;
|
|
87
60
|
}
|
|
88
61
|
|
|
89
|
-
/**
|
|
90
|
-
* Check if prerequisites are met for an MCP entry
|
|
91
|
-
* @param {Object} mcpEntry - MCP entry from registry
|
|
92
|
-
* @returns {Array} Array of { name, met, installUrl } for each prerequisite
|
|
93
|
-
*/
|
|
94
62
|
export function checkPrerequisites(mcpEntry) {
|
|
95
63
|
const prereqs = mcpEntry.install?.prerequisites || [];
|
|
96
64
|
const results = [];
|
|
@@ -101,7 +69,6 @@ export function checkPrerequisites(mcpEntry) {
|
|
|
101
69
|
execSync(prereq.checkCommand, { stdio: 'ignore', timeout: 5000 });
|
|
102
70
|
met = true;
|
|
103
71
|
} catch {
|
|
104
|
-
// Command failed or not found
|
|
105
72
|
}
|
|
106
73
|
results.push({
|
|
107
74
|
name: prereq.name,
|
|
@@ -113,25 +80,13 @@ export function checkPrerequisites(mcpEntry) {
|
|
|
113
80
|
return results;
|
|
114
81
|
}
|
|
115
82
|
|
|
116
|
-
/**
|
|
117
|
-
* Check if an MCP config uses remote HTTP transport (no local process).
|
|
118
|
-
* @param {Object} config - MCP server config
|
|
119
|
-
* @returns {boolean}
|
|
120
|
-
*/
|
|
121
83
|
export function isRemoteMcp(config) {
|
|
122
84
|
return config.type === 'http' || config.url != null;
|
|
123
85
|
}
|
|
124
86
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
* On Windows, npx must be invoked via `cmd /c` to avoid ENOENT errors.
|
|
128
|
-
* Registry already stores cmd/c format; this handles legacy configs that still use npx directly.
|
|
129
|
-
* Remote HTTP MCPs are returned as-is (no local process to patch).
|
|
130
|
-
* @param {Object} config - MCP server config { command, args, env? } or { type: "http", url }
|
|
131
|
-
* @returns {Object} Patched config (new object, original unmodified)
|
|
132
|
-
*/
|
|
87
|
+
// On Windows npx must run via `cmd /c` (ENOENT otherwise); the registry already stores that form,
|
|
88
|
+
// this covers legacy configs.
|
|
133
89
|
export function patchConfigForPlatform(config) {
|
|
134
|
-
// Remote MCPs need no platform patching
|
|
135
90
|
if (isRemoteMcp(config)) return config;
|
|
136
91
|
if (config.command !== 'npx') return config;
|
|
137
92
|
return {
|
|
@@ -141,20 +96,12 @@ export function patchConfigForPlatform(config) {
|
|
|
141
96
|
};
|
|
142
97
|
}
|
|
143
98
|
|
|
144
|
-
/**
|
|
145
|
-
* Install auto-installable MCPs (no credentials, no prerequisites)
|
|
146
|
-
* @param {string} targetPath - Project root directory
|
|
147
|
-
* @param {Object} mcpsToInstall - Map of mcpName → mcpEntry
|
|
148
|
-
* @returns {Promise<Object>} Result from installMcpServers
|
|
149
|
-
*/
|
|
150
99
|
export async function installAutoMcps(targetPath, mcpsToInstall) {
|
|
151
100
|
const servers = {};
|
|
152
101
|
|
|
153
102
|
for (const [name, entry] of Object.entries(mcpsToInstall)) {
|
|
154
103
|
let config = { ...entry.install.config };
|
|
155
|
-
// Remote MCPs don't have env to strip
|
|
156
104
|
if (!isRemoteMcp(config)) {
|
|
157
|
-
// Only include env if it has actual values
|
|
158
105
|
if (config.env && Object.values(config.env).every(v => !v)) {
|
|
159
106
|
delete config.env;
|
|
160
107
|
}
|
|
@@ -165,13 +112,6 @@ export async function installAutoMcps(targetPath, mcpsToInstall) {
|
|
|
165
112
|
return installMcpServers(targetPath, servers);
|
|
166
113
|
}
|
|
167
114
|
|
|
168
|
-
/**
|
|
169
|
-
* Install companion skills for MCPs that define them (e.g., Stitch agent skills).
|
|
170
|
-
* Reads the `companionSkills` field from registry entries and runs the install command.
|
|
171
|
-
* @param {Object} mcpsInstalled - Map of mcpName → mcpEntry that were just installed
|
|
172
|
-
* @param {Function} exec - execSync function (for testing)
|
|
173
|
-
* @returns {Array<{name: string, repo: string, success: boolean}>}
|
|
174
|
-
*/
|
|
175
115
|
export function installCompanionSkills(mcpsInstalled, exec = execSync) {
|
|
176
116
|
const results = [];
|
|
177
117
|
|
|
@@ -190,13 +130,6 @@ export function installCompanionSkills(mcpsInstalled, exec = execSync) {
|
|
|
190
130
|
return results;
|
|
191
131
|
}
|
|
192
132
|
|
|
193
|
-
/**
|
|
194
|
-
* Generate the Stitch OAuth proxy template script.
|
|
195
|
-
* Cross-platform (Windows gcloud.cmd + Unix gcloud), fetch()-based,
|
|
196
|
-
* with readline, session tracking, and graceful shutdown.
|
|
197
|
-
* @param {string} gcpProject - GCP project ID for x-goog-user-project header
|
|
198
|
-
* @returns {string} CJS proxy script content
|
|
199
|
-
*/
|
|
200
133
|
export function getStitchProxyTemplate(gcpProject) {
|
|
201
134
|
return `#!/usr/bin/env node
|
|
202
135
|
/**
|
|
@@ -309,12 +242,6 @@ rl.on('close', () => { stdinClosed = true; maybeExit(); });
|
|
|
309
242
|
`;
|
|
310
243
|
}
|
|
311
244
|
|
|
312
|
-
/**
|
|
313
|
-
* Generate and write the Stitch OAuth proxy script to the target project.
|
|
314
|
-
* @param {string} targetPath - Project root directory
|
|
315
|
-
* @param {string} gcpProject - GCP project ID
|
|
316
|
-
* @returns {{ proxyPath: string }} Path to the generated proxy script
|
|
317
|
-
*/
|
|
318
245
|
export function generateStitchProxy(targetPath, gcpProject) {
|
|
319
246
|
const claudeDir = join(targetPath, '.claude');
|
|
320
247
|
if (!existsSync(claudeDir)) {
|
|
@@ -327,20 +254,12 @@ export function generateStitchProxy(targetPath, gcpProject) {
|
|
|
327
254
|
return { proxyPath };
|
|
328
255
|
}
|
|
329
256
|
|
|
330
|
-
/**
|
|
331
|
-
* Resolve the gcloud CLI binary path.
|
|
332
|
-
* Checks: system PATH → bundled at ~/.stitch-mcp/ → null.
|
|
333
|
-
* @param {Function} exec - execSync function (for testing)
|
|
334
|
-
* @returns {{ gcloudPath: string|null, source: string }}
|
|
335
|
-
*/
|
|
336
257
|
export function findGcloud(exec = execSync) {
|
|
337
|
-
// Try system PATH first
|
|
338
258
|
try {
|
|
339
259
|
exec('gcloud --version', { stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000 });
|
|
340
260
|
return { gcloudPath: 'gcloud', source: 'system' };
|
|
341
|
-
} catch {
|
|
261
|
+
} catch {}
|
|
342
262
|
|
|
343
|
-
// Windows: try Cloud SDK default install location
|
|
344
263
|
if (process.platform === 'win32') {
|
|
345
264
|
const winPath = join(process.env.LOCALAPPDATA || '', 'Google', 'Cloud SDK', 'google-cloud-sdk', 'bin', 'gcloud.cmd');
|
|
346
265
|
if (existsSync(winPath)) {
|
|
@@ -348,7 +267,6 @@ export function findGcloud(exec = execSync) {
|
|
|
348
267
|
}
|
|
349
268
|
}
|
|
350
269
|
|
|
351
|
-
// Try bundled at ~/.stitch-mcp/
|
|
352
270
|
const bundledDir = join(homedir(), '.stitch-mcp');
|
|
353
271
|
const bundledBin = process.platform === 'win32'
|
|
354
272
|
? join(bundledDir, 'google-cloud-sdk', 'bin', 'gcloud.cmd')
|
|
@@ -360,29 +278,10 @@ export function findGcloud(exec = execSync) {
|
|
|
360
278
|
return { gcloudPath: null, source: 'not-found' };
|
|
361
279
|
}
|
|
362
280
|
|
|
363
|
-
/**
|
|
364
|
-
* Fully automated Stitch MCP setup orchestrator.
|
|
365
|
-
*
|
|
366
|
-
* Steps:
|
|
367
|
-
* 1. Find gcloud CLI
|
|
368
|
-
* 2. Verify ADC credentials
|
|
369
|
-
* 3. Detect GCP project
|
|
370
|
-
* 4. Enable stitch.googleapis.com API
|
|
371
|
-
* 5. Generate proxy script
|
|
372
|
-
* 6. Install MCP config in .mcp.json
|
|
373
|
-
* 7. Health-check the proxy
|
|
374
|
-
*
|
|
375
|
-
* @param {string} targetPath - Project root directory
|
|
376
|
-
* @param {Object} options
|
|
377
|
-
* @param {string} [options.gcpProject] - GCP project ID (auto-detected if omitted)
|
|
378
|
-
* @param {Function} [options._exec] - execSync override for testing
|
|
379
|
-
* @returns {Promise<Object>} Step-by-step results
|
|
380
|
-
*/
|
|
381
281
|
export async function setupStitch(targetPath, options = {}) {
|
|
382
282
|
const exec = options._exec || execSync;
|
|
383
283
|
const steps = [];
|
|
384
284
|
|
|
385
|
-
// Step 1: Find gcloud
|
|
386
285
|
const { gcloudPath, source: gcloudSource } = findGcloud(exec);
|
|
387
286
|
if (!gcloudPath) {
|
|
388
287
|
steps.push({ step: 'find-gcloud', success: false, error: 'gcloud CLI not found. Install from: https://cloud.google.com/sdk/docs/install' });
|
|
@@ -392,7 +291,6 @@ export async function setupStitch(targetPath, options = {}) {
|
|
|
392
291
|
|
|
393
292
|
const gcloudCmd = gcloudPath.includes(' ') ? `"${gcloudPath}"` : gcloudPath;
|
|
394
293
|
|
|
395
|
-
// Step 2: Verify ADC credentials
|
|
396
294
|
try {
|
|
397
295
|
exec(`${gcloudCmd} auth application-default print-access-token`, {
|
|
398
296
|
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 15000
|
|
@@ -403,26 +301,23 @@ export async function setupStitch(targetPath, options = {}) {
|
|
|
403
301
|
return { success: false, steps };
|
|
404
302
|
}
|
|
405
303
|
|
|
406
|
-
// Step 3: Detect GCP project
|
|
407
304
|
let gcpProject = options.gcpProject;
|
|
408
305
|
if (!gcpProject) {
|
|
409
|
-
// Try config.json
|
|
410
306
|
const configPath = join(targetPath, '.morph', 'config', 'config.json');
|
|
411
307
|
if (existsSync(configPath)) {
|
|
412
308
|
try {
|
|
413
309
|
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
414
310
|
gcpProject = config.gcpProject || config.project?.gcpProject;
|
|
415
|
-
} catch {
|
|
311
|
+
} catch {}
|
|
416
312
|
}
|
|
417
313
|
}
|
|
418
314
|
if (!gcpProject) {
|
|
419
|
-
// Try gcloud config
|
|
420
315
|
try {
|
|
421
316
|
gcpProject = exec(`${gcloudCmd} config get-value project`, {
|
|
422
317
|
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000
|
|
423
318
|
}).trim();
|
|
424
319
|
if (!gcpProject || gcpProject === '(unset)') gcpProject = null;
|
|
425
|
-
} catch {
|
|
320
|
+
} catch {}
|
|
426
321
|
}
|
|
427
322
|
if (!gcpProject) {
|
|
428
323
|
steps.push({ step: 'detect-project', success: false, error: 'Could not detect GCP project. Pass it explicitly or set via: gcloud config set project <id>' });
|
|
@@ -430,7 +325,6 @@ export async function setupStitch(targetPath, options = {}) {
|
|
|
430
325
|
}
|
|
431
326
|
steps.push({ step: 'detect-project', success: true, gcpProject });
|
|
432
327
|
|
|
433
|
-
// Step 4: Enable Stitch API
|
|
434
328
|
try {
|
|
435
329
|
exec(`${gcloudCmd} services enable stitch.googleapis.com --project=${gcpProject}`, {
|
|
436
330
|
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 30000
|
|
@@ -438,20 +332,17 @@ export async function setupStitch(targetPath, options = {}) {
|
|
|
438
332
|
steps.push({ step: 'enable-api', success: true });
|
|
439
333
|
} catch (err) {
|
|
440
334
|
steps.push({ step: 'enable-api', success: false, error: `Failed to enable stitch API: ${err.message}` });
|
|
441
|
-
// Non-fatal
|
|
335
|
+
// Non-fatal: the API may already be enabled and the user may lack permission to re-enable it.
|
|
442
336
|
}
|
|
443
337
|
|
|
444
|
-
// Step 5: Generate proxy
|
|
445
338
|
const { proxyPath } = generateStitchProxy(targetPath, gcpProject);
|
|
446
339
|
steps.push({ step: 'generate-proxy', success: true, proxyPath });
|
|
447
340
|
|
|
448
|
-
// Step 6: Install MCP config in .mcp.json
|
|
449
341
|
const mcpResult = await installMcpServers(targetPath, {
|
|
450
342
|
stitch: { type: 'stdio', command: 'node', args: ['.claude/stitch-proxy.cjs'] }
|
|
451
343
|
});
|
|
452
344
|
steps.push({ step: 'install-mcp', success: true, added: mcpResult.added, skipped: mcpResult.skipped });
|
|
453
345
|
|
|
454
|
-
// Step 7: Health-check
|
|
455
346
|
const healthResult = await healthCheckStitchProxy(proxyPath);
|
|
456
347
|
steps.push({ step: 'health-check', ...healthResult });
|
|
457
348
|
|
|
@@ -462,11 +353,6 @@ export async function setupStitch(targetPath, options = {}) {
|
|
|
462
353
|
return { success: allCriticalPassed, steps, gcpProject };
|
|
463
354
|
}
|
|
464
355
|
|
|
465
|
-
/**
|
|
466
|
-
* Health-check the Stitch proxy by sending an MCP initialize handshake.
|
|
467
|
-
* @param {string} proxyPath - Path to stitch-proxy.cjs
|
|
468
|
-
* @returns {Promise<Object>} { success, protocolVersion?, error? }
|
|
469
|
-
*/
|
|
470
356
|
export function healthCheckStitchProxy(proxyPath) {
|
|
471
357
|
return new Promise((resolve) => {
|
|
472
358
|
const initMsg = JSON.stringify({
|
|
@@ -492,7 +378,6 @@ export function healthCheckStitchProxy(proxyPath) {
|
|
|
492
378
|
|
|
493
379
|
child.stdout.on('data', (data) => {
|
|
494
380
|
output += data.toString();
|
|
495
|
-
// Check if we got a response
|
|
496
381
|
const lines = output.split('\n').filter(l => l.trim());
|
|
497
382
|
for (const line of lines) {
|
|
498
383
|
try {
|
|
@@ -509,7 +394,7 @@ export function healthCheckStitchProxy(proxyPath) {
|
|
|
509
394
|
resolve({ success: false, error: resp.error.message || 'MCP error' });
|
|
510
395
|
return;
|
|
511
396
|
}
|
|
512
|
-
} catch {
|
|
397
|
+
} catch {}
|
|
513
398
|
}
|
|
514
399
|
});
|
|
515
400
|
|
|
@@ -530,38 +415,22 @@ export function healthCheckStitchProxy(proxyPath) {
|
|
|
530
415
|
}
|
|
531
416
|
});
|
|
532
417
|
|
|
533
|
-
// Send the initialize message
|
|
534
418
|
child.stdin.write(initMsg + '\n');
|
|
535
419
|
child.stdin.end();
|
|
536
420
|
});
|
|
537
421
|
}
|
|
538
422
|
|
|
539
|
-
/**
|
|
540
|
-
* Install a single MCP with provided credentials
|
|
541
|
-
* @param {string} targetPath - Project root directory
|
|
542
|
-
* @param {string} name - MCP name
|
|
543
|
-
* @param {Object} mcpEntry - MCP entry from registry
|
|
544
|
-
* @param {Object} credentialValues - Map of envVar → value
|
|
545
|
-
* @returns {Promise<Object>} Result from installMcpServers
|
|
546
|
-
*/
|
|
547
423
|
export async function installMcpWithCredentials(targetPath, name, mcpEntry, credentialValues) {
|
|
548
424
|
const config = patchConfigForPlatform({ ...mcpEntry.install.config });
|
|
549
425
|
config.env = { ...config.env, ...credentialValues };
|
|
550
426
|
return installMcpServers(targetPath, { [name]: config });
|
|
551
427
|
}
|
|
552
428
|
|
|
553
|
-
/**
|
|
554
|
-
* Generate setup instructions for a credential-requiring MCP
|
|
555
|
-
* @param {string} name - MCP name
|
|
556
|
-
* @param {Object} mcpEntry - MCP entry from registry
|
|
557
|
-
* @returns {Object} { configSnippet, credentialUrls, cliCommand }
|
|
558
|
-
*/
|
|
559
429
|
export function generateSetupInstructions(name, mcpEntry) {
|
|
560
430
|
const config = mcpEntry.install.config;
|
|
561
431
|
const credentials = mcpEntry.install.credentials || [];
|
|
562
432
|
const setupGuide = mcpEntry.install.setupGuide;
|
|
563
433
|
|
|
564
|
-
// Remote HTTP MCPs — show claude mcp add command instead of config snippet
|
|
565
434
|
if (isRemoteMcp(config)) {
|
|
566
435
|
const configSnippet = JSON.stringify({ [name]: config }, null, 2);
|
|
567
436
|
const credentialUrls = credentials.map(c => ({
|
|
@@ -577,7 +446,6 @@ export function generateSetupInstructions(name, mcpEntry) {
|
|
|
577
446
|
};
|
|
578
447
|
}
|
|
579
448
|
|
|
580
|
-
// Build the config snippet with placeholder values
|
|
581
449
|
const snippetConfig = { ...config };
|
|
582
450
|
if (credentials.length > 0) {
|
|
583
451
|
snippetConfig.env = {};
|
|
@@ -601,17 +469,6 @@ export function generateSetupInstructions(name, mcpEntry) {
|
|
|
601
469
|
};
|
|
602
470
|
}
|
|
603
471
|
|
|
604
|
-
/**
|
|
605
|
-
* Full orchestrator for MCP setup during init or `mcp setup`
|
|
606
|
-
* @param {string} targetPath - Project root directory
|
|
607
|
-
* @param {string} stack - Detected project stack
|
|
608
|
-
* @param {Array} existingMcps - From detectClaudeConfig().mcpServers
|
|
609
|
-
* @param {Object} options
|
|
610
|
-
* @param {boolean} options.autoOnly - Only handle auto-installable MCPs
|
|
611
|
-
* @param {string[]} [options.integrations] - `project.integrations`, matched like the stack
|
|
612
|
-
* @param {string[]} [options.enabledPlugins] - `detectClaudeConfig().enabledPlugins`
|
|
613
|
-
* @returns {Object} { autoInstallable, alreadyInstalled, needsManualSetup, notRelevant, prerequisitesMissing }
|
|
614
|
-
*/
|
|
615
472
|
export function orchestrateMcpSetup(targetPath, stack, existingMcps, options = {}) {
|
|
616
473
|
const registry = loadMcpRegistry();
|
|
617
474
|
const relevant = filterByStack(registry, stack, options.integrations ?? []);
|
|
@@ -623,23 +480,19 @@ export function orchestrateMcpSetup(targetPath, stack, existingMcps, options = {
|
|
|
623
480
|
const notRelevant = {};
|
|
624
481
|
const prerequisitesMissing = {};
|
|
625
482
|
|
|
626
|
-
// Categorize all MCPs
|
|
627
483
|
for (const [name, entry] of Object.entries(registry.mcps)) {
|
|
628
484
|
if (!entry.install) continue;
|
|
629
485
|
|
|
630
|
-
// Check if already installed
|
|
631
486
|
if (installed[name]) {
|
|
632
487
|
alreadyInstalled[name] = { entry, source: installed[name].source };
|
|
633
488
|
continue;
|
|
634
489
|
}
|
|
635
490
|
|
|
636
|
-
// Check if relevant to stack
|
|
637
491
|
if (!relevant[name]) {
|
|
638
492
|
notRelevant[name] = entry;
|
|
639
493
|
continue;
|
|
640
494
|
}
|
|
641
495
|
|
|
642
|
-
// Check prerequisites
|
|
643
496
|
const prereqResults = checkPrerequisites(entry);
|
|
644
497
|
const unmetPrereqs = prereqResults.filter(p => !p.met);
|
|
645
498
|
if (unmetPrereqs.length > 0) {
|
|
@@ -647,7 +500,6 @@ export function orchestrateMcpSetup(targetPath, stack, existingMcps, options = {
|
|
|
647
500
|
continue;
|
|
648
501
|
}
|
|
649
502
|
|
|
650
|
-
// Categorize by install type
|
|
651
503
|
if (entry.install.autoInstall) {
|
|
652
504
|
autoInstallable[name] = entry;
|
|
653
505
|
} else if (!options.autoOnly) {
|
|
@@ -665,18 +517,11 @@ export function orchestrateMcpSetup(targetPath, stack, existingMcps, options = {
|
|
|
665
517
|
};
|
|
666
518
|
}
|
|
667
519
|
|
|
668
|
-
/**
|
|
669
|
-
* Format MCP status table for display
|
|
670
|
-
* @param {Object} orchestration - Result from orchestrateMcpSetup
|
|
671
|
-
* @param {Object} justInstalled - Map of names just installed during this run
|
|
672
|
-
* @returns {Array<Object>} Array of { name, status, detail }
|
|
673
|
-
*/
|
|
674
520
|
export function formatMcpStatusTable(orchestration, justInstalled = {}) {
|
|
675
521
|
const rows = [];
|
|
676
522
|
|
|
677
523
|
const { autoInstallable, alreadyInstalled, needsManualSetup, notRelevant, prerequisitesMissing, registry } = orchestration;
|
|
678
524
|
|
|
679
|
-
// Installed during this run
|
|
680
525
|
for (const [name, entry] of Object.entries(justInstalled)) {
|
|
681
526
|
const mcpEntry = registry.mcps[name];
|
|
682
527
|
rows.push({
|
|
@@ -686,7 +531,6 @@ export function formatMcpStatusTable(orchestration, justInstalled = {}) {
|
|
|
686
531
|
});
|
|
687
532
|
}
|
|
688
533
|
|
|
689
|
-
// Auto-installable but not yet installed (and not just-installed)
|
|
690
534
|
for (const [name, entry] of Object.entries(autoInstallable)) {
|
|
691
535
|
if (justInstalled[name]) continue;
|
|
692
536
|
rows.push({
|
|
@@ -696,7 +540,6 @@ export function formatMcpStatusTable(orchestration, justInstalled = {}) {
|
|
|
696
540
|
});
|
|
697
541
|
}
|
|
698
542
|
|
|
699
|
-
// Already installed
|
|
700
543
|
for (const [name, { source }] of Object.entries(alreadyInstalled)) {
|
|
701
544
|
rows.push({
|
|
702
545
|
name,
|
|
@@ -705,7 +548,6 @@ export function formatMcpStatusTable(orchestration, justInstalled = {}) {
|
|
|
705
548
|
});
|
|
706
549
|
}
|
|
707
550
|
|
|
708
|
-
// Needs manual setup
|
|
709
551
|
for (const [name, entry] of Object.entries(needsManualSetup)) {
|
|
710
552
|
rows.push({
|
|
711
553
|
name,
|
|
@@ -714,7 +556,6 @@ export function formatMcpStatusTable(orchestration, justInstalled = {}) {
|
|
|
714
556
|
});
|
|
715
557
|
}
|
|
716
558
|
|
|
717
|
-
// Prerequisites missing
|
|
718
559
|
for (const [name, { unmet }] of Object.entries(prerequisitesMissing)) {
|
|
719
560
|
const missing = unmet.map(p => p.name).join(', ');
|
|
720
561
|
rows.push({
|
|
@@ -724,7 +565,6 @@ export function formatMcpStatusTable(orchestration, justInstalled = {}) {
|
|
|
724
565
|
});
|
|
725
566
|
}
|
|
726
567
|
|
|
727
|
-
// Not relevant
|
|
728
568
|
for (const [name] of Object.entries(notRelevant)) {
|
|
729
569
|
rows.push({
|
|
730
570
|
name,
|
|
@@ -1,15 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Writes two memory files (project + reference) plus an entry in MEMORY.md
|
|
5
|
-
* so future Claude Code sessions enter pre-contextualized about the
|
|
6
|
-
* morph-spec setup of this project.
|
|
7
|
-
*
|
|
8
|
-
* Memory location: ~/.claude/projects/<encoded-project-path>/memory/
|
|
9
|
-
*
|
|
10
|
-
* The path encoding mirrors Claude Code's own scheme: replace slashes and
|
|
11
|
-
* colons with dashes. Tests can override via MORPH_MEMORY_ROOT env var.
|
|
12
|
-
*/
|
|
1
|
+
// The path encoding mirrors Claude Code's scheme (slashes and colons become dashes);
|
|
2
|
+
// MORPH_MEMORY_ROOT overrides the root in tests.
|
|
13
3
|
|
|
14
4
|
import fs from 'fs-extra';
|
|
15
5
|
import { join } from 'path';
|
|
@@ -49,7 +39,7 @@ metadata:
|
|
|
49
39
|
|
|
50
40
|
| Command | Skill invoked |
|
|
51
41
|
|---------|---------------|
|
|
52
|
-
| \`/morph-
|
|
42
|
+
| \`/morph-plan {feature}\` | \`Skill(morph-plan)\` — entrevista → plan.md → plan-check → gate do plano |
|
|
53
43
|
| \`/morph-apply {feature}\` | \`Skill(morph-apply)\` — fatias do plano → verify → review → Gate 3 |
|
|
54
44
|
| \`/morph-status [feature]\` | inspect state.json |
|
|
55
45
|
| \`/morph-archive {feature}\` | move completed feature to .morph/archive/ |
|
|
@@ -64,7 +54,6 @@ metadata:
|
|
|
64
54
|
|
|
65
55
|
**How to apply:**
|
|
66
56
|
- When user mentions a feature, check \`.morph/features/\` first.
|
|
67
|
-
- When user types \`/morph-proposal\`, invoke \`Skill(morph-plan)\` directly.
|
|
68
57
|
- In \`trust: "auto"\`, gates auto-pass; only AskUserQuestion on risk signals.
|
|
69
58
|
`;
|
|
70
59
|
}
|
|
@@ -87,19 +76,7 @@ metadata:
|
|
|
87
76
|
- \`.morph/framework/evals/review-checklist.md\` — what the Gate 3 verifier, reviewer and security check (verdict SHIP/FIX + evidence-typed blockers)
|
|
88
77
|
`;
|
|
89
78
|
|
|
90
|
-
|
|
91
|
-
* Populate Claude Code persistent memory for a morph-spec project.
|
|
92
|
-
*
|
|
93
|
-
* Idempotent: re-running overwrites the memory files with current info
|
|
94
|
-
* but appends MEMORY.md index entries only if missing.
|
|
95
|
-
*
|
|
96
|
-
* @param {string} projectPath - Absolute path of the project root
|
|
97
|
-
* @param {object} info - Project facts to embed in the memory
|
|
98
|
-
* @param {string} info.projectName
|
|
99
|
-
* @param {string} info.frameworkVersion
|
|
100
|
-
* @param {string} [info.stack]
|
|
101
|
-
* @returns {Promise<{ projectMemoryPath: string, referenceMemoryPath: string, indexPath: string }>}
|
|
102
|
-
*/
|
|
79
|
+
// Idempotent: memory files are overwritten, MEMORY.md entries appended only if missing.
|
|
103
80
|
export async function installProjectMemory(projectPath, info) {
|
|
104
81
|
const memoryRoot = getMemoryRoot(projectPath);
|
|
105
82
|
await fs.ensureDir(memoryRoot);
|