@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
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { join, dirname } from 'path';
|
|
12
|
-
import { readFile
|
|
12
|
+
import { readFile } from 'fs/promises';
|
|
13
13
|
import { existsSync, chmodSync, readdirSync } from 'fs';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
import { execSync } from 'child_process';
|
|
16
|
+
import { realEffects } from '../lib/installers/effects.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Current hooks schema version — bump when hook definitions change.
|
|
@@ -280,9 +281,14 @@ const MORPH_HOOKS = [
|
|
|
280
281
|
* - Tracks installed version via _morph_managed.hooks_version
|
|
281
282
|
*
|
|
282
283
|
* @param {string} targetPath - Project root directory
|
|
284
|
+
* @param {object} [options]
|
|
285
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
286
|
+
* Coletor de efeitos. Esta função é o seam mais barato do caminho de update:
|
|
287
|
+
* o objeto de settings INTEIRO já é montado em memória e há UMA escrita, no
|
|
288
|
+
* fim. Previsão e execução percorrem a mesma montagem.
|
|
283
289
|
* @returns {Promise<{ installed: number, updated: boolean }>}
|
|
284
290
|
*/
|
|
285
|
-
export async function installClaudeHooks(targetPath) {
|
|
291
|
+
export async function installClaudeHooks(targetPath, { fx = realEffects() } = {}) {
|
|
286
292
|
const claudeDir = join(targetPath, '.claude');
|
|
287
293
|
const settingsPath = join(claudeDir, 'settings.local.json');
|
|
288
294
|
|
|
@@ -409,11 +415,9 @@ export async function installClaudeHooks(targetPath) {
|
|
|
409
415
|
settings._morph_managed.generated_at = new Date().toISOString();
|
|
410
416
|
|
|
411
417
|
// Ensure .claude directory exists
|
|
412
|
-
|
|
413
|
-
await mkdir(claudeDir, { recursive: true });
|
|
414
|
-
}
|
|
418
|
+
fx.ensureDir(claudeDir);
|
|
415
419
|
|
|
416
|
-
|
|
420
|
+
fx.writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
417
421
|
return { installed, updated: !alreadyCurrent };
|
|
418
422
|
}
|
|
419
423
|
|
|
@@ -530,19 +534,24 @@ function resolvePythonForStatusline() {
|
|
|
530
534
|
*
|
|
531
535
|
* @param {string} hooksSourceDir - Path to framework/hooks/claude-code/
|
|
532
536
|
* @param {string} [globalClaudeDirOverride] - Override ~/.claude/ (for testing)
|
|
537
|
+
* @param {object} [options]
|
|
538
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
539
|
+
* Coletor de efeitos. Estes são os efeitos FORA do projeto — o relatório do
|
|
540
|
+
* `--dry-run` é obrigado a nomeá-los separadamente, porque ninguém os
|
|
541
|
+
* adivinha da palavra "update".
|
|
533
542
|
* @returns {Promise<{ installed: boolean, globalDir: string }>}
|
|
534
543
|
*/
|
|
535
|
-
export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null) {
|
|
544
|
+
export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOverride = null, { fx = realEffects() } = {}) {
|
|
536
545
|
const globalDir = globalClaudeDirOverride || join(homedir(), '.claude');
|
|
537
|
-
|
|
546
|
+
fx.ensureDir(globalDir);
|
|
538
547
|
|
|
539
548
|
// Copy statusline scripts to ~/.claude/
|
|
540
549
|
const scripts = ['statusline.sh', 'statusline.py'];
|
|
541
550
|
for (const fname of scripts) {
|
|
542
551
|
const src = join(hooksSourceDir, fname);
|
|
543
552
|
if (existsSync(src)) {
|
|
544
|
-
|
|
545
|
-
if (process.platform !== 'win32') {
|
|
553
|
+
fx.copyFile(src, join(globalDir, fname));
|
|
554
|
+
if (process.platform !== 'win32' && !fx.dryRun) {
|
|
546
555
|
try { chmodSync(join(globalDir, fname), 0o755); } catch { /* non-critical */ }
|
|
547
556
|
}
|
|
548
557
|
}
|
|
@@ -550,14 +559,6 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
|
|
|
550
559
|
|
|
551
560
|
// Register statusLine in ~/.claude/settings.json (global user settings)
|
|
552
561
|
const settingsPath = join(globalDir, 'settings.json');
|
|
553
|
-
let settings = {};
|
|
554
|
-
if (existsSync(settingsPath)) {
|
|
555
|
-
try {
|
|
556
|
-
settings = JSON.parse(await readFile(settingsPath, 'utf-8'));
|
|
557
|
-
} catch {
|
|
558
|
-
settings = {};
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
562
|
|
|
562
563
|
// On Windows, Claude Code cannot execute .sh files directly — call Python instead.
|
|
563
564
|
const statuslineCommand = process.platform === 'win32'
|
|
@@ -569,14 +570,16 @@ export async function installGlobalStatusline(hooksSourceDir, globalClaudeDirOve
|
|
|
569
570
|
// `↻` countdown to the 5h rate-limit reset — the only widget that moves on
|
|
570
571
|
// its own — sits frozen through a long turn. Everything else is event-driven
|
|
571
572
|
// and would be fine without this.
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
573
|
+
// Fusão rasa: só `statusLine` é do morph-spec; tudo o mais no
|
|
574
|
+
// `~/.claude/settings.json` é do usuário e sobrevive intocado.
|
|
575
|
+
fx.mergeJson(settingsPath, {
|
|
576
|
+
statusLine: {
|
|
577
|
+
type: 'command',
|
|
578
|
+
command: statuslineCommand,
|
|
579
|
+
padding: 2,
|
|
580
|
+
refreshInterval: 10
|
|
581
|
+
}
|
|
582
|
+
});
|
|
580
583
|
return { installed: true, globalDir };
|
|
581
584
|
}
|
|
582
585
|
|
|
@@ -14,10 +14,11 @@
|
|
|
14
14
|
* execute scripts and load references on demand.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
import { join } from 'path';
|
|
17
|
+
import { existsSync, readdirSync, statSync, readFileSync } from 'fs';
|
|
18
|
+
import { basename, join } from 'path';
|
|
19
19
|
import { fileURLToPath } from 'url';
|
|
20
20
|
import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
|
|
21
|
+
import { realEffects } from '../lib/installers/effects.js';
|
|
21
22
|
|
|
22
23
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
23
24
|
const FRAMEWORK_SKILLS_DIR = join(__dirname, '..', '..', 'framework', 'skills');
|
|
@@ -39,24 +40,17 @@ export function shouldInstallSkill(skillContent, projectTags) {
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
43
|
+
* Copy a skill directory tree from src to dest, skipping README.md files.
|
|
44
|
+
*
|
|
45
|
+
* UM efeito por skill (não um por arquivo): é assim que o instalador pensa
|
|
46
|
+
* ("instalar a skill X"), e é o que o relatório do `--dry-run` precisa dizer.
|
|
44
47
|
*
|
|
45
48
|
* @param {string} src - Source directory path
|
|
46
49
|
* @param {string} dest - Destination directory path
|
|
50
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} fx
|
|
47
51
|
*/
|
|
48
|
-
function
|
|
49
|
-
|
|
50
|
-
for (const entry of readdirSync(src)) {
|
|
51
|
-
if (entry === 'README.md') continue;
|
|
52
|
-
const srcPath = join(src, entry);
|
|
53
|
-
const destPath = join(dest, entry);
|
|
54
|
-
if (statSync(srcPath).isDirectory()) {
|
|
55
|
-
copyDirectory(srcPath, destPath);
|
|
56
|
-
} else {
|
|
57
|
-
copyFileSync(srcPath, destPath);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
52
|
+
function copySkillDirectory(src, dest, fx) {
|
|
53
|
+
fx.copyDir(src, dest, { filter: (from) => basename(from) !== 'README.md' });
|
|
60
54
|
}
|
|
61
55
|
|
|
62
56
|
/**
|
|
@@ -82,7 +76,7 @@ function copyDirectory(src, dest) {
|
|
|
82
76
|
* previous run with different projectTags", which is exactly the stale
|
|
83
77
|
* entry a stack change needs cleaned up.
|
|
84
78
|
*/
|
|
85
|
-
function installSkillsFromDir(srcDir, destDir, projectTags, installedNow) {
|
|
79
|
+
function installSkillsFromDir(srcDir, destDir, projectTags, installedNow, fx) {
|
|
86
80
|
const entries = readdirSync(srcDir);
|
|
87
81
|
for (const entry of entries) {
|
|
88
82
|
const srcPath = join(srcDir, entry);
|
|
@@ -91,15 +85,17 @@ function installSkillsFromDir(srcDir, destDir, projectTags, installedNow) {
|
|
|
91
85
|
if (stat.isDirectory()) {
|
|
92
86
|
const skillMdPath = join(srcPath, 'SKILL.md');
|
|
93
87
|
if (existsSync(skillMdPath)) {
|
|
94
|
-
// Directory-based skill — check stack filter before copying
|
|
88
|
+
// Directory-based skill — check stack filter before copying.
|
|
89
|
+
// O filtro é PURO e continua sendo o único: previsão e execução
|
|
90
|
+
// percorrem esta mesma linha.
|
|
95
91
|
const content = readFileSync(skillMdPath, 'utf-8');
|
|
96
92
|
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
97
93
|
const skillDestDir = join(destDir, entry);
|
|
98
|
-
|
|
94
|
+
copySkillDirectory(srcPath, skillDestDir, fx);
|
|
99
95
|
installedNow.add(entry);
|
|
100
96
|
} else {
|
|
101
97
|
// Level/category directory — recurse
|
|
102
|
-
installSkillsFromDir(srcPath, destDir, projectTags, installedNow);
|
|
98
|
+
installSkillsFromDir(srcPath, destDir, projectTags, installedNow, fx);
|
|
103
99
|
}
|
|
104
100
|
} else if (entry.endsWith('.md') && entry !== 'README.md') {
|
|
105
101
|
// Legacy flat .md skill — check stack filter before copying
|
|
@@ -107,8 +103,8 @@ function installSkillsFromDir(srcDir, destDir, projectTags, installedNow) {
|
|
|
107
103
|
if (!shouldInstallSkill(content, projectTags)) continue;
|
|
108
104
|
const skillName = entry.slice(0, -3);
|
|
109
105
|
const skillDir = join(destDir, skillName);
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
fx.ensureDir(skillDir);
|
|
107
|
+
fx.copyFile(srcPath, join(skillDir, 'SKILL.md'));
|
|
112
108
|
installedNow.add(skillName);
|
|
113
109
|
}
|
|
114
110
|
}
|
|
@@ -150,10 +146,12 @@ function collectNamesFromDir(dir, names) {
|
|
|
150
146
|
* @param {string} projectDir - Target project root directory
|
|
151
147
|
* @param {Object} [options]
|
|
152
148
|
* @param {string[]} [options.projectTags=[]] - Stack tags from config
|
|
149
|
+
* @param {ReturnType<import('../lib/installers/effects.js').createEffects>} [options.fx]
|
|
150
|
+
* Coletor de efeitos; o default executa e registra numa lista descartada.
|
|
153
151
|
*/
|
|
154
|
-
export async function installSkills(projectDir, { projectTags = [] } = {}) {
|
|
152
|
+
export async function installSkills(projectDir, { projectTags = [], fx = realEffects() } = {}) {
|
|
155
153
|
const claudeSkillsDir = join(projectDir, '.claude', 'skills');
|
|
156
|
-
|
|
154
|
+
fx.ensureDir(claudeSkillsDir);
|
|
157
155
|
|
|
158
156
|
// Skill names this call actually (re)installs, given projectTags. Tracked
|
|
159
157
|
// as we go (NOT by re-scanning claudeSkillsDir afterwards) — a disk scan
|
|
@@ -164,7 +162,7 @@ export async function installSkills(projectDir, { projectTags = [] } = {}) {
|
|
|
164
162
|
for (const level of SKILL_LEVELS_TO_INSTALL) {
|
|
165
163
|
const levelDir = join(FRAMEWORK_SKILLS_DIR, level);
|
|
166
164
|
if (!existsSync(levelDir)) continue;
|
|
167
|
-
installSkillsFromDir(levelDir, claudeSkillsDir, projectTags, installedNow);
|
|
165
|
+
installSkillsFromDir(levelDir, claudeSkillsDir, projectTags, installedNow, fx);
|
|
168
166
|
}
|
|
169
167
|
|
|
170
168
|
// Clean-slate: remove any skill dir that is a KNOWN framework skill (still
|
|
@@ -176,10 +174,19 @@ export async function installSkills(projectDir, { projectTags = [] } = {}) {
|
|
|
176
174
|
// `umbraco` in the stack, drop the stack later, and they'd survive every
|
|
177
175
|
// reinstall/update forever. Names outside allSourceSkills (user-added,
|
|
178
176
|
// non-framework skills) are never touched.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
//
|
|
178
|
+
// Em `dryRun` este laço NÃO emite nada, e a razão está na D6: numa execução
|
|
179
|
+
// real do `update` ele sempre vê um diretório VAZIO, porque
|
|
180
|
+
// `cleanFrameworkDirs()` acabou de apagar `.claude/skills/` inteiro. Num dry
|
|
181
|
+
// ingênuo nada foi apagado, ele veria o diretório POPULADO, e o relatório
|
|
182
|
+
// preveria remoções que a execução real nunca faz — errado na direção que
|
|
183
|
+
// assusta o usuário. O clean subsome este laço, provadamente.
|
|
184
|
+
if (!fx.dryRun) {
|
|
185
|
+
const allSourceSkills = collectSourceSkillNames();
|
|
186
|
+
for (const entry of readdirSync(claudeSkillsDir)) {
|
|
187
|
+
if (!installedNow.has(entry) && allSourceSkills.has(entry)) {
|
|
188
|
+
fx.remove(join(claudeSkillsDir, entry));
|
|
189
|
+
}
|
|
183
190
|
}
|
|
184
191
|
}
|
|
185
192
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { readFileSync } from 'fs';
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
|
-
import { pathExists, readJson
|
|
4
|
+
import { pathExists, readJson } from './file-copier.js';
|
|
5
|
+
import { realEffects } from '../lib/installers/effects.js';
|
|
5
6
|
|
|
6
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
8
|
|
|
@@ -58,14 +59,14 @@ export async function getProjectMorphVersion(projectPath) {
|
|
|
58
59
|
* @param {string} version - Version to save
|
|
59
60
|
* @returns {Promise<void>}
|
|
60
61
|
*/
|
|
61
|
-
export async function saveProjectMorphVersion(projectPath, version) {
|
|
62
|
+
export async function saveProjectMorphVersion(projectPath, version, fx = realEffects()) {
|
|
62
63
|
const versionPath = join(projectPath, '.morph', '.morphversion');
|
|
63
64
|
const versionInfo = {
|
|
64
65
|
morphVersion: version,
|
|
65
66
|
installedAt: new Date().toISOString(),
|
|
66
67
|
cliVersion: version
|
|
67
68
|
};
|
|
68
|
-
|
|
69
|
+
fx.writeJson(versionPath, versionInfo);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
/**
|
|
@@ -91,6 +92,70 @@ export function compareVersions(v1, v2) {
|
|
|
91
92
|
return 0;
|
|
92
93
|
}
|
|
93
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Major de uma versão, ou `null` quando a string não permite decidir.
|
|
97
|
+
*
|
|
98
|
+
* Vive aqui, ao lado de `compareVersions`, de propósito: uma segunda
|
|
99
|
+
* aritmética de versão noutro arquivo é exatamente como duas verdades sobre a
|
|
100
|
+
* mesma versão nascem.
|
|
101
|
+
*
|
|
102
|
+
* @param {string|null|undefined} version
|
|
103
|
+
* @returns {number|null}
|
|
104
|
+
*/
|
|
105
|
+
export function majorOf(version) {
|
|
106
|
+
if (typeof version !== 'string') return null;
|
|
107
|
+
const m = /^\s*v?(\d+)\./.exec(version) ?? /^\s*v?(\d+)\s*$/.exec(version);
|
|
108
|
+
return m ? Number(m[1]) : null;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Limiar do freio: um salto de DOIS majors ou mais recusa sem `--force`.
|
|
113
|
+
*
|
|
114
|
+
* O limiar não é "quantos majors dá para pular com segurança" — é "a partir de
|
|
115
|
+
* onde o usuário merece ler o relatório antes". Medido: um projeto em 7.x indo
|
|
116
|
+
* para 9.x recusa; um em 8.x atualiza sem atrito.
|
|
117
|
+
*/
|
|
118
|
+
export const MAJOR_JUMP_LIMIT = 2;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Avalia o salto entre a versão instalada no projeto e a versão de destino.
|
|
122
|
+
*
|
|
123
|
+
* Três resultados, e a diferença entre eles é o que a mensagem tem de dizer:
|
|
124
|
+
*
|
|
125
|
+
* - `reason: 'unknown-installed'` — a versão instalada é indeterminável.
|
|
126
|
+
* Tratada como salto grande, com motivo PRÓPRIO: "não sei quão velho isto é"
|
|
127
|
+
* é precisamente quando sobrescrever às cegas é mais perigoso, e tratá-la
|
|
128
|
+
* como zero seria a única leitura que libera o pior caso.
|
|
129
|
+
* - `reason: 'major-jump'` — salto medido de `jump` majors, >= ao limiar.
|
|
130
|
+
* - `reason: null` — sem freio (mesmo major, um só major de diferença, ou
|
|
131
|
+
* destino não superior à instalada).
|
|
132
|
+
*
|
|
133
|
+
* @param {string|null} installedVersion
|
|
134
|
+
* @param {string|null} targetVersion
|
|
135
|
+
* @param {{ limit?: number }} [options]
|
|
136
|
+
* @returns {{ refuse: boolean, reason: 'unknown-installed'|'major-jump'|null, jump: number|null }}
|
|
137
|
+
*/
|
|
138
|
+
export function majorJump(installedVersion, targetVersion, { limit = MAJOR_JUMP_LIMIT } = {}) {
|
|
139
|
+
const installedMajor = majorOf(installedVersion);
|
|
140
|
+
const targetMajor = majorOf(targetVersion);
|
|
141
|
+
|
|
142
|
+
if (installedMajor === null) {
|
|
143
|
+
return { refuse: true, reason: 'unknown-installed', jump: null };
|
|
144
|
+
}
|
|
145
|
+
if (targetMajor === null) {
|
|
146
|
+
// Sem destino legível não há salto a medir — e inventar um seria decidir
|
|
147
|
+
// por um número que ninguém mantém.
|
|
148
|
+
return { refuse: false, reason: null, jump: null };
|
|
149
|
+
}
|
|
150
|
+
if (compareVersions(installedVersion, targetVersion) >= 0) {
|
|
151
|
+
return { refuse: false, reason: null, jump: 0 };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const jump = targetMajor - installedMajor;
|
|
155
|
+
if (jump >= limit) return { refuse: true, reason: 'major-jump', jump };
|
|
156
|
+
return { refuse: false, reason: null, jump };
|
|
157
|
+
}
|
|
158
|
+
|
|
94
159
|
/**
|
|
95
160
|
* Detect how the CLI was installed
|
|
96
161
|
* @returns {'global'|'npx'|'unknown'}
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# Prompts in DB — Editable, versioned agent instructions
|
|
2
|
-
|
|
3
|
-
> **Scope:** stacks=["dotnet"]
|
|
4
|
-
> **Layer:** 1 (on-keyword)
|
|
5
|
-
> **Keywords:** prompts in db, agent prompts, prompt versioning, prompt hot-reload, editable instructions, prompt management
|
|
6
|
-
> **Read by Claude in:** implement (when an agent's instructions should be runtime-editable)
|
|
7
|
-
|
|
8
|
-
**Verified against:** MAF 1.0 GA. Last-verified: 2026-05-19.
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Por que prompts no DB
|
|
13
|
-
|
|
14
|
-
Hardcoded instructions in source code mean every prompt tweak requires a full build and deploy cycle. With prompts in the database the team can tune agent behavior without touching code, pushing a commit, or triggering CI.
|
|
15
|
-
|
|
16
|
-
Key benefits:
|
|
17
|
-
|
|
18
|
-
| Benefit | Explanation |
|
|
19
|
-
|---------|-------------|
|
|
20
|
-
| **No deploy to tune** | Product/ML team edits prompts via a backoffice or SQL — the agent picks up the change on next load |
|
|
21
|
-
| **Versioning and rollback** | Every version is kept as an immutable row; rollback = flip `is_active` |
|
|
22
|
-
| **A/B testing** | Two rows for the same `agent_key`, activate one; compare metrics; flip back if needed |
|
|
23
|
-
| **Audit trail** | `created_at` per version gives a full timeline of what prompt was active when |
|
|
24
|
-
|
|
25
|
-
The agent receives its `instructions:` value from the repository at construction time — not from a string literal in code.
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## O pattern
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────┐
|
|
33
|
-
│ Agent Handler │──────▶│ PromptRepository │──────▶│ agent_prompts table │
|
|
34
|
-
│ (constructs │ │ (IMemoryCache │ │ (PostgreSQL) │
|
|
35
|
-
│ AIAgent) │◀──────│ TTL: 5 min) │◀──────│ │
|
|
36
|
-
└─────────────────┘ └──────────────────┘ └──────────────────────┘
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
1. The handler (or DI factory) calls `IPromptRepository.GetActivePromptAsync(agentKey, ct)`.
|
|
40
|
-
2. `PromptRepository` checks `IMemoryCache`. On a hit, returns the cached string. On a miss, queries the DB for the single row where `agent_key = @key AND is_active = true`, caches it with a short TTL (e.g. 5 minutes), and returns it.
|
|
41
|
-
3. The handler constructs the agent: `chatClient.AsAIAgent(instructions: prompt, name: "MyAgent")`.
|
|
42
|
-
4. **Agents are cheap to construct** — recreate on every request (or per scoped lifetime). Do NOT attempt to mutate a running agent's instructions.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## Schema da tabela
|
|
47
|
-
|
|
48
|
-
The `agent_prompts` table stores one row per prompt version. See the full migration in:
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
templates/code/sql/ai-agents/agent-prompts-migration.sql.template
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
Column summary:
|
|
55
|
-
|
|
56
|
-
| Column | Type | Notes |
|
|
57
|
-
|--------|------|-------|
|
|
58
|
-
| `id` | `uuid` | PK, `gen_random_uuid()` |
|
|
59
|
-
| `agent_key` | `text NOT NULL` | Logical identifier, e.g. `"proposal-agent"` |
|
|
60
|
-
| `version` | `int NOT NULL` | Monotonically increasing per `agent_key` |
|
|
61
|
-
| `content` | `text NOT NULL` | The full system-prompt text |
|
|
62
|
-
| `is_active` | `boolean NOT NULL DEFAULT false` | Only one row active per `agent_key` |
|
|
63
|
-
| `created_at` | `timestamptz NOT NULL DEFAULT now()` | Immutable audit timestamp |
|
|
64
|
-
|
|
65
|
-
### Unique-partial-index trick
|
|
66
|
-
|
|
67
|
-
```sql
|
|
68
|
-
CREATE UNIQUE INDEX ix_agent_prompts_active
|
|
69
|
-
ON {{SCHEMA}}.agent_prompts (agent_key)
|
|
70
|
-
WHERE is_active;
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
This index enforces at the database level that only one row per `agent_key` can have `is_active = true`. An attempt to activate a second row for the same key will fail with a unique-constraint violation — no application logic needed to guard this invariant.
|
|
74
|
-
|
|
75
|
-
---
|
|
76
|
-
|
|
77
|
-
## O repository
|
|
78
|
-
|
|
79
|
-
Reference implementation:
|
|
80
|
-
|
|
81
|
-
```
|
|
82
|
-
templates/code/dotnet/ai-agents/PromptRepository.cs.template
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
Interface contract:
|
|
86
|
-
|
|
87
|
-
```csharp
|
|
88
|
-
public interface IPromptRepository
|
|
89
|
-
{
|
|
90
|
-
/// <summary>
|
|
91
|
-
/// Returns the content of the currently active prompt for the given agent key.
|
|
92
|
-
/// Throws <see cref="InvalidOperationException"/> if no active prompt exists.
|
|
93
|
-
/// </summary>
|
|
94
|
-
Task<string> GetActivePromptAsync(string agentKey, CancellationToken ct = default);
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
The concrete `PromptRepository`:
|
|
99
|
-
|
|
100
|
-
- Receives `DbContext` and `IMemoryCache` via primary constructor.
|
|
101
|
-
- Uses a cache key of `$"agent_prompt:{agentKey}"` with a sliding expiration of 5 minutes.
|
|
102
|
-
- On a cache miss, queries `AgentPrompts.Where(p => p.AgentKey == agentKey && p.IsActive)` via EF Core.
|
|
103
|
-
- Throws `InvalidOperationException($"No active prompt found for agent '{agentKey}'.")` when no row matches — fail fast so misconfiguration is immediately visible.
|
|
104
|
-
- Does NOT catch DB exceptions — let the caller decide how to handle transient failures.
|
|
105
|
-
|
|
106
|
-
DI registration (in `Program.cs` or a bootstrap extension):
|
|
107
|
-
|
|
108
|
-
```csharp
|
|
109
|
-
builder.Services.AddMemoryCache();
|
|
110
|
-
builder.Services.AddScoped<IPromptRepository, PromptRepository>();
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
---
|
|
114
|
-
|
|
115
|
-
## Integração com o agente
|
|
116
|
-
|
|
117
|
-
### Scoped construction (recommended)
|
|
118
|
-
|
|
119
|
-
```csharp
|
|
120
|
-
public sealed class ProposalAgentHandler(
|
|
121
|
-
IPromptRepository promptRepo,
|
|
122
|
-
ModelRegistry modelRegistry)
|
|
123
|
-
{
|
|
124
|
-
public async Task<string> RunAsync(string userMessage, CancellationToken ct)
|
|
125
|
-
{
|
|
126
|
-
var prompt = await promptRepo.GetActivePromptAsync("proposal-agent", ct);
|
|
127
|
-
|
|
128
|
-
var agent = modelRegistry
|
|
129
|
-
.GetChatClient("text-default")
|
|
130
|
-
.AsAIAgent(instructions: prompt, name: "ProposalAgent");
|
|
131
|
-
|
|
132
|
-
var response = await agent.RunAsync(userMessage, ct);
|
|
133
|
-
return response.Text;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### Hot-reload semantics
|
|
139
|
-
|
|
140
|
-
When a new prompt is saved with `is_active = true`:
|
|
141
|
-
|
|
142
|
-
1. The old active row is set to `is_active = false` (see §Versionamento).
|
|
143
|
-
2. The cache entry expires within the configured TTL (5 min by default).
|
|
144
|
-
3. The next agent construction after expiry picks up the new prompt automatically.
|
|
145
|
-
|
|
146
|
-
**Do NOT mutate a running agent.** Agents are lightweight value-like objects in MAF — recreate on each scoped lifetime instead of attempting to patch `instructions` on a live instance.
|
|
147
|
-
|
|
148
|
-
### Keyed singleton (long-lived, explicit refresh)
|
|
149
|
-
|
|
150
|
-
For agents registered as keyed singletons (e.g. via `builder.AddAIAgent`), hot-reload requires explicit invalidation. In that pattern, call `IMemoryCache.Remove($"agent_prompt:{key}")` from an admin endpoint or background job after saving a new version, then recreate the agent in the factory. This is more complex; prefer scoped construction unless the agent carries expensive state (e.g. an embedded tool pipeline that is costly to rebuild).
|
|
151
|
-
|
|
152
|
-
---
|
|
153
|
-
|
|
154
|
-
## Versionamento
|
|
155
|
-
|
|
156
|
-
**Rule: never UPDATE the `content` column.** The content of a prompt row is immutable once written.
|
|
157
|
-
|
|
158
|
-
To release a new prompt version:
|
|
159
|
-
|
|
160
|
-
```sql
|
|
161
|
-
-- 1. Deactivate the current active version
|
|
162
|
-
UPDATE {{SCHEMA}}.agent_prompts
|
|
163
|
-
SET is_active = false
|
|
164
|
-
WHERE agent_key = 'proposal-agent' AND is_active = true;
|
|
165
|
-
|
|
166
|
-
-- 2. Insert the new version (version = previous max + 1)
|
|
167
|
-
INSERT INTO {{SCHEMA}}.agent_prompts (agent_key, version, content, is_active)
|
|
168
|
-
VALUES (
|
|
169
|
-
'proposal-agent',
|
|
170
|
-
(SELECT COALESCE(MAX(version), 0) + 1 FROM {{SCHEMA}}.agent_prompts WHERE agent_key = 'proposal-agent'),
|
|
171
|
-
'Your new system prompt text here...',
|
|
172
|
-
true
|
|
173
|
-
);
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
This keeps a complete, auditable history. Rollback = deactivate the current row and reactivate any previous row.
|
|
177
|
-
|
|
178
|
-
---
|
|
179
|
-
|
|
180
|
-
## Anti-patterns
|
|
181
|
-
|
|
182
|
-
| Anti-pattern | Por quê é problema | Solução |
|
|
183
|
-
|---|---|---|
|
|
184
|
-
| Hardcoded prompt string in production code | Every tune requires a build + deploy | Move to `agent_prompts` table |
|
|
185
|
-
| `UPDATE agent_prompts SET content = '...'` without a version | Destroys history; no rollback | INSERT new row with version+1, flip `is_active` |
|
|
186
|
-
| No cache — DB query on every agent invocation | High latency + DB load under traffic | Use `IMemoryCache` with a short TTL |
|
|
187
|
-
| `appsettings.json` prompts | Still requires a deploy to change; no versioning | Move to DB or environment-variable-agnostic store |
|
|
188
|
-
| Single global `IMemoryCache` key collisions | Two agents with similar keys overwrite each other | Always prefix: `$"agent_prompt:{agentKey}"` |
|
|
189
|
-
| Activating a new row without deactivating the old one | Two active rows for the same key — undefined behavior | The partial unique index prevents this at DB level; always deactivate first in app code |
|
|
190
|
-
|
|
191
|
-
---
|
|
192
|
-
|
|
193
|
-
## Checklist (verifiable by morph-eval)
|
|
194
|
-
|
|
195
|
-
- [ ] `agent_prompts` table exists with all required columns (`id`, `agent_key`, `version`, `content`, `is_active`, `created_at`)
|
|
196
|
-
- [ ] Unique partial index `ix_agent_prompts_active` on `(agent_key) WHERE is_active` is present
|
|
197
|
-
- [ ] `IPromptRepository` registered in DI (`AddScoped`)
|
|
198
|
-
- [ ] `IMemoryCache` registered (`builder.Services.AddMemoryCache()`)
|
|
199
|
-
- [ ] No prompt string literals in production agent construction code
|
|
200
|
-
- [ ] `GetActivePromptAsync` throws `InvalidOperationException` (not returns null) when no active prompt found
|
|
201
|
-
- [ ] Versionamento pattern followed: INSERT + flip, never UPDATE content
|
|
202
|
-
- [ ] At least one seed row per `agent_key` in migrations or seed script
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
## References
|
|
207
|
-
|
|
208
|
-
- `ai-agents-setup` — how to construct an `AIAgent` via `AsAIAgent(instructions:)`
|
|
209
|
-
- `ai-agents-providers-model-registry` — how to obtain the `IChatClient` from the Model Registry
|
|
210
|
-
- `backend-database-postgresql-database` — naming conventions, timestamptz, uuid PKs, partial indexes
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
*MORPH-SPEC by Polymorphism Tech — ai-agents/prompts-in-db.md v1.0 (2026-05-19)*
|