@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
|
@@ -1006,8 +1006,15 @@ function isLiveSiblingWorktreeRegistration(relPath, root, feature) {
|
|
|
1006
1006
|
* @param {string} rootPath - the primary root, for the replica comparison
|
|
1007
1007
|
* @param {{ discardIgnored?: boolean }} options
|
|
1008
1008
|
* @returns {{ shieldable: string[], blocking: string[], discarded: string[], replicable: string[] }}
|
|
1009
|
+
*
|
|
1010
|
+
* EXPORTADA para que o `finish --dry-run` (pré-voo) responda EXATAMENTE a
|
|
1011
|
+
* mesma pergunta que o teardown real vai responder. Uma segunda enumeração de
|
|
1012
|
+
* `git status --ignored` no finish.js — com a sua própria cópia de
|
|
1013
|
+
* IGNORED_SHIELDABLE_PREFIXES — faria o pré-voo prometer uma coisa e o
|
|
1014
|
+
* comando fazer outra, que é a única maneira de um dry-run ser pior que
|
|
1015
|
+
* nenhum. Consumida read-only: o pré-voo não copia, não apaga, não decide.
|
|
1009
1016
|
*/
|
|
1010
|
-
function classifyIgnoredLeftovers(worktreePath, rootPath, options = {}) {
|
|
1017
|
+
export function classifyIgnoredLeftovers(worktreePath, rootPath, options = {}) {
|
|
1011
1018
|
let out;
|
|
1012
1019
|
try {
|
|
1013
1020
|
// `-z` + execFile: NUL-delimited, no shell, no C-style quoting to undo —
|
|
@@ -1296,19 +1303,46 @@ function classifyRemovalError(stderr) {
|
|
|
1296
1303
|
* @returns {Promise<{ removed: boolean, failure: {kind: string, detail: string}|null }>}
|
|
1297
1304
|
*/
|
|
1298
1305
|
async function attemptWorktreeRemoval(worktreePath, root) {
|
|
1299
|
-
|
|
1306
|
+
const stderrs = [];
|
|
1300
1307
|
for (let round = 0; round < 2; round++) {
|
|
1301
1308
|
for (const args of [['worktree', 'remove', worktreePath], ['worktree', 'remove', '--force', worktreePath]]) {
|
|
1302
1309
|
try {
|
|
1303
1310
|
execFileSync('git', args, { cwd: root, stdio: 'pipe' });
|
|
1304
1311
|
return { removed: true, failure: null };
|
|
1305
1312
|
} catch (err) {
|
|
1306
|
-
|
|
1313
|
+
stderrs.push(err?.stderr ? String(err.stderr) : (err?.message || ''));
|
|
1307
1314
|
}
|
|
1308
1315
|
}
|
|
1309
1316
|
if (round === 0) await new Promise((r) => setTimeout(r, 200));
|
|
1310
1317
|
}
|
|
1311
|
-
return { removed: false, failure:
|
|
1318
|
+
return { removed: false, failure: pickRemovalFailure(stderrs) };
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/**
|
|
1322
|
+
* Which of the attempts' errors the user should read.
|
|
1323
|
+
*
|
|
1324
|
+
* The LAST one used to win, and it was the worst choice: when the first
|
|
1325
|
+
* `git worktree remove` deletes the content and de-registers the tree but the
|
|
1326
|
+
* directory itself is held (a session with its cwd there), every later attempt
|
|
1327
|
+
* answers only `'…' is not a working tree` — an echo that hid the real cause
|
|
1328
|
+
* (the lock) and made the hint send the user to a command that can no longer
|
|
1329
|
+
* reach the tree. A lock anywhere wins; otherwise the first REAL error wins.
|
|
1330
|
+
*
|
|
1331
|
+
* @param {string[]} stderrs - one entry per failed attempt, in order
|
|
1332
|
+
* @returns {{ kind: string, detail: string, alreadyDeregistered: boolean }}
|
|
1333
|
+
*/
|
|
1334
|
+
export function pickRemovalFailure(stderrs) {
|
|
1335
|
+
const list = (stderrs || []).map((s) => String(s || '')).filter((s) => s.trim());
|
|
1336
|
+
const notAWorktree = (s) => /is not a working tree/i.test(s);
|
|
1337
|
+
const classified = list.map(classifyRemovalError);
|
|
1338
|
+
const firstReal = list.findIndex((s) => !notAWorktree(s));
|
|
1339
|
+
const chosen = classified.find((c) => c.kind === 'in-use')
|
|
1340
|
+
|| (firstReal >= 0 ? classified[firstReal] : classified[classified.length - 1])
|
|
1341
|
+
|| { kind: 'unknown', detail: '' };
|
|
1342
|
+
// Git dropped the registration BETWEEN attempts: a failure for another reason
|
|
1343
|
+
// followed by "is not a working tree". Only the directory is left.
|
|
1344
|
+
const alreadyDeregistered = firstReal >= 0 && list.slice(firstReal + 1).some(notAWorktree);
|
|
1345
|
+
return { ...chosen, alreadyDeregistered };
|
|
1312
1346
|
}
|
|
1313
1347
|
|
|
1314
1348
|
/**
|
|
@@ -1444,6 +1478,10 @@ export async function teardownWorktree(worktreePath, { root, feature, branch, op
|
|
|
1444
1478
|
? `worktree NÃO removido: o backup falhou. Estes arquivos existem só neste worktree e a remoção os apagaria: ${sample(ignored.shieldable)}. `
|
|
1445
1479
|
+ (ignored.replicable.length ? `(${sample(ignored.replicable)} são idênticos aos da raiz — esses não são o problema.) ` : '')
|
|
1446
1480
|
+ `O backup iria para \`${backupDir}\`; libere espaço/permissão ali e rode \`${removeCmd}\`, ou copie os arquivos você mesmo e rode \`${removeCmd} --discard-ignored\` para pular o backup`
|
|
1481
|
+
: removalFailure?.alreadyDeregistered
|
|
1482
|
+
? `worktree NÃO removido por inteiro: a 1ª tentativa do git já apagou o conteúdo e desregistrou o worktree, mas o diretório "${worktreePath}" ficou`
|
|
1483
|
+
+ (removalFailure.kind === 'in-use' ? ' — EM USO por outro processo (instância do Claude Code, VS Code, dev server ou terminal com cwd nele)' : '')
|
|
1484
|
+
+ `${removalFailure.detail ? ` (${removalFailure.detail})` : ''}. \`${removeCmd}\` não o alcança mais — o git não o conhece. Feche o que o usa, confira que está vazio e sem junction, e apague só o diretório (sem recursão)`
|
|
1447
1485
|
: removalFailure?.kind === 'in-use'
|
|
1448
1486
|
? `worktree NÃO removido: o diretório está EM USO por outro processo (instância do Claude Code, VS Code, dev server ou terminal com cwd em "${worktreePath}"). A infra compartilhada ficou DESMONTADA de propósito — feche o que está usando o diretório e rode \`${removeCmd}\`. Se preferir continuar trabalhando nele, restaure a infra com \`morph-spec worktree link --all\``
|
|
1449
1487
|
: `worktree NÃO removido: a remoção falhou${removalFailure?.detail ? ` (${removalFailure.detail})` : ''}. A infra compartilhada ficou DESMONTADA de propósito — rode \`${removeCmd}\` para tentar de novo, ou \`morph-spec worktree link --all\` para voltar a trabalhar nele`;
|
|
@@ -1490,6 +1528,30 @@ export function cwdIsInsideWorktree(cwd, worktreePath) {
|
|
|
1490
1528
|
return cwdNorm === wtNorm || cwdNorm.startsWith(`${wtNorm}/`);
|
|
1491
1529
|
}
|
|
1492
1530
|
|
|
1531
|
+
/**
|
|
1532
|
+
* Is the Claude Code SESSION that ran this command anchored in the worktree?
|
|
1533
|
+
*
|
|
1534
|
+
* Not the same question as `cwdIsInsideWorktree`. That one sees the PROCESS
|
|
1535
|
+
* cwd, and `cd <root> && morph-spec finish` answers it "no" — while the session
|
|
1536
|
+
* stays anchored in the worktree: its CLAUDE_PROJECT_DIR is the worktree, and
|
|
1537
|
+
* every project hook runs `node "$CLAUDE_PROJECT_DIR/.morph/framework/hooks/…"`.
|
|
1538
|
+
* Tearing that worktree down unlinks the junction those hooks resolve through,
|
|
1539
|
+
* and the directory stays held by the session — so the removal fails half-way
|
|
1540
|
+
* and every hook of every later tool call dies with MODULE_NOT_FOUND.
|
|
1541
|
+
*
|
|
1542
|
+
* The anchor arrives as MORPH_SESSION_PROJECT_DIR, exported by the
|
|
1543
|
+
* `worktree-env-inject` PreToolUse hook (the Bash tool does not export
|
|
1544
|
+
* CLAUDE_PROJECT_DIR itself); CLAUDE_PROJECT_DIR is honoured when present.
|
|
1545
|
+
*
|
|
1546
|
+
* @param {string} worktreePath
|
|
1547
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
1548
|
+
* @returns {boolean}
|
|
1549
|
+
*/
|
|
1550
|
+
export function sessionAnchoredInWorktree(worktreePath, env = process.env) {
|
|
1551
|
+
const anchor = env.MORPH_SESSION_PROJECT_DIR || env.CLAUDE_PROJECT_DIR || '';
|
|
1552
|
+
return anchor ? cwdIsInsideWorktree(anchor, worktreePath) : false;
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1493
1555
|
// ── remove ───────────────────────────────────────────────────────────────
|
|
1494
1556
|
|
|
1495
1557
|
/**
|
|
@@ -1573,6 +1635,16 @@ export async function worktreeRemoveCommand(featureArg, options = {}) {
|
|
|
1573
1635
|
hint: `saia do worktree antes de removê-lo — o processo trava o próprio diretório de trabalho. Rode de novo a partir de "${root}"` }, 1);
|
|
1574
1636
|
}
|
|
1575
1637
|
|
|
1638
|
+
// The SESSION's anchor, not the process cwd (see sessionAnchoredInWorktree):
|
|
1639
|
+
// refusing here, before the junctions are touched, leaves the session's hooks
|
|
1640
|
+
// intact. Unlike finish there is nothing else to do in this command, so the
|
|
1641
|
+
// refusal IS the deferral.
|
|
1642
|
+
if (sessionAnchoredInWorktree(worktreePath)) {
|
|
1643
|
+
const again = featureAddressable && feature ? `morph-spec worktree remove ${feature}` : `morph-spec worktree remove --target "${worktreePath}"`;
|
|
1644
|
+
emit({ removed: false, error: 'session-anchored-in-worktree', path: worktreePath, root,
|
|
1645
|
+
hint: `a sessão do Claude Code que rodou este comando está ancorada neste worktree — os hooks dela resolvem \`.morph/framework\` por ele, e desmontá-lo quebraria todo tool call seguinte (e o diretório seguiria travado pela sessão). Feche esta sessão e rode \`${again}\` de uma sessão aberta em "${root}"` }, 1);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1576
1648
|
// `--if-clean` — the WorktreeRemove hook's contract: Claude Code fires it
|
|
1577
1649
|
// when it believes the tree is disposable, but "disposable" is the user's
|
|
1578
1650
|
// call, not the harness's. Any uncommitted or untracked file refuses the
|
|
@@ -198,7 +198,23 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
198
198
|
process.exit(1);
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
// Gate idempotency (F15): `setApprovalGate` REPLACES the gate object whole
|
|
202
|
+
// (state-manager.js), so re-writing an already-approved gate does not
|
|
203
|
+
// refresh it — it destroys `approvedBy`, `timestamp`, `notes` and
|
|
204
|
+
// `selfAssessed` in one assignment. And `resolveApprovedBy` above answers
|
|
205
|
+
// "auto" in every headless context, so `approve f proposal --approver
|
|
206
|
+
// lucas` followed by `advance f` used to rewrite a human's signature as
|
|
207
|
+
// "auto" — the one stamp the charter forbids on a human-approved gate.
|
|
208
|
+
// The state-sync hook already carries exactly this guard; `advance`, the
|
|
209
|
+
// other gate write-path, simply never got it.
|
|
210
|
+
//
|
|
211
|
+
// It skips ONLY the write. The scaffold and the handoff below still run,
|
|
212
|
+
// so re-running `advance` after a failed scaffold keeps working — the
|
|
213
|
+
// reason this is a guard and not an abort.
|
|
214
|
+
const priorGate = gate ? feature.approvalGates?.[gate] : null;
|
|
215
|
+
const alreadyApproved = priorGate?.approved === true;
|
|
216
|
+
|
|
217
|
+
if (gate && !alreadyApproved) {
|
|
202
218
|
await setApprovalGate(featureName, gate, true, {
|
|
203
219
|
approvedBy,
|
|
204
220
|
approvedAt: new Date().toISOString(),
|
|
@@ -226,7 +242,13 @@ export async function advanceCommand(featureName, options = {}) {
|
|
|
226
242
|
if (missingArtifacts.length) {
|
|
227
243
|
console.log(chalk.yellow(` ⚠️ --force: avançou SEM ${missingArtifacts.join(', ')}.`));
|
|
228
244
|
}
|
|
229
|
-
|
|
245
|
+
// Report what actually happened: claiming "approved (auto)" when nothing
|
|
246
|
+
// was written is how the destroyed signature stayed invisible.
|
|
247
|
+
if (gate && alreadyApproved) {
|
|
248
|
+
console.log(chalk.gray(` Gate "${gate}" was already approved (${priorGate.approvedBy || 'unknown'}) — approval preserved, nothing re-written.`));
|
|
249
|
+
} else if (gate) {
|
|
250
|
+
console.log(chalk.gray(` Gate "${gate}" approved (${approvedBy}).`));
|
|
251
|
+
}
|
|
230
252
|
if (nextDir) console.log(chalk.gray(` Scaffolded ${nextDir}/ — ready for the next phase's first artifact.\n`));
|
|
231
253
|
else console.log('');
|
|
232
254
|
} catch (error) {
|
|
@@ -180,7 +180,13 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
180
180
|
if (currentStatus?.approved) {
|
|
181
181
|
console.log(chalk.yellow(`\n⚠️ Gate "${gateName}" is already approved`));
|
|
182
182
|
console.log(chalk.gray(`Approved at: ${currentStatus.timestamp}`));
|
|
183
|
-
console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}
|
|
183
|
+
console.log(chalk.gray(`Approved by: ${currentStatus.approvedBy || 'Unknown'}`));
|
|
184
|
+
// This early-out is a no-op that still exits 0 — deliberate, and
|
|
185
|
+
// `--force` does NOT cover it (that flag only covers gate ORDER). A
|
|
186
|
+
// caller who meant to re-approve therefore reads success and is left
|
|
187
|
+
// with nowhere to go. The way out already exists as `unapprove`; the
|
|
188
|
+
// message just never named it. Naming it is the whole fix (no new flag).
|
|
189
|
+
console.log(chalk.gray(`To approve it again, revoke it first: morph-spec unapprove ${featureName} ${gateName}\n`));
|
|
184
190
|
return;
|
|
185
191
|
}
|
|
186
192
|
|
|
@@ -349,6 +355,32 @@ export async function approveCommand(featureName, gateName, options = {}) {
|
|
|
349
355
|
}
|
|
350
356
|
selfAssessed = true;
|
|
351
357
|
}
|
|
358
|
+
|
|
359
|
+
// REAVALIAÇÃO VISÍVEL. O `evaluator-report.md` é append-only, então uma
|
|
360
|
+
// segunda rodada acrescenta um `**Composite-Score:**` ao fim e o arquivo
|
|
361
|
+
// passa a carregar mais de um veredito. O gate decide pelo ÚLTIMO (o
|
|
362
|
+
// vigente) — mas aprovar em silêncio um relatório com notas superadas
|
|
363
|
+
// esconde do humano exatamente o fato que ele precisa ver: a nota MUDOU,
|
|
364
|
+
// e numa das rodadas medidas em campo ela CAIU (8.9 → 8.3, quando uma
|
|
365
|
+
// correção abriu um bloqueante).
|
|
366
|
+
//
|
|
367
|
+
// Aqui é o lugar: `approve` é a precondição humana do Gate 3 e a única
|
|
368
|
+
// superfície onde uma pessoa lê o veredito antes de carimbá-lo. Sem este
|
|
369
|
+
// bloco, `scoreTokens`/`superseded` seriam mitigação inerte — campo que
|
|
370
|
+
// ninguém lê não mitiga nada.
|
|
371
|
+
if (report.status === 'ok' && report.scoreTokens > 1) {
|
|
372
|
+
console.log(chalk.yellow(
|
|
373
|
+
`
|
|
374
|
+
⚠️ O relatório do avaliador tem ${report.scoreTokens} vereditos (o arquivo é append-only).`
|
|
375
|
+
));
|
|
376
|
+
console.log(chalk.gray(` Vigente (o que o gate usa): ${report.score}`));
|
|
377
|
+
console.log(chalk.gray(` Superado(s), na ordem do arquivo: ${report.superseded.join(', ')}`));
|
|
378
|
+
if (report.superseded.some((n) => n > report.score)) {
|
|
379
|
+
console.log(chalk.yellow(' A nota CAIU em relação a uma rodada anterior — confira o que a correção'));
|
|
380
|
+
console.log(chalk.yellow(' abriu antes de aprovar; o relatório mantém a análise das duas rodadas.'));
|
|
381
|
+
}
|
|
382
|
+
console.log('');
|
|
383
|
+
}
|
|
352
384
|
}
|
|
353
385
|
}
|
|
354
386
|
|
|
@@ -15,32 +15,61 @@ import { detectDefaultBranch } from '../../lib/git/default-branch.js';
|
|
|
15
15
|
* `morph-spec finish` can later merge/PR it back. Symmetric with the worktree
|
|
16
16
|
* flow, which gets its branch from `runWorktreeSetup`.
|
|
17
17
|
*
|
|
18
|
+
* That checkout moves the dev's PRIMARY working tree, and `stdio: 'pipe'`
|
|
19
|
+
* swallows even git's own message — so the move is reported by the caller
|
|
20
|
+
* (`branchMoveWarning`), never silent. `checkout: false` (`--no-checkout`)
|
|
21
|
+
* still CREATES the branch — `finish --pr|--merge` depends on it existing in
|
|
22
|
+
* both flows — and only skips moving the tree.
|
|
23
|
+
*
|
|
18
24
|
* Fail-open: outside a git repo, with no resolvable default, or on any git
|
|
19
25
|
* error, the feature is simply left on the current branch — never a failed birth.
|
|
20
26
|
*
|
|
21
27
|
* @param {string} featureName
|
|
22
|
-
* @
|
|
28
|
+
* @param {{ checkout?: boolean }} [opts] - `checkout: false` keeps the tree put
|
|
29
|
+
* @returns {{ branched: boolean, checkedOut: boolean, branch?: string, from?: string,
|
|
30
|
+
* movedFrom?: string, stayedOn?: string, reason?: string }}
|
|
31
|
+
* `movedFrom` is present ONLY when the working tree actually moved (it names
|
|
32
|
+
* the branch left behind); `stayedOn` only when the move was declined.
|
|
23
33
|
*/
|
|
24
|
-
function branchCommonFlow(featureName) {
|
|
34
|
+
function branchCommonFlow(featureName, { checkout = true } = {}) {
|
|
25
35
|
const cwd = process.cwd();
|
|
26
36
|
try {
|
|
27
|
-
if (!isGitRepo(cwd)) return { branched: false, reason: 'not-a-git-repo' };
|
|
37
|
+
if (!isGitRepo(cwd)) return { branched: false, checkedOut: false, reason: 'not-a-git-repo' };
|
|
28
38
|
const branch = buildBranchName(featureName);
|
|
29
39
|
const defaultBranch = detectDefaultBranch(cwd);
|
|
30
|
-
if (!defaultBranch) return { branched: false, reason: 'no-default-branch' };
|
|
40
|
+
if (!defaultBranch) return { branched: false, checkedOut: false, reason: 'no-default-branch' };
|
|
31
41
|
if (!branchExists(branch, cwd)) {
|
|
32
42
|
execFileSync('git', ['branch', branch, defaultBranch], { cwd, stdio: 'pipe' });
|
|
33
43
|
}
|
|
34
44
|
const current = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, stdio: 'pipe' }).toString().trim();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
45
|
+
// Already there: nothing moved, so nothing to warn about.
|
|
46
|
+
if (current === branch) return { branched: true, checkedOut: true, branch, from: defaultBranch };
|
|
47
|
+
if (!checkout) return { branched: true, checkedOut: false, branch, from: defaultBranch, stayedOn: current };
|
|
48
|
+
execFileSync('git', ['checkout', branch], { cwd, stdio: 'pipe' });
|
|
49
|
+
return { branched: true, checkedOut: true, branch, from: defaultBranch, movedFrom: current };
|
|
39
50
|
} catch {
|
|
40
|
-
return { branched: false, reason: 'git-error' };
|
|
51
|
+
return { branched: false, checkedOut: false, reason: 'git-error' };
|
|
41
52
|
}
|
|
42
53
|
}
|
|
43
54
|
|
|
55
|
+
/**
|
|
56
|
+
* The alert for a checkout that MOVED. Built once and rendered on both
|
|
57
|
+
* surfaces — stdout (human) and stderr (under `--json`, so it survives a
|
|
58
|
+
* stdout redirect) — so the two can never drift apart.
|
|
59
|
+
*
|
|
60
|
+
* @param {{ branch: string, movedFrom: string }} branchResult
|
|
61
|
+
* @returns {string[]} lines, unstyled and unindented
|
|
62
|
+
*/
|
|
63
|
+
function branchMoveWarning({ branch, movedFrom }) {
|
|
64
|
+
return [
|
|
65
|
+
`⚠️ Working tree MOVED: ${movedFrom} → ${branch}`,
|
|
66
|
+
` This checkout is now ON the feature branch — uncommitted work came along,`,
|
|
67
|
+
` and anything committed here from now on lands on ${branch}, not ${movedFrom}.`,
|
|
68
|
+
` Use --no-checkout to create the branch without moving the tree, or --worktree`,
|
|
69
|
+
` (the recommended route for parallel work) to isolate the feature in its own tree.`,
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
44
73
|
/** Feature names are kebab-case: lowercase alphanumerics and hyphens. */
|
|
45
74
|
const FEATURE_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
46
75
|
|
|
@@ -130,6 +159,24 @@ export async function createCommand(featureName, options = {}) {
|
|
|
130
159
|
process.exit(1);
|
|
131
160
|
}
|
|
132
161
|
|
|
162
|
+
// `--no-checkout` pede que o checkout PRINCIPAL não se mova; `--worktree`
|
|
163
|
+
// nunca o move — a feature nasce numa árvore separada, e o caminho de
|
|
164
|
+
// worktree sequer chama `branchCommonFlow`, que é o único leitor do flag.
|
|
165
|
+
// Aceitar a combinação em silêncio é dizer que o CLI honrou um pedido que
|
|
166
|
+
// ele ignorou — o mesmo anti-padrão que esta série de fixes tirou do
|
|
167
|
+
// `finish --pr --push`. Recusa ALTA, e AQUI: junto das outras validações
|
|
168
|
+
// de forma, ANTES de `createFeature` — a primeira mutação do `create`.
|
|
169
|
+
// Uma recusa depois dela deixaria a feature nascida por um comando que
|
|
170
|
+
// falhou, pior que o no-op que ela substitui.
|
|
171
|
+
if (options.worktree && options.checkout === false) {
|
|
172
|
+
console.error(chalk.red(`\n❌ no-checkout-with-worktree: --no-checkout não se combina com --worktree`));
|
|
173
|
+
console.error(chalk.gray(` O --worktree já não move o checkout desta árvore: a feature nasce em worktrees/${featureName},`));
|
|
174
|
+
console.error(chalk.gray(` na branch morph/${featureName}, e o caminho de worktree nem chega a ler o --no-checkout.`));
|
|
175
|
+
console.error(chalk.gray(` Para isolar a feature na própria árvore: morph-spec create ${featureName} --worktree`));
|
|
176
|
+
console.error(chalk.gray(` Para criar só a branch morph/${featureName} sem mover esta árvore: morph-spec create ${featureName} --no-checkout\n`));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
133
180
|
const now = new Date().toISOString();
|
|
134
181
|
const { workType, workTypeDetection } = resolveWorkType(options, now);
|
|
135
182
|
|
|
@@ -225,7 +272,8 @@ export async function createCommand(featureName, options = {}) {
|
|
|
225
272
|
} else {
|
|
226
273
|
// Common flow: still isolate the feature on its own branch so `finish`
|
|
227
274
|
// can merge/PR it back. Fail-open (see branchCommonFlow).
|
|
228
|
-
|
|
275
|
+
// Commander's `--no-checkout` sets options.checkout === false.
|
|
276
|
+
branchResult = branchCommonFlow(featureName, { checkout: options.checkout !== false });
|
|
229
277
|
}
|
|
230
278
|
}
|
|
231
279
|
|
|
@@ -238,6 +286,11 @@ export async function createCommand(featureName, options = {}) {
|
|
|
238
286
|
const out = { feature: featureName, created, ...finalFeature };
|
|
239
287
|
if (worktreeResult) out.worktree = worktreeResult;
|
|
240
288
|
if (branchResult) out.branch = branchResult;
|
|
289
|
+
// The move of the PRIMARY checkout is never silent — under --json it goes
|
|
290
|
+
// to stderr so it survives `... --json > out.json` instead of vanishing.
|
|
291
|
+
if (branchResult?.movedFrom) {
|
|
292
|
+
console.error(chalk.yellow(branchMoveWarning(branchResult).join('\n')));
|
|
293
|
+
}
|
|
241
294
|
console.log(JSON.stringify(out, null, 2));
|
|
242
295
|
return;
|
|
243
296
|
}
|
|
@@ -262,7 +315,15 @@ export async function createCommand(featureName, options = {}) {
|
|
|
262
315
|
}
|
|
263
316
|
} else if (branchResult?.branched) {
|
|
264
317
|
console.log(chalk.green(` branch: ${branchResult.branch} (from ${branchResult.from})`));
|
|
265
|
-
console.log(chalk.gray(` close it with: morph-spec finish ${featureName} --pr | --merge
|
|
318
|
+
console.log(chalk.gray(` close it with: morph-spec finish ${featureName} --pr | --merge`));
|
|
319
|
+
if (branchResult.movedFrom) {
|
|
320
|
+
// Alert, not one more success line: the tree the dev is standing in moved.
|
|
321
|
+
console.log('');
|
|
322
|
+
for (const line of branchMoveWarning(branchResult)) console.log(chalk.yellow(` ${line}`));
|
|
323
|
+
} else if (branchResult.checkedOut === false) {
|
|
324
|
+
console.log(chalk.gray(` working tree left on ${branchResult.stayedOn} (--no-checkout)`));
|
|
325
|
+
}
|
|
326
|
+
console.log('');
|
|
266
327
|
}
|
|
267
328
|
} else {
|
|
268
329
|
console.log(chalk.yellow(`\n⚠️ Feature "${featureName}" already exists — left untouched`));
|