@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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registry-audit.js — o registry de modelos do projeto: tarifa por alias e
|
|
3
|
+
* protocolo (`api`) por alias.
|
|
4
|
+
*
|
|
5
|
+
* As duas perguntas saem do standard `ai-agents/providers/model-registry.md`,
|
|
6
|
+
* não da memória de quem implementa:
|
|
7
|
+
*
|
|
8
|
+
* - **tarifa por alias** — um alias sem tarifa é um custo que ninguém
|
|
9
|
+
* consegue prever antes de a chamada ser paga. O caso medido no acervo é o
|
|
10
|
+
* GHLBrain, cujo mapa `Pricing.Models` é literalmente `{}`.
|
|
11
|
+
* - **`api` por alias** — o standard mede, sobre o pin, que
|
|
12
|
+
* `AgentApi.Responses = 0`, ou seja `default(AgentApi)` é Responses, e que
|
|
13
|
+
* `ModelRegistry.ReadApi` devolve `AgentApi.Responses` quando o campo está
|
|
14
|
+
* ausente. A ausência do campo, portanto, NÃO é defeito: é uma decisão
|
|
15
|
+
* tomada por omissão. O achado NOMEIA o default assumido e pede
|
|
16
|
+
* confirmação — nunca afirma defeito.
|
|
17
|
+
*
|
|
18
|
+
* Severidade: `warn` nas duas. Só `pin-drift` produz `error`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readFileSync } from 'node:fs';
|
|
22
|
+
import { existsSync } from 'node:fs';
|
|
23
|
+
import { isAbsolute, join, basename } from 'node:path';
|
|
24
|
+
import { collectProjectFiles } from './ai-project-scan.js';
|
|
25
|
+
import { jsoncToJson } from './comment-stripper.js';
|
|
26
|
+
|
|
27
|
+
/** Limite de tamanho para um JSON candidato (bytes de texto) — evita ler dumps. */
|
|
28
|
+
const MAX_JSON_CHARS = 512 * 1024;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Lê um candidato a registry aceitando **JSONC**.
|
|
32
|
+
*
|
|
33
|
+
* Não é indulgência: é paridade com o leitor de produção. O exemplar canônico
|
|
34
|
+
* `framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json`
|
|
35
|
+
* — que diz na primeira linha "copie para `config/model-registry.json` no
|
|
36
|
+
* projeto" — tem 20 linhas de `//`, e `ModelRegistry.FromJson` do kit declara
|
|
37
|
+
* `CommentHandling = JsonCommentHandling.Skip` + `AllowTrailingCommas = true`.
|
|
38
|
+
* Com `JSON.parse` cru, as duas checagens de registry saíam `skip` sobre a
|
|
39
|
+
* ÚNICA forma que existem para auditar — e `skip` não muda o código de saída,
|
|
40
|
+
* então o silêncio era total. A auditoria tem de enxergar a mesma forma que o
|
|
41
|
+
* leitor de produção enxerga.
|
|
42
|
+
*/
|
|
43
|
+
function readJsonSafe(file) {
|
|
44
|
+
try {
|
|
45
|
+
const raw = readFileSync(file, 'utf8');
|
|
46
|
+
if (raw.length > MAX_JSON_CHARS) return null;
|
|
47
|
+
const text = raw.charCodeAt(0) === 0xfeff ? raw.slice(1) : raw;
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(text);
|
|
50
|
+
} catch {
|
|
51
|
+
return JSON.parse(jsoncToJson(text));
|
|
52
|
+
}
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isPlainObject(value) {
|
|
59
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** A forma que `ai-agents/providers/model-registry.md` define. */
|
|
63
|
+
function asAliasSource(file, data) {
|
|
64
|
+
if (!isPlainObject(data)) return null;
|
|
65
|
+
if (typeof data.defaultAlias !== 'string') return null;
|
|
66
|
+
if (!isPlainObject(data.aliases)) return null;
|
|
67
|
+
return { file, aliases: data.aliases, defaultAlias: data.defaultAlias, data };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** A forma medida no GHLBrain: a seção `Pricing` de um `appsettings*.json`. */
|
|
71
|
+
function asPricingSource(file, data) {
|
|
72
|
+
if (!isPlainObject(data)) return null;
|
|
73
|
+
const pricing = data.Pricing ?? data.pricing;
|
|
74
|
+
if (!isPlainObject(pricing)) return null;
|
|
75
|
+
const models = pricing.Models ?? pricing.models;
|
|
76
|
+
if (!isPlainObject(models)) return null;
|
|
77
|
+
return { file, models };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Localiza o registry de modelos do projeto.
|
|
82
|
+
*
|
|
83
|
+
* Ordem, primeira ocorrência ganha (spec §3.5):
|
|
84
|
+
* 1. path declarado em `.morph/config/config.json → project.ai.modelRegistry`;
|
|
85
|
+
* 2. um `*.json` cujo objeto raiz tenha `defaultAlias` **e** um mapa `aliases`;
|
|
86
|
+
* 3. a seção `Pricing` de um `appsettings*.json`.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} root - raiz do projeto
|
|
89
|
+
* @param {object|null} config - conteúdo de `.morph/config/config.json`, se houver
|
|
90
|
+
* @returns {null | { aliasSource: object|null, pricingSource: object|null }}
|
|
91
|
+
* `null` quando NENHUMA das três formas foi encontrada.
|
|
92
|
+
*/
|
|
93
|
+
export function locateAiRegistry(root, config = null) {
|
|
94
|
+
let aliasSource = null;
|
|
95
|
+
let pricingSource = null;
|
|
96
|
+
|
|
97
|
+
const declared = config?.project?.ai?.modelRegistry;
|
|
98
|
+
if (typeof declared === 'string' && declared.trim()) {
|
|
99
|
+
const file = isAbsolute(declared) ? declared : join(root, declared);
|
|
100
|
+
if (existsSync(file)) {
|
|
101
|
+
const data = readJsonSafe(file);
|
|
102
|
+
aliasSource = asAliasSource(file, data);
|
|
103
|
+
pricingSource = extractPricingFromRegistry(file, data) ?? pricingSource;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!aliasSource) {
|
|
108
|
+
const candidates = collectProjectFiles(root, (name) => name.toLowerCase().endsWith('.json'));
|
|
109
|
+
for (const file of candidates) {
|
|
110
|
+
const data = readJsonSafe(file);
|
|
111
|
+
const found = asAliasSource(file, data);
|
|
112
|
+
if (found) {
|
|
113
|
+
aliasSource = found;
|
|
114
|
+
pricingSource = pricingSource ?? extractPricingFromRegistry(file, data);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (!pricingSource) {
|
|
121
|
+
const appsettings = collectProjectFiles(root, (name) => {
|
|
122
|
+
const lower = name.toLowerCase();
|
|
123
|
+
return lower.startsWith('appsettings') && lower.endsWith('.json');
|
|
124
|
+
});
|
|
125
|
+
for (const file of appsettings) {
|
|
126
|
+
const found = asPricingSource(file, readJsonSafe(file));
|
|
127
|
+
if (found) { pricingSource = found; break; }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!aliasSource && !pricingSource) return null;
|
|
132
|
+
return { aliasSource, pricingSource };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Mapa de tarifas embutido no próprio registry de modelos, quando existir. */
|
|
136
|
+
function extractPricingFromRegistry(file, data) {
|
|
137
|
+
if (!isPlainObject(data)) return null;
|
|
138
|
+
const pricing = data.pricing ?? data.Pricing;
|
|
139
|
+
if (isPlainObject(pricing)) {
|
|
140
|
+
const models = pricing.Models ?? pricing.models;
|
|
141
|
+
return { file, models: isPlainObject(models) ? models : pricing };
|
|
142
|
+
}
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** O alias declara tarifa **no próprio elemento** (a forma v2 do exemplar canônico)? */
|
|
147
|
+
function aliasHasPricing(def) {
|
|
148
|
+
return isPlainObject(def) && isPlainObject(def.pricing);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** O alias declara a AUSÊNCIA de tarifa por escrito? */
|
|
152
|
+
function aliasHasWaiver(def) {
|
|
153
|
+
return isPlainObject(def) && typeof def.pricingWaiver === 'string' && def.pricingWaiver.trim() !== '';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* O documento usa a forma **por alias** (`pricing` / `pricingWaiver` dentro de
|
|
158
|
+
* cada alias) em vez da forma de mapa no topo?
|
|
159
|
+
*
|
|
160
|
+
* Basta UM alias declarar qualquer uma das duas: um documento meio-preenchido
|
|
161
|
+
* ainda é um documento na forma v2, e os aliases que faltam é que são o achado.
|
|
162
|
+
*/
|
|
163
|
+
export function usesPerAliasPricing(aliasSource) {
|
|
164
|
+
if (!aliasSource) return false;
|
|
165
|
+
return Object.values(aliasSource.aliases).some((d) => aliasHasPricing(d) || aliasHasWaiver(d));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Todo alias do registry tem uma DECISÃO de tarifa?
|
|
170
|
+
*
|
|
171
|
+
* Duas formas, e a checagem cobra a que o documento usa:
|
|
172
|
+
*
|
|
173
|
+
* 1. **Por alias** (`model-registry.v2.json`, o exemplar que o framework manda
|
|
174
|
+
* copiar) — a REGRA DURA escrita no próprio exemplar e imposta por
|
|
175
|
+
* `ModelRegistry.ReadPricing`: *"todo alias declara `pricing` OU
|
|
176
|
+
* `pricingWaiver` — nunca os dois, nunca nenhum"*. Um `pricingWaiver` é uma
|
|
177
|
+
* tarifa **decidida**: a ausência está escrita, não esquecida, e acusá-la
|
|
178
|
+
* seria gritar em código correto.
|
|
179
|
+
* 2. **Mapa no topo** (`pricing` na raiz, ou a seção `Pricing.Models` de um
|
|
180
|
+
* `appsettings*.json` — a forma medida no GHLBrain, cujo mapa é
|
|
181
|
+
* literalmente `{}`).
|
|
182
|
+
*
|
|
183
|
+
* Os achados de MAPA (`pricing-map-missing` / `pricing-map-empty`) só existem
|
|
184
|
+
* na forma 2: num documento v2 não há mapa a faltar, e emitir o achado ali era
|
|
185
|
+
* o primeiro dos seis falsos positivos sobre o exemplar canônico.
|
|
186
|
+
*
|
|
187
|
+
* @param {ReturnType<typeof locateAiRegistry>} registry
|
|
188
|
+
* @returns {null | Array<{ id: string, severity: 'warn', alias?: string, message: string }>}
|
|
189
|
+
*/
|
|
190
|
+
export function auditModelPricing(registry) {
|
|
191
|
+
if (!registry) return null;
|
|
192
|
+
const { aliasSource, pricingSource } = registry;
|
|
193
|
+
const findings = [];
|
|
194
|
+
|
|
195
|
+
const models = pricingSource?.models ?? null;
|
|
196
|
+
const tariffKeys = models ? new Set(Object.keys(models)) : new Set();
|
|
197
|
+
const perAlias = usesPerAliasPricing(aliasSource);
|
|
198
|
+
|
|
199
|
+
if (!perAlias) {
|
|
200
|
+
if (models === null) {
|
|
201
|
+
findings.push({
|
|
202
|
+
id: 'pricing-map-missing',
|
|
203
|
+
severity: 'warn',
|
|
204
|
+
message: 'Nenhuma tarifa encontrada: nem `pricing`/`pricingWaiver` por alias (a forma do '
|
|
205
|
+
+ 'exemplar `model-registry.v2.json`), nem `pricing` na raiz, nem `Pricing.Models` num '
|
|
206
|
+
+ 'appsettings. Sem tarifa não há previsão de custo antes de a chamada ser paga.',
|
|
207
|
+
});
|
|
208
|
+
} else if (tariffKeys.size === 0) {
|
|
209
|
+
findings.push({
|
|
210
|
+
id: 'pricing-map-empty',
|
|
211
|
+
severity: 'warn',
|
|
212
|
+
message: `O mapa de tarifas em \`${basename(pricingSource.file)}\` está vazio — declarado e `
|
|
213
|
+
+ 'sem uma única entrada. É a forma mais silenciosa de não ter tarifa: o campo existe, o '
|
|
214
|
+
+ 'dado não.',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (aliasSource) {
|
|
220
|
+
for (const [alias, def] of Object.entries(aliasSource.aliases)) {
|
|
221
|
+
const model = isPlainObject(def) ? def.model : null;
|
|
222
|
+
const rotulo = `\`${alias}\`${model ? ` (modelo \`${model}\`)` : ''}`;
|
|
223
|
+
const hasPricing = aliasHasPricing(def);
|
|
224
|
+
const hasWaiver = aliasHasWaiver(def);
|
|
225
|
+
|
|
226
|
+
if (hasPricing && hasWaiver) {
|
|
227
|
+
findings.push({
|
|
228
|
+
id: 'alias-pricing-and-waiver',
|
|
229
|
+
severity: 'warn',
|
|
230
|
+
alias,
|
|
231
|
+
message: `O alias ${rotulo} declara \`pricing\` E \`pricingWaiver\`. A regra do exemplar `
|
|
232
|
+
+ 'é uma OU a outra, nunca as duas: com as duas presentes não dá para saber se a '
|
|
233
|
+
+ 'tarifa vale ou se a isenção vale.',
|
|
234
|
+
});
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (hasPricing || hasWaiver) continue; // decisão tomada — inclusive a de não haver tarifa
|
|
238
|
+
|
|
239
|
+
const cobertoPeloMapa = tariffKeys.has(alias) || (model !== null && tariffKeys.has(model));
|
|
240
|
+
if (cobertoPeloMapa) continue;
|
|
241
|
+
|
|
242
|
+
findings.push({
|
|
243
|
+
id: 'alias-without-pricing',
|
|
244
|
+
severity: 'warn',
|
|
245
|
+
alias,
|
|
246
|
+
message: `O alias ${rotulo} não declara \`pricing\` nem \`pricingWaiver\`, e não aparece em `
|
|
247
|
+
+ 'nenhum mapa de tarifas. Não é a tarifa que falta: falta a DECISÃO — um `pricingWaiver` '
|
|
248
|
+
+ 'com o motivo por extenso também fecha a questão.',
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return findings;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Todo alias declara `api`?
|
|
258
|
+
*
|
|
259
|
+
* O default do standard é **`responses`**: medido sobre o pin, `AgentApi.Responses = 0`
|
|
260
|
+
* e `ModelRegistry.ReadApi` devolve Responses quando o campo está ausente. A mensagem
|
|
261
|
+
* nomeia esse default e PEDE CONFIRMAÇÃO — a ausência do campo é uma decisão por
|
|
262
|
+
* omissão, não um defeito. É a pergunta que o `ModelRegistry.cs` do ProspectPRO
|
|
263
|
+
* documenta ter custado um bug.
|
|
264
|
+
*
|
|
265
|
+
* @param {ReturnType<typeof locateAiRegistry>} registry
|
|
266
|
+
* @returns {null | Array<{ id: string, severity: 'warn', alias: string, message: string }>}
|
|
267
|
+
* `null` quando não há aliases para perguntar.
|
|
268
|
+
*/
|
|
269
|
+
export function auditApiPerAlias(registry) {
|
|
270
|
+
if (!registry || !registry.aliasSource) return null;
|
|
271
|
+
const findings = [];
|
|
272
|
+
for (const [alias, def] of Object.entries(registry.aliasSource.aliases)) {
|
|
273
|
+
const api = isPlainObject(def) ? def.api : undefined;
|
|
274
|
+
if (typeof api === 'string' && api.trim()) continue;
|
|
275
|
+
findings.push({
|
|
276
|
+
id: 'alias-without-api',
|
|
277
|
+
severity: 'warn',
|
|
278
|
+
alias,
|
|
279
|
+
message: `O alias \`${alias}\` não declara o campo \`api\`. O registry trata a omissão como `
|
|
280
|
+
+ '`responses` (Responses API) — confirme que este alias não precisa falar `chat` '
|
|
281
|
+
+ '(Chat Completions). Não é acusação: é a decisão que está sendo tomada por omissão.',
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return findings;
|
|
285
|
+
}
|
|
@@ -1,188 +1,188 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Design System Detector
|
|
3
|
-
*
|
|
4
|
-
* Detects if a design system exists for the project or feature.
|
|
5
|
-
* Checks multiple locations in priority order.
|
|
6
|
-
*
|
|
7
|
-
* @module design-system-detector
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
|
|
13
|
-
const SKIP_DIRS = new Set(['node_modules', '.git', '.morph', 'dist', 'build', '.next', 'obj', 'bin', '.vs']);
|
|
14
|
-
const CSS_SCAN_MAX_DEPTH = 6;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Recursively walk a directory collecting files with given extensions.
|
|
18
|
-
* @param {string} dir - Directory to walk
|
|
19
|
-
* @param {string[]} extensions - File extensions to collect (e.g. ['.css', '.scss'])
|
|
20
|
-
* @param {string[]} files - Accumulator array (mutated in-place)
|
|
21
|
-
* @param {number} depth - Current recursion depth
|
|
22
|
-
* @param {number} maxDepth - Maximum recursion depth
|
|
23
|
-
*/
|
|
24
|
-
function walkDirForCss(dir, extensions, files, depth, maxDepth) {
|
|
25
|
-
if (depth > maxDepth) return;
|
|
26
|
-
let entries;
|
|
27
|
-
try {
|
|
28
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
29
|
-
} catch {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
for (const entry of entries) {
|
|
33
|
-
if (entry.isDirectory()) {
|
|
34
|
-
if (!SKIP_DIRS.has(entry.name)) {
|
|
35
|
-
walkDirForCss(join(dir, entry.name), extensions, files, depth + 1, maxDepth);
|
|
36
|
-
}
|
|
37
|
-
} else if (entry.isFile()) {
|
|
38
|
-
const lower = entry.name.toLowerCase();
|
|
39
|
-
if (extensions.some(ext => lower.endsWith(ext))) {
|
|
40
|
-
files.push(join(dir, entry.name));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Detect design system for project or feature
|
|
48
|
-
* @param {string} projectPath - Root path of the project
|
|
49
|
-
* @param {string|null} featureName - Feature name (optional, checks feature-level if provided)
|
|
50
|
-
* @returns {Object} { hasDesignSystem, path, isProjectLevel, isFeatureLevel, source }
|
|
51
|
-
*/
|
|
52
|
-
export function detectDesignSystem(projectPath, featureName = null) {
|
|
53
|
-
const result = {
|
|
54
|
-
hasDesignSystem: false,
|
|
55
|
-
path: null,
|
|
56
|
-
isProjectLevel: false,
|
|
57
|
-
isFeatureLevel: false,
|
|
58
|
-
source: null
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Priority 1: Feature-level design system (if feature specified)
|
|
62
|
-
if (featureName) {
|
|
63
|
-
const featureDesignSystemPath = join(
|
|
64
|
-
projectPath,
|
|
65
|
-
'.morph/features',
|
|
66
|
-
featureName,
|
|
67
|
-
'1-design',
|
|
68
|
-
'design-system.md'
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
if (existsSync(featureDesignSystemPath)) {
|
|
72
|
-
return {
|
|
73
|
-
hasDesignSystem: true,
|
|
74
|
-
path: featureDesignSystemPath,
|
|
75
|
-
isProjectLevel: false,
|
|
76
|
-
isFeatureLevel: true,
|
|
77
|
-
source: 'feature-output'
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Priority 2: Project-level design system (markdown)
|
|
83
|
-
const projectDesignSystemPath = join(projectPath, '.morph/context/design-system.md');
|
|
84
|
-
if (existsSync(projectDesignSystemPath)) {
|
|
85
|
-
return {
|
|
86
|
-
hasDesignSystem: true,
|
|
87
|
-
path: projectDesignSystemPath,
|
|
88
|
-
isProjectLevel: true,
|
|
89
|
-
isFeatureLevel: false,
|
|
90
|
-
source: 'project-markdown'
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Priority 3: Generated CSS design system
|
|
95
|
-
const cssDesignSystemPath = join(projectPath, 'wwwroot/css/design-system.css');
|
|
96
|
-
if (existsSync(cssDesignSystemPath)) {
|
|
97
|
-
return {
|
|
98
|
-
hasDesignSystem: true,
|
|
99
|
-
path: cssDesignSystemPath,
|
|
100
|
-
isProjectLevel: true,
|
|
101
|
-
isFeatureLevel: false,
|
|
102
|
-
source: 'generated-css'
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Priority 4: Recursively scan frontend root directories for CSS/SCSS with design tokens
|
|
107
|
-
const frontendRoots = ['wwwroot', 'styles', 'src/styles', 'src', 'public'];
|
|
108
|
-
const extensions = ['.css', '.scss'];
|
|
109
|
-
const collected = [];
|
|
110
|
-
|
|
111
|
-
for (const root of frontendRoots) {
|
|
112
|
-
const rootPath = join(projectPath, root);
|
|
113
|
-
if (existsSync(rootPath)) {
|
|
114
|
-
walkDirForCss(rootPath, extensions, collected, 0, CSS_SCAN_MAX_DEPTH);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Deduplicate by resolved path
|
|
119
|
-
const seen = new Set();
|
|
120
|
-
const cssFiles = [];
|
|
121
|
-
for (const file of collected) {
|
|
122
|
-
if (!seen.has(file)) {
|
|
123
|
-
seen.add(file);
|
|
124
|
-
cssFiles.push(file);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Sort for deterministic first-match across platforms
|
|
129
|
-
cssFiles.sort();
|
|
130
|
-
|
|
131
|
-
for (const cssFile of cssFiles) {
|
|
132
|
-
try {
|
|
133
|
-
const content = readFileSync(cssFile, 'utf8');
|
|
134
|
-
const hasDesignTokens = /:root\s*\{[\s\S]*?--/.test(content);
|
|
135
|
-
if (hasDesignTokens) {
|
|
136
|
-
return {
|
|
137
|
-
hasDesignSystem: true,
|
|
138
|
-
path: cssFile,
|
|
139
|
-
isProjectLevel: true,
|
|
140
|
-
isFeatureLevel: false,
|
|
141
|
-
source: 'existing-css-variables'
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
} catch {
|
|
145
|
-
// Continue checking other files
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// No design system found
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Get recommended design system locations based on project type
|
|
155
|
-
* @param {string} projectPath - Root path of the project
|
|
156
|
-
* @returns {Object} { markdown, css, description }
|
|
157
|
-
*/
|
|
158
|
-
export function getRecommendedLocations(projectPath) {
|
|
159
|
-
try {
|
|
160
|
-
const configPath = join(projectPath, '.morph/config/config.json');
|
|
161
|
-
|
|
162
|
-
let projectType = 'unknown';
|
|
163
|
-
if (existsSync(configPath)) {
|
|
164
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
165
|
-
projectType = config.project?.type || 'unknown';
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Default locations
|
|
169
|
-
const locations = {
|
|
170
|
-
markdown: '.morph/context/design-system.md',
|
|
171
|
-
css: 'public/design-system.css',
|
|
172
|
-
description: 'Project-level design system (shared across features)'
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// Adjust based on project type
|
|
176
|
-
if (projectType === 'nextjs' || projectType.includes('nextjs')) {
|
|
177
|
-
locations.css = 'styles/design-system.css';
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return locations;
|
|
181
|
-
} catch {
|
|
182
|
-
return {
|
|
183
|
-
markdown: '.morph/context/design-system.md',
|
|
184
|
-
css: 'wwwroot/css/design-system.css',
|
|
185
|
-
description: 'Project-level design system (shared across features)'
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Design System Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects if a design system exists for the project or feature.
|
|
5
|
+
* Checks multiple locations in priority order.
|
|
6
|
+
*
|
|
7
|
+
* @module design-system-detector
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', '.morph', 'dist', 'build', '.next', 'obj', 'bin', '.vs']);
|
|
14
|
+
const CSS_SCAN_MAX_DEPTH = 6;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Recursively walk a directory collecting files with given extensions.
|
|
18
|
+
* @param {string} dir - Directory to walk
|
|
19
|
+
* @param {string[]} extensions - File extensions to collect (e.g. ['.css', '.scss'])
|
|
20
|
+
* @param {string[]} files - Accumulator array (mutated in-place)
|
|
21
|
+
* @param {number} depth - Current recursion depth
|
|
22
|
+
* @param {number} maxDepth - Maximum recursion depth
|
|
23
|
+
*/
|
|
24
|
+
function walkDirForCss(dir, extensions, files, depth, maxDepth) {
|
|
25
|
+
if (depth > maxDepth) return;
|
|
26
|
+
let entries;
|
|
27
|
+
try {
|
|
28
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
29
|
+
} catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
if (entry.isDirectory()) {
|
|
34
|
+
if (!SKIP_DIRS.has(entry.name)) {
|
|
35
|
+
walkDirForCss(join(dir, entry.name), extensions, files, depth + 1, maxDepth);
|
|
36
|
+
}
|
|
37
|
+
} else if (entry.isFile()) {
|
|
38
|
+
const lower = entry.name.toLowerCase();
|
|
39
|
+
if (extensions.some(ext => lower.endsWith(ext))) {
|
|
40
|
+
files.push(join(dir, entry.name));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Detect design system for project or feature
|
|
48
|
+
* @param {string} projectPath - Root path of the project
|
|
49
|
+
* @param {string|null} featureName - Feature name (optional, checks feature-level if provided)
|
|
50
|
+
* @returns {Object} { hasDesignSystem, path, isProjectLevel, isFeatureLevel, source }
|
|
51
|
+
*/
|
|
52
|
+
export function detectDesignSystem(projectPath, featureName = null) {
|
|
53
|
+
const result = {
|
|
54
|
+
hasDesignSystem: false,
|
|
55
|
+
path: null,
|
|
56
|
+
isProjectLevel: false,
|
|
57
|
+
isFeatureLevel: false,
|
|
58
|
+
source: null
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Priority 1: Feature-level design system (if feature specified)
|
|
62
|
+
if (featureName) {
|
|
63
|
+
const featureDesignSystemPath = join(
|
|
64
|
+
projectPath,
|
|
65
|
+
'.morph/features',
|
|
66
|
+
featureName,
|
|
67
|
+
'1-design',
|
|
68
|
+
'design-system.md'
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (existsSync(featureDesignSystemPath)) {
|
|
72
|
+
return {
|
|
73
|
+
hasDesignSystem: true,
|
|
74
|
+
path: featureDesignSystemPath,
|
|
75
|
+
isProjectLevel: false,
|
|
76
|
+
isFeatureLevel: true,
|
|
77
|
+
source: 'feature-output'
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Priority 2: Project-level design system (markdown)
|
|
83
|
+
const projectDesignSystemPath = join(projectPath, '.morph/context/design-system.md');
|
|
84
|
+
if (existsSync(projectDesignSystemPath)) {
|
|
85
|
+
return {
|
|
86
|
+
hasDesignSystem: true,
|
|
87
|
+
path: projectDesignSystemPath,
|
|
88
|
+
isProjectLevel: true,
|
|
89
|
+
isFeatureLevel: false,
|
|
90
|
+
source: 'project-markdown'
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Priority 3: Generated CSS design system
|
|
95
|
+
const cssDesignSystemPath = join(projectPath, 'wwwroot/css/design-system.css');
|
|
96
|
+
if (existsSync(cssDesignSystemPath)) {
|
|
97
|
+
return {
|
|
98
|
+
hasDesignSystem: true,
|
|
99
|
+
path: cssDesignSystemPath,
|
|
100
|
+
isProjectLevel: true,
|
|
101
|
+
isFeatureLevel: false,
|
|
102
|
+
source: 'generated-css'
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Priority 4: Recursively scan frontend root directories for CSS/SCSS with design tokens
|
|
107
|
+
const frontendRoots = ['wwwroot', 'styles', 'src/styles', 'src', 'public'];
|
|
108
|
+
const extensions = ['.css', '.scss'];
|
|
109
|
+
const collected = [];
|
|
110
|
+
|
|
111
|
+
for (const root of frontendRoots) {
|
|
112
|
+
const rootPath = join(projectPath, root);
|
|
113
|
+
if (existsSync(rootPath)) {
|
|
114
|
+
walkDirForCss(rootPath, extensions, collected, 0, CSS_SCAN_MAX_DEPTH);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Deduplicate by resolved path
|
|
119
|
+
const seen = new Set();
|
|
120
|
+
const cssFiles = [];
|
|
121
|
+
for (const file of collected) {
|
|
122
|
+
if (!seen.has(file)) {
|
|
123
|
+
seen.add(file);
|
|
124
|
+
cssFiles.push(file);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Sort for deterministic first-match across platforms
|
|
129
|
+
cssFiles.sort();
|
|
130
|
+
|
|
131
|
+
for (const cssFile of cssFiles) {
|
|
132
|
+
try {
|
|
133
|
+
const content = readFileSync(cssFile, 'utf8');
|
|
134
|
+
const hasDesignTokens = /:root\s*\{[\s\S]*?--/.test(content);
|
|
135
|
+
if (hasDesignTokens) {
|
|
136
|
+
return {
|
|
137
|
+
hasDesignSystem: true,
|
|
138
|
+
path: cssFile,
|
|
139
|
+
isProjectLevel: true,
|
|
140
|
+
isFeatureLevel: false,
|
|
141
|
+
source: 'existing-css-variables'
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
// Continue checking other files
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// No design system found
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get recommended design system locations based on project type
|
|
155
|
+
* @param {string} projectPath - Root path of the project
|
|
156
|
+
* @returns {Object} { markdown, css, description }
|
|
157
|
+
*/
|
|
158
|
+
export function getRecommendedLocations(projectPath) {
|
|
159
|
+
try {
|
|
160
|
+
const configPath = join(projectPath, '.morph/config/config.json');
|
|
161
|
+
|
|
162
|
+
let projectType = 'unknown';
|
|
163
|
+
if (existsSync(configPath)) {
|
|
164
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
165
|
+
projectType = config.project?.type || 'unknown';
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Default locations
|
|
169
|
+
const locations = {
|
|
170
|
+
markdown: '.morph/context/design-system.md',
|
|
171
|
+
css: 'public/design-system.css',
|
|
172
|
+
description: 'Project-level design system (shared across features)'
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
// Adjust based on project type
|
|
176
|
+
if (projectType === 'nextjs' || projectType.includes('nextjs')) {
|
|
177
|
+
locations.css = 'styles/design-system.css';
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return locations;
|
|
181
|
+
} catch {
|
|
182
|
+
return {
|
|
183
|
+
markdown: '.morph/context/design-system.md',
|
|
184
|
+
css: 'wwwroot/css/design-system.css',
|
|
185
|
+
description: 'Project-level design system (shared across features)'
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|