@polymorphism-tech/morph-spec 8.34.2 → 8.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +22 -5
- package/README.md +2 -2
- package/bin/morph-spec.js +18 -4
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +16 -17
- package/framework/agents.json +197 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +236 -11
- package/framework/evals/proof-rubrics/README.md +76 -0
- package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
- package/framework/evals/proof-rubrics/fiacao.md +54 -0
- package/framework/evals/proof-rubrics/guard-cego.md +52 -0
- package/framework/evals/proof-rubrics/media-claims.md +109 -0
- package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
- package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
- package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
- package/framework/evals/rubrics/architecture.md +30 -0
- package/framework/evals/rubrics/code-quality.md +26 -0
- package/framework/evals/rubrics/test-coverage.md +20 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/core/worktree-env-inject.js +43 -0
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/e2e-check.js +135 -22
- package/framework/hooks/shared/payload-utils.js +15 -2
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/schemas/ai-pin.schema.json +97 -0
- package/framework/schemas/config.schema.json +107 -0
- package/framework/schemas/feature.schema.json +23 -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 +26 -5
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +18 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +33 -2
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +166 -9
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/PROVENANCE.json +81 -3
- package/framework/standards/STANDARDS.json +1483 -839
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +164 -68
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +61 -37
- package/framework/standards/ai-agents/context-providers.md +97 -24
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
- package/framework/standards/ai-agents/cost-and-budget.md +256 -0
- package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
- package/framework/standards/ai-agents/evals-with-cache.md +289 -0
- package/framework/standards/ai-agents/guardrails.md +222 -0
- package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
- package/framework/standards/ai-agents/mcp-server.md +119 -18
- package/framework/standards/ai-agents/mcp-tools.md +85 -32
- package/framework/standards/ai-agents/media-pipeline.md +352 -0
- package/framework/standards/ai-agents/media-video.md +267 -0
- package/framework/standards/ai-agents/middleware-patterns.md +39 -5
- package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
- package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
- package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
- package/framework/standards/ai-agents/modalities-vision.md +6 -2
- package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +38 -9
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +174 -39
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +27 -19
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +34 -3
- package/framework/standards/ai-agents/sweet-spot.md +23 -3
- package/framework/standards/ai-agents/testing-ai.md +236 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
- package/framework/standards/ai-agents/workflows.md +27 -7
- package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
- package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +13 -0
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
- package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
- package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
- package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
- package/framework/templates/dotnet/ai-kit/README.md +128 -0
- package/framework/templates/dotnet/ai-kit/global.json +9 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +3 -3
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/e2e/e2e.js +15 -2
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +148 -6
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +76 -4
- package/src/commands/state/advance.js +24 -2
- package/src/commands/state/approve.js +33 -1
- package/src/commands/state/create.js +72 -11
- package/src/commands/state/finish.js +379 -24
- package/src/commands/state/gate-check.js +190 -6
- package/src/commands/state/score.js +36 -1
- package/src/commands/validation/verify.js +134 -25
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +9 -1
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +182 -0
- package/src/lib/ai/ai-project-scan.js +347 -0
- package/src/lib/ai/code-audit.js +181 -0
- package/src/lib/ai/comment-stripper.js +184 -0
- package/src/lib/ai/pin-drift.js +138 -0
- package/src/lib/ai/registry-audit.js +285 -0
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/e2e/compose-env.js +50 -4
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/git/pr-files.js +56 -0
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/effects.js +193 -0
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/templates-reconcile.js +3 -3
- package/src/lib/risk-detector.js +284 -0
- package/src/lib/standards/verified-against.js +276 -0
- package/src/lib/tasks/eval-node.js +485 -0
- package/src/lib/tasks/linux-runner.js +297 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +410 -33
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/verify-runner.js +583 -28
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +24 -0
- package/src/lib/worktree/worktree-audit.js +30 -1
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +11 -4
- package/src/utils/hooks-installer.js +29 -26
- package/src/utils/skills-installer.js +36 -29
- package/src/utils/version-checker.js +68 -3
- package/framework/standards/ai-agents/prompts-in-db.md +0 -214
|
@@ -1,21 +1,17 @@
|
|
|
1
|
-
import { join, dirname } from 'path';
|
|
1
|
+
import { join, dirname, relative, isAbsolute } from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import { spawnSync } from 'child_process';
|
|
4
|
-
import { existsSync } from 'fs';
|
|
4
|
+
import { existsSync, readdirSync } from 'fs';
|
|
5
|
+
import { homedir } from 'os';
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
import fs from 'fs-extra';
|
|
8
8
|
import ora from 'ora';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import { logger } from '../../utils/logger.js';
|
|
11
11
|
import {
|
|
12
12
|
getContentDir,
|
|
13
|
-
copyDirectory,
|
|
14
|
-
copyFile,
|
|
15
13
|
pathExists,
|
|
16
|
-
ensureDir,
|
|
17
14
|
readJson,
|
|
18
|
-
writeJson,
|
|
19
15
|
updateGitignore,
|
|
20
16
|
ensureGitattributes
|
|
21
17
|
} from '../../utils/file-copier.js';
|
|
@@ -24,7 +20,10 @@ import {
|
|
|
24
20
|
checkProjectOutdated,
|
|
25
21
|
saveProjectMorphVersion,
|
|
26
22
|
getUpdateInstructions,
|
|
27
|
-
detectInstallMethod
|
|
23
|
+
detectInstallMethod,
|
|
24
|
+
getInstalledCLIVersion,
|
|
25
|
+
majorJump,
|
|
26
|
+
MAJOR_JUMP_LIMIT
|
|
28
27
|
} from '../../utils/version-checker.js';
|
|
29
28
|
import { installClaudeHooks, installGlobalStatusline } from '../../utils/claude-settings-manager.js';
|
|
30
29
|
import { installSkills } from '../../utils/skills-installer.js';
|
|
@@ -33,6 +32,7 @@ import { injectMorphImport } from '../../utils/claude-md-injector.js';
|
|
|
33
32
|
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
34
33
|
import { updateClaudeMdAgentsBlock, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
35
34
|
import { reconcileLegacyTemplatesCopy } from '../../lib/installers/templates-reconcile.js';
|
|
35
|
+
import { createEffects } from '../../lib/installers/effects.js';
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Backup user's config.json before cleaning
|
|
@@ -53,13 +53,23 @@ async function backupUserConfig(morphPath) {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* Os DEZ diretórios que `cleanFrameworkDirs` apaga por inteiro, sem condição.
|
|
57
|
+
*
|
|
58
|
+
* O número está pinado à mão em `test/commands/update-dry-run.test.js`
|
|
59
|
+
* (`DIRETORIOS_APAGADOS_POR_INTEIRO`), com os dez caminhos escritos: derivar a
|
|
60
|
+
* expectativa desta função deixaria a suíte cega a uma entrada removida.
|
|
61
|
+
*
|
|
62
|
+
* Exportado porque o relatório do `--dry-run` é obrigado a nomeá-los: "update"
|
|
63
|
+
* não sugere a ninguém que `.claude/skills/` e `.claude/agents/` são apagados
|
|
64
|
+
* inteiros antes da reinstalação.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} morphPath
|
|
67
|
+
* @param {string} targetPath
|
|
68
|
+
* @returns {string[]}
|
|
59
69
|
*/
|
|
60
|
-
|
|
70
|
+
export function frameworkDirsToClean(morphPath, targetPath) {
|
|
61
71
|
const claudeDir = join(targetPath, '.claude');
|
|
62
|
-
|
|
72
|
+
return [
|
|
63
73
|
// NOTE: framework/templates is intentionally NOT in this list (R3 perf
|
|
64
74
|
// plan, Task 2). It's no longer copied/refreshed here — see
|
|
65
75
|
// reconcileLegacyTemplatesCopy() below, which decides per-run whether a
|
|
@@ -77,16 +87,68 @@ async function cleanFrameworkDirs(morphPath, targetPath) {
|
|
|
77
87
|
join(claudeDir, 'skills'),
|
|
78
88
|
join(claudeDir, 'agents'),
|
|
79
89
|
];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Emite (e, fora do dry, executa) a remoção recursiva de um diretório.
|
|
94
|
+
*
|
|
95
|
+
* Um efeito por arquivo, mais um pelo diretório. É a varredura que a §5.2 do
|
|
96
|
+
* spec exige, e é dela que sai a contagem "N arquivos" do relatório.
|
|
97
|
+
*
|
|
98
|
+
* @returns {number} arquivos varridos
|
|
99
|
+
*/
|
|
100
|
+
function sweepRemove(dir, fx) {
|
|
101
|
+
let entries;
|
|
102
|
+
try {
|
|
103
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
104
|
+
} catch {
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
let files = 0;
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
const full = join(dir, entry.name);
|
|
110
|
+
if (entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
111
|
+
files += sweepRemove(full, fx);
|
|
112
|
+
} else {
|
|
113
|
+
fx.remove(full);
|
|
114
|
+
files += 1;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
fx.remove(dir);
|
|
118
|
+
return files;
|
|
119
|
+
}
|
|
80
120
|
|
|
81
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Clean framework directories but preserve .morph/project/.
|
|
123
|
+
*
|
|
124
|
+
* **A armadilha que esta função resolve (decisions.md D6).** Ela apaga
|
|
125
|
+
* exatamente os destinos que os laços de limpeza de órfão DEPOIS varrem
|
|
126
|
+
* (`skills-installer.js`, `agents-installer.js`, `installRulesForStack`).
|
|
127
|
+
* Numa execução real esses laços sempre encontram diretório vazio e não
|
|
128
|
+
* apagam nada. Num `--dry-run` ingênuo nada foi apagado, eles encontrariam o
|
|
129
|
+
* diretório POPULADO e o relatório preveria remoções que a execução real
|
|
130
|
+
* nunca faz — errado na direção que assusta o usuário. Como o clean é
|
|
131
|
+
* incondicional e total, a previsão correta é "tudo que hoje está sob estes
|
|
132
|
+
* dez é apagado", e os laços de órfão ficam calados em dry: são provadamente
|
|
133
|
+
* subsumidos.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} morphPath - Path to .morph directory
|
|
136
|
+
* @param {string} targetPath - Project root path
|
|
137
|
+
* @param {ReturnType<import('../../lib/installers/effects.js').createEffects>} fx
|
|
138
|
+
* @returns {Array<{ dir: string, files: number }>} o que foi (ou seria) apagado
|
|
139
|
+
*/
|
|
140
|
+
function cleanFrameworkDirs(morphPath, targetPath, fx) {
|
|
141
|
+
const cleaned = [];
|
|
142
|
+
for (const dir of frameworkDirsToClean(morphPath, targetPath)) {
|
|
82
143
|
try {
|
|
83
|
-
if (
|
|
84
|
-
|
|
144
|
+
if (existsSync(dir)) {
|
|
145
|
+
cleaned.push({ dir, files: sweepRemove(dir, fx) });
|
|
85
146
|
}
|
|
86
147
|
} catch (error) {
|
|
87
148
|
// Continue even if cleanup fails for one directory
|
|
88
149
|
}
|
|
89
150
|
}
|
|
151
|
+
return cleaned;
|
|
90
152
|
}
|
|
91
153
|
|
|
92
154
|
/**
|
|
@@ -95,16 +157,17 @@ async function cleanFrameworkDirs(morphPath, targetPath) {
|
|
|
95
157
|
* @param {string} targetPath - Project root path
|
|
96
158
|
* @param {object|null} configBackup - Backup of config.json
|
|
97
159
|
* @param {string} currentVersion - Current CLI version
|
|
160
|
+
* @param {ReturnType<import('../../lib/installers/effects.js').createEffects>} fx
|
|
98
161
|
*/
|
|
99
|
-
|
|
162
|
+
function restoreUserConfig(morphPath, targetPath, configBackup, currentVersion, fx) {
|
|
100
163
|
const configDir = join(morphPath, 'config');
|
|
101
|
-
|
|
164
|
+
fx.ensureDir(configDir);
|
|
102
165
|
const configPath = join(configDir, 'config.json');
|
|
103
166
|
|
|
104
167
|
if (configBackup) {
|
|
105
168
|
// Preserve user data but update framework version
|
|
106
169
|
configBackup.frameworkVersion = currentVersion;
|
|
107
|
-
|
|
170
|
+
fx.writeJson(configPath, configBackup);
|
|
108
171
|
} else {
|
|
109
172
|
// Create basic config if none existed
|
|
110
173
|
const dirName = targetPath.split(/[\\/]/).pop();
|
|
@@ -116,7 +179,32 @@ async function restoreUserConfig(morphPath, targetPath, configBackup, currentVer
|
|
|
116
179
|
architecture: 'unknown'
|
|
117
180
|
}
|
|
118
181
|
};
|
|
119
|
-
|
|
182
|
+
fx.writeJson(configPath, defaultConfig);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* `.morph/state.json` já está no índice do git?
|
|
188
|
+
*
|
|
189
|
+
* SONDA, separada da mutação de propósito: sondar é ler, e ler acontece nos
|
|
190
|
+
* dois modos. É o que faz previsão e execução concordarem sobre este efeito.
|
|
191
|
+
*
|
|
192
|
+
* @param {string} targetPath
|
|
193
|
+
* @returns {boolean}
|
|
194
|
+
*/
|
|
195
|
+
export function stateJsonIsTracked(targetPath) {
|
|
196
|
+
try {
|
|
197
|
+
const isRepo = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
|
|
198
|
+
cwd: targetPath, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore']
|
|
199
|
+
});
|
|
200
|
+
if (isRepo.status !== 0) return false;
|
|
201
|
+
|
|
202
|
+
const tracked = spawnSync('git', ['ls-files', '--error-unmatch', '.morph/state.json'], {
|
|
203
|
+
cwd: targetPath, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore']
|
|
204
|
+
});
|
|
205
|
+
return tracked.status === 0;
|
|
206
|
+
} catch {
|
|
207
|
+
return false;
|
|
120
208
|
}
|
|
121
209
|
}
|
|
122
210
|
|
|
@@ -133,56 +221,227 @@ async function restoreUserConfig(morphPath, targetPath, configBackup, currentVer
|
|
|
133
221
|
* derivable index. Fail-open — a non-git project or any git error is a silent no-op.
|
|
134
222
|
*
|
|
135
223
|
* @param {string} targetPath - Project root
|
|
224
|
+
* @param {ReturnType<import('../../lib/installers/effects.js').createEffects>} fx
|
|
136
225
|
* @returns {boolean} true if state.json was untracked, false otherwise
|
|
137
226
|
*/
|
|
138
|
-
export function untrackCommittedStateJson(targetPath) {
|
|
227
|
+
export function untrackCommittedStateJson(targetPath, fx = createEffects({ dryRun: false })) {
|
|
228
|
+
if (!stateJsonIsTracked(targetPath)) return false; // not tracked — nothing to migrate
|
|
229
|
+
return fx.gitIndex('rm --cached --quiet', '.morph/state.json', { cwd: targetPath });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Os dois registros de versão que um projeto morph-spec carrega.
|
|
234
|
+
*
|
|
235
|
+
* `.morph/.morphversion → morphVersion` é o que o `update` LÊ para decidir.
|
|
236
|
+
* `config.json → frameworkVersion` é o que o `update` ESCREVE e nunca lê para
|
|
237
|
+
* decidir coisa alguma — e no MORPH_OS os dois divergem por um major inteiro
|
|
238
|
+
* (7.5.0 contra 6.0.0). Um freio calibrado no campo errado recusaria (ou
|
|
239
|
+
* liberaria) com base num número que ninguém mantém.
|
|
240
|
+
*
|
|
241
|
+
* @param {string} targetPath
|
|
242
|
+
* @returns {Promise<{ morphVersion: string|null, frameworkVersion: string|null, diverged: boolean }>}
|
|
243
|
+
*/
|
|
244
|
+
export async function readVersionRegisters(targetPath) {
|
|
245
|
+
let morphVersion = null;
|
|
246
|
+
let frameworkVersion = null;
|
|
139
247
|
try {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
248
|
+
const v = await readJson(join(targetPath, '.morph', '.morphversion'));
|
|
249
|
+
morphVersion = typeof v?.morphVersion === 'string' ? v.morphVersion : null;
|
|
250
|
+
} catch { /* ausente ou ilegível — indeterminável, e isso é um dado */ }
|
|
251
|
+
try {
|
|
252
|
+
const c = await readJson(join(targetPath, '.morph', 'config', 'config.json'));
|
|
253
|
+
frameworkVersion = typeof c?.frameworkVersion === 'string' ? c.frameworkVersion : null;
|
|
254
|
+
} catch { /* idem */ }
|
|
144
255
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
149
|
-
if (tracked.status !== 0) return false; // not tracked — nothing to migrate
|
|
256
|
+
const diverged = Boolean(morphVersion && frameworkVersion && morphVersion !== frameworkVersion);
|
|
257
|
+
return { morphVersion, frameworkVersion, diverged };
|
|
258
|
+
}
|
|
150
259
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
260
|
+
/**
|
|
261
|
+
* As linhas da recusa do freio de major.
|
|
262
|
+
*
|
|
263
|
+
* Separada do comando para ser testável sem rodar o `update`, e porque a
|
|
264
|
+
* mensagem carrega três obrigações: a versão instalada, a versão de destino e
|
|
265
|
+
* **o registro de onde a versão instalada foi lida**. Quando os dois registros
|
|
266
|
+
* discordam, imprime OS DOIS e diz qual decidiu — escolher em silêncio é
|
|
267
|
+
* acertar metade das vezes e nunca saber qual metade.
|
|
268
|
+
*
|
|
269
|
+
* @param {{ refuse: boolean, reason: string|null, jump: number|null }} verdict
|
|
270
|
+
* @param {{ morphVersion: string|null, frameworkVersion: string|null, diverged: boolean }} registers
|
|
271
|
+
* @param {string} targetVersion
|
|
272
|
+
* @returns {string[]}
|
|
273
|
+
*/
|
|
274
|
+
export function majorGuardLines(verdict, registers, targetVersion) {
|
|
275
|
+
const lines = [];
|
|
276
|
+
if (verdict.reason === 'unknown-installed') {
|
|
277
|
+
lines.push(`Recusado: versão instalada indeterminável (\`.morph/.morphversion\` ausente ou ilegível).`);
|
|
278
|
+
lines.push(` Tratado como salto grande, com motivo próprio: "não sei quão velho isto é" é `
|
|
279
|
+
+ `precisamente quando sobrescrever às cegas é mais perigoso.`);
|
|
280
|
+
} else {
|
|
281
|
+
lines.push(`Recusado: salto de ${verdict.jump} majors (${registers.morphVersion} → ${targetVersion}); `
|
|
282
|
+
+ `o limite sem \`--force\` é ${MAJOR_JUMP_LIMIT}.`);
|
|
157
283
|
}
|
|
284
|
+
lines.push(` Registro que decidiu: \`.morph/.morphversion → morphVersion\` = `
|
|
285
|
+
+ `${registers.morphVersion ?? 'ausente'}.`);
|
|
286
|
+
if (registers.diverged) {
|
|
287
|
+
lines.push(` Divergência: \`config.json → frameworkVersion\` diz ${registers.frameworkVersion}, `
|
|
288
|
+
+ `e NÃO foi ele que decidiu — o \`update\` escreve esse campo e nunca o lê para decidir.`);
|
|
289
|
+
}
|
|
290
|
+
lines.push(' Para prosseguir mesmo assim: `morph-spec update --force`.');
|
|
291
|
+
lines.push(' Para ver o que aconteceria antes de decidir: `morph-spec update --dry-run`.');
|
|
292
|
+
return lines;
|
|
158
293
|
}
|
|
159
294
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
295
|
+
/** Rótulo humano de cada verbo, para agrupar o relatório por natureza do efeito. */
|
|
296
|
+
const VERB_GROUPS = {
|
|
297
|
+
remove: 'Apagar',
|
|
298
|
+
ensureDir: 'Criar diretório',
|
|
299
|
+
copyDir: 'Copiar diretório',
|
|
300
|
+
copyFile: 'Copiar arquivo',
|
|
301
|
+
writeFile: 'Escrever',
|
|
302
|
+
writeJson: 'Escrever (JSON)',
|
|
303
|
+
mergeJson: 'Mesclar (JSON)',
|
|
304
|
+
spawn: 'Executar',
|
|
305
|
+
gitIndex: 'Índice do git',
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
function isUnder(path, root) {
|
|
309
|
+
if (!path || !root) return false;
|
|
310
|
+
const rel = relative(root, path);
|
|
311
|
+
return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
|
|
312
|
+
}
|
|
164
313
|
|
|
165
|
-
|
|
314
|
+
/**
|
|
315
|
+
* ONDE um efeito age.
|
|
316
|
+
*
|
|
317
|
+
* Para quase todo verbo é o próprio `target`. Para `spawn` NÃO é: o `target`
|
|
318
|
+
* de um spawn é o binário (`node`), e classificar por ele esconderia
|
|
319
|
+
* exatamente o efeito que a §5.3 obriga a nomear — `generate-refs.js`
|
|
320
|
+
* escrevendo `framework/hooks/shared/phase-utils.js` DENTRO do pacote da CLI.
|
|
321
|
+
*
|
|
322
|
+
* O script sai de `effect.args`, o **array**, nunca de um split do `detail`
|
|
323
|
+
* por espaço: o caminho real deste pacote é
|
|
324
|
+
* `R:\Polymorphism Tech\repos\...` — um espaço no meio, e o split devolvia
|
|
325
|
+
* `Tech\repos\...\generate-refs.js`, que não é subcaminho de nada. O efeito ia
|
|
326
|
+
* calado para o grupo errado. Medido rodando o comando de verdade.
|
|
327
|
+
*/
|
|
328
|
+
function effectLocation(effect) {
|
|
329
|
+
if (effect.verb !== 'spawn') return effect.target;
|
|
330
|
+
const args = Array.isArray(effect.args) ? effect.args : [];
|
|
331
|
+
const script = args.find((a) => /\.(js|mjs|cjs)$/.test(String(a)));
|
|
332
|
+
return script || effect.target;
|
|
333
|
+
}
|
|
166
334
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
335
|
+
/**
|
|
336
|
+
* Imprime o relatório de efeitos.
|
|
337
|
+
*
|
|
338
|
+
* Três grupos ganham nome próprio, porque o usuário não os adivinharia da
|
|
339
|
+
* palavra "update": o que é apagado por INTEIRO antes da reinstalação, o que
|
|
340
|
+
* acontece FORA do projeto, e o que é escrito DENTRO do pacote da CLI (que num
|
|
341
|
+
* `npm link` de desenvolvimento é o próprio repositório do framework).
|
|
342
|
+
*
|
|
343
|
+
* @param {Array} effects - saída de `fx.list()`
|
|
344
|
+
* @param {object} context
|
|
345
|
+
*/
|
|
346
|
+
export function renderEffectsReport(effects, context) {
|
|
347
|
+
const { targetPath, packageRoot, dryRun, cleaned = [], notices = [] } = context;
|
|
348
|
+
const globalClaudeDir = join(homedir(), '.claude');
|
|
172
349
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
350
|
+
logger.blank();
|
|
351
|
+
// O cabeçalho não pode dizer "nem executado": em dry o `spawn` do
|
|
352
|
+
// `generate-refs.js` RODA, na variante `--check`, que verifica e não escreve.
|
|
353
|
+
// Prometer silêncio total e rodar um processo é mentir num detalhe pequeno —
|
|
354
|
+
// e é o tipo de detalhe pequeno que faz o usuário parar de acreditar no resto.
|
|
355
|
+
logger.info(dryRun
|
|
356
|
+
? 'Relatório do --dry-run — nada foi escrito, apagado nem removido '
|
|
357
|
+
+ '(o único processo executado é a variante `--check` do gerador, que só verifica):'
|
|
358
|
+
: 'Efeitos desta execução:');
|
|
359
|
+
logger.blank();
|
|
360
|
+
|
|
361
|
+
for (const notice of notices) logger.warn(notice);
|
|
362
|
+
if (notices.length) logger.blank();
|
|
363
|
+
|
|
364
|
+
if (cleaned.length) {
|
|
365
|
+
logger.info('Apagados por inteiro antes da reinstalação:');
|
|
366
|
+
for (const { dir, files } of cleaned) {
|
|
367
|
+
logger.dim(` − ${relative(targetPath, dir) || dir} (${files} arquivo(s))`);
|
|
368
|
+
}
|
|
369
|
+
logger.blank();
|
|
179
370
|
}
|
|
180
371
|
|
|
181
|
-
|
|
372
|
+
const cleanedRoots = cleaned.map((c) => c.dir);
|
|
373
|
+
const insideCleaned = (e) => e.verb === 'remove'
|
|
374
|
+
&& cleanedRoots.some((root) => e.target === root || isUnder(e.target, root));
|
|
375
|
+
|
|
376
|
+
const outside = [];
|
|
377
|
+
const inPackage = [];
|
|
378
|
+
const rest = [];
|
|
379
|
+
for (const e of effects) {
|
|
380
|
+
if (insideCleaned(e)) continue; // já contabilizado acima, e são centenas
|
|
381
|
+
const where = effectLocation(e);
|
|
382
|
+
if (isUnder(where, globalClaudeDir) || where === globalClaudeDir) outside.push(e);
|
|
383
|
+
else if (isUnder(where, packageRoot) && !isUnder(where, targetPath)) inPackage.push(e);
|
|
384
|
+
else rest.push(e);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const line = (e) => {
|
|
388
|
+
const label = VERB_GROUPS[e.verb] ?? e.verb;
|
|
389
|
+
const target = isUnder(e.target, targetPath) ? relative(targetPath, e.target) : e.target;
|
|
390
|
+
const suffix = e.detail ? ` ${e.detail}` : '';
|
|
391
|
+
return ` ${label}: ${target}${suffix}`;
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
if (rest.length) {
|
|
395
|
+
logger.info('No projeto:');
|
|
396
|
+
for (const e of rest) logger.dim(line(e));
|
|
397
|
+
logger.blank();
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (outside.length) {
|
|
401
|
+
logger.info('FORA do projeto (diretório pessoal do usuário):');
|
|
402
|
+
for (const e of outside) logger.dim(line(e));
|
|
403
|
+
logger.blank();
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (inPackage.length) {
|
|
407
|
+
logger.info('DENTRO do pacote da CLI (num `npm link` isto é o repositório do framework):');
|
|
408
|
+
for (const e of inPackage) logger.dim(line(e));
|
|
409
|
+
logger.blank();
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
logger.dim(`Total: ${effects.length} efeito(s) — ${cleaned.length} diretório(s) apagado(s) por inteiro, `
|
|
413
|
+
+ `${outside.length} fora do projeto, ${inPackage.length} dentro do pacote da CLI.`);
|
|
414
|
+
logger.blank();
|
|
415
|
+
|
|
416
|
+
// Risco conhecido, reportado em vez de escondido (decisions.md D9): o backup
|
|
417
|
+
// de `config.json` só existe em memória durante a execução.
|
|
418
|
+
logger.dim('Nota: durante uma execução real o backup de `.morph/config/config.json` existe apenas '
|
|
419
|
+
+ 'em memória — uma falha no meio do caminho deixa o arquivo apagado. Follow-up nomeado.');
|
|
420
|
+
logger.blank();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
export async function updateCommand(options = {}) {
|
|
424
|
+
const targetPath = process.cwd();
|
|
425
|
+
const contentDir = getContentDir();
|
|
426
|
+
const morphPath = join(targetPath, '.morph');
|
|
427
|
+
const dryRun = Boolean(options.dryRun);
|
|
428
|
+
const force = Boolean(options.force);
|
|
429
|
+
const fx = options.effects ?? createEffects({ dryRun });
|
|
430
|
+
const notices = [];
|
|
431
|
+
|
|
432
|
+
logger.header(dryRun ? 'MORPH-SPEC Update (--dry-run)' : 'MORPH-SPEC Update');
|
|
433
|
+
|
|
434
|
+
// ── Versões primeiro, e NADA de mutação antes do freio ───────────────────
|
|
435
|
+
// O passo de criação de diretórios ficava aqui em cima e criava `.morph/`
|
|
436
|
+
// ANTES de qualquer guarda. Recusar depois de mutar é meio-estado — o mesmo
|
|
437
|
+
// defeito que o `finish --merge` pagou uma vez na v8.24.3 ("valida a raiz
|
|
438
|
+
// ANTES de arquivar").
|
|
182
439
|
const spinner = ora('Checking versions...').start();
|
|
183
|
-
|
|
440
|
+
// `cliCheck` é injetável para que o canário de igualdade dry × real não
|
|
441
|
+
// dependa do registro npm; a CLI nunca o passa.
|
|
442
|
+
const cliCheck = options.cliCheck ?? await checkCLIOutdated();
|
|
184
443
|
const projectCheck = await checkProjectOutdated(targetPath);
|
|
185
|
-
|
|
444
|
+
const registers = await readVersionRegisters(targetPath);
|
|
186
445
|
spinner.stop();
|
|
187
446
|
|
|
188
447
|
// Display version info
|
|
@@ -202,33 +461,83 @@ export async function updateCommand(options) {
|
|
|
202
461
|
}
|
|
203
462
|
logger.blank();
|
|
204
463
|
|
|
205
|
-
// If CLI is outdated, stop and instruct user
|
|
464
|
+
// If CLI is outdated, stop and instruct user.
|
|
465
|
+
// O `--dry-run` PASSA POR CIMA desta saída antecipada: bloquear o relatório
|
|
466
|
+
// justamente quando o usuário está decidindo se atualiza seria inútil. O
|
|
467
|
+
// aviso vira a primeira linha do relatório.
|
|
206
468
|
if (cliCheck.isOutdated && cliCheck.latest) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
469
|
+
if (dryRun) {
|
|
470
|
+
notices.push(`A CLI local está desatualizada (${cliCheck.current} → ${cliCheck.latest}). `
|
|
471
|
+
+ 'O relatório abaixo é o do que a CLI ATUAL faria.');
|
|
472
|
+
} else {
|
|
473
|
+
logger.warn(`Your CLI is outdated (${cliCheck.current} → ${cliCheck.latest})`);
|
|
474
|
+
logger.blank();
|
|
475
|
+
logger.info('Please update the CLI first:');
|
|
476
|
+
logger.blank();
|
|
477
|
+
|
|
478
|
+
const method = detectInstallMethod();
|
|
479
|
+
const instructions = getUpdateInstructions(method);
|
|
480
|
+
|
|
481
|
+
instructions.forEach(line => {
|
|
482
|
+
if (line === '') {
|
|
483
|
+
logger.blank();
|
|
484
|
+
} else if (line.startsWith('Or ')) {
|
|
485
|
+
logger.dim(line);
|
|
486
|
+
} else {
|
|
487
|
+
logger.box([line]);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
211
490
|
|
|
212
|
-
|
|
213
|
-
|
|
491
|
+
logger.blank();
|
|
492
|
+
logger.dim('Then run "morph-spec update" again.');
|
|
493
|
+
process.exit(0);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
214
496
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
497
|
+
// ── Freio de major, ANTES de qualquer mutação ────────────────────────────
|
|
498
|
+
const needsInitialization = !(await pathExists(morphPath));
|
|
499
|
+
const targetVersion = cliCheck.current ?? getInstalledCLIVersion();
|
|
500
|
+
const verdict = needsInitialization
|
|
501
|
+
// Instalação nova: não há versão anterior a saltar. O freio existe para
|
|
502
|
+
// proteger uma instalação EXISTENTE de ser sobrescrita às cegas.
|
|
503
|
+
? { refuse: false, reason: null, jump: null }
|
|
504
|
+
: majorJump(registers.morphVersion, targetVersion);
|
|
505
|
+
|
|
506
|
+
if (verdict.refuse) {
|
|
507
|
+
const lines = majorGuardLines(verdict, registers, targetVersion);
|
|
508
|
+
if (force) {
|
|
509
|
+
notices.push(`Freio de major DISPENSADO por \`--force\`: ${lines[0]}`);
|
|
510
|
+
} else if (dryRun) {
|
|
511
|
+
// UC-27 A1: em dry ninguém precisa ser protegido de nada, e esconder o
|
|
512
|
+
// relatório seria esconder justamente a informação que motivou o comando.
|
|
513
|
+
notices.push(...lines);
|
|
514
|
+
} else {
|
|
515
|
+
logger.error(lines[0]);
|
|
516
|
+
for (const l of lines.slice(1)) logger.dim(l);
|
|
517
|
+
logger.blank();
|
|
518
|
+
process.exitCode = 1;
|
|
519
|
+
return { refused: true, reason: verdict.reason, effects: fx.list() };
|
|
520
|
+
}
|
|
521
|
+
} else if (registers.diverged) {
|
|
522
|
+
notices.push(`Os dois registros de versão do projeto discordam: `
|
|
523
|
+
+ `\`.morph/.morphversion\` = ${registers.morphVersion}, `
|
|
524
|
+
+ `\`config.json → frameworkVersion\` = ${registers.frameworkVersion}. `
|
|
525
|
+
+ `Quem decide é o primeiro.`);
|
|
526
|
+
}
|
|
224
527
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
528
|
+
// ── A partir daqui, tudo passa pelo coletor ──────────────────────────────
|
|
529
|
+
if (needsInitialization) {
|
|
530
|
+
logger.warn('MORPH not fully initialized. Creating basic structure...');
|
|
531
|
+
fx.ensureDir(join(morphPath, 'context'));
|
|
532
|
+
fx.ensureDir(join(morphPath, 'features'));
|
|
533
|
+
fx.ensureDir(join(morphPath, 'framework'));
|
|
534
|
+
fx.ensureDir(join(morphPath, 'config'));
|
|
535
|
+
logger.dim(' ✓ Created .morph directory structure');
|
|
228
536
|
}
|
|
229
537
|
|
|
230
538
|
// Proceed with update
|
|
231
|
-
const updateSpinner = ora('Updating MORPH-SPEC...').start();
|
|
539
|
+
const updateSpinner = ora(dryRun ? 'Prevendo efeitos...' : 'Updating MORPH-SPEC...').start();
|
|
540
|
+
let cleaned = [];
|
|
232
541
|
|
|
233
542
|
try {
|
|
234
543
|
// Backup user config before cleaning
|
|
@@ -240,7 +549,7 @@ export async function updateCommand(options) {
|
|
|
240
549
|
|
|
241
550
|
// Clean framework directories (preserve .morph/project/)
|
|
242
551
|
updateSpinner.text = 'Cleaning old framework files...';
|
|
243
|
-
|
|
552
|
+
cleaned = cleanFrameworkDirs(morphPath, targetPath, fx);
|
|
244
553
|
logger.dim(' ✓ Old framework files removed');
|
|
245
554
|
|
|
246
555
|
const updateTemplates = !options.standards || options.templates;
|
|
@@ -253,7 +562,7 @@ export async function updateCommand(options) {
|
|
|
253
562
|
if (updateTemplates) {
|
|
254
563
|
updateSpinner.text = 'Checking for a legacy templates copy...';
|
|
255
564
|
const templatesDest = join(morphPath, 'framework', 'templates');
|
|
256
|
-
templatesReconcile = reconcileLegacyTemplatesCopy(templatesDest, projectCheck.current);
|
|
565
|
+
templatesReconcile = reconcileLegacyTemplatesCopy(templatesDest, projectCheck.current, fx);
|
|
257
566
|
}
|
|
258
567
|
|
|
259
568
|
// Update standards
|
|
@@ -261,7 +570,7 @@ export async function updateCommand(options) {
|
|
|
261
570
|
updateSpinner.text = 'Updating standards...';
|
|
262
571
|
const standardsSrc = join(contentDir, 'framework', 'standards');
|
|
263
572
|
const standardsDest = join(morphPath, 'framework', 'standards');
|
|
264
|
-
|
|
573
|
+
fx.copyDir(standardsSrc, standardsDest);
|
|
265
574
|
}
|
|
266
575
|
|
|
267
576
|
// Update hooks (runtime hook scripts under .morph/framework/hooks/)
|
|
@@ -269,7 +578,7 @@ export async function updateCommand(options) {
|
|
|
269
578
|
const hooksSrc = join(__dirname, '..', '..', '..', 'framework', 'hooks');
|
|
270
579
|
const hooksDest = join(morphPath, 'framework', 'hooks');
|
|
271
580
|
if (await pathExists(hooksSrc)) {
|
|
272
|
-
|
|
581
|
+
fx.copyDir(hooksSrc, hooksDest);
|
|
273
582
|
}
|
|
274
583
|
|
|
275
584
|
// Update agents.json (sourced from framework/ — canonical single source of truth)
|
|
@@ -277,7 +586,7 @@ export async function updateCommand(options) {
|
|
|
277
586
|
const agentsSrc = join(__dirname, '..', '..', '..', 'framework', 'agents.json');
|
|
278
587
|
const agentsDest = join(morphPath, 'framework', 'agents.json');
|
|
279
588
|
if (await pathExists(agentsSrc)) {
|
|
280
|
-
|
|
589
|
+
fx.copyFile(agentsSrc, agentsDest);
|
|
281
590
|
}
|
|
282
591
|
|
|
283
592
|
// Update the remaining framework payloads (parity with setup-infra 7b/7c/8b/8c/8d) —
|
|
@@ -288,13 +597,13 @@ export async function updateCommand(options) {
|
|
|
288
597
|
for (const file of ['MORPH.md', 'phases.json']) {
|
|
289
598
|
const src = join(canonicalFrameworkDir, file);
|
|
290
599
|
if (await pathExists(src)) {
|
|
291
|
-
|
|
600
|
+
fx.copyFile(src, join(morphPath, 'framework', file));
|
|
292
601
|
}
|
|
293
602
|
}
|
|
294
603
|
for (const dir of ['schemas', 'rubrics', 'evals']) {
|
|
295
604
|
const src = join(canonicalFrameworkDir, dir);
|
|
296
605
|
if (await pathExists(src)) {
|
|
297
|
-
|
|
606
|
+
fx.copyDir(src, join(morphPath, 'framework', dir));
|
|
298
607
|
}
|
|
299
608
|
}
|
|
300
609
|
// CLI.md vive em framework/docs/ na fonte mas instala achatado em
|
|
@@ -302,17 +611,21 @@ export async function updateCommand(options) {
|
|
|
302
611
|
// por isso não entra no loop de nomes acima.
|
|
303
612
|
const cliMdSrc = join(canonicalFrameworkDir, 'docs', 'CLI.md');
|
|
304
613
|
if (await pathExists(cliMdSrc)) {
|
|
305
|
-
|
|
614
|
+
fx.copyFile(cliMdSrc, join(morphPath, 'framework', 'CLI.md'));
|
|
306
615
|
}
|
|
307
616
|
|
|
308
|
-
// Regenerate derived files (phase-utils.js, skill output tables) from output-schema.js
|
|
309
|
-
//
|
|
617
|
+
// Regenerate derived files (phase-utils.js, skill output tables) from output-schema.js.
|
|
618
|
+
// Escreve DENTRO do pacote da CLI — num `npm link` de desenvolvimento, no
|
|
619
|
+
// próprio repositório do framework. Em dry a variante `--check` roda no
|
|
620
|
+
// lugar: ela verifica e não escreve.
|
|
310
621
|
updateSpinner.text = 'Syncing generated refs...';
|
|
311
622
|
try {
|
|
312
623
|
const generateScript = join(__dirname, '..', '..', '..', 'scripts', 'generate-refs.js');
|
|
313
624
|
if (existsSync(generateScript)) {
|
|
314
|
-
const result =
|
|
315
|
-
|
|
625
|
+
const result = fx.spawn(process.execPath, [generateScript], {
|
|
626
|
+
dryRunArgs: [generateScript, '--check'],
|
|
627
|
+
});
|
|
628
|
+
if (result && result.status === 0 && !dryRun) {
|
|
316
629
|
logger.dim(' ✓ Generated refs synced (phase-utils.js, skill outputs)');
|
|
317
630
|
}
|
|
318
631
|
}
|
|
@@ -332,7 +645,7 @@ export async function updateCommand(options) {
|
|
|
332
645
|
const commandsSrc = join(frameworkDir, 'commands');
|
|
333
646
|
const commandsDest = join(claudeDest, 'commands');
|
|
334
647
|
if (await pathExists(commandsSrc)) {
|
|
335
|
-
|
|
648
|
+
fx.copyDir(commandsSrc, commandsDest);
|
|
336
649
|
commandsCopied = true;
|
|
337
650
|
} else {
|
|
338
651
|
logger.warn(' ⚠ framework/commands/ source missing — commands not updated');
|
|
@@ -350,12 +663,12 @@ export async function updateCommand(options) {
|
|
|
350
663
|
|
|
351
664
|
// Sync morph skills to .claude/skills/ for native Claude Code discovery
|
|
352
665
|
updateSpinner.text = 'Syncing morph skills to .claude/skills/...';
|
|
353
|
-
const skillsResult = await installSkills(targetPath, { projectTags });
|
|
666
|
+
const skillsResult = await installSkills(targetPath, { projectTags, fx });
|
|
354
667
|
|
|
355
668
|
// Sync native subagents to .claude/agents/
|
|
356
669
|
updateSpinner.text = 'Syncing agents to .claude/agents/...';
|
|
357
|
-
const agentsResult = await installAgents(targetPath, frameworkDir, { projectTags });
|
|
358
|
-
await installDomainAgents(targetPath, frameworkDir, { projectTags });
|
|
670
|
+
const agentsResult = await installAgents(targetPath, frameworkDir, { projectTags, fx });
|
|
671
|
+
await installDomainAgents(targetPath, frameworkDir, { projectTags, fx });
|
|
359
672
|
|
|
360
673
|
// Sync path-scoped rules to .claude/rules/ — stack-filtered with orphan
|
|
361
674
|
// cleanup (parity with setup-infra), so `update` is a true superset of
|
|
@@ -365,8 +678,8 @@ export async function updateCommand(options) {
|
|
|
365
678
|
const rulesDest = join(claudeDest, 'rules');
|
|
366
679
|
let rulesResult = null;
|
|
367
680
|
if (await pathExists(rulesSrc)) {
|
|
368
|
-
|
|
369
|
-
rulesResult = installRulesForStack(rulesSrc, rulesDest, projectTags);
|
|
681
|
+
fx.ensureDir(rulesDest);
|
|
682
|
+
rulesResult = installRulesForStack(rulesSrc, rulesDest, projectTags, fx);
|
|
370
683
|
}
|
|
371
684
|
|
|
372
685
|
// Sync runtime CLAUDE.md to .claude/CLAUDE.md and derive the personas
|
|
@@ -375,15 +688,19 @@ export async function updateCommand(options) {
|
|
|
375
688
|
const runtimeSrc = join(frameworkDir, 'CLAUDE.md');
|
|
376
689
|
const runtimeDest = join(claudeDest, 'CLAUDE.md');
|
|
377
690
|
if (await pathExists(runtimeSrc)) {
|
|
378
|
-
|
|
379
|
-
|
|
691
|
+
fx.copyFile(runtimeSrc, runtimeDest);
|
|
692
|
+
// Em dry o destino ainda não recebeu a cópia acima; ler a ORIGEM é o que
|
|
693
|
+
// faz previsão e execução decidirem igual sobre os marcadores.
|
|
694
|
+
await updateClaudeMdAgentsBlock(runtimeDest, agentsResult?.agents ?? [], {
|
|
695
|
+
fx, readFrom: dryRun ? runtimeSrc : runtimeDest,
|
|
696
|
+
});
|
|
380
697
|
}
|
|
381
698
|
|
|
382
699
|
// Sync statusline globally to ~/.claude/
|
|
383
700
|
updateSpinner.text = 'Syncing statusline to ~/.claude/...';
|
|
384
701
|
const HOOKS_SRC = join(__dirname, '..', '..', '..', 'framework', 'hooks', 'claude-code');
|
|
385
702
|
try {
|
|
386
|
-
await installGlobalStatusline(HOOKS_SRC);
|
|
703
|
+
await installGlobalStatusline(HOOKS_SRC, null, { fx });
|
|
387
704
|
} catch {
|
|
388
705
|
// Non-critical: global dir may not be writable in all environments
|
|
389
706
|
logger.dim(' ⚠ Could not install statusline globally (non-critical)');
|
|
@@ -391,88 +708,87 @@ export async function updateCommand(options) {
|
|
|
391
708
|
|
|
392
709
|
// Update Claude Code hooks in .claude/settings.local.json
|
|
393
710
|
updateSpinner.text = 'Updating Claude Code hooks...';
|
|
394
|
-
const hooksResult = await installClaudeHooks(targetPath);
|
|
711
|
+
const hooksResult = await installClaudeHooks(targetPath, { fx });
|
|
395
712
|
|
|
396
713
|
// Ensure root CLAUDE.md has morph-spec @import (preserves user content)
|
|
397
714
|
updateSpinner.text = 'Checking CLAUDE.md import...';
|
|
398
715
|
const claudeMdDest = join(targetPath, 'CLAUDE.md');
|
|
399
|
-
await injectMorphImport(claudeMdDest);
|
|
716
|
+
await injectMorphImport(claudeMdDest, fx);
|
|
400
717
|
|
|
401
718
|
// Restore user config after framework reinstallation
|
|
402
719
|
updateSpinner.text = 'Restoring user configuration...';
|
|
403
|
-
|
|
720
|
+
restoreUserConfig(morphPath, targetPath, configBackup, cliCheck.current, fx);
|
|
404
721
|
logger.dim(' ✓ User config restored with updated framework version');
|
|
405
722
|
|
|
406
723
|
// Update .gitignore + .gitattributes with current morph rules
|
|
407
724
|
updateSpinner.text = 'Updating .gitignore + .gitattributes...';
|
|
408
|
-
await updateGitignore(targetPath);
|
|
409
|
-
await ensureGitattributes(targetPath);
|
|
725
|
+
await updateGitignore(targetPath, fx);
|
|
726
|
+
await ensureGitattributes(targetPath, fx);
|
|
410
727
|
|
|
411
728
|
// Migration: untrack a pre-v5 committed state.json so it stops being swept
|
|
412
729
|
// into feature commits. Gitignore alone can't do this for an already-tracked file.
|
|
413
|
-
const untracked = untrackCommittedStateJson(targetPath);
|
|
730
|
+
const untracked = untrackCommittedStateJson(targetPath, fx);
|
|
414
731
|
|
|
415
732
|
// Update .morphversion
|
|
416
733
|
updateSpinner.text = 'Saving version info...';
|
|
417
|
-
await saveProjectMorphVersion(targetPath, cliCheck.current);
|
|
734
|
+
await saveProjectMorphVersion(targetPath, cliCheck.current, fx);
|
|
418
735
|
|
|
419
|
-
updateSpinner.succeed('MORPH-SPEC updated successfully!');
|
|
736
|
+
updateSpinner.succeed(dryRun ? 'Previsão concluída (nada foi tocado).' : 'MORPH-SPEC updated successfully!');
|
|
420
737
|
logger.blank();
|
|
421
738
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
logger.success(`Already up to date (v${cliCheck.current})`);
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
logger.blank();
|
|
432
|
-
logger.info('Updated files:');
|
|
433
|
-
if (updateTemplates) {
|
|
434
|
-
if (templatesReconcile === 'removed-legacy') {
|
|
435
|
-
logger.dim(' ✓ .morph/framework/templates/ (legacy installer copy removed — resolved from package)');
|
|
436
|
-
} else if (templatesReconcile === 'preserved-override') {
|
|
437
|
-
logger.dim(' ⚠ .morph/framework/templates/ (local override present — preserved untouched, not updated)');
|
|
739
|
+
if (!dryRun) {
|
|
740
|
+
// Show version change
|
|
741
|
+
if (projectCheck.current && projectCheck.current !== cliCheck.current) {
|
|
742
|
+
logger.success(`Updated: ${projectCheck.current} → ${cliCheck.current}`);
|
|
743
|
+
} else if (!projectCheck.current) {
|
|
744
|
+
logger.success(`Updated to v${cliCheck.current}`);
|
|
438
745
|
} else {
|
|
439
|
-
logger.
|
|
746
|
+
logger.success(`Already up to date (v${cliCheck.current})`);
|
|
440
747
|
}
|
|
748
|
+
|
|
749
|
+
logger.blank();
|
|
750
|
+
logger.info('Updated files:');
|
|
751
|
+
if (updateTemplates) {
|
|
752
|
+
if (templatesReconcile === 'removed-legacy') {
|
|
753
|
+
logger.dim(' ✓ .morph/framework/templates/ (legacy installer copy removed — resolved from package)');
|
|
754
|
+
} else if (templatesReconcile === 'preserved-override') {
|
|
755
|
+
logger.dim(' ⚠ .morph/framework/templates/ (local override present — preserved untouched, not updated)');
|
|
756
|
+
} else {
|
|
757
|
+
logger.dim(' · .morph/framework/templates/ (not present — resolved from package, no local override)');
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Counts make a silent stack-filter downgrade visible at a glance —
|
|
762
|
+
// a dotnet project reporting "1 persona" is a red flag, not a success.
|
|
763
|
+
// Display only compact tags: project.integrations entries are long
|
|
764
|
+
// free-text descriptions and would flood the summary line.
|
|
765
|
+
const compactTags = projectTags.filter(t => !t.includes(' '));
|
|
766
|
+
const tagsLabel = compactTags.length > 0 ? compactTags.join('+') : 'universal only';
|
|
767
|
+
logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
|
|
768
|
+
logger.dim(` ✓ .claude/skills/ (${skillsResult?.installed ?? '?'} skills — stack: ${tagsLabel})`);
|
|
769
|
+
logger.dim(` ✓ .claude/agents/ (${agentsResult?.agents?.length ?? '?'} personas — stack: ${tagsLabel})`);
|
|
770
|
+
logger.dim(` ✓ .claude/rules/ (${rulesResult?.installed ?? '?'} stack-filtered rules)`);
|
|
771
|
+
if (commandsCopied) logger.dim(' ✓ .claude/commands/');
|
|
772
|
+
if (untracked) {
|
|
773
|
+
logger.dim(' ✓ .morph/state.json untracked (git rm --cached) — commit to finalize');
|
|
774
|
+
logger.warn('Migration: .morph/state.json was removed from git tracking. The');
|
|
775
|
+
logger.dim(' working file stays (rebuilt from feature.json); commit the deletion');
|
|
776
|
+
logger.dim(' so it stops being swept into per-feature commits.');
|
|
777
|
+
}
|
|
778
|
+
logger.blank();
|
|
779
|
+
logger.info('Your config.json was preserved.');
|
|
441
780
|
}
|
|
442
|
-
if (updateStandards) logger.dim(' ✓ .morph/framework/standards/');
|
|
443
|
-
logger.dim(' ✓ .morph/framework/hooks/');
|
|
444
|
-
logger.dim(' ✓ .morph/framework/agents.json');
|
|
445
|
-
logger.dim(' ✓ .morph/framework/MORPH.md + phases.json (NLH charter)');
|
|
446
|
-
logger.dim(' ✓ .morph/framework/schemas/ + rubrics/ + evals/ (bar 9 + LLM rubrics)');
|
|
447
|
-
if (commandsCopied) logger.dim(' ✓ .claude/commands/');
|
|
448
|
-
|
|
449
|
-
// Counts make a silent stack-filter downgrade visible at a glance —
|
|
450
|
-
// a dotnet project reporting "1 persona" is a red flag, not a success.
|
|
451
|
-
// Display only compact tags: project.integrations entries are long
|
|
452
|
-
// free-text descriptions and would flood the summary line.
|
|
453
|
-
const compactTags = projectTags.filter(t => !t.includes(' '));
|
|
454
|
-
const tagsLabel = compactTags.length > 0 ? compactTags.join('+') : 'universal only';
|
|
455
|
-
logger.dim(` ✓ .claude/settings.local.json (${hooksResult.installed} hooks installed)`);
|
|
456
|
-
logger.dim(` ✓ .claude/skills/ (${skillsResult?.installed ?? '?'} skills — stack: ${tagsLabel})`);
|
|
457
|
-
logger.dim(` ✓ .claude/agents/ (${agentsResult?.agents?.length ?? '?'} personas — stack: ${tagsLabel})`);
|
|
458
|
-
logger.dim(` ✓ .claude/rules/ (${rulesResult?.installed ?? '?'} stack-filtered rules)`);
|
|
459
|
-
logger.dim(' ✓ .claude/CLAUDE.md (runtime quick reference)');
|
|
460
|
-
logger.dim(' ✓ ~/.claude/statusline.sh (global statusline synced)');
|
|
461
|
-
logger.dim(' ✓ CLAUDE.md');
|
|
462
|
-
logger.dim(' ✓ .gitignore (morph rules updated)');
|
|
463
|
-
if (untracked) {
|
|
464
|
-
logger.dim(' ✓ .morph/state.json untracked (git rm --cached) — commit to finalize');
|
|
465
|
-
logger.warn('Migration: .morph/state.json was removed from git tracking. The');
|
|
466
|
-
logger.dim(' working file stays (rebuilt from feature.json); commit the deletion');
|
|
467
|
-
logger.dim(' so it stops being swept into per-feature commits.');
|
|
468
|
-
}
|
|
469
|
-
logger.blank();
|
|
470
|
-
logger.info('Your config.json was preserved.');
|
|
471
|
-
logger.dim('Review the updated files for any new features.');
|
|
472
|
-
logger.blank();
|
|
473
781
|
|
|
782
|
+
// O resumo sai de `list()` NOS DOIS MODOS — é essa escolha que faz um
|
|
783
|
+
// ponto de emissão esquecido sumir das duas saídas ao mesmo tempo, em vez
|
|
784
|
+
// de só do `--dry-run`.
|
|
785
|
+
renderEffectsReport(fx.list(), {
|
|
786
|
+
targetPath, packageRoot: contentDir, dryRun, cleaned, notices,
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
return { refused: false, dryRun, effects: fx.list() };
|
|
474
790
|
} catch (error) {
|
|
475
|
-
updateSpinner.fail('Update failed');
|
|
791
|
+
updateSpinner.fail(dryRun ? 'Previsão falhou' : 'Update failed');
|
|
476
792
|
logger.error(error.message);
|
|
477
793
|
process.exit(1);
|
|
478
794
|
}
|