@polymorphism-tech/morph-spec 8.37.1 → 8.40.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 +37 -13
- package/README.md +2 -2
- package/bin/morph-spec.js +75 -49
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +6 -5
- package/framework/MORPH.md +25 -22
- package/framework/agents.json +96 -36
- package/framework/docs/CLI.md +54 -17
- package/framework/evals/rubrics/code-quality.md +5 -0
- package/framework/evals/rubrics/condensed.md +6 -2
- package/framework/evals/rubrics/contracts.md +20 -0
- package/framework/evals/rubrics/test-coverage.md +17 -6
- package/framework/hooks/README.md +9 -3
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +52 -0
- package/framework/hooks/claude-code/core/telemetry-log.js +172 -87
- package/framework/hooks/claude-code/core/validate-completion.js +16 -90
- package/framework/hooks/claude-code/core/worktree-context.js +10 -0
- package/framework/hooks/shared/command-tokens.js +96 -0
- package/framework/hooks/shared/git-exclude.js +44 -0
- package/framework/hooks/shared/git-paths.js +52 -0
- package/framework/hooks/shared/graph-freshness.js +380 -0
- package/framework/hooks/shared/graph-refresh.js +416 -0
- package/framework/hooks/shared/morph-cli.js +17 -5
- package/framework/hooks/shared/redact-secrets.js +55 -0
- package/framework/hooks/shared/telemetry-logger.js +25 -2
- package/framework/hooks/shared/transcript-usage.js +80 -0
- package/framework/mcp/registry.json +33 -2
- package/framework/rubrics/backend.json +2 -2
- package/framework/rubrics/default.json +2 -2
- package/framework/rubrics/frontend.json +2 -2
- package/framework/rules/csharp-standards.md +1 -0
- package/framework/rules/infrastructure-standards.md +0 -1
- package/framework/rules/morph-workflow.md +10 -4
- package/framework/rules/nextjs-standards.md +1 -0
- package/framework/schemas/config.schema.json +41 -1
- package/framework/schemas/feature.schema.json +5 -1
- 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 +40 -11
- package/framework/skills/core/morph-brainstorming/SKILL.md +16 -7
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +44 -10
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +73 -15
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +51 -13
- 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 +39 -13
- package/framework/standards/STANDARDS.json +189 -14
- package/framework/standards/ai-agents/context-providers.md +16 -26
- package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
- package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
- package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
- package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
- package/framework/standards/ai-agents/modalities-vision.md +1 -18
- package/framework/standards/ai-agents/prompt-sources.md +0 -4
- package/framework/standards/ai-agents/testing-ai.md +2 -1
- package/framework/standards/architecture/code-comments.md +100 -0
- package/framework/standards/backend/dotnet/testing.md +22 -0
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
- package/framework/standards/frontend/admin-app/data-table.md +562 -0
- package/framework/standards/frontend/admin-app/feedback.md +236 -0
- package/framework/standards/frontend/admin-app/forms.md +232 -0
- package/framework/standards/frontend/admin-app/page-structure.md +203 -0
- package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
- package/framework/standards/frontend/nextjs/components.md +37 -58
- package/framework/standards/frontend/nextjs/data-fetching.md +1 -0
- package/framework/standards/frontend/nextjs/forms.md +31 -30
- package/framework/standards/frontend/nextjs/state-management.md +2 -1
- package/framework/standards/frontend/nextjs/testing.md +18 -0
- package/framework/standards/integration/mcp/mcp-tools.md +48 -117
- package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
- package/framework/templates/code/dotnet/database/migration.cs +49 -83
- package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
- package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
- package/framework/templates/code/dotnet/jobs/job.cs +2 -97
- package/framework/templates/code/dotnet/test.cs +2 -87
- package/framework/templates/code/typescript/contracts.ts +140 -167
- package/framework/templates/context/design-system.md +3 -3
- package/framework/templates/docs/proposal.md +11 -0
- package/framework/templates/docs/spec.md +15 -0
- package/framework/templates/docs/ui-mockups.md +115 -98
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
- package/framework/templates/feature/decisions.md +124 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
- package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
- package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
- package/framework/templates/integrations/asaas-client.cs +332 -387
- package/framework/templates/integrations/asaas-webhook.cs +290 -351
- package/framework/templates/integrations/neon-auth-config.cs +9 -105
- package/framework/templates/project/validate.js +97 -120
- package/package.json +2 -2
- package/src/commands/dashboard/dashboard.js +80 -53
- package/src/commands/graph/affected.js +4 -3
- package/src/commands/graph/build.js +109 -18
- package/src/commands/graph/explain.js +8 -5
- package/src/commands/graph/status.js +14 -8
- package/src/commands/mcp/mcp-setup.js +37 -9
- package/src/commands/observability/cost.js +148 -18
- package/src/commands/project/doctor.js +85 -15
- package/src/commands/project/init.js +6 -3
- package/src/commands/project/install-plugin-cmd.js +1 -1
- package/src/commands/project/update.js +30 -0
- package/src/commands/project/worktree.js +73 -90
- package/src/commands/state/advance.js +18 -0
- package/src/commands/state/approve.js +15 -0
- package/src/commands/state/archive.js +62 -9
- package/src/commands/state/finish.js +62 -14
- package/src/commands/state/gate-check.js +8 -1
- package/src/commands/state/gate-decision.js +8 -4
- package/src/commands/state/report-check.js +90 -9
- package/src/commands/state/usecase.js +66 -0
- package/src/commands/tasks/dag.js +17 -2
- package/src/commands/validation/validate-feature.js +6 -1
- package/src/commands/validation/verify.js +20 -1
- package/src/dashboard/app/App.jsx +80 -16
- package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
- package/src/dashboard/app/components/CommandPalette.jsx +155 -0
- package/src/dashboard/app/components/DetailPanel.jsx +47 -11
- package/src/dashboard/app/components/EntityLink.jsx +55 -0
- package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
- package/src/dashboard/app/components/FlowGraph.jsx +19 -17
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
- package/src/dashboard/app/components/Markdown.jsx +47 -0
- package/src/dashboard/app/components/PhaseRail.jsx +111 -0
- package/src/dashboard/app/components/SearchFilter.jsx +40 -22
- package/src/dashboard/app/components/Sidebar.jsx +146 -37
- package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
- package/src/dashboard/app/hooks/usePref.js +38 -0
- package/src/dashboard/app/lib/format.js +69 -0
- package/src/dashboard/app/lib/phases.js +56 -0
- package/src/dashboard/app/lib/project.js +50 -0
- package/src/dashboard/app/pages/Activity.jsx +24 -0
- package/src/dashboard/app/pages/AgentMap.jsx +371 -370
- package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
- package/src/dashboard/app/pages/Features.jsx +400 -0
- package/src/dashboard/app/pages/Guide.jsx +146 -0
- package/src/dashboard/app/pages/HookInspector.jsx +357 -322
- package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
- package/src/dashboard/app/pages/Portfolio.jsx +156 -0
- package/src/dashboard/app/pages/Rubrics.jsx +101 -0
- package/src/dashboard/app/pages/RulesMap.jsx +300 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
- package/src/dashboard/app/pages/Standards.jsx +407 -332
- package/src/dashboard/app/styles/index.css +187 -47
- package/src/dashboard/index.html +14 -12
- package/src/dashboard/package.json +32 -24
- package/src/dashboard/scanner/activity-scanner.js +351 -0
- package/src/dashboard/scanner/agents-scanner.js +63 -1
- package/src/dashboard/scanner/docs-scanner.js +59 -0
- package/src/dashboard/scanner/feature-status.js +10 -0
- package/src/dashboard/scanner/features-scanner.js +360 -0
- package/src/dashboard/scanner/hooks-scanner.js +8 -1
- package/src/dashboard/scanner/index.js +14 -7
- package/src/dashboard/scanner/phases-scanner.js +14 -15
- package/src/dashboard/scanner/portfolio.js +153 -0
- package/src/dashboard/scanner/project-api.js +195 -0
- package/src/dashboard/scanner/rules-scanner.js +42 -37
- package/src/dashboard/scanner/skills-scanner.js +105 -43
- package/src/dashboard/scanner/standards-scanner.js +4 -1
- package/src/dashboard/vite-plugin-morph-data.js +134 -17
- package/src/lib/ai/comment-stripper.js +101 -4
- package/src/lib/detectors/claude-config-detector.js +2 -7
- package/src/lib/detectors/index.js +5 -1
- package/src/lib/evaluator-report.js +47 -0
- package/src/lib/generators/handoff-generator.js +4 -18
- package/src/lib/generators/settings-generator.js +208 -209
- package/src/lib/git/default-branch.js +52 -0
- package/src/lib/git/tree-fingerprint.js +58 -0
- package/src/lib/git/tree-state.js +167 -0
- package/src/lib/graph/god-nodes.js +12 -3
- package/src/lib/graph/graph-health.js +48 -2
- package/src/lib/graph/graph-loader.js +99 -25
- package/src/lib/graph/graphifyignore.js +145 -28
- package/src/lib/graph/sanitize.js +117 -11
- package/src/lib/installers/mcp-installer.js +724 -719
- package/src/lib/installers/memory-installer.js +1 -2
- package/src/lib/projects/registry.js +154 -0
- package/src/lib/risk-detector.js +90 -0
- package/src/lib/specs/acervo-format.js +70 -0
- package/src/lib/specs/acervo-lifecycle.js +264 -0
- package/src/lib/specs/adr-index.js +169 -0
- package/src/lib/specs/revocations.js +156 -0
- package/src/lib/specs/screen-checklist.js +357 -0
- package/src/lib/specs/usecase-check.js +20 -16
- package/src/lib/specs/usecase-promoter.js +103 -38
- package/src/lib/specs/usecase-registry.js +7 -7
- package/src/lib/tasks/tasks-json-parser.js +2 -3
- package/src/lib/telemetry/metrics.js +386 -0
- package/src/lib/telemetry/telemetry-reader.js +161 -51
- package/src/lib/telemetry/verify-event.js +79 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +200 -0
- package/src/lib/validators/code-budget/comment-scanner.js +75 -0
- package/src/lib/validators/code-budget/index.js +9 -0
- package/src/lib/validators/reuse/reuse-validator.js +4 -2
- package/src/lib/validators/reuse/symbol-scanner.js +27 -22
- package/src/lib/validators/validation-runner.js +21 -5
- package/src/lib/validators/verify-runner.js +11 -0
- package/src/lib/worktree/finish-backup.js +739 -0
- package/src/lib/worktree/ignored-triage.js +15 -8
- package/src/lib/worktree/worktree-audit.js +5 -0
- package/src/scripts/install-plugin.js +1 -1
- package/src/scripts/setup-infra.js +31 -22
- package/src/utils/agents-installer.js +338 -24
- package/src/utils/banner.js +1 -1
- package/src/utils/hooks-installer.js +75 -15
- package/src/commands/index.js +0 -13
- package/src/commands/project/checkpoint.js +0 -209
- package/src/core/state/phase-state-machine.js +0 -197
- package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
- package/src/dashboard/app/pages/LiveState.jsx +0 -206
- package/src/dashboard/scanner/squads-scanner.js +0 -13
- package/src/dashboard/scanner/state-scanner.js +0 -85
- package/src/lib/generators/index.js +0 -10
- package/src/lib/hooks/hook-executor.js +0 -167
- package/src/lib/validators/checkpoint-persistence.js +0 -39
- package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
- package/src/lib/validators/shared/index.js +0 -12
- package/src/lib/validators/shared/issue-counter.js +0 -14
- package/src/lib/validators/shared/result-formatter.js +0 -124
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs
CHANGED
|
@@ -4,9 +4,6 @@ using Xunit;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Tests.Evals;
|
|
6
6
|
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// O cliente que RECUSA: a peca de que depende "o eval roda no CI sem credencial".
|
|
9
|
-
/// </summary>
|
|
10
7
|
public sealed class OfflineChatClientTests
|
|
11
8
|
{
|
|
12
9
|
private static OfflineChatClient Build(Func<string>? scenario = null)
|
|
@@ -19,13 +16,9 @@ public sealed class OfflineChatClientTests
|
|
|
19
16
|
|
|
20
17
|
var metadata = Assert.IsType<ChatClientMetadata>(client.GetService(typeof(ChatClientMetadata)));
|
|
21
18
|
|
|
22
|
-
// Estes DOIS campos entram na chave de cache. Se divergissem do run que gravou, todo
|
|
23
|
-
// cenario daria miss e a cache quente seria indistinguivel de uma cache vazia.
|
|
24
19
|
Assert.Equal("morph-fake", metadata.ProviderName);
|
|
25
20
|
Assert.Equal("modelo-exemplar", metadata.DefaultModelId);
|
|
26
21
|
|
|
27
|
-
// E o endpoint e NULO de proposito: ele nao entra na chave (medido na v10.9.0), e um
|
|
28
|
-
// cliente que nunca liga para lugar nenhum nao deve anunciar um destino.
|
|
29
22
|
Assert.Null(metadata.ProviderUri);
|
|
30
23
|
}
|
|
31
24
|
|
|
@@ -59,9 +52,7 @@ public sealed class OfflineChatClientTests
|
|
|
59
52
|
[Fact]
|
|
60
53
|
public void TheScenarioNameIsReadAtCALLTIME_NotCaptured()
|
|
61
54
|
{
|
|
62
|
-
//
|
|
63
|
-
// construcao, toda mensagem de miss nomearia o primeiro cenario e mandaria o leitor
|
|
64
|
-
// investigar o arquivo errado.
|
|
55
|
+
// O fixture roda N cenarios com o mesmo cliente: o nome do cenario nao pode ser capturado na construcao.
|
|
65
56
|
var current = "primeiro";
|
|
66
57
|
using var client = Build(() => current);
|
|
67
58
|
|
|
@@ -3,30 +3,6 @@ using Microsoft.Extensions.AI;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Tests.Fakes;
|
|
5
5
|
|
|
6
|
-
/// <summary>
|
|
7
|
-
/// <see cref="IChatClient"/> roteirizado: a costura que dispensa rede e chave de API na
|
|
8
|
-
/// suite inteira do kit.
|
|
9
|
-
/// </summary>
|
|
10
|
-
/// <remarks>
|
|
11
|
-
/// <para>
|
|
12
|
-
/// <b>Por que ele e o coracao da historia de teste:</b> o unico ponto em que o kit toca um
|
|
13
|
-
/// provider e um <see cref="IChatClient"/>. Trocando esse ponto por uma fila de respostas
|
|
14
|
-
/// escritas a mao, TODO o resto — <c>ModelRegistry</c>, <c>AgentFactory</c>,
|
|
15
|
-
/// <c>UsageCostMiddleware</c>, <c>ConversationStore</c> — passa a ser testavel sem socket,
|
|
16
|
-
/// sem segredo e sem flake. Nenhum tipo desta pasta abre cliente HTTP nem le variavel de
|
|
17
|
-
/// ambiente — o canario estrutural disso e um grep sobre <c>tests/</c> (chave de provider,
|
|
18
|
-
/// leitura de ambiente, cliente HTTP) que precisa voltar VAZIO, e o canario de
|
|
19
|
-
/// comportamento e
|
|
20
|
-
/// <c>FakeChatClientTests.TheWholeSeamAnswersFromMemoryAloneWithNoConfigurationAndNoConnection</c>.
|
|
21
|
-
/// </para>
|
|
22
|
-
/// <para>
|
|
23
|
-
/// <b>Chamada nao roteirizada e defeito de teste, nao "comportamento padrao".</b> Esgotada
|
|
24
|
-
/// a fila, o fake lanca com a contagem exata de chamadas. Devolver uma resposta vazia
|
|
25
|
-
/// silenciosamente faria um teste passar por engano — que e a unica coisa pior do que um
|
|
26
|
-
/// teste falhando.
|
|
27
|
-
/// </para>
|
|
28
|
-
/// <para>Seguro para uso concorrente: fila, contadores e capturas vivem sob um lock unico.</para>
|
|
29
|
-
/// </remarks>
|
|
30
6
|
public sealed class FakeChatClient : IChatClient
|
|
31
7
|
{
|
|
32
8
|
private readonly Lock _gate = new();
|
|
@@ -34,12 +10,10 @@ public sealed class FakeChatClient : IChatClient
|
|
|
34
10
|
private readonly List<ChatOptions?> _receivedOptions = [];
|
|
35
11
|
private readonly List<IReadOnlyList<ChatMessage>> _receivedMessages = [];
|
|
36
12
|
|
|
37
|
-
/// <summary>Cria um fake com a fila vazia. Enfileire antes de usar.</summary>
|
|
38
13
|
public FakeChatClient()
|
|
39
14
|
{
|
|
40
15
|
}
|
|
41
16
|
|
|
42
|
-
/// <summary>Cria um fake ja roteirizado com as respostas dadas, na ordem.</summary>
|
|
43
17
|
public FakeChatClient(params ChatResponse[] scripted)
|
|
44
18
|
{
|
|
45
19
|
ArgumentNullException.ThrowIfNull(scripted);
|
|
@@ -50,7 +24,6 @@ public sealed class FakeChatClient : IChatClient
|
|
|
50
24
|
}
|
|
51
25
|
}
|
|
52
26
|
|
|
53
|
-
/// <summary>Atalho: uma resposta de assistente por texto, na ordem dada.</summary>
|
|
54
27
|
public static FakeChatClient Scripted(params string[] texts)
|
|
55
28
|
{
|
|
56
29
|
ArgumentNullException.ThrowIfNull(texts);
|
|
@@ -64,51 +37,35 @@ public sealed class FakeChatClient : IChatClient
|
|
|
64
37
|
return client;
|
|
65
38
|
}
|
|
66
39
|
|
|
67
|
-
/// <summary>Quantas vezes o cliente foi invocado (streaming incluso).</summary>
|
|
68
40
|
public int CallCount { get; private set; }
|
|
69
41
|
|
|
70
|
-
|
|
71
|
-
/// O <c>ProviderName</c> anunciado no <see cref="ChatClientMetadata"/>. Configuravel
|
|
72
|
-
/// porque ele ENTRA NA CHAVE da cache de eval: a arvore que este fake grava so e legivel
|
|
73
|
-
/// depois pelo <c>OfflineChatClient</c> se os dois se apresentarem com a mesma
|
|
74
|
-
/// identidade. Default preservado ("fake"): quem nao usa eval nao muda de comportamento.
|
|
75
|
-
/// </summary>
|
|
42
|
+
// ProviderName e DefaultModelId entram na chave da cache de eval: o OfflineChatClient precisa anunciar os mesmos.
|
|
76
43
|
public string MetadataProviderName { get; init; } = "fake";
|
|
77
44
|
|
|
78
|
-
/// <summary>
|
|
79
|
-
/// O <c>DefaultModelId</c> anunciado no <see cref="ChatClientMetadata"/>. Tambem entra na
|
|
80
|
-
/// chave de cache. Nulo por default, como antes.
|
|
81
|
-
/// </summary>
|
|
82
45
|
public string? MetadataDefaultModelId { get; init; }
|
|
83
46
|
|
|
84
|
-
/// <summary>Se <see cref="Dispose"/> ja foi chamado — usado para provar ownership de cache.</summary>
|
|
85
47
|
public bool IsDisposed { get; private set; }
|
|
86
48
|
|
|
87
|
-
/// <summary>As <see cref="ChatOptions"/> recebidas, uma entrada por chamada, na ordem.</summary>
|
|
88
49
|
public IReadOnlyList<ChatOptions?> ReceivedOptions
|
|
89
50
|
{
|
|
90
51
|
get { lock (_gate) { return [.. _receivedOptions]; } }
|
|
91
52
|
}
|
|
92
53
|
|
|
93
|
-
/// <summary>As mensagens recebidas, uma entrada por chamada, na ordem.</summary>
|
|
94
54
|
public IReadOnlyList<IReadOnlyList<ChatMessage>> ReceivedMessages
|
|
95
55
|
{
|
|
96
56
|
get { lock (_gate) { return [.. _receivedMessages]; } }
|
|
97
57
|
}
|
|
98
58
|
|
|
99
|
-
/// <summary>As opcoes da ultima chamada, ou nulo se nunca foi chamado.</summary>
|
|
100
59
|
public ChatOptions? LastOptions
|
|
101
60
|
{
|
|
102
61
|
get { lock (_gate) { return _receivedOptions.Count == 0 ? null : _receivedOptions[^1]; } }
|
|
103
62
|
}
|
|
104
63
|
|
|
105
|
-
/// <summary>As mensagens da ultima chamada, ou nulo se nunca foi chamado.</summary>
|
|
106
64
|
public IReadOnlyList<ChatMessage>? LastMessages
|
|
107
65
|
{
|
|
108
66
|
get { lock (_gate) { return _receivedMessages.Count == 0 ? null : _receivedMessages[^1]; } }
|
|
109
67
|
}
|
|
110
68
|
|
|
111
|
-
/// <summary>Enfileira uma resposta ja montada. Devolve o proprio fake, para encadear.</summary>
|
|
112
69
|
public FakeChatClient Enqueue(ChatResponse response)
|
|
113
70
|
{
|
|
114
71
|
ArgumentNullException.ThrowIfNull(response);
|
|
@@ -121,18 +78,9 @@ public sealed class FakeChatClient : IChatClient
|
|
|
121
78
|
return this;
|
|
122
79
|
}
|
|
123
80
|
|
|
124
|
-
/// <summary>
|
|
125
|
-
/// Enfileira uma resposta de assistente com o texto dado e, opcionalmente, um
|
|
126
|
-
/// <see cref="UsageDetails"/> controlado — e assim que o teste do
|
|
127
|
-
/// <c>UsageCostMiddleware</c> (T11) verifica tarifa sem rede.
|
|
128
|
-
/// </summary>
|
|
129
81
|
public FakeChatClient EnqueueText(string text, UsageDetails? usage = null)
|
|
130
82
|
=> Enqueue(new ChatResponse(new ChatMessage(ChatRole.Assistant, text)) { Usage = usage });
|
|
131
83
|
|
|
132
|
-
/// <summary>
|
|
133
|
-
/// Enfileira uma resposta com contagem de tokens conhecida. Atalho para o caso mais
|
|
134
|
-
/// comum de teste de custo.
|
|
135
|
-
/// </summary>
|
|
136
84
|
public FakeChatClient EnqueueTextWithUsage(
|
|
137
85
|
string text,
|
|
138
86
|
long inputTokens,
|
|
@@ -148,7 +96,6 @@ public sealed class FakeChatClient : IChatClient
|
|
|
148
96
|
CachedInputTokenCount = cachedInputTokens,
|
|
149
97
|
});
|
|
150
98
|
|
|
151
|
-
/// <inheritdoc />
|
|
152
99
|
public Task<ChatResponse> GetResponseAsync(
|
|
153
100
|
IEnumerable<ChatMessage> messages,
|
|
154
101
|
ChatOptions? options = null,
|
|
@@ -160,7 +107,6 @@ public sealed class FakeChatClient : IChatClient
|
|
|
160
107
|
return Task.FromResult(Record(messages, options));
|
|
161
108
|
}
|
|
162
109
|
|
|
163
|
-
/// <inheritdoc />
|
|
164
110
|
public async IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
|
|
165
111
|
IEnumerable<ChatMessage> messages,
|
|
166
112
|
ChatOptions? options = null,
|
|
@@ -180,7 +126,6 @@ public sealed class FakeChatClient : IChatClient
|
|
|
180
126
|
await Task.CompletedTask.ConfigureAwait(false);
|
|
181
127
|
}
|
|
182
128
|
|
|
183
|
-
/// <inheritdoc />
|
|
184
129
|
public object? GetService(Type serviceType, object? serviceKey = null)
|
|
185
130
|
{
|
|
186
131
|
ArgumentNullException.ThrowIfNull(serviceType);
|
|
@@ -198,7 +143,6 @@ public sealed class FakeChatClient : IChatClient
|
|
|
198
143
|
return serviceType.IsInstanceOfType(this) ? this : null;
|
|
199
144
|
}
|
|
200
145
|
|
|
201
|
-
/// <inheritdoc />
|
|
202
146
|
public void Dispose() => IsDisposed = true;
|
|
203
147
|
|
|
204
148
|
private ChatResponse Record(IEnumerable<ChatMessage> messages, ChatOptions? options)
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs
CHANGED
|
@@ -4,17 +4,6 @@ using Xunit;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Tests.Fakes;
|
|
6
6
|
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// Canarios da costura de teste (T9). Um fake so vale se ele proprio for testado: um fake
|
|
9
|
-
/// que devolve na ordem errada, ou que engole as <see cref="ChatOptions"/> recebidas,
|
|
10
|
-
/// transforma todo teste que depende dele num teste que nao mede nada.
|
|
11
|
-
/// </summary>
|
|
12
|
-
/// <remarks>
|
|
13
|
-
/// Todo <c>await</c> passa <c>TestContext.Current.CancellationToken</c> porque o analisador
|
|
14
|
-
/// xUnit1051 cobra isso e <c>TreatWarningsAsErrors</c> transforma a cobranca em build
|
|
15
|
-
/// vermelho. Nao ha <c>#pragma</c> aqui: a regra e boa (teste cancelavel) e o custo e um
|
|
16
|
-
/// argumento.
|
|
17
|
-
/// </remarks>
|
|
18
7
|
public sealed class FakeChatClientTests
|
|
19
8
|
{
|
|
20
9
|
[Fact]
|
|
@@ -114,8 +103,6 @@ public sealed class FakeChatClientTests
|
|
|
114
103
|
[Fact]
|
|
115
104
|
public async Task UsageIsControlledByTheTestAndSurvivesTheRoundTrip()
|
|
116
105
|
{
|
|
117
|
-
// E assim que a T11 vai verificar tarifa sem rede: Usage conhecido entra,
|
|
118
|
-
// Usage conhecido sai.
|
|
119
106
|
using var client = new FakeChatClient();
|
|
120
107
|
client.EnqueueTextWithUsage("ok", inputTokens: 1_000, outputTokens: 250, cachedInputTokens: 400);
|
|
121
108
|
|
|
@@ -155,17 +142,6 @@ public sealed class FakeChatClientTests
|
|
|
155
142
|
[Fact]
|
|
156
143
|
public async Task TheWholeSeamAnswersFromMemoryAloneWithNoConfigurationAndNoConnection()
|
|
157
144
|
{
|
|
158
|
-
// O caminho inteiro que os consumidores usam — documento JSON, registry, fabrica,
|
|
159
|
-
// cliente — exercitado com nada alem de objetos em memoria: nenhuma chave, nenhum
|
|
160
|
-
// arquivo, nenhum socket. E este o teste a apontar quando alguem perguntar "como o
|
|
161
|
-
// kit testa IA sem provider".
|
|
162
|
-
//
|
|
163
|
-
// A propriedade "passa sem chave de provider no ambiente" NAO e afirmada aqui por
|
|
164
|
-
// codigo: afirma-la exigiria ler variavel de ambiente, e um teste que faz isso
|
|
165
|
-
// derruba o canario estrutural da T9 — o grep sobre tests/ (chave de provider,
|
|
166
|
-
// leitura de ambiente, cliente HTTP) tem de voltar VAZIO. A propriedade e
|
|
167
|
-
// DEMONSTRADA rodando a suite com o ambiente esvaziado, o que cobre os 50 testes
|
|
168
|
-
// em vez de um.
|
|
169
145
|
var document = """
|
|
170
146
|
{
|
|
171
147
|
"version": "2.0",
|
|
@@ -195,8 +171,6 @@ public sealed class FakeChatClientTests
|
|
|
195
171
|
[Fact]
|
|
196
172
|
public async Task ConcurrentCallersNeverLoseACallOrACapture()
|
|
197
173
|
{
|
|
198
|
-
// ConversationStore (T13) exercita duas sessoes sobre a MESMA instancia. Se o fake
|
|
199
|
-
// perdesse chamada sob concorrencia, aquele teste mediria o fake, nao o store.
|
|
200
174
|
using var client = new FakeChatClient();
|
|
201
175
|
for (var i = 0; i < 50; i++)
|
|
202
176
|
{
|
|
@@ -1,30 +1,12 @@
|
|
|
1
1
|
namespace Morph.AiKit.Tests.Fakes;
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
/// <see cref="TimeProvider"/> controlado pelo teste: o relogio so anda quando o teste manda.
|
|
5
|
-
/// </summary>
|
|
6
|
-
/// <remarks>
|
|
7
|
-
/// <para>
|
|
8
|
-
/// Existe <b>escrito a mao, em 20 linhas</b>, e nao via
|
|
9
|
-
/// <c>Microsoft.Extensions.TimeProvider.Testing</c>, por uma razao de contrato desta
|
|
10
|
-
/// feature: toda versao de pacote vem de <c>framework/ai-pin.json</c>, e aquele pacote nao
|
|
11
|
-
/// esta no pin. Acrescenta-lo seria uma versao decidida fora do pin — exatamente a segunda
|
|
12
|
-
/// verdade que o kit existe para impedir. O achado esta registrado no relatorio da T11.
|
|
13
|
-
/// </para>
|
|
14
|
-
/// <para>
|
|
15
|
-
/// So o necessario esta implementado: <see cref="GetUtcNow"/> e o avanco manual. Timers nao
|
|
16
|
-
/// sao usados por nenhum tipo do kit; herdar o comportamento base e mais honesto que
|
|
17
|
-
/// simular um agendador que ninguem exercita.
|
|
18
|
-
/// </para>
|
|
19
|
-
/// </remarks>
|
|
3
|
+
// Escrito a mao: Microsoft.Extensions.TimeProvider.Testing nao esta no pin (framework/ai-pin.json).
|
|
20
4
|
public sealed class FakeClock(DateTimeOffset start) : TimeProvider
|
|
21
5
|
{
|
|
22
6
|
private DateTimeOffset _now = start;
|
|
23
7
|
|
|
24
|
-
/// <inheritdoc />
|
|
25
8
|
public override DateTimeOffset GetUtcNow() => _now;
|
|
26
9
|
|
|
27
|
-
/// <summary>Avanca o relogio. Rejeita andar para tras: o tempo do teste e monotono.</summary>
|
|
28
10
|
public void Advance(TimeSpan delta)
|
|
29
11
|
{
|
|
30
12
|
ArgumentOutOfRangeException.ThrowIfLessThan(delta, TimeSpan.Zero);
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs
CHANGED
|
@@ -2,30 +2,6 @@ using Morph.AiKit.Media;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Tests.Fakes;
|
|
4
4
|
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// <see cref="IMediaProvider"/> roteirizado: a costura que dispensa rede, chave de API e
|
|
7
|
-
/// <b>fatura</b> na suite inteira de midia.
|
|
8
|
-
/// </summary>
|
|
9
|
-
/// <remarks>
|
|
10
|
-
/// <para>
|
|
11
|
-
/// <b>Por que ele existe:</b> o unico ponto do subsistema que gasta dinheiro e um
|
|
12
|
-
/// <c>IMediaProvider</c>. Trocando-o por uma fila de respostas escritas a mao, todo o resto
|
|
13
|
-
/// — <c>MediaGenerator</c>, <c>MediaWorker</c>, <c>MediaJob</c>, <c>MediaStore</c> — passa a
|
|
14
|
-
/// ser testavel sem socket, sem segredo e sem cobranca. Mesmo desenho do
|
|
15
|
-
/// <see cref="FakeChatClient"/>, pela mesma razao.
|
|
16
|
-
/// </para>
|
|
17
|
-
/// <para>
|
|
18
|
-
/// Ele so e construivel porque <c>InternalsVisibleTo("Morph.AiKit.Tests")</c> existe no
|
|
19
|
-
/// <c>.csproj</c> da biblioteca. Um projeto consumidor <b>nao</b> consegue escrever esta
|
|
20
|
-
/// classe — e essa impossibilidade e a regra dura de <c>ai-agents/media-pipeline.md</c>,
|
|
21
|
-
/// asserida em <c>MediaAccessibilityTests</c>.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// <para>
|
|
24
|
-
/// <b>Chamada nao roteirizada e defeito de teste.</b> Esgotada a fila, o fake devolve uma
|
|
25
|
-
/// falha nomeada com a contagem de chamadas, em vez de inventar bytes — um artefato
|
|
26
|
-
/// fabricado pelo proprio fake faria um teste passar por engano.
|
|
27
|
-
/// </para>
|
|
28
|
-
/// </remarks>
|
|
29
5
|
internal sealed class FakeMediaProvider : IMediaProvider
|
|
30
6
|
{
|
|
31
7
|
private readonly Queue<MediaResult<MediaArtifact>> _scripted = new();
|
|
@@ -37,19 +13,14 @@ internal sealed class FakeMediaProvider : IMediaProvider
|
|
|
37
13
|
Supported = supports;
|
|
38
14
|
}
|
|
39
15
|
|
|
40
|
-
/// <inheritdoc />
|
|
41
16
|
public string Provider { get; }
|
|
42
17
|
|
|
43
|
-
/// <summary>Que tipo de midia este fake diz atender.</summary>
|
|
44
18
|
public MediaKind Supported { get; }
|
|
45
19
|
|
|
46
|
-
/// <summary>Quantas vezes foi chamado. E como um teste prova que NAO houve segunda chamada paga.</summary>
|
|
47
20
|
public int Calls { get; private set; }
|
|
48
21
|
|
|
49
|
-
/// <summary>Os pedidos vistos, na ordem.</summary>
|
|
50
22
|
public List<MediaRequest> Seen { get; } = [];
|
|
51
23
|
|
|
52
|
-
/// <summary>Enfileira bytes de sucesso.</summary>
|
|
53
24
|
public FakeMediaProvider Returns(byte[] bytes, string mediaType = "image/png", string? revisedPrompt = null)
|
|
54
25
|
{
|
|
55
26
|
_scripted.Enqueue(MediaResult<MediaArtifact>.Ok(new MediaArtifact(
|
|
@@ -57,17 +28,14 @@ internal sealed class FakeMediaProvider : IMediaProvider
|
|
|
57
28
|
return this;
|
|
58
29
|
}
|
|
59
30
|
|
|
60
|
-
/// <summary>Enfileira uma recusa com o codigo dado — inclusive <c>PolicyBlocked</c>.</summary>
|
|
61
31
|
public FakeMediaProvider Fails(MediaErrorCode code, string message = "roteirizado")
|
|
62
32
|
{
|
|
63
33
|
_scripted.Enqueue(MediaResult<MediaArtifact>.Fail(code, message));
|
|
64
34
|
return this;
|
|
65
35
|
}
|
|
66
36
|
|
|
67
|
-
/// <inheritdoc />
|
|
68
37
|
public bool Supports(MediaKind kind) => kind == Supported;
|
|
69
38
|
|
|
70
|
-
/// <inheritdoc />
|
|
71
39
|
public Task<MediaResult<MediaArtifact>> ProduceAsync(
|
|
72
40
|
MediaRequest request,
|
|
73
41
|
MediaAliasBinding binding,
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs
CHANGED
|
@@ -3,40 +3,26 @@ using Morph.AiKit.Providers;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Tests.Fakes;
|
|
5
5
|
|
|
6
|
-
/// <summary>
|
|
7
|
-
/// <see cref="IProviderClientFactory"/> de teste: devolve <see cref="FakeChatClient"/> e
|
|
8
|
-
/// <b>conta quantas vezes foi chamada</b>. E o contador que torna a memoizacao do
|
|
9
|
-
/// <c>ModelRegistry</c> uma afirmacao verificavel em vez de uma promessa.
|
|
10
|
-
/// </summary>
|
|
11
6
|
public sealed class FakeProviderClientFactory : IProviderClientFactory
|
|
12
7
|
{
|
|
13
8
|
private readonly Func<ModelAlias, IChatClient?> _create;
|
|
14
9
|
private readonly Dictionary<string, IChatClient> _created = new(StringComparer.Ordinal);
|
|
15
10
|
private readonly List<ModelAlias> _requested = [];
|
|
16
11
|
|
|
17
|
-
/// <summary>Fabrica que devolve um <see cref="FakeChatClient"/> novo por alias.</summary>
|
|
18
12
|
public FakeProviderClientFactory()
|
|
19
13
|
: this(_ => new FakeChatClient())
|
|
20
14
|
{
|
|
21
15
|
}
|
|
22
16
|
|
|
23
|
-
/// <summary>
|
|
24
|
-
/// Fabrica com construcao sob medida — inclusive devolvendo nulo, para exercitar a
|
|
25
|
-
/// recusa do registry a um cliente inexistente.
|
|
26
|
-
/// </summary>
|
|
27
17
|
public FakeProviderClientFactory(Func<ModelAlias, IChatClient?> create)
|
|
28
18
|
=> _create = create;
|
|
29
19
|
|
|
30
|
-
/// <summary>Quantas vezes <see cref="CreateChatClient"/> foi realmente executada.</summary>
|
|
31
20
|
public int CreateCount { get; private set; }
|
|
32
21
|
|
|
33
|
-
/// <summary>Os aliases para os quais um cliente foi criado, na ordem.</summary>
|
|
34
22
|
public IReadOnlyList<ModelAlias> Requested => _requested;
|
|
35
23
|
|
|
36
|
-
/// <summary>Os clientes criados, por nome de alias.</summary>
|
|
37
24
|
public IReadOnlyDictionary<string, IChatClient> Created => _created;
|
|
38
25
|
|
|
39
|
-
/// <inheritdoc />
|
|
40
26
|
public IChatClient CreateChatClient(ModelAlias alias)
|
|
41
27
|
{
|
|
42
28
|
ArgumentNullException.ThrowIfNull(alias);
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs
CHANGED
|
@@ -2,55 +2,31 @@ using Morph.AiKit.Middleware;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Tests.Fakes;
|
|
4
4
|
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// Sink de teste: acumula os registros de custo publicados pelo
|
|
7
|
-
/// <see cref="UsageCostMiddleware"/>.
|
|
8
|
-
/// </summary>
|
|
9
|
-
/// <remarks>
|
|
10
|
-
/// <para>
|
|
11
|
-
/// Estava nos <c>outputs</c> da T9, mas depende de <see cref="IUsageCostSink"/> e
|
|
12
|
-
/// <see cref="UsageCostRecord"/>, que sao <c>outputs</c> da T11. A T9 recusou-se a criar os
|
|
13
|
-
/// dois tipos por fora (seriam arquivos de outra task, e duplica-los criaria a segunda
|
|
14
|
-
/// verdade do registro de custo) e deixou este codigo pronto no seu relatorio; a errata E8
|
|
15
|
-
/// do <c>spec.md</c> moveu formalmente o arquivo para ca. Nasce junto dos tipos que
|
|
16
|
-
/// implementa.
|
|
17
|
-
/// </para>
|
|
18
|
-
/// <para>
|
|
19
|
-
/// Seguro sob concorrencia pelo mesmo motivo do <see cref="FakeChatClient"/>: um mesmo
|
|
20
|
-
/// pipeline atende varias conversas, e um sink que perdesse linha sob concorrencia faria o
|
|
21
|
-
/// teste medir o sink em vez do middleware.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// </remarks>
|
|
24
5
|
public sealed class FakeUsageCostSink : IUsageCostSink
|
|
25
6
|
{
|
|
26
7
|
private readonly Lock _gate = new();
|
|
27
8
|
private readonly List<UsageCostRecord> _records = [];
|
|
28
9
|
|
|
29
|
-
/// <summary>Tudo o que foi publicado, na ordem. Snapshot — iterar e seguro.</summary>
|
|
30
10
|
public IReadOnlyList<UsageCostRecord> Records
|
|
31
11
|
{
|
|
32
12
|
get { lock (_gate) { return [.. _records]; } }
|
|
33
13
|
}
|
|
34
14
|
|
|
35
|
-
/// <summary>O ultimo registro publicado, ou nulo se nenhum foi.</summary>
|
|
36
15
|
public UsageCostRecord? Last
|
|
37
16
|
{
|
|
38
17
|
get { lock (_gate) { return _records.Count == 0 ? null : _records[^1]; } }
|
|
39
18
|
}
|
|
40
19
|
|
|
41
|
-
/// <summary>Quantas linhas de custo foram publicadas.</summary>
|
|
42
20
|
public int Count
|
|
43
21
|
{
|
|
44
22
|
get { lock (_gate) { return _records.Count; } }
|
|
45
23
|
}
|
|
46
24
|
|
|
47
|
-
/// <summary>A soma dos custos <b>medidos</b> — os demais status nao entram na conta.</summary>
|
|
48
25
|
public decimal MeasuredTotalUsd
|
|
49
26
|
{
|
|
50
27
|
get { lock (_gate) { return _records.Where(r => r.IsMeasuredCost).Sum(r => r.CostUsd); } }
|
|
51
28
|
}
|
|
52
29
|
|
|
53
|
-
/// <inheritdoc />
|
|
54
30
|
public void Record(UsageCostRecord record)
|
|
55
31
|
{
|
|
56
32
|
ArgumentNullException.ThrowIfNull(record);
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs
CHANGED
|
@@ -3,29 +3,17 @@ using Morph.AiKit.Media;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Tests.Fakes;
|
|
5
5
|
|
|
6
|
-
/// <summary>
|
|
7
|
-
/// Blob em memoria. <b>Conta as gravacoes</b> — e a contagem e o que prova que o
|
|
8
|
-
/// <see cref="MediaStore"/> deduplicou em vez de regravar.
|
|
9
|
-
/// </summary>
|
|
10
|
-
/// <remarks>
|
|
11
|
-
/// Um teste que so confira "o hash e o mesmo" nao distingue dedupe de regravacao: os dois
|
|
12
|
-
/// produzem o mesmo hash. Por isso <see cref="Writes"/> existe.
|
|
13
|
-
/// </remarks>
|
|
14
6
|
public sealed class InMemoryMediaBlobs : IMediaBlobs
|
|
15
7
|
{
|
|
16
8
|
private readonly ConcurrentDictionary<string, byte[]> _files = new(StringComparer.Ordinal);
|
|
17
9
|
|
|
18
|
-
/// <summary>Quantas gravacoes de verdade aconteceram.</summary>
|
|
19
10
|
public int Writes { get; private set; }
|
|
20
11
|
|
|
21
|
-
/// <summary>Os caminhos gravados.</summary>
|
|
22
12
|
public IReadOnlyCollection<string> Paths => _files.Keys.ToArray();
|
|
23
13
|
|
|
24
|
-
/// <inheritdoc />
|
|
25
14
|
public ValueTask<bool> ExistsAsync(string relativePath, CancellationToken cancellationToken)
|
|
26
15
|
=> ValueTask.FromResult(_files.ContainsKey(relativePath));
|
|
27
16
|
|
|
28
|
-
/// <inheritdoc />
|
|
29
17
|
public ValueTask WriteAsync(string relativePath, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken)
|
|
30
18
|
{
|
|
31
19
|
Writes++;
|
|
@@ -34,25 +22,18 @@ public sealed class InMemoryMediaBlobs : IMediaBlobs
|
|
|
34
22
|
}
|
|
35
23
|
}
|
|
36
24
|
|
|
37
|
-
/// <summary>
|
|
38
|
-
/// <see cref="IMediaStore"/> em memoria, para os testes que precisam de UM storage e nao
|
|
39
|
-
/// estao medindo o storage.
|
|
40
|
-
/// </summary>
|
|
41
25
|
public sealed class InMemoryMediaStore : IMediaStore
|
|
42
26
|
{
|
|
43
27
|
private readonly MediaStore _inner;
|
|
44
28
|
|
|
45
|
-
/// <summary>Cria o store sobre um blob em memoria proprio.</summary>
|
|
46
29
|
public InMemoryMediaStore()
|
|
47
30
|
{
|
|
48
31
|
Blobs = new InMemoryMediaBlobs();
|
|
49
32
|
_inner = new MediaStore(Blobs);
|
|
50
33
|
}
|
|
51
34
|
|
|
52
|
-
/// <summary>O blob por baixo, para conferir gravacoes.</summary>
|
|
53
35
|
public InMemoryMediaBlobs Blobs { get; }
|
|
54
36
|
|
|
55
|
-
/// <inheritdoc />
|
|
56
37
|
public ValueTask<MediaStoreEntry> PutAsync(
|
|
57
38
|
ReadOnlyMemory<byte> bytes,
|
|
58
39
|
string mediaType,
|
|
@@ -60,25 +41,20 @@ public sealed class InMemoryMediaStore : IMediaStore
|
|
|
60
41
|
=> _inner.PutAsync(bytes, mediaType, cancellationToken);
|
|
61
42
|
}
|
|
62
43
|
|
|
63
|
-
/// <summary>Acervo de jobs em memoria — a costura com o banco do projeto, em teste.</summary>
|
|
64
44
|
public sealed class InMemoryMediaJobs : IMediaJobRepository
|
|
65
45
|
{
|
|
66
46
|
private readonly ConcurrentDictionary<Guid, MediaJob> _byId = new();
|
|
67
47
|
|
|
68
|
-
/// <summary>Quantas gravacoes aconteceram. Prova que o worker persistiu cada passo.</summary>
|
|
69
48
|
public int Saves { get; private set; }
|
|
70
49
|
|
|
71
|
-
/// <inheritdoc />
|
|
72
50
|
public ValueTask<MediaJob?> FindByIdempotencyKeyAsync(
|
|
73
51
|
MediaIdempotencyKey key,
|
|
74
52
|
CancellationToken cancellationToken)
|
|
75
53
|
=> ValueTask.FromResult(_byId.Values.FirstOrDefault(j => j.IdempotencyKey == key));
|
|
76
54
|
|
|
77
|
-
/// <inheritdoc />
|
|
78
55
|
public ValueTask<MediaJob?> FindAsync(MediaJobId id, CancellationToken cancellationToken)
|
|
79
56
|
=> ValueTask.FromResult(_byId.TryGetValue(id.Value, out var job) ? job : null);
|
|
80
57
|
|
|
81
|
-
/// <inheritdoc />
|
|
82
58
|
public ValueTask SaveAsync(MediaJob job, CancellationToken cancellationToken)
|
|
83
59
|
{
|
|
84
60
|
Saves++;
|
|
@@ -87,32 +63,26 @@ public sealed class InMemoryMediaJobs : IMediaJobRepository
|
|
|
87
63
|
}
|
|
88
64
|
}
|
|
89
65
|
|
|
90
|
-
/// <summary>Resolvedor de alias de teste: um alias, um binding.</summary>
|
|
91
66
|
public sealed class StubAliasResolver : IMediaAliasResolver
|
|
92
67
|
{
|
|
93
68
|
private readonly Dictionary<string, MediaAliasBinding> _bindings = new(StringComparer.Ordinal);
|
|
94
69
|
|
|
95
|
-
/// <summary>Declara um alias.</summary>
|
|
96
70
|
public StubAliasResolver With(MediaAliasBinding binding)
|
|
97
71
|
{
|
|
98
72
|
_bindings[binding.Alias] = binding;
|
|
99
73
|
return this;
|
|
100
74
|
}
|
|
101
75
|
|
|
102
|
-
/// <inheritdoc />
|
|
103
76
|
public MediaResult<MediaAliasBinding> Resolve(string alias)
|
|
104
77
|
=> _bindings.TryGetValue(alias, out var binding)
|
|
105
78
|
? MediaResult<MediaAliasBinding>.Ok(binding)
|
|
106
79
|
: MediaResult<MediaAliasBinding>.Fail(MediaErrorCode.InvalidRequest, $"alias '{alias}' nao existe.");
|
|
107
80
|
}
|
|
108
81
|
|
|
109
|
-
/// <summary>Teto de custo de teste: aceita ate um limite, e registra o que foi pedido.</summary>
|
|
110
82
|
public sealed class CappedBudget(decimal cap) : IMediaBudget
|
|
111
83
|
{
|
|
112
|
-
/// <summary>Os custos estimados que chegaram, na ordem.</summary>
|
|
113
84
|
public List<decimal> Seen { get; } = [];
|
|
114
85
|
|
|
115
|
-
/// <inheritdoc />
|
|
116
86
|
public ValueTask<bool> TryReserveAsync(
|
|
117
87
|
MediaRequest request,
|
|
118
88
|
decimal estimatedCostUsd,
|
|
@@ -123,9 +93,7 @@ public sealed class CappedBudget(decimal cap) : IMediaBudget
|
|
|
123
93
|
}
|
|
124
94
|
}
|
|
125
95
|
|
|
126
|
-
/// <summary>Relogio parado, para que o carimbo de tempo seja um fato do teste.</summary>
|
|
127
96
|
public sealed class FixedMediaClock(DateTimeOffset now) : IMediaClock
|
|
128
97
|
{
|
|
129
|
-
/// <inheritdoc />
|
|
130
98
|
public DateTimeOffset UtcNow { get; } = now;
|
|
131
99
|
}
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs
CHANGED
|
@@ -4,33 +4,10 @@ using Xunit;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Tests;
|
|
6
6
|
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// O canario da REGRA DURA de <c>ai-agents/media-pipeline.md</c>:
|
|
9
|
-
/// <b>nunca gere midia dentro de um turno de conversa</b>.
|
|
10
|
-
/// </summary>
|
|
11
|
-
/// <remarks>
|
|
12
|
-
/// <para>
|
|
13
|
-
/// A regra nao e presa por prosa — e presa por ACESSIBILIDADE. O tipo que produz bytes e
|
|
14
|
-
/// <c>internal</c> ao assembly; um handler de mensagem no projeto consumidor nao consegue
|
|
15
|
-
/// nomea-lo, e o codigo nao compila. Este arquivo e o que mantem essa afirmacao verdadeira
|
|
16
|
-
/// depois de qualquer refatoracao.
|
|
17
|
-
/// </para>
|
|
18
|
-
/// <para>
|
|
19
|
-
/// <b>A mutacao foi rodada</b> (T10): tornar <c>IMediaProvider</c> publico derruba
|
|
20
|
-
/// <see cref="TheTypeThatProducesBytesIsNotPublic"/> nomeando o defeito. Um canario que
|
|
21
|
-
/// ninguem viu morder pode ser o canario errado.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// <para>
|
|
24
|
-
/// A leitura e por <b>reflexao sobre a assembly</b>, e nao por <c>grep</c> no fonte: o
|
|
25
|
-
/// modificador que importa e o que sobreviveu a compilacao. Um <c>grep</c> por
|
|
26
|
-
/// <c>"internal interface"</c> mediria o texto, e o texto e uma copia do objeto.
|
|
27
|
-
/// </para>
|
|
28
|
-
/// </remarks>
|
|
29
7
|
public sealed class MediaAccessibilityTests
|
|
30
8
|
{
|
|
31
9
|
private static readonly Assembly Kit = typeof(MediaJob).Assembly;
|
|
32
10
|
|
|
33
|
-
/// <summary>Os nomes que produzem, ou orquestram a producao de, bytes pagos.</summary>
|
|
34
11
|
public static TheoryData<string> ByteProducingTypes() =>
|
|
35
12
|
[
|
|
36
13
|
"Morph.AiKit.Media.IMediaProvider",
|
|
@@ -44,10 +21,6 @@ public sealed class MediaAccessibilityTests
|
|
|
44
21
|
"Morph.AiKit.Media.MediaArtifact",
|
|
45
22
|
];
|
|
46
23
|
|
|
47
|
-
/// <summary>
|
|
48
|
-
/// A asserção literal do plano:
|
|
49
|
-
/// <c>typeof(MediaJob).Assembly.GetType("Morph.AiKit.Media.IMediaProvider")!.IsPublic == false</c>.
|
|
50
|
-
/// </summary>
|
|
51
24
|
[Fact]
|
|
52
25
|
public void TheTypeThatProducesBytesIsNotPublic()
|
|
53
26
|
{
|
|
@@ -61,7 +34,6 @@ public sealed class MediaAccessibilityTests
|
|
|
61
34
|
"compilador. E prosa outra vez.");
|
|
62
35
|
}
|
|
63
36
|
|
|
64
|
-
/// <summary>Uma rodada por tipo: nenhum deles pode vazar para fora do assembly.</summary>
|
|
65
37
|
[Theory]
|
|
66
38
|
[MemberData(nameof(ByteProducingTypes))]
|
|
67
39
|
public void NoByteProducingTypeIsVisibleOutsideTheAssembly(string fullName)
|
|
@@ -73,11 +45,6 @@ public sealed class MediaAccessibilityTests
|
|
|
73
45
|
Assert.False(type.IsNestedPublic, $"{fullName} nao pode ser aninhado publico.");
|
|
74
46
|
}
|
|
75
47
|
|
|
76
|
-
/// <summary>
|
|
77
|
-
/// O outro lado da mesma afirmacao: a superficie publica EXISTE, e ela nunca devolve
|
|
78
|
-
/// bytes. Sem esta metade, tornar tudo internal passaria — e um subsistema inalcancavel
|
|
79
|
-
/// tambem esta errado.
|
|
80
|
-
/// </summary>
|
|
81
48
|
[Fact]
|
|
82
49
|
public void ThePublicEntryPointExistsAndNeverReturnsBytes()
|
|
83
50
|
{
|
|
@@ -101,10 +68,6 @@ public sealed class MediaAccessibilityTests
|
|
|
101
68
|
}
|
|
102
69
|
}
|
|
103
70
|
|
|
104
|
-
/// <summary>
|
|
105
|
-
/// <see cref="MediaArtifact"/> — o registro que CARREGA os bytes — nao pode aparecer em
|
|
106
|
-
/// nenhuma assinatura publica do subsistema. Ele existe entre dois tipos internal.
|
|
107
|
-
/// </summary>
|
|
108
71
|
[Fact]
|
|
109
72
|
public void TheArtifactNeverAppearsInAPublicSignature()
|
|
110
73
|
{
|