@polymorphism-tech/morph-spec 8.33.0 → 8.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +22 -2
- package/README.md +2 -2
- package/bin/morph-spec.js +43 -5
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -2
- package/framework/MORPH.md +20 -22
- package/framework/agents.json +198 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +238 -10
- 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/README.md +11 -1
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +5 -8
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +79 -0
- package/framework/hooks/claude-code/core/chain.js +27 -7
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/core/worktree-context.js +83 -14
- package/framework/hooks/claude-code/core/worktree-create.js +206 -0
- package/framework/hooks/claude-code/core/worktree-env-inject.js +87 -0
- package/framework/hooks/claude-code/core/worktree-remove.js +81 -0
- package/framework/hooks/shared/command-tokens.js +23 -0
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/git-paths.js +25 -0
- package/framework/hooks/shared/hook-response.js +17 -0
- package/framework/hooks/shared/morph-cli.js +134 -0
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/rules/infrastructure-standards.md +3 -1
- 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 +28 -7
- 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 +20 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +38 -4
- 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 +1504 -830
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +256 -160
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +217 -193
- package/framework/standards/ai-agents/context-providers.md +261 -188
- 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 +351 -250
- package/framework/standards/ai-agents/mcp-tools.md +250 -197
- 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 +241 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +422 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +177 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +189 -181
- package/framework/standards/ai-agents/modalities-vision.md +205 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +211 -155
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +260 -231
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +293 -158
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +143 -135
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +280 -249
- package/framework/standards/ai-agents/sweet-spot.md +129 -109
- 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 +90 -0
- package/framework/templates/REGISTRY.json +14 -1
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- 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/infrastructure/docker/docker-compose.template.yml +24 -14
- package/package.json +3 -3
- package/src/commands/e2e/e2e.js +91 -40
- package/src/commands/project/doctor.js +143 -6
- package/src/commands/project/env.js +95 -0
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +274 -53
- 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/delete.js +8 -0
- package/src/commands/state/finish.js +360 -53
- 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/state-manager.js +9 -1
- package/src/dashboard/app/pages/HookInspector.jsx +430 -418
- package/src/dashboard/scanner/hooks-scanner.js +5 -1
- 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/e2e/compose-env.js +55 -14
- package/src/lib/e2e/stack-teardown.js +115 -0
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/architecture-scanner.js +2 -1
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/graph/graphifyignore.js +2 -1
- package/src/lib/installers/effects.js +193 -0
- 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/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +431 -37
- package/src/lib/validators/verify-runner.js +590 -28
- package/src/lib/worktree/dirs.js +32 -0
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +69 -11
- package/src/lib/worktree/worktree-audit.js +70 -3
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/file-copier.js +28 -16
- package/src/utils/hooks-installer.js +75 -29
- package/src/utils/skills-installer.js +194 -187
- package/src/utils/version-checker.js +68 -3
- package/framework/standards/ai-agents/prompts-in-db.md +0 -214
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scenarioName": "triagem.acme.a-saudacao",
|
|
3
|
+
"iterationName": "1",
|
|
4
|
+
"key": "19F631228C6E00BA41892982E6EA1BB23822FA53D6E62C79B6724893618E7E03AE4F70F94332FDEE0DAA960974BA1171",
|
|
5
|
+
"creation": "2026-09-09T04:54:34.8385364+00:00",
|
|
6
|
+
"expiration": "2036-09-06T04:54:34.8385364+00:00"
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scenarioName": "triagem.acme.b-escalonamento",
|
|
3
|
+
"iterationName": "1",
|
|
4
|
+
"key": "A24A7C9BDD8239D87A60FD05A4BF870DAB4CFBE8BE7A796FF86534B6F37F4517321D3420D76ED6B5EF000C68CB18301F",
|
|
5
|
+
"creation": "2026-09-09T04:54:34.8448053+00:00",
|
|
6
|
+
"expiration": "2036-09-06T04:54:34.8448053+00:00"
|
|
7
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"scenarioName": "triagem.globex.a-saudacao",
|
|
3
|
+
"iterationName": "1",
|
|
4
|
+
"key": "E38CD37DF003EDB77815FF1A086F48ACBDD63E743D44DFFDF2A23EEED34731E8D3B6E51A4EBB740CB0BF2720D8AA02ED",
|
|
5
|
+
"creation": "2026-09-09T04:54:34.846003+00:00",
|
|
6
|
+
"expiration": "2036-09-06T04:54:34.846003+00:00"
|
|
7
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"messages": [
|
|
3
|
+
{
|
|
4
|
+
"role": "system",
|
|
5
|
+
"text": "Responda SOMENTE com um objeto JSON com as chaves valor e prazo."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"role": "user",
|
|
9
|
+
"text": "quanto custa a revisao anual?"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"assertions": [
|
|
13
|
+
{
|
|
14
|
+
"name": "tem-valor",
|
|
15
|
+
"kind": "json-has-key",
|
|
16
|
+
"target": "valor"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "tem-prazo",
|
|
20
|
+
"kind": "json-has-key",
|
|
21
|
+
"target": "prazo"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"messages": [
|
|
3
|
+
{
|
|
4
|
+
"role": "system",
|
|
5
|
+
"text": "Voce e o atendente da Acme. Responda em portugues, em uma frase."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"role": "user",
|
|
9
|
+
"text": "bom dia"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"assertions": [
|
|
13
|
+
{
|
|
14
|
+
"name": "cumprimenta",
|
|
15
|
+
"kind": "contains",
|
|
16
|
+
"value": "bom dia"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "sem-dado-pessoal",
|
|
20
|
+
"kind": "no-pii"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"messages": [
|
|
3
|
+
{
|
|
4
|
+
"role": "system",
|
|
5
|
+
"text": "Voce e o atendente da Acme. Responda em portugues, em uma frase."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"role": "user",
|
|
9
|
+
"text": "quero falar com um humano agora"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"assertions": [
|
|
13
|
+
{
|
|
14
|
+
"name": "confirma-encaminhamento",
|
|
15
|
+
"kind": "contains",
|
|
16
|
+
"value": "encaminhando"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "nao-promete-prazo",
|
|
20
|
+
"kind": "not-contains",
|
|
21
|
+
"value": "garantimos"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"messages": [
|
|
3
|
+
{
|
|
4
|
+
"role": "system",
|
|
5
|
+
"text": "Voce e o atendente da Globex. Responda em portugues, em uma frase."
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
"role": "user",
|
|
9
|
+
"text": "bom dia"
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
"assertions": [
|
|
13
|
+
{
|
|
14
|
+
"name": "cumprimenta",
|
|
15
|
+
"kind": "contains",
|
|
16
|
+
"value": "Globex"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "sem-dado-pessoal",
|
|
20
|
+
"kind": "no-pii"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
# Docker Compose for Local Development
|
|
2
2
|
# Next.js + Neon Stack
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
4
|
+
# One compose project per worktree. The harness sets
|
|
5
|
+
# COMPOSE_PROJECT_NAME=morph-{feature}, which prefixes every container, network
|
|
6
|
+
# and volume — so two features running side by side never share a resource.
|
|
7
|
+
# Explicit `container_name:` entries are NOT prefixed, which is why this file
|
|
8
|
+
# declares none: a named container is a collision waiting for the second
|
|
9
|
+
# worktree.
|
|
9
10
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
11
|
+
# Host ports come from the feature's port block (see env-allocator.js):
|
|
12
|
+
# PORT block + 0 (web)
|
|
13
|
+
# API_PORT block + 1 (api)
|
|
14
|
+
# POSTGRES_PORT block + 2 (postgres)
|
|
15
|
+
# REDIS_PORT block + 3 (redis)
|
|
16
|
+
# Every published port reads one of those variables with a single-stack
|
|
17
|
+
# default. A fixed host port here would defeat the block no matter what the
|
|
18
|
+
# harness injects — `morph-spec e2e up` and the verify e2e node refuse such a
|
|
19
|
+
# compose inside a worktree (`compose-not-isolable`).
|
|
13
20
|
#
|
|
14
|
-
#
|
|
21
|
+
# Named volumes are reclaimed by `morph-spec finish` when the feature closes
|
|
22
|
+
# (`--keep-volumes` opts out). Healthchecks are what make `up --wait`
|
|
23
|
+
# meaningful: without them Compose reports "started" the moment the process
|
|
24
|
+
# launches, and the e2e node would navigate an application not serving yet.
|
|
25
|
+
#
|
|
26
|
+
# The harness also exports PLAYWRIGHT_BASE_URL for the allocated port, so
|
|
15
27
|
# `playwright.config.ts` must read it instead of hardcoding a URL:
|
|
16
28
|
# baseURL: process.env.PLAYWRIGHT_BASE_URL ?? `http://localhost:${process.env.PORT ?? 3000}`
|
|
17
29
|
|
|
@@ -21,12 +33,11 @@ services:
|
|
|
21
33
|
build:
|
|
22
34
|
context: .
|
|
23
35
|
dockerfile: Dockerfile.dev
|
|
24
|
-
container_name: {{kebabCase FEATURE_NAME}}-app
|
|
25
36
|
ports:
|
|
26
37
|
- "${PORT:-3000}:3000"
|
|
27
38
|
environment:
|
|
28
39
|
- NODE_ENV=development
|
|
29
|
-
- DATABASE_URL=${DATABASE_URL}
|
|
40
|
+
- DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/${POSTGRES_DB:-app}}
|
|
30
41
|
volumes:
|
|
31
42
|
- .:/app
|
|
32
43
|
- /app/node_modules
|
|
@@ -46,19 +57,18 @@ services:
|
|
|
46
57
|
# PostgreSQL Database (Local development)
|
|
47
58
|
postgres:
|
|
48
59
|
image: postgres:15-alpine
|
|
49
|
-
container_name: {{kebabCase FEATURE_NAME}}-postgres
|
|
50
60
|
restart: unless-stopped
|
|
51
61
|
ports:
|
|
52
62
|
- "${POSTGRES_PORT:-5432}:5432"
|
|
53
63
|
environment:
|
|
54
64
|
POSTGRES_USER: postgres
|
|
55
65
|
POSTGRES_PASSWORD: postgres
|
|
56
|
-
POSTGRES_DB: {
|
|
66
|
+
POSTGRES_DB: ${POSTGRES_DB:-app}
|
|
57
67
|
volumes:
|
|
58
68
|
- postgres-data:/var/lib/postgresql/data
|
|
59
69
|
- ./migrations:/docker-entrypoint-initdb.d
|
|
60
70
|
healthcheck:
|
|
61
|
-
test: ["CMD-SHELL", "pg_isready -U postgres -d {
|
|
71
|
+
test: ["CMD-SHELL", "pg_isready -U postgres -d ${POSTGRES_DB:-app}"]
|
|
62
72
|
interval: 5s
|
|
63
73
|
timeout: 5s
|
|
64
74
|
retries: 10
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polymorphism-tech/morph-spec",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.35.0",
|
|
4
4
|
"description": "MORPH-SPEC: NLH (Natural Language Harness) for spec-driven development with Claude Code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -41,8 +41,8 @@
|
|
|
41
41
|
},
|
|
42
42
|
"scripts": {
|
|
43
43
|
"start": "node bin/morph-spec.js",
|
|
44
|
-
"generate": "node scripts/generate-refs.js",
|
|
45
|
-
"generate:check": "node scripts/generate-refs.js --check",
|
|
44
|
+
"generate": "node scripts/generate-refs.js && node scripts/generate-ai-pin-props.js && node scripts/api-probe/probe.mjs --generate",
|
|
45
|
+
"generate:check": "node scripts/generate-refs.js --check && node scripts/generate-ai-pin-props.js --check && node scripts/api-probe/probe.mjs --check",
|
|
46
46
|
"generate-standards": "node scripts/generate-standards-registry.js",
|
|
47
47
|
"generate-standards:check": "node scripts/generate-standards-registry.js --check",
|
|
48
48
|
"validate": "node scripts/validate-framework.js && node scripts/check-rubric-parity.js",
|
package/src/commands/e2e/e2e.js
CHANGED
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
* running still matches the working tree, and `down` releases it — which
|
|
8
8
|
* `morph-spec finish` also does as part of teardown.
|
|
9
9
|
*
|
|
10
|
+
* Every subcommand acts in the TREE THAT OWNS THE FEATURE: run from the
|
|
11
|
+
* primary root for a feature that lives in a worktree, the compose file, the
|
|
12
|
+
* bind mount and the stamp are all the worktree's — the root's copy is a
|
|
13
|
+
* different checkout of different code.
|
|
14
|
+
*
|
|
10
15
|
* All the real logic lives in framework/hooks/shared/e2e-check.js, shared with
|
|
11
16
|
* the `verify` e2e node so the two can never drift.
|
|
12
17
|
*
|
|
@@ -22,16 +27,21 @@ import {
|
|
|
22
27
|
bringUpStack,
|
|
23
28
|
composeProjectName,
|
|
24
29
|
computeSourceStamp,
|
|
30
|
+
decideIsolation,
|
|
25
31
|
defaultExec,
|
|
32
|
+
deleteStamp,
|
|
26
33
|
getStackStatus,
|
|
27
34
|
isDockerAvailable,
|
|
35
|
+
lintComposeIsolation,
|
|
28
36
|
prepareAuth,
|
|
29
37
|
readStamp,
|
|
30
38
|
resolveComposeFile,
|
|
31
39
|
resolveSpecCommand,
|
|
32
40
|
tearDownStack,
|
|
33
41
|
} from '../../../framework/hooks/shared/e2e-check.js';
|
|
42
|
+
import { isSecondaryWorktree } from '../../../framework/hooks/shared/git-paths.js';
|
|
34
43
|
import { buildComposeEnv } from '../../lib/e2e/compose-env.js';
|
|
44
|
+
import { findWorktreeForFeature, resolvePrimaryRoot } from '../project/worktree.js';
|
|
35
45
|
|
|
36
46
|
/** Exit code for a usage error, matching `verify`'s convention. */
|
|
37
47
|
const EXIT_USAGE = 2;
|
|
@@ -41,20 +51,26 @@ const EXIT_USAGE = 2;
|
|
|
41
51
|
// ============================================================================
|
|
42
52
|
|
|
43
53
|
/**
|
|
44
|
-
* Everything the three subcommands need, resolved once
|
|
45
|
-
*
|
|
46
|
-
*
|
|
54
|
+
* Everything the three subcommands need, resolved once — against the tree
|
|
55
|
+
* that owns the feature (its live worktree when it has one, the root
|
|
56
|
+
* otherwise).
|
|
57
|
+
*
|
|
58
|
+
* Returns null (after printing) when that tree has no compose file — a usage
|
|
59
|
+
* error here, unlike in the verify node where it is a graded skip.
|
|
47
60
|
*/
|
|
48
61
|
function resolveContext(featureName, { json = false } = {}) {
|
|
49
|
-
const
|
|
50
|
-
const
|
|
62
|
+
const root = resolvePrimaryRoot(process.cwd());
|
|
63
|
+
const found = findWorktreeForFeature(featureName, root);
|
|
64
|
+
const cwd = found?.path ?? root;
|
|
65
|
+
|
|
66
|
+
const config = readConfig(cwd) ?? readConfig(root);
|
|
51
67
|
const composeFile = resolveComposeFile(cwd, config);
|
|
52
68
|
|
|
53
69
|
if (!composeFile) {
|
|
54
70
|
const message =
|
|
55
|
-
|
|
71
|
+
`No compose file found in ${cwd} (compose.yaml, docker-compose.yml, …). ` +
|
|
56
72
|
'Set project.e2e.composeFile in .morph/config/config.json if it lives elsewhere.';
|
|
57
|
-
if (json) console.log(JSON.stringify({ error: message }, null, 2));
|
|
73
|
+
if (json) console.log(JSON.stringify({ error: message, tree: cwd }, null, 2));
|
|
58
74
|
else console.error(chalk.red(`\n❌ ${message}\n`));
|
|
59
75
|
return null;
|
|
60
76
|
}
|
|
@@ -64,6 +80,9 @@ function resolveContext(featureName, { json = false } = {}) {
|
|
|
64
80
|
|
|
65
81
|
return {
|
|
66
82
|
cwd,
|
|
83
|
+
root,
|
|
84
|
+
tree: cwd,
|
|
85
|
+
insideWorktree: isSecondaryWorktree(cwd),
|
|
67
86
|
config,
|
|
68
87
|
composeFile,
|
|
69
88
|
projectName,
|
|
@@ -79,39 +98,17 @@ function resolveContext(featureName, { json = false } = {}) {
|
|
|
79
98
|
* The port block comes from `buildComposeEnv` rather than being assembled here
|
|
80
99
|
* so this command and the `e2e` node inside `verify` agree by construction —
|
|
81
100
|
* isolation that exists only in the command nobody runs is no isolation at all.
|
|
82
|
-
* `COMPOSE_PROJECT_NAME` isolates container NAMES but not host ports; a compose
|
|
83
|
-
* that hardcodes ports instead of reading `${PORT:-3000}` cannot be isolated at
|
|
84
|
-
* all, which is surfaced as a warning rather than silently accepted.
|
|
85
101
|
*/
|
|
86
102
|
function buildEnv({ cwd, config, featureName, projectName, gitCommonDir }) {
|
|
103
|
+
// COMPOSE_PROJECT_NAME is part of the port layout now; the explicit key stays
|
|
104
|
+
// as the fail-open floor for a repo with no git common dir (no block → no
|
|
105
|
+
// layout), where the project name is still what isolates the containers.
|
|
87
106
|
return {
|
|
88
107
|
COMPOSE_PROJECT_NAME: projectName,
|
|
89
108
|
...buildComposeEnv({ cwd, config, featureName, gitCommonDir }),
|
|
90
109
|
};
|
|
91
110
|
}
|
|
92
111
|
|
|
93
|
-
/**
|
|
94
|
-
* Warn once when the compose file publishes fixed host ports: with a hardcoded
|
|
95
|
-
* `"3000:3000"` the fleet port block cannot take effect, so two features
|
|
96
|
-
* running simultaneously will collide no matter what we inject.
|
|
97
|
-
*/
|
|
98
|
-
function warnOnFixedPorts(cwd, composeFile) {
|
|
99
|
-
try {
|
|
100
|
-
const raw = readFileSync(join(cwd, composeFile), 'utf8');
|
|
101
|
-
const publishesFixed = /^\s*-\s*["']?\d+:\d+["']?\s*$/m.test(raw);
|
|
102
|
-
if (publishesFixed) {
|
|
103
|
-
console.log(
|
|
104
|
-
chalk.yellow(
|
|
105
|
-
'⚠️ Compose publishes fixed host ports. Parallel worktrees will collide — ' +
|
|
106
|
-
'use ${PORT:-3000}:3000 so the fleet port block applies.'
|
|
107
|
-
)
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
} catch {
|
|
111
|
-
// Unreadable compose is the caller's problem, not this warning's.
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
112
|
// ============================================================================
|
|
116
113
|
// up
|
|
117
114
|
// ============================================================================
|
|
@@ -119,20 +116,48 @@ function warnOnFixedPorts(cwd, composeFile) {
|
|
|
119
116
|
/**
|
|
120
117
|
* `morph-spec e2e up <feature> [--fresh]`
|
|
121
118
|
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
119
|
+
* `--fresh` is destructive on purpose: it tears the previous stack down WITH
|
|
120
|
+
* its named volumes and forces the auth seed to run again. A rebuild that
|
|
121
|
+
* preserved the database would hand the next suite the previous run's rows,
|
|
122
|
+
* and a storageState captured against that database would keep authenticating
|
|
123
|
+
* a user that no longer exists.
|
|
124
|
+
*
|
|
125
|
+
* Exit codes: 0 = stack healthy · 1 = failed to come up (or auth broke, or the
|
|
126
|
+
* compose cannot be isolated inside a worktree) · 2 = usage (no compose file /
|
|
127
|
+
* no docker).
|
|
124
128
|
*/
|
|
125
129
|
export async function e2eUpCommand(featureName, options = {}) {
|
|
126
130
|
const ctx = resolveContext(featureName, options);
|
|
127
131
|
if (!ctx) process.exit(EXIT_USAGE);
|
|
128
132
|
|
|
133
|
+
// Isolation BEFORE the docker check: `COMPOSE_PROJECT_NAME` isolates
|
|
134
|
+
// container names but not host ports, and a compose that hardcodes them (or
|
|
135
|
+
// names its containers) collides with the sibling worktree no matter what we
|
|
136
|
+
// inject. Same rule as the verify node — see decideIsolation().
|
|
137
|
+
const lint = lintComposeIsolation(readComposeText(ctx.cwd, ctx.composeFile));
|
|
138
|
+
const isolation = decideIsolation({ findings: lint.findings, insideWorktree: ctx.insideWorktree });
|
|
139
|
+
if (isolation.status === 'fail') {
|
|
140
|
+
if (options.json) {
|
|
141
|
+
console.log(JSON.stringify(
|
|
142
|
+
{ error: 'compose-not-isolable', feature: featureName, tree: ctx.tree, composeFile: ctx.composeFile, reason: isolation.reason, findings: lint.findings },
|
|
143
|
+
null, 2,
|
|
144
|
+
));
|
|
145
|
+
} else {
|
|
146
|
+
console.error(chalk.red(`\n❌ ${isolation.reason}`));
|
|
147
|
+
for (const f of lint.findings) console.error(chalk.gray(` ${ctx.composeFile}:${f.line}: ${f.text}`));
|
|
148
|
+
console.error('');
|
|
149
|
+
}
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
if (isolation.status === 'warn' && !options.json) {
|
|
153
|
+
console.log(chalk.yellow(`⚠️ ${isolation.reason}`));
|
|
154
|
+
}
|
|
155
|
+
|
|
129
156
|
if (!isDockerAvailable(ctx.cwd)) {
|
|
130
157
|
console.error(chalk.red('\n❌ Docker is not available in this environment.\n'));
|
|
131
158
|
process.exit(EXIT_USAGE);
|
|
132
159
|
}
|
|
133
160
|
|
|
134
|
-
if (!options.json) warnOnFixedPorts(ctx.cwd, ctx.composeFile);
|
|
135
|
-
|
|
136
161
|
const result = bringUpStack(ctx.cwd, {
|
|
137
162
|
composeFile: ctx.composeFile,
|
|
138
163
|
projectName: ctx.projectName,
|
|
@@ -143,18 +168,26 @@ export async function e2eUpCommand(featureName, options = {}) {
|
|
|
143
168
|
|
|
144
169
|
const auth =
|
|
145
170
|
result.status === 'pass'
|
|
146
|
-
? prepareAuth(ctx.cwd, { config: ctx.config, env: ctx.env })
|
|
171
|
+
? prepareAuth(ctx.cwd, { config: ctx.config, env: ctx.env, forceSeed: Boolean(options.fresh) })
|
|
147
172
|
: { status: 'skip', reason: null };
|
|
148
173
|
|
|
174
|
+
// Same warnings[] contract as the verify e2e node: a seedCommand that never
|
|
175
|
+
// runs because `mode` switches it off is reported, never swallowed.
|
|
176
|
+
const warnings = [];
|
|
177
|
+
if (isolation.status === 'warn') warnings.push(isolation.reason);
|
|
178
|
+
if (auth.warning) warnings.push(auth.warning);
|
|
179
|
+
|
|
149
180
|
const failed = result.status === 'fail' || auth.status === 'fail';
|
|
150
181
|
const payload = {
|
|
151
182
|
feature: featureName,
|
|
152
183
|
project: ctx.projectName,
|
|
184
|
+
tree: ctx.tree,
|
|
153
185
|
composeFile: ctx.composeFile,
|
|
154
186
|
status: failed ? 'fail' : 'pass',
|
|
155
187
|
reused: result.reused,
|
|
156
188
|
reason: result.reason,
|
|
157
189
|
auth: { status: auth.status, reason: auth.reason },
|
|
190
|
+
...(warnings.length ? { warnings } : {}),
|
|
158
191
|
};
|
|
159
192
|
|
|
160
193
|
if (options.json) {
|
|
@@ -165,7 +198,9 @@ export async function e2eUpCommand(featureName, options = {}) {
|
|
|
165
198
|
} else {
|
|
166
199
|
const how = result.reused ? chalk.gray(`(${result.reason})`) : chalk.gray('(built)');
|
|
167
200
|
console.log(chalk.green(`\n✓ Stack up — ${ctx.projectName} ${how}`));
|
|
201
|
+
console.log(chalk.gray(` tree: ${ctx.tree}`));
|
|
168
202
|
if (auth.reason) console.log(chalk.gray(` auth: ${auth.reason}`));
|
|
203
|
+
if (auth.warning) console.log(chalk.yellow(`⚠️ ${auth.warning}`));
|
|
169
204
|
console.log('');
|
|
170
205
|
}
|
|
171
206
|
|
|
@@ -217,6 +252,7 @@ export async function e2eStatusCommand(featureName, options = {}) {
|
|
|
217
252
|
const payload = {
|
|
218
253
|
feature: featureName,
|
|
219
254
|
project: ctx.projectName,
|
|
255
|
+
tree: ctx.tree,
|
|
220
256
|
composeFile: ctx.composeFile,
|
|
221
257
|
up: status.up,
|
|
222
258
|
healthy: status.healthy,
|
|
@@ -236,6 +272,7 @@ export async function e2eStatusCommand(featureName, options = {}) {
|
|
|
236
272
|
} else {
|
|
237
273
|
console.log('');
|
|
238
274
|
console.log(chalk.bold(`E2E stack — ${ctx.projectName}`));
|
|
275
|
+
console.log(chalk.gray(`tree: ${ctx.tree}`));
|
|
239
276
|
console.log(chalk.gray(`compose: ${ctx.composeFile}`));
|
|
240
277
|
console.log(chalk.gray(`url: ${payload.baseUrl}`));
|
|
241
278
|
console.log('');
|
|
@@ -262,7 +299,8 @@ export async function e2eStatusCommand(featureName, options = {}) {
|
|
|
262
299
|
* `morph-spec e2e down <feature> [--volumes]`
|
|
263
300
|
*
|
|
264
301
|
* Teardown is best-effort by design — a stack that will not come down must
|
|
265
|
-
* never block `finish`.
|
|
302
|
+
* never block `finish`. The freshness stamp goes regardless: a stamp that
|
|
303
|
+
* outlives its containers would let the next `up` reuse a stack that is gone.
|
|
266
304
|
*/
|
|
267
305
|
export async function e2eDownCommand(featureName, options = {}) {
|
|
268
306
|
const ctx = resolveContext(featureName, options);
|
|
@@ -274,11 +312,16 @@ export async function e2eDownCommand(featureName, options = {}) {
|
|
|
274
312
|
env: ctx.env,
|
|
275
313
|
volumes: Boolean(options.volumes),
|
|
276
314
|
});
|
|
315
|
+
const stampDeleted = deleteStamp(ctx.gitCommonDir, ctx.projectName);
|
|
277
316
|
|
|
278
317
|
if (options.json) {
|
|
279
|
-
console.log(JSON.stringify(
|
|
318
|
+
console.log(JSON.stringify(
|
|
319
|
+
{ feature: featureName, project: ctx.projectName, tree: ctx.tree, status: result.status, stampDeleted },
|
|
320
|
+
null, 2,
|
|
321
|
+
));
|
|
280
322
|
} else if (result.status === 'pass') {
|
|
281
|
-
console.log(chalk.green(`\n✓ Stack down — ${ctx.projectName}
|
|
323
|
+
console.log(chalk.green(`\n✓ Stack down — ${ctx.projectName}`));
|
|
324
|
+
console.log(chalk.gray(` tree: ${ctx.tree}\n`));
|
|
282
325
|
} else {
|
|
283
326
|
console.log(chalk.yellow(`\n⚠️ Teardown reported a problem (not fatal):\n${result.output}\n`));
|
|
284
327
|
}
|
|
@@ -300,6 +343,14 @@ function readConfig(cwd) {
|
|
|
300
343
|
}
|
|
301
344
|
}
|
|
302
345
|
|
|
346
|
+
function readComposeText(cwd, composeFile) {
|
|
347
|
+
try {
|
|
348
|
+
return readFileSync(join(cwd, composeFile), 'utf8');
|
|
349
|
+
} catch {
|
|
350
|
+
return '';
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
303
354
|
/** Same resolution (and same fail-open) as verify-runner's. */
|
|
304
355
|
function resolveGitCommonDir(cwd) {
|
|
305
356
|
try {
|