@polymorphism-tech/morph-spec 8.34.2 → 8.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +20 -3
- 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 +234 -9
- 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/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/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 +140 -5
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +8 -1
- 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 +333 -21
- 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/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,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-checks.js — o coletor das cinco checagens de `morph-spec doctor --ai`.
|
|
3
|
+
*
|
|
4
|
+
* Contrato de uma checagem:
|
|
5
|
+
*
|
|
6
|
+
* { id, name, status: 'ok'|'warn'|'error'|'skip', reason?, findings: [], unverified?: [] }
|
|
7
|
+
*
|
|
8
|
+
* Duas regras que este módulo existe para tornar mecânicas:
|
|
9
|
+
*
|
|
10
|
+
* 1. **`status: 'skip'` sai com `reason` obrigatória.** "Não verificado" sem
|
|
11
|
+
* motivo é ruído — quem lê não consegue decidir se falta insumo ou se falta
|
|
12
|
+
* checagem. Um `skip` sem razão é bug deste arquivo, e há canário.
|
|
13
|
+
* 2. **`null` de uma função de checagem é DESCONHECIDO, nunca "não achei
|
|
14
|
+
* nada".** `[]` é "perguntei e não achei" e vira `ok`. É a doutrina de
|
|
15
|
+
* `pr-index.js`, já viva no `doctor`.
|
|
16
|
+
*
|
|
17
|
+
* E uma proibição: **nada alcançável daqui escreve.** Os candidatos óbvios ao
|
|
18
|
+
* erro — `scripts/generate-ai-pin-props.js` e
|
|
19
|
+
* `scripts/generate-standards-registry.js` — escrevem por padrão e NÃO entram
|
|
20
|
+
* no grafo de imports deste módulo. É a lição do T10 da #90, onde a auditoria
|
|
21
|
+
* ia ALOCAR um bloco de porta ao auditar, inventando o estado que reportava.
|
|
22
|
+
* O canário assere a lista de chamadas feitas, não só o retorno.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { scanAiPackageRefs } from './ai-project-scan.js';
|
|
26
|
+
import { resolveAiPin, auditPinDrift } from './pin-drift.js';
|
|
27
|
+
import { locateAiRegistry, auditModelPricing, auditApiPerAlias } from './registry-audit.js';
|
|
28
|
+
import { auditRenamedApi, auditPromptSource, renamedApiGate } from './code-audit.js';
|
|
29
|
+
|
|
30
|
+
/** Os cinco ids, na ordem em que o relatório os imprime. */
|
|
31
|
+
export const AI_CHECK_IDS = ['pin-drift', 'removed-api', 'pricing-missing', 'api-per-alias', 'prompt-source'];
|
|
32
|
+
|
|
33
|
+
/** Nome legível de cada checagem. */
|
|
34
|
+
export const AI_CHECK_NAMES = {
|
|
35
|
+
'pin-drift': 'pin × projeto (versões de pacote de IA)',
|
|
36
|
+
'removed-api': 'API renomeada antes do 1.0 GA',
|
|
37
|
+
'pricing-missing': 'tarifa por alias do registry de modelos',
|
|
38
|
+
'api-per-alias': 'campo `api` por alias do registry de modelos',
|
|
39
|
+
'prompt-source': 'fonte declarada das instruções do agente',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** O pacote cuja versão gateia a checagem de API renomeada. */
|
|
43
|
+
export const MAF_PACKAGE = 'Microsoft.Agents.AI';
|
|
44
|
+
|
|
45
|
+
/** As dependências reais. Substituíveis inteiras ou em parte, por canário. */
|
|
46
|
+
export function defaultAiDeps() {
|
|
47
|
+
return {
|
|
48
|
+
scanAiPackageRefs,
|
|
49
|
+
resolveAiPin,
|
|
50
|
+
auditPinDrift,
|
|
51
|
+
renamedApiGate,
|
|
52
|
+
auditRenamedApi,
|
|
53
|
+
locateAiRegistry,
|
|
54
|
+
auditModelPricing,
|
|
55
|
+
auditApiPerAlias,
|
|
56
|
+
auditPromptSource,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function statusFrom(findings) {
|
|
61
|
+
if (findings.some((f) => f.severity === 'error')) return 'error';
|
|
62
|
+
if (findings.some((f) => f.severity === 'warn')) return 'warn';
|
|
63
|
+
return 'ok';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function skip(id, reason, extra = {}) {
|
|
67
|
+
return { id, name: AI_CHECK_NAMES[id], status: 'skip', reason, findings: [], ...extra };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function resolved(id, findings, extra = {}) {
|
|
71
|
+
return { id, name: AI_CHECK_NAMES[id], status: statusFrom(findings), findings, ...extra };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function skipAll(reason) {
|
|
75
|
+
return AI_CHECK_IDS.map((id) => skip(id, reason));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Roda as cinco checagens sobre um projeto.
|
|
80
|
+
*
|
|
81
|
+
* As três condições de silêncio total acontecem ANTES de qualquer leitura do
|
|
82
|
+
* pin — e é essa ordem, não o retorno, que o canário assere: sem `.csproj`
|
|
83
|
+
* alcançável, `resolveAiPin` não é sequer chamada.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} targetPath - raiz do projeto
|
|
86
|
+
* @param {object} [options]
|
|
87
|
+
* @param {object|null} [options.config] - `.morph/config/config.json` já lido
|
|
88
|
+
* @param {object} [options.deps] - substituição parcial de `defaultAiDeps()`
|
|
89
|
+
* @returns {Array<{id: string, name: string, status: string, reason?: string, findings: Array}>}
|
|
90
|
+
*/
|
|
91
|
+
export function collectAiChecks(targetPath, { config = null, deps = {} } = {}) {
|
|
92
|
+
const d = { ...defaultAiDeps(), ...deps };
|
|
93
|
+
|
|
94
|
+
const scan = d.scanAiPackageRefs(targetPath);
|
|
95
|
+
|
|
96
|
+
// Condição 4 do silêncio: UNKNOWN nunca vira "sem drift".
|
|
97
|
+
if (scan === null) {
|
|
98
|
+
return skipAll('nenhum arquivo de projeto .NET foi legível a partir desta raiz — '
|
|
99
|
+
+ 'não verificado, o que NÃO é o mesmo que "sem drift"');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Condição 1: sem `.csproj` alcançável. O pin nem é lido.
|
|
103
|
+
if (scan.projectFiles.length === 0) {
|
|
104
|
+
return skipAll('nenhum `.csproj` alcançável a partir desta raiz — não há projeto .NET a julgar');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Condição 3: `.csproj` presentes, nenhum pacote de IA entre eles.
|
|
108
|
+
const aiPackages = scan.packages.filter((p) => p.ai);
|
|
109
|
+
if (aiPackages.length === 0) {
|
|
110
|
+
return skipAll(`${scan.projectFiles.length} projeto(s) .NET varrido(s) e nenhum pacote de IA `
|
|
111
|
+
+ 'declarado entre eles — um projeto .NET sem MAF não é um projeto MAF quebrado');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const unverified = scan.unreadable.slice();
|
|
115
|
+
const checks = [];
|
|
116
|
+
|
|
117
|
+
// ── pin-drift ────────────────────────────────────────────────────────────
|
|
118
|
+
// Condição 2: sem pin resolvível, a checagem que depende dele sai skip.
|
|
119
|
+
const pin = d.resolveAiPin(targetPath);
|
|
120
|
+
if (!pin) {
|
|
121
|
+
checks.push(skip('pin-drift', 'nenhum `ai-pin.json` resolvível (nem em `.morph/framework/`, nem '
|
|
122
|
+
+ 'no pacote da CLI) — falta de régua é ausência de dado, nunca defeito do projeto', { unverified }));
|
|
123
|
+
} else {
|
|
124
|
+
const findings = d.auditPinDrift({ scan, pin });
|
|
125
|
+
checks.push(findings === null
|
|
126
|
+
? skip('pin-drift', 'a comparação com o pin não pôde ser feita', { unverified })
|
|
127
|
+
: resolved('pin-drift', findings, { unverified }));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── removed-api ──────────────────────────────────────────────────────────
|
|
131
|
+
const maf = scan.packages.find((p) => p.name === MAF_PACKAGE) ?? null;
|
|
132
|
+
const gate = d.renamedApiGate(maf);
|
|
133
|
+
if (gate !== null) {
|
|
134
|
+
checks.push(skip('removed-api', gate));
|
|
135
|
+
} else {
|
|
136
|
+
const findings = d.auditRenamedApi(targetPath, { mafPackage: maf });
|
|
137
|
+
checks.push(findings === null
|
|
138
|
+
? skip('removed-api', 'a varredura de `.cs` não pôde ser feita')
|
|
139
|
+
: resolved('removed-api', findings));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── pricing-missing e api-per-alias ──────────────────────────────────────
|
|
143
|
+
const registry = d.locateAiRegistry(targetPath, config);
|
|
144
|
+
const noRegistry = 'nenhum registry de modelos encontrado — nem `project.ai.modelRegistry` no '
|
|
145
|
+
+ 'config, nem um `.json` com `defaultAlias` + mapa de aliases, nem uma seção `Pricing` num '
|
|
146
|
+
+ '`appsettings*.json`';
|
|
147
|
+
|
|
148
|
+
const pricing = registry ? d.auditModelPricing(registry) : null;
|
|
149
|
+
checks.push(pricing === null ? skip('pricing-missing', noRegistry) : resolved('pricing-missing', pricing));
|
|
150
|
+
|
|
151
|
+
const perAlias = registry ? d.auditApiPerAlias(registry) : null;
|
|
152
|
+
checks.push(perAlias === null
|
|
153
|
+
? skip('api-per-alias', registry
|
|
154
|
+
? 'o registry encontrado não declara um mapa de aliases — não há alias sobre o qual perguntar'
|
|
155
|
+
: noRegistry)
|
|
156
|
+
: resolved('api-per-alias', perAlias));
|
|
157
|
+
|
|
158
|
+
// ── prompt-source ────────────────────────────────────────────────────────
|
|
159
|
+
const prompts = d.auditPromptSource(targetPath);
|
|
160
|
+
checks.push(prompts === null
|
|
161
|
+
? skip('prompt-source', 'nenhum `.cs` alcançável a partir desta raiz')
|
|
162
|
+
: resolved('prompt-source', prompts));
|
|
163
|
+
|
|
164
|
+
return checks;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Código de saída do `doctor --ai`: 1 quando há ao menos um `error`, 0 caso
|
|
169
|
+
* contrário. `warn` e `skip` NUNCA mudam o código de saída — só `pin-drift`
|
|
170
|
+
* produz `error`, e só na faixa flutuante, a única classe com prejuízo medido.
|
|
171
|
+
*
|
|
172
|
+
* @param {Array<{status: string}>} checks
|
|
173
|
+
* @returns {0|1}
|
|
174
|
+
*/
|
|
175
|
+
export function aiExitCode(checks) {
|
|
176
|
+
return checks.some((c) => c.status === 'error') ? 1 : 0;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** true quando NENHUMA checagem teve insumo — o relatório sai em uma linha. */
|
|
180
|
+
export function allSkipped(checks) {
|
|
181
|
+
return checks.length > 0 && checks.every((c) => c.status === 'skip');
|
|
182
|
+
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ai-project-scan.js — varredura READ-ONLY dos `.csproj` / `Directory.Packages.props`
|
|
3
|
+
* de um projeto .NET, para responder "o que este projeto declara sobre pacotes de IA?".
|
|
4
|
+
*
|
|
5
|
+
* Três invariantes deste módulo, todas cobradas por canário:
|
|
6
|
+
*
|
|
7
|
+
* 1. **Nada aqui escreve.** Nenhum import de `fs` que mute, nenhum `spawn`.
|
|
8
|
+
* A auditoria não pode inventar o estado que reporta — é a lição do T10 da
|
|
9
|
+
* #90, onde a auditoria de portas ia ALOCAR um bloco ao auditar.
|
|
10
|
+
* 2. **`null` é DESCONHECIDO, `[]` é "perguntei e não achei".** Colapsar os
|
|
11
|
+
* dois é a doutrina que `src/lib/git/pr-index.js` já pinou neste repositório.
|
|
12
|
+
* Aqui: `scanAiPackageRefs` devolve `null` quando havia arquivos candidatos
|
|
13
|
+
* e NENHUM foi legível; devolve um resultado com `projectFiles: []` quando
|
|
14
|
+
* varreu e não achou `.csproj` nenhum.
|
|
15
|
+
* 3. **Worktree é o checkout de OUTRA feature.** `worktrees/` e `.worktrees/`
|
|
16
|
+
* ficam fora da varredura pela mesma razão pinada em `test-runner.js`:
|
|
17
|
+
* varrê-los reporta sobre código fora da árvore avaliada. Os nomes vêm de
|
|
18
|
+
* `src/lib/worktree/dirs.js`, nunca duplicados aqui.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readdirSync, readFileSync, statSync, existsSync } from 'node:fs';
|
|
22
|
+
import { join, basename } from 'node:path';
|
|
23
|
+
import { WORKTREE_DIR_NAMES } from '../worktree/dirs.js';
|
|
24
|
+
|
|
25
|
+
/** Diretórios que a varredura nunca desce. */
|
|
26
|
+
export const SCAN_IGNORED_DIRS = new Set([
|
|
27
|
+
'node_modules',
|
|
28
|
+
'bin',
|
|
29
|
+
'obj',
|
|
30
|
+
'.git',
|
|
31
|
+
...WORKTREE_DIR_NAMES,
|
|
32
|
+
]);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Prefixos de pacote NuGet que caracterizam "pacote de IA".
|
|
36
|
+
*
|
|
37
|
+
* Deliberadamente curto: a lista existe para decidir se há assunto (condição 3
|
|
38
|
+
* do silêncio do `doctor --ai`), não para catalogar o ecossistema. Um pacote de
|
|
39
|
+
* IA fora desta lista aparece na varredura como pacote comum e, se estiver no
|
|
40
|
+
* pin, `pin-drift` o pega mesmo assim — o pin é a outra metade do predicado.
|
|
41
|
+
*/
|
|
42
|
+
export const AI_PACKAGE_PREFIXES = [
|
|
43
|
+
'Microsoft.Agents.AI',
|
|
44
|
+
'Microsoft.Extensions.AI',
|
|
45
|
+
'Microsoft.SemanticKernel',
|
|
46
|
+
'OpenAI',
|
|
47
|
+
'Azure.AI.',
|
|
48
|
+
'Google.GenAI',
|
|
49
|
+
'ModelContextProtocol',
|
|
50
|
+
'Anthropic.',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @param {string} name - nome do pacote NuGet
|
|
55
|
+
* @returns {boolean} true quando o nome casa com um dos prefixos de IA
|
|
56
|
+
*/
|
|
57
|
+
export function isAiPackage(name) {
|
|
58
|
+
if (typeof name !== 'string' || name.length === 0) return false;
|
|
59
|
+
return AI_PACKAGE_PREFIXES.some(
|
|
60
|
+
(p) => name === p || name.startsWith(p.endsWith('.') ? p : `${p}.`)
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Remove o BOM UTF-8 do início do texto (mesmo cuidado de `doctor.js` e `env.js`). */
|
|
65
|
+
function stripBom(text) {
|
|
66
|
+
return text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Checagem de boa-formação de XML suficiente para MSBuild, sem dependência nova.
|
|
71
|
+
*
|
|
72
|
+
* Não valida schema: valida que as tags fecham na ordem certa e que todo `<`
|
|
73
|
+
* tem o seu `>`. É o predicado que separa "li o arquivo" de "não pude
|
|
74
|
+
* perguntar" — e é ele que faz um `.csproj` truncado virar `unreadable` em vez
|
|
75
|
+
* de virar "sem drift".
|
|
76
|
+
*
|
|
77
|
+
* @param {string} xml
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
export function isWellFormedXml(xml) {
|
|
81
|
+
const stack = [];
|
|
82
|
+
let i = 0;
|
|
83
|
+
while (i < xml.length) {
|
|
84
|
+
const lt = xml.indexOf('<', i);
|
|
85
|
+
if (lt === -1) break;
|
|
86
|
+
|
|
87
|
+
if (xml.startsWith('<!--', lt)) {
|
|
88
|
+
const end = xml.indexOf('-->', lt + 4);
|
|
89
|
+
if (end === -1) return false;
|
|
90
|
+
i = end + 3;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (xml.startsWith('<![CDATA[', lt)) {
|
|
94
|
+
const end = xml.indexOf(']]>', lt + 9);
|
|
95
|
+
if (end === -1) return false;
|
|
96
|
+
i = end + 3;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (xml.startsWith('<?', lt) || xml.startsWith('<!', lt)) {
|
|
100
|
+
const end = xml.indexOf('>', lt);
|
|
101
|
+
if (end === -1) return false;
|
|
102
|
+
i = end + 1;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const gt = xml.indexOf('>', lt);
|
|
107
|
+
if (gt === -1) return false;
|
|
108
|
+
const raw = xml.slice(lt + 1, gt).trim();
|
|
109
|
+
if (raw.length === 0) return false;
|
|
110
|
+
|
|
111
|
+
if (raw.startsWith('/')) {
|
|
112
|
+
const name = raw.slice(1).trim();
|
|
113
|
+
if (stack.pop() !== name) return false;
|
|
114
|
+
} else if (!raw.endsWith('/')) {
|
|
115
|
+
const name = raw.split(/[\s/>]/)[0];
|
|
116
|
+
if (!name) return false;
|
|
117
|
+
stack.push(name);
|
|
118
|
+
}
|
|
119
|
+
i = gt + 1;
|
|
120
|
+
}
|
|
121
|
+
return stack.length === 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Todas as tags de um nome, com os atributos crus. */
|
|
125
|
+
function* elements(xml, tagName) {
|
|
126
|
+
const re = new RegExp(`<${tagName}\\b([^>]*?)(/?)>`, 'g');
|
|
127
|
+
let m;
|
|
128
|
+
while ((m = re.exec(xml)) !== null) {
|
|
129
|
+
yield { attrs: m[1], selfClosing: m[2] === '/', index: m.index, end: re.lastIndex };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Valor de um atributo dentro do texto cru de atributos de uma tag. */
|
|
134
|
+
function attr(attrs, name) {
|
|
135
|
+
const m = new RegExp(`\\b${name}\\s*=\\s*"([^"]*)"`, 'i').exec(attrs)
|
|
136
|
+
|| new RegExp(`\\b${name}\\s*=\\s*'([^']*)'`, 'i').exec(attrs);
|
|
137
|
+
return m ? m[1].trim() : null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Elemento filho `<Version>x</Version>` de um `<PackageReference>` não
|
|
142
|
+
* auto-fechado. Recebe o XML e o índice logo após a tag de abertura.
|
|
143
|
+
*/
|
|
144
|
+
function childVersion(xml, startIndex, tagName) {
|
|
145
|
+
const close = xml.indexOf(`</${tagName}>`, startIndex);
|
|
146
|
+
if (close === -1) return null;
|
|
147
|
+
const inner = xml.slice(startIndex, close);
|
|
148
|
+
const m = /<Version\s*>([^<]*)<\/Version\s*>/i.exec(inner);
|
|
149
|
+
return m ? m[1].trim() : null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Classifica a string de versão declarada no projeto.
|
|
154
|
+
*
|
|
155
|
+
* - `fixed` — `1.20.0` ou a forma de igualdade exata `[1.20.0]`
|
|
156
|
+
* - `floating` — contém `*`, ou é intervalo (`[1.0,)`, `[1.0,2.0)`, `(,2.0]`)
|
|
157
|
+
* - `unresolved` — referência a propriedade MSBuild (`$(MafVersion)`): a
|
|
158
|
+
* varredura NÃO resolve propriedades, e chutar seria inventar o dado
|
|
159
|
+
* - `absent` — sem versão declarada e sem entrada de CPM
|
|
160
|
+
*
|
|
161
|
+
* @param {string|null} raw
|
|
162
|
+
* @returns {{ kind: 'fixed'|'floating'|'unresolved'|'absent', value: string|null }}
|
|
163
|
+
*/
|
|
164
|
+
export function classifyVersion(raw) {
|
|
165
|
+
if (raw === null || raw === undefined || String(raw).trim() === '') {
|
|
166
|
+
return { kind: 'absent', value: null };
|
|
167
|
+
}
|
|
168
|
+
const value = String(raw).trim();
|
|
169
|
+
if (value.includes('$(')) return { kind: 'unresolved', value };
|
|
170
|
+
if (value.includes('*')) return { kind: 'floating', value };
|
|
171
|
+
if (/^\[\s*[^,\[\]()]+\s*\]$/.test(value)) {
|
|
172
|
+
return { kind: 'fixed', value: value.slice(1, -1).trim() };
|
|
173
|
+
}
|
|
174
|
+
if (/[[\](),]/.test(value)) return { kind: 'floating', value };
|
|
175
|
+
return { kind: 'fixed', value };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Coleta recursiva de arquivos que casam com um predicado, respeitando as
|
|
180
|
+
* exclusões de `SCAN_IGNORED_DIRS`.
|
|
181
|
+
*
|
|
182
|
+
* Exportada porque `registry-audit.js` e `code-audit.js` precisam da MESMA
|
|
183
|
+
* travessia com as MESMAS exclusões. Uma segunda cópia da lista de diretórios
|
|
184
|
+
* ignorados é como um dos três volta a varrer `worktrees/` sozinho.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} root
|
|
187
|
+
* @param {(name: string, fullPath: string) => boolean} matches
|
|
188
|
+
* @returns {string[]}
|
|
189
|
+
*/
|
|
190
|
+
export function collectProjectFiles(root, matches) {
|
|
191
|
+
const out = [];
|
|
192
|
+
if (!root || !existsSync(root)) return out;
|
|
193
|
+
collectFiles(root, matches, out);
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Coleta recursiva de arquivos que casam com um predicado, respeitando as exclusões. */
|
|
198
|
+
function collectFiles(root, matches, out, depth = 0) {
|
|
199
|
+
if (depth > 24) return;
|
|
200
|
+
let entries;
|
|
201
|
+
try {
|
|
202
|
+
entries = readdirSync(root, { withFileTypes: true });
|
|
203
|
+
} catch {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
for (const entry of entries) {
|
|
207
|
+
const name = entry.name;
|
|
208
|
+
const full = join(root, name);
|
|
209
|
+
let isDir = entry.isDirectory();
|
|
210
|
+
if (entry.isSymbolicLink()) {
|
|
211
|
+
// Junction/symlink: pode apontar para fora da árvore (é assim que um
|
|
212
|
+
// worktree do Windows enxerga `.claude/`). Não descemos.
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
if (isDir) {
|
|
216
|
+
if (SCAN_IGNORED_DIRS.has(name)) continue;
|
|
217
|
+
collectFiles(full, matches, out, depth + 1);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (matches(name, full)) out.push(full);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Varre `root` e devolve o que os projetos .NET declaram sobre pacotes.
|
|
226
|
+
*
|
|
227
|
+
* Sem limite de 10 arquivos (o de `scanStackDependencies`, que é do relatório
|
|
228
|
+
* de arquitetura e tem outro consumidor): uma solução .NET real tem dezenas de
|
|
229
|
+
* `.csproj`, e cortar em 10 é reportar sobre uma amostra chamando-a de todo.
|
|
230
|
+
*
|
|
231
|
+
* @param {string} root - raiz da varredura (o projeto)
|
|
232
|
+
* @returns {null | {
|
|
233
|
+
* projectFiles: string[],
|
|
234
|
+
* cpmFiles: string[],
|
|
235
|
+
* unreadable: string[],
|
|
236
|
+
* packages: Array<{ name: string, file: string, ai: boolean,
|
|
237
|
+
* version: string|null, versionKind: string,
|
|
238
|
+
* declared: string|null, fromCpm: boolean }>
|
|
239
|
+
* }} `null` = não pude perguntar (havia candidatos e nenhum era legível).
|
|
240
|
+
*/
|
|
241
|
+
export function scanAiPackageRefs(root) {
|
|
242
|
+
if (!root || !existsSync(root)) return null;
|
|
243
|
+
try {
|
|
244
|
+
if (!statSync(root).isDirectory()) return null;
|
|
245
|
+
} catch {
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const projectFiles = [];
|
|
250
|
+
const cpmFiles = [];
|
|
251
|
+
collectFiles(root, (n) => n.toLowerCase().endsWith('.csproj'), projectFiles);
|
|
252
|
+
collectFiles(root, (n) => n.toLowerCase() === 'directory.packages.props', cpmFiles);
|
|
253
|
+
|
|
254
|
+
const candidates = [...cpmFiles, ...projectFiles];
|
|
255
|
+
if (candidates.length === 0) {
|
|
256
|
+
// Perguntei e não achei — NÃO é desconhecido.
|
|
257
|
+
return { projectFiles: [], cpmFiles: [], unreadable: [], packages: [] };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const unreadable = [];
|
|
261
|
+
const readable = new Map();
|
|
262
|
+
for (const file of candidates) {
|
|
263
|
+
let xml;
|
|
264
|
+
try {
|
|
265
|
+
xml = stripBom(readFileSync(file, 'utf8'));
|
|
266
|
+
} catch {
|
|
267
|
+
unreadable.push(file);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (!isWellFormedXml(xml)) {
|
|
271
|
+
unreadable.push(file);
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
readable.set(file, xml);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (readable.size === 0) {
|
|
278
|
+
// Havia o que perguntar e nenhuma resposta chegou: DESCONHECIDO.
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Central Package Management primeiro: um PackageReference sem Version num
|
|
283
|
+
// projeto com CPM não é achado de versão ausente (UC-24 A1).
|
|
284
|
+
const cpm = new Map();
|
|
285
|
+
for (const file of cpmFiles) {
|
|
286
|
+
const xml = readable.get(file);
|
|
287
|
+
if (!xml) continue;
|
|
288
|
+
for (const el of elements(xml, 'PackageVersion')) {
|
|
289
|
+
const name = attr(el.attrs, 'Include') ?? attr(el.attrs, 'Update');
|
|
290
|
+
if (!name) continue;
|
|
291
|
+
const version = attr(el.attrs, 'Version')
|
|
292
|
+
?? (el.selfClosing ? null : childVersion(xml, el.end, 'PackageVersion'));
|
|
293
|
+
cpm.set(name, { version, file });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const packages = [];
|
|
298
|
+
for (const file of projectFiles) {
|
|
299
|
+
const xml = readable.get(file);
|
|
300
|
+
if (!xml) continue;
|
|
301
|
+
for (const el of elements(xml, 'PackageReference')) {
|
|
302
|
+
const name = attr(el.attrs, 'Include') ?? attr(el.attrs, 'Update');
|
|
303
|
+
if (!name) continue;
|
|
304
|
+
const inline = attr(el.attrs, 'Version')
|
|
305
|
+
?? (el.selfClosing ? null : childVersion(xml, el.end, 'PackageReference'));
|
|
306
|
+
const central = cpm.get(name);
|
|
307
|
+
const fromCpm = inline === null && central !== undefined;
|
|
308
|
+
const declared = inline !== null ? inline : (fromCpm ? central.version : null);
|
|
309
|
+
const { kind, value } = classifyVersion(declared);
|
|
310
|
+
packages.push({
|
|
311
|
+
name,
|
|
312
|
+
file,
|
|
313
|
+
ai: isAiPackage(name),
|
|
314
|
+
version: value,
|
|
315
|
+
versionKind: kind,
|
|
316
|
+
declared,
|
|
317
|
+
fromCpm,
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Um Directory.Packages.props com PackageVersion de pacote de IA e nenhum
|
|
323
|
+
// .csproj que o referencie ainda declara a intenção — mas não é uso, e este
|
|
324
|
+
// módulo reporta declaração de uso. Fica de fora, de propósito.
|
|
325
|
+
|
|
326
|
+
return { projectFiles, cpmFiles, unreadable, packages };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Versão declarada de um pacote específico, ou `null` quando o projeto não a
|
|
331
|
+
* declara de forma resolvível. Usado pelo gate do `removed-api` (só roda com
|
|
332
|
+
* `Microsoft.Agents.AI` >= 1.0) e pelo relatório.
|
|
333
|
+
*
|
|
334
|
+
* @param {ReturnType<typeof scanAiPackageRefs>} scan
|
|
335
|
+
* @param {string} name
|
|
336
|
+
* @returns {{ name: string, version: string|null, versionKind: string, declared: string|null, file: string } | null}
|
|
337
|
+
*/
|
|
338
|
+
export function findPackage(scan, name) {
|
|
339
|
+
if (!scan || !Array.isArray(scan.packages)) return null;
|
|
340
|
+
return scan.packages.find((p) => p.name === name) ?? null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Nomes de arquivo (sem diretório) dos projetos varridos — para mensagens. */
|
|
344
|
+
export function projectFileNames(scan) {
|
|
345
|
+
if (!scan) return [];
|
|
346
|
+
return scan.projectFiles.map((f) => basename(f));
|
|
347
|
+
}
|