@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
|
@@ -7,21 +7,6 @@ using Xunit;
|
|
|
7
7
|
|
|
8
8
|
namespace Morph.AiKit.Tests;
|
|
9
9
|
|
|
10
|
-
/// <summary>
|
|
11
|
-
/// Canarios do <see cref="ConversationStore"/> (T13). A afirmacao central: <b>uma unica
|
|
12
|
-
/// instancia de provider atende todas as sessoes, e o historico de uma conversa nunca
|
|
13
|
-
/// aparece em outra</b> — porque o estado por sessao vive na
|
|
14
|
-
/// <see cref="AgentSession"/> (via <see cref="ProviderSessionState{TState}"/>), jamais num
|
|
15
|
-
/// campo do provider.
|
|
16
|
-
/// </summary>
|
|
17
|
-
/// <remarks>
|
|
18
|
-
/// Os testes rodam pelo caminho <b>real</b> do MAF: um <see cref="ChatClientAgent"/> de
|
|
19
|
-
/// verdade, com <c>ChatClientAgentOptions.ChatHistoryProvider</c> apontando para o
|
|
20
|
-
/// <see cref="ConversationStore"/>, sessoes criadas por <c>CreateSessionAsync</c> e voltas
|
|
21
|
-
/// disparadas por <c>RunAsync</c>. O unico fake e o <see cref="FakeChatClient"/> no lugar do
|
|
22
|
-
/// provider de modelo — e por isso "o provider e chamado" e um fato observado, e nao uma
|
|
23
|
-
/// chamada manual ao metodo protegido fingindo ser o framework.
|
|
24
|
-
/// </remarks>
|
|
25
10
|
public sealed class ConversationStoreTests
|
|
26
11
|
{
|
|
27
12
|
private static ChatClientAgent Agent(FakeChatClient client, ConversationStore history)
|
|
@@ -37,15 +22,6 @@ public sealed class ConversationStoreTests
|
|
|
37
22
|
private static string[] TextsOf(IEnumerable<ChatMessage> messages)
|
|
38
23
|
=> [.. messages.Select(m => m.Text)];
|
|
39
24
|
|
|
40
|
-
// ---------------------------------------------------------------------------------
|
|
41
|
-
// doneCriteria 2: AppendAsync recebe a volta; LoadAsync alimenta a volta seguinte
|
|
42
|
-
// ---------------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
/// <summary>
|
|
45
|
-
/// Uma volta e gravada com pedido e resposta, nessa ordem — e a volta seguinte chega ao
|
|
46
|
-
/// modelo ja com essa historia na frente. As duas metades do contrato, medidas nos dois
|
|
47
|
-
/// lados: no acervo e no que o cliente de modelo recebeu.
|
|
48
|
-
/// </summary>
|
|
49
25
|
[Fact]
|
|
50
26
|
public async Task TheRoundIsAppendedAndFeedsTheHistoryOfTheNextRound()
|
|
51
27
|
{
|
|
@@ -61,7 +37,6 @@ public sealed class ConversationStoreTests
|
|
|
61
37
|
|
|
62
38
|
await agent.RunAsync("Meu pedido 1234 esta atrasado.", session, cancellationToken: Ct);
|
|
63
39
|
|
|
64
|
-
// Depois da 1a volta o acervo tem exatamente o pedido e a resposta daquela volta.
|
|
65
40
|
var afterFirst = await store.LoadAsync("conversa-A", Ct);
|
|
66
41
|
Assert.Equal(
|
|
67
42
|
["Meu pedido 1234 esta atrasado.", "Vou verificar o pedido 1234."],
|
|
@@ -69,8 +44,6 @@ public sealed class ConversationStoreTests
|
|
|
69
44
|
|
|
70
45
|
await agent.RunAsync("E quando foi despachado?", session, cancellationToken: Ct);
|
|
71
46
|
|
|
72
|
-
// A 2a ida ao modelo levou o historico carregado do acervo, e so uma vez cada
|
|
73
|
-
// mensagem — o turno corrente nao vem duplicado.
|
|
74
47
|
var secondCall = TextsOf(client.ReceivedMessages[1]);
|
|
75
48
|
Assert.Equal(
|
|
76
49
|
[
|
|
@@ -84,10 +57,6 @@ public sealed class ConversationStoreTests
|
|
|
84
57
|
Assert.Equal(4, afterSecond.Count);
|
|
85
58
|
}
|
|
86
59
|
|
|
87
|
-
/// <summary>
|
|
88
|
-
/// A primeira volta nao carrega historico nenhum: conversa inexistente e lista vazia, nao
|
|
89
|
-
/// erro. E o caso mais comum de todos.
|
|
90
|
-
/// </summary>
|
|
91
60
|
[Fact]
|
|
92
61
|
public async Task AConversationThatDoesNotExistYetLoadsEmptyInsteadOfFailing()
|
|
93
62
|
{
|
|
@@ -106,19 +75,6 @@ public sealed class ConversationStoreTests
|
|
|
106
75
|
Assert.Equal(["oi", "ola"], TextsOf(await store.LoadAsync("conversa-nova", Ct)));
|
|
107
76
|
}
|
|
108
77
|
|
|
109
|
-
// ---------------------------------------------------------------------------------
|
|
110
|
-
// doneCriteria 1: DUAS sessoes concorrentes na MESMA instancia, sem vazamento
|
|
111
|
-
// ---------------------------------------------------------------------------------
|
|
112
|
-
|
|
113
|
-
/// <summary>
|
|
114
|
-
/// <b>O canario da invariante.</b> Uma instancia de <see cref="ConversationStore"/>, duas
|
|
115
|
-
/// sessoes, muitas voltas intercaladas em paralelo: nenhuma mensagem de uma conversa
|
|
116
|
-
/// aparece na outra, e cada uma tem exatamente as suas.
|
|
117
|
-
/// </summary>
|
|
118
|
-
/// <remarks>
|
|
119
|
-
/// Este e o teste que morre se alguem guardar "a conversa atual" num campo do provider —
|
|
120
|
-
/// o defeito que em producao aparece como historico de um usuario na tela de outro.
|
|
121
|
-
/// </remarks>
|
|
122
78
|
[Fact]
|
|
123
79
|
public async Task TwoConcurrentSessionsOnTheSameProviderNeverSeeEachOthersMessages()
|
|
124
80
|
{
|
|
@@ -136,7 +92,6 @@ public sealed class ConversationStoreTests
|
|
|
136
92
|
clientB.EnqueueText($"resposta-B-{i}");
|
|
137
93
|
}
|
|
138
94
|
|
|
139
|
-
// Duas conversas distintas, o MESMO provider de historico nos dois agentes.
|
|
140
95
|
var agentA = Agent(clientA, history);
|
|
141
96
|
var agentB = Agent(clientB, history);
|
|
142
97
|
|
|
@@ -145,7 +100,7 @@ public sealed class ConversationStoreTests
|
|
|
145
100
|
history.BindConversation(sessionA, "conversa-A");
|
|
146
101
|
history.BindConversation(sessionB, "conversa-B");
|
|
147
102
|
|
|
148
|
-
// Sequencial
|
|
103
|
+
// Sequencial dentro de cada sessao (uma conversa e uma fila), concorrente entre elas.
|
|
149
104
|
var driveA = Task.Run(async () =>
|
|
150
105
|
{
|
|
151
106
|
for (var i = 0; i < Rounds; i++)
|
|
@@ -170,26 +125,18 @@ public sealed class ConversationStoreTests
|
|
|
170
125
|
Assert.Equal(Rounds * 2, conversationA.Length);
|
|
171
126
|
Assert.Equal(Rounds * 2, conversationB.Length);
|
|
172
127
|
|
|
173
|
-
// Nenhum texto do outro lado atravessou.
|
|
174
128
|
Assert.DoesNotContain(conversationA, t => t.Contains("-B-", StringComparison.Ordinal));
|
|
175
129
|
Assert.DoesNotContain(conversationB, t => t.Contains("-A-", StringComparison.Ordinal));
|
|
176
130
|
|
|
177
|
-
// E cada conversa tem as suas, na ordem.
|
|
178
131
|
Assert.Equal(
|
|
179
132
|
[.. Enumerable.Range(0, Rounds).SelectMany(i => new[] { $"pergunta-A-{i}", $"resposta-A-{i}" })],
|
|
180
133
|
conversationA);
|
|
181
134
|
|
|
182
|
-
// O que o modelo VIU tambem nao se misturou: a ultima ida de A nao carrega nada de B.
|
|
183
135
|
Assert.DoesNotContain(
|
|
184
136
|
TextsOf(clientA.ReceivedMessages[^1]),
|
|
185
137
|
t => t.Contains("-B-", StringComparison.Ordinal));
|
|
186
138
|
}
|
|
187
139
|
|
|
188
|
-
/// <summary>
|
|
189
|
-
/// A prova direta de que o estado por sessao <b>mora na sessao</b>: o mesmo provider
|
|
190
|
-
/// devolve ids diferentes para sessoes diferentes, e o id de cada sessao esta gravado
|
|
191
|
-
/// no <c>StateBag</c> dela — que e onde o MAF o serializa e o retoma.
|
|
192
|
-
/// </summary>
|
|
193
140
|
[Fact]
|
|
194
141
|
public async Task TheConversationIdLivesInTheSessionStateNotInAProviderField()
|
|
195
142
|
{
|
|
@@ -204,19 +151,13 @@ public sealed class ConversationStoreTests
|
|
|
204
151
|
var secondId = history.GetConversationId(second);
|
|
205
152
|
|
|
206
153
|
Assert.NotEqual(firstId, secondId);
|
|
207
|
-
Assert.Equal(firstId, history.GetConversationId(first));
|
|
154
|
+
Assert.Equal(firstId, history.GetConversationId(first));
|
|
208
155
|
|
|
209
|
-
// Esta gravado na sessao, sob a chave que o provider declara em StateKeys.
|
|
210
156
|
Assert.True(first.StateBag.TryGetValue<string>(ConversationStore.ConversationIdStateKey, out var stored));
|
|
211
157
|
Assert.Equal(firstId, stored);
|
|
212
158
|
Assert.Contains(ConversationStore.ConversationIdStateKey, history.StateKeys);
|
|
213
159
|
}
|
|
214
160
|
|
|
215
|
-
/// <summary>
|
|
216
|
-
/// Sem amarracao explicita o id e um <see cref="Guid.CreateVersion7"/> — ordenavel no
|
|
217
|
-
/// tempo, ao contrario de <c>Guid.NewGuid</c>. A versao 7 e legivel no proprio valor
|
|
218
|
-
/// (o nibble de versao), entao a regra da casa e verificavel e nao so declarada.
|
|
219
|
-
/// </summary>
|
|
220
161
|
[Fact]
|
|
221
162
|
public async Task AnUnboundSessionGetsATimeOrderedIdentifier()
|
|
222
163
|
{
|
|
@@ -231,11 +172,6 @@ public sealed class ConversationStoreTests
|
|
|
231
172
|
Assert.Equal(7, parsed.ToByteArray(bigEndian: true)[6] >> 4);
|
|
232
173
|
}
|
|
233
174
|
|
|
234
|
-
/// <summary>
|
|
235
|
-
/// O caminho do CRM: a conversa ja existe como registro, e a sessao entra nela — o
|
|
236
|
-
/// historico anterior aparece na primeira volta desta sessao. E o que torna a decisao
|
|
237
|
-
/// "conversa no acervo do projeto" util em vez de teorica.
|
|
238
|
-
/// </summary>
|
|
239
175
|
[Fact]
|
|
240
176
|
public async Task ASessionCanJoinAConversationThatAlreadyExistsInTheProjectStore()
|
|
241
177
|
{
|
|
@@ -258,21 +194,10 @@ public sealed class ConversationStoreTests
|
|
|
258
194
|
await agent.RunAsync("Nao chegou.", session, cancellationToken: Ct);
|
|
259
195
|
|
|
260
196
|
Assert.Contains("Ontem eu pedi um orcamento.", TextsOf(client.ReceivedMessages[0]));
|
|
261
|
-
// 2 do acervo
|
|
262
|
-
// mesma: regravar o historico inteiro faria a conversa crescer em progressao
|
|
263
|
-
// geometrica a cada turno.
|
|
197
|
+
// 2 do acervo + pedido e resposta desta volta: a volta grava so a si mesma.
|
|
264
198
|
Assert.Equal(4, (await store.LoadAsync("crm-conversa-42", Ct)).Count);
|
|
265
199
|
}
|
|
266
200
|
|
|
267
|
-
// ---------------------------------------------------------------------------------
|
|
268
|
-
// O acervo e plugavel: o kit nao conhece banco
|
|
269
|
-
// ---------------------------------------------------------------------------------
|
|
270
|
-
|
|
271
|
-
/// <summary>
|
|
272
|
-
/// A costura e substituivel: um acervo de teste que apenas anota as chamadas prova que o
|
|
273
|
-
/// provider fala com <see cref="IConversationStore"/> e com mais nada — e um projeto que
|
|
274
|
-
/// troque memoria por <c>jsonb</c> so implementa esta interface.
|
|
275
|
-
/// </summary>
|
|
276
201
|
[Fact]
|
|
277
202
|
public async Task TheProviderTalksOnlyToTheSeamSoPersistenceIsReplaceable()
|
|
278
203
|
{
|
|
@@ -289,11 +214,6 @@ public sealed class ConversationStoreTests
|
|
|
289
214
|
Assert.Equal(["load:c-1", "append:c-1"], spy.Calls);
|
|
290
215
|
}
|
|
291
216
|
|
|
292
|
-
/// <summary>
|
|
293
|
-
/// Volta que falhou nao vira historico. Persistir o pedido sem resposta deixaria a
|
|
294
|
-
/// conversa terminando numa pergunta nunca respondida — e a volta seguinte a repetiria,
|
|
295
|
-
/// com o usuario se vendo duplicado.
|
|
296
|
-
/// </summary>
|
|
297
217
|
[Fact]
|
|
298
218
|
public async Task AFailedRoundIsNotPersisted()
|
|
299
219
|
{
|
|
@@ -311,22 +231,7 @@ public sealed class ConversationStoreTests
|
|
|
311
231
|
Assert.Empty(await store.LoadAsync("conversa-falha", Ct));
|
|
312
232
|
}
|
|
313
233
|
|
|
314
|
-
|
|
315
|
-
/// <b>Uma volta sem sessao.</b> O MAF declara <c>InvokingContext.Session</c> como
|
|
316
|
-
/// anulavel, e o provider recusa esse caso: inventar um id gravaria conversa orfa e usar
|
|
317
|
-
/// uma chave fixa juntaria tenants no mesmo balde — as duas saidas silenciosas sao
|
|
318
|
-
/// piores que a recusa. Este teste mede o que o framework realmente faz por esse
|
|
319
|
-
/// caminho, em vez de supor: e o comportamento observado que fica pinado aqui.
|
|
320
|
-
/// </summary>
|
|
321
|
-
/// <remarks>
|
|
322
|
-
/// O caminho de construir <c>InvokingContext</c> a mao — que seria a forma direta de
|
|
323
|
-
/// exercitar a guarda — <b>nao esta disponivel</b>: no MAF 1.20.0 os construtores de
|
|
324
|
-
/// <c>InvokingContext</c> e <c>InvokedContext</c> sao os unicos membros da area de
|
|
325
|
-
/// <c>ChatHistoryProvider</c> marcados <c>[Experimental("MAAI001")]</c>, e com
|
|
326
|
-
/// <c>TreatWarningsAsErrors</c> isso e erro de compilacao. Consequencia registrada: um
|
|
327
|
-
/// provider de historico so pode ser testado <b>atraves de um agente real</b>, que e
|
|
328
|
-
/// como esta classe inteira o testa.
|
|
329
|
-
/// </remarks>
|
|
234
|
+
// No MAF 1.20.0 o construtor de InvokingContext e [Experimental("MAAI001")]: o provider so se exercita via agente real.
|
|
330
235
|
[Fact]
|
|
331
236
|
public async Task ARoundWithoutAnExplicitSessionStillHasAConversationOfItsOwn()
|
|
332
237
|
{
|
|
@@ -337,14 +242,10 @@ public sealed class ConversationStoreTests
|
|
|
337
242
|
|
|
338
243
|
await agent.RunAsync("sem sessao", cancellationToken: Ct);
|
|
339
244
|
|
|
340
|
-
//
|
|
341
|
-
// alcancada por este caminho, e a conversa nasce com id proprio em vez de cair num
|
|
342
|
-
// balde compartilhado.
|
|
245
|
+
// Sem sessao, o agente cria uma efemera para a volta: a conversa nasce com id proprio.
|
|
343
246
|
var conversation = Assert.Single(store.ConversationIds);
|
|
344
247
|
Assert.Equal(["sem sessao", "respondido"], TextsOf(await store.LoadAsync(conversation, Ct)));
|
|
345
248
|
|
|
346
|
-
// E a volta seguinte, tambem sem sessao, NAO herda a conversa anterior — sem sessao
|
|
347
|
-
// nao ha continuidade, que e a semantica correta de uma chamada avulsa.
|
|
348
249
|
client.EnqueueText("de novo");
|
|
349
250
|
await agent.RunAsync("outra avulsa", cancellationToken: Ct);
|
|
350
251
|
|
|
@@ -352,15 +253,10 @@ public sealed class ConversationStoreTests
|
|
|
352
253
|
Assert.DoesNotContain("sem sessao", TextsOf(client.ReceivedMessages[^1]));
|
|
353
254
|
}
|
|
354
255
|
|
|
355
|
-
/// <summary>Provider sem acervo e defeito de composicao, e falha na construcao.</summary>
|
|
356
256
|
[Fact]
|
|
357
257
|
public void TheProviderRefusesToBeBuiltWithoutAStore()
|
|
358
258
|
=> Assert.Throws<ArgumentNullException>(() => new ConversationStore(null!));
|
|
359
259
|
|
|
360
|
-
/// <summary>
|
|
361
|
-
/// O acervo em memoria e ele proprio seguro sob concorrencia — senao o canario das duas
|
|
362
|
-
/// sessoes mediria a falta de trava aqui, e nao o isolamento do provider.
|
|
363
|
-
/// </summary>
|
|
364
260
|
[Fact]
|
|
365
261
|
public async Task TheInMemoryStoreDoesNotLoseWritesUnderConcurrency()
|
|
366
262
|
{
|
|
@@ -378,7 +274,6 @@ public sealed class ConversationStoreTests
|
|
|
378
274
|
Assert.Empty(await store.LoadAsync("inexistente", Ct));
|
|
379
275
|
}
|
|
380
276
|
|
|
381
|
-
/// <summary>Acervo de teste que so anota o que lhe pediram, na ordem.</summary>
|
|
382
277
|
private sealed class RecordingConversationStore : IConversationStore
|
|
383
278
|
{
|
|
384
279
|
private readonly ConcurrentQueue<string> _calls = new();
|
|
@@ -3,47 +3,23 @@ using Xunit;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Tests;
|
|
5
5
|
|
|
6
|
-
/// <summary>
|
|
7
|
-
/// Canarios do <see cref="DeltaIngest"/> (T14). A afirmacao central e uma so, e ela e
|
|
8
|
-
/// <b>demonstrada</b> contra um acervo, nao afirmada: <b>ingerir a mesma coisa duas vezes nao
|
|
9
|
-
/// duplica nada, e mudar um campo atualiza em vez de acrescentar</b>.
|
|
10
|
-
/// </summary>
|
|
11
6
|
public sealed class DeltaIngestTests
|
|
12
7
|
{
|
|
13
|
-
/// <summary>Um documento de acervo: identidade estavel + conteudo que muda.</summary>
|
|
14
8
|
private sealed record Doc(string Slug, string Body);
|
|
15
9
|
|
|
16
10
|
private static string Key(Doc d) => d.Slug;
|
|
17
11
|
|
|
18
|
-
/// <summary>
|
|
19
|
-
/// Hash de conteudo do teste. Nao precisa ser criptografico — precisa mudar quando o
|
|
20
|
-
/// conteudo muda, que e a unica propriedade que o <see cref="DeltaIngest"/> usa.
|
|
21
|
-
/// </summary>
|
|
22
12
|
private static string Hash(Doc d) => $"h:{d.Body.Length}:{d.Body.GetHashCode(StringComparison.Ordinal)}";
|
|
23
13
|
|
|
24
|
-
/// <summary>
|
|
25
|
-
/// Um acervo minusculo que <b>aplica</b> o plano — e a unica forma de a idempotencia ser
|
|
26
|
-
/// medida no que ficou guardado, em vez de nos nomes das listas do plano.
|
|
27
|
-
/// </summary>
|
|
28
|
-
/// <remarks>
|
|
29
|
-
/// Modela um acervo de verdade: <c>Added</c> vira <b>INSERT</b> numa lista de linhas,
|
|
30
|
-
/// <c>Updated</c> vira UPDATE da linha existente e <c>Removed</c> vira DELETE. O indice de
|
|
31
|
-
/// hashes e mantido a parte, como um indice de banco. Isso importa: se o acervo fizesse
|
|
32
|
-
/// upsert de tudo, uma re-ingestao que classificasse errado sairia invisivel — aqui ela
|
|
33
|
-
/// aparece como linha duplicada, que e o defeito real que esta task combate.
|
|
34
|
-
/// </remarks>
|
|
35
14
|
private sealed class Corpus
|
|
36
15
|
{
|
|
37
16
|
private readonly List<Doc> _rows = [];
|
|
38
17
|
private readonly Dictionary<string, string> _index = new(StringComparer.Ordinal);
|
|
39
18
|
|
|
40
|
-
/// <summary>Quantos embeddings foram pagos ao todo. Nao sobe numa ingestao no-op.</summary>
|
|
41
19
|
public int EmbeddingCalls { get; private set; }
|
|
42
20
|
|
|
43
|
-
/// <summary>As linhas guardadas, na ordem em que entraram.</summary>
|
|
44
21
|
public IReadOnlyList<Doc> Rows => _rows;
|
|
45
22
|
|
|
46
|
-
/// <summary>O indice lookup-key para hash, que alimenta o proximo plano.</summary>
|
|
47
23
|
public IReadOnlyDictionary<string, string> Hashes => _index;
|
|
48
24
|
|
|
49
25
|
public DeltaPlan<Doc> Ingest(IEnumerable<Doc> incoming)
|
|
@@ -52,21 +28,21 @@ public sealed class DeltaIngestTests
|
|
|
52
28
|
|
|
53
29
|
foreach (var doc in plan.Added)
|
|
54
30
|
{
|
|
55
|
-
_rows.Add(doc);
|
|
31
|
+
_rows.Add(doc);
|
|
56
32
|
_index[doc.Slug] = Hash(doc);
|
|
57
33
|
EmbeddingCalls++;
|
|
58
34
|
}
|
|
59
35
|
|
|
60
36
|
foreach (var doc in plan.Updated)
|
|
61
37
|
{
|
|
62
|
-
_rows[_rows.FindIndex(r => r.Slug == doc.Slug)] = doc;
|
|
38
|
+
_rows[_rows.FindIndex(r => r.Slug == doc.Slug)] = doc;
|
|
63
39
|
_index[doc.Slug] = Hash(doc);
|
|
64
40
|
EmbeddingCalls++;
|
|
65
41
|
}
|
|
66
42
|
|
|
67
43
|
foreach (var slug in plan.Removed)
|
|
68
44
|
{
|
|
69
|
-
_rows.RemoveAll(r => r.Slug == slug);
|
|
45
|
+
_rows.RemoveAll(r => r.Slug == slug);
|
|
70
46
|
_index.Remove(slug);
|
|
71
47
|
}
|
|
72
48
|
|
|
@@ -81,14 +57,6 @@ public sealed class DeltaIngestTests
|
|
|
81
57
|
new("garantia", "Garantia de 12 meses."),
|
|
82
58
|
];
|
|
83
59
|
|
|
84
|
-
// ---------------------------------------------------------------------------------
|
|
85
|
-
// doneCriteria: os quatro baldes
|
|
86
|
-
// ---------------------------------------------------------------------------------
|
|
87
|
-
|
|
88
|
-
/// <summary>
|
|
89
|
-
/// Os quatro baldes numa unica passada: um item novo, um com hash mudado, um com hash
|
|
90
|
-
/// igual e uma chave que sumiu da origem.
|
|
91
|
-
/// </summary>
|
|
92
60
|
[Fact]
|
|
93
61
|
public void TheFourBucketsAreSortedInASinglePass()
|
|
94
62
|
{
|
|
@@ -109,7 +77,6 @@ public sealed class DeltaIngestTests
|
|
|
109
77
|
Assert.Equal(3, plan.WriteCount);
|
|
110
78
|
}
|
|
111
79
|
|
|
112
|
-
/// <summary>Acervo vazio: tudo e novo, nada e removido.</summary>
|
|
113
80
|
[Fact]
|
|
114
81
|
public void AnEmptyCorpusMakesEverythingNew()
|
|
115
82
|
{
|
|
@@ -121,12 +88,6 @@ public sealed class DeltaIngestTests
|
|
|
121
88
|
Assert.Empty(plan.Removed);
|
|
122
89
|
}
|
|
123
90
|
|
|
124
|
-
/// <summary>
|
|
125
|
-
/// Remessa vazia com acervo cheio: <b>tudo</b> sai em <c>Removed</c>. E o comportamento
|
|
126
|
-
/// correto e tambem o mais perigoso — e por isso o plano e um plano: quem decide se
|
|
127
|
-
/// aplica um <c>Removed</c> de 100% e o projeto, que sabe se a origem realmente esvaziou
|
|
128
|
-
/// ou se o crawler falhou.
|
|
129
|
-
/// </summary>
|
|
130
91
|
[Fact]
|
|
131
92
|
public void AnEmptyShipmentAgainstAFullCorpusRemovesEverything()
|
|
132
93
|
{
|
|
@@ -140,15 +101,6 @@ public sealed class DeltaIngestTests
|
|
|
140
101
|
Assert.Empty(plan.Updated);
|
|
141
102
|
}
|
|
142
103
|
|
|
143
|
-
// ---------------------------------------------------------------------------------
|
|
144
|
-
// doneCriteria: IDEMPOTENCIA, medida no acervo
|
|
145
|
-
// ---------------------------------------------------------------------------------
|
|
146
|
-
|
|
147
|
-
/// <summary>
|
|
148
|
-
/// <b>A prova principal.</b> Ingerir a mesma remessa duas vezes: na segunda o plano e
|
|
149
|
-
/// vazio nos tres baldes de escrita, o acervo tem exatamente os mesmos documentos e
|
|
150
|
-
/// <b>nenhum embedding a mais foi pago</b>.
|
|
151
|
-
/// </summary>
|
|
152
104
|
[Fact]
|
|
153
105
|
public void IngestingTheSameShipmentTwiceLeavesTheCorpusUntouched()
|
|
154
106
|
{
|
|
@@ -168,21 +120,14 @@ public sealed class DeltaIngestTests
|
|
|
168
120
|
Assert.Equal(0, second.WriteCount);
|
|
169
121
|
Assert.Equal(3, second.Unchanged.Count);
|
|
170
122
|
|
|
171
|
-
|
|
172
|
-
Assert.Equal(3, corpus.Rows.Count); // <- a linha que morre se a mesma coisa duplicar
|
|
123
|
+
Assert.Equal(3, corpus.Rows.Count);
|
|
173
124
|
Assert.Equal(before, corpus.Rows.OrderBy(d => d.Slug, StringComparer.Ordinal));
|
|
174
125
|
Assert.Equal(3, corpus.EmbeddingCalls);
|
|
175
126
|
|
|
176
|
-
// E uma terceira, para que "duas vezes" nao pareca coincidencia.
|
|
177
127
|
Assert.True(corpus.Ingest(shipment).IsNoOp);
|
|
178
128
|
Assert.Equal(3, corpus.EmbeddingCalls);
|
|
179
129
|
}
|
|
180
130
|
|
|
181
|
-
/// <summary>
|
|
182
|
-
/// <b>Mudar um campo atualiza, nao acrescenta.</b> A contagem do acervo nao sobe, o slug
|
|
183
|
-
/// continua unico e o corpo e o novo — que e a diferenca entre uma ingestao delta e a
|
|
184
|
-
/// ingestao apaga-tudo que este tipo existe para substituir.
|
|
185
|
-
/// </summary>
|
|
186
131
|
[Fact]
|
|
187
132
|
public void ChangingOneFieldUpdatesInPlaceInsteadOfAppending()
|
|
188
133
|
{
|
|
@@ -192,7 +137,7 @@ public sealed class DeltaIngestTests
|
|
|
192
137
|
Doc[] revised =
|
|
193
138
|
[
|
|
194
139
|
new("politica-troca", "Trocas em ate 7 dias."),
|
|
195
|
-
new("prazo-entrega", "Entrega em 1 dia util."),
|
|
140
|
+
new("prazo-entrega", "Entrega em 1 dia util."),
|
|
196
141
|
new("garantia", "Garantia de 12 meses."),
|
|
197
142
|
];
|
|
198
143
|
|
|
@@ -203,22 +148,15 @@ public sealed class DeltaIngestTests
|
|
|
203
148
|
Assert.Empty(plan.Removed);
|
|
204
149
|
Assert.Equal(2, plan.Unchanged.Count);
|
|
205
150
|
|
|
206
|
-
Assert.Equal(3, corpus.Rows.Count);
|
|
207
|
-
Assert.Single(corpus.Rows, d => d.Slug == "prazo-entrega");
|
|
151
|
+
Assert.Equal(3, corpus.Rows.Count);
|
|
152
|
+
Assert.Single(corpus.Rows, d => d.Slug == "prazo-entrega");
|
|
208
153
|
Assert.Equal("Entrega em 1 dia util.", corpus.Rows.Single(d => d.Slug == "prazo-entrega").Body);
|
|
209
154
|
Assert.Equal(4, corpus.EmbeddingCalls); // 3 iniciais + 1 mudanca
|
|
210
155
|
|
|
211
|
-
// E a re-ingestao da remessa revisada volta a ser no-op.
|
|
212
156
|
Assert.True(corpus.Ingest(revised).IsNoOp);
|
|
213
157
|
Assert.Equal(4, corpus.EmbeddingCalls);
|
|
214
158
|
}
|
|
215
159
|
|
|
216
|
-
/// <summary>
|
|
217
|
-
/// A mesma afirmacao dita como <b>contagem de linhas</b>, e antes de qualquer outra
|
|
218
|
-
/// asserçao: re-ingerir nao insere uma segunda copia. E a linha que fica vermelha com
|
|
219
|
-
/// "6" no lugar de "3" quando a comparacao por lookup-key se quebra — a duplicacao
|
|
220
|
-
/// literal, e nao um nome de balde trocado.
|
|
221
|
-
/// </summary>
|
|
222
160
|
[Fact]
|
|
223
161
|
public void ReIngestingDoesNotInsertASecondCopyOfTheSameDocument()
|
|
224
162
|
{
|
|
@@ -233,10 +171,6 @@ public sealed class DeltaIngestTests
|
|
|
233
171
|
Assert.Equal(3, corpus.EmbeddingCalls);
|
|
234
172
|
}
|
|
235
173
|
|
|
236
|
-
/// <summary>
|
|
237
|
-
/// Idempotencia sobrevive a reordenacao: a mesma remessa embaralhada continua sendo
|
|
238
|
-
/// no-op. A identidade e a chave, nunca a posicao na lista.
|
|
239
|
-
/// </summary>
|
|
240
174
|
[Fact]
|
|
241
175
|
public void OrderDoesNotMakeAShipmentLookDifferent()
|
|
242
176
|
{
|
|
@@ -249,15 +183,6 @@ public sealed class DeltaIngestTests
|
|
|
249
183
|
Assert.Equal(3, corpus.EmbeddingCalls);
|
|
250
184
|
}
|
|
251
185
|
|
|
252
|
-
// ---------------------------------------------------------------------------------
|
|
253
|
-
// doneCriteria: chave duplicada falha de forma explicita
|
|
254
|
-
// ---------------------------------------------------------------------------------
|
|
255
|
-
|
|
256
|
-
/// <summary>
|
|
257
|
-
/// Chave repetida derruba o plano dizendo a chave <b>e as duas posicoes</b>. Ficar com o
|
|
258
|
-
/// ultimo gravaria um dos dois ao acaso; deduplicar sozinho esconderia o defeito da
|
|
259
|
-
/// origem, que e onde ele tem conserto.
|
|
260
|
-
/// </summary>
|
|
261
186
|
[Fact]
|
|
262
187
|
public void ADuplicateLookupKeyFailsNamingTheKeyAndBothPositions()
|
|
263
188
|
{
|
|
@@ -277,7 +202,6 @@ public sealed class DeltaIngestTests
|
|
|
277
202
|
Assert.Contains("deixa de ser deterministico", failure.Message, StringComparison.Ordinal);
|
|
278
203
|
}
|
|
279
204
|
|
|
280
|
-
/// <summary>Chave em branco colapsaria itens distintos num so — e recusada.</summary>
|
|
281
205
|
[Theory]
|
|
282
206
|
[InlineData("")]
|
|
283
207
|
[InlineData(" ")]
|
|
@@ -289,10 +213,6 @@ public sealed class DeltaIngestTests
|
|
|
289
213
|
Assert.Contains("posicao 0", failure.Message, StringComparison.Ordinal);
|
|
290
214
|
}
|
|
291
215
|
|
|
292
|
-
/// <summary>
|
|
293
|
-
/// Hash em branco e recusado: sem versao nao ha como separar "nao mudou" de "mudou", e
|
|
294
|
-
/// tratar a ausencia como "mudou" reintroduz o re-embedding de tudo — o defeito original.
|
|
295
|
-
/// </summary>
|
|
296
216
|
[Fact]
|
|
297
217
|
public void ABlankContentHashIsRefusedInsteadOfMeaningChanged()
|
|
298
218
|
{
|
|
@@ -302,7 +222,6 @@ public sealed class DeltaIngestTests
|
|
|
302
222
|
Assert.Contains("politica-troca", failure.Message, StringComparison.Ordinal);
|
|
303
223
|
}
|
|
304
224
|
|
|
305
|
-
/// <summary>Argumento nulo e defeito de chamada, e falha na chamada.</summary>
|
|
306
225
|
[Fact]
|
|
307
226
|
public void TheRequiredArgumentsAreNotOptional()
|
|
308
227
|
{
|
|
@@ -314,15 +233,6 @@ public sealed class DeltaIngestTests
|
|
|
314
233
|
Assert.Throws<ArgumentNullException>(() => DeltaIngest.Plan(ThreeDocs(), Key, Hash, null!));
|
|
315
234
|
}
|
|
316
235
|
|
|
317
|
-
// ---------------------------------------------------------------------------------
|
|
318
|
-
// Identidade e versao sao perguntas diferentes
|
|
319
|
-
// ---------------------------------------------------------------------------------
|
|
320
|
-
|
|
321
|
-
/// <summary>
|
|
322
|
-
/// A comparacao de chave e <b>ordinal</b>: <c>Politica</c> e <c>politica</c> sao dois
|
|
323
|
-
/// documentos. Deixar a cultura decidir faria o mesmo corpus produzir planos diferentes
|
|
324
|
-
/// conforme a maquina que roda a ingestao.
|
|
325
|
-
/// </summary>
|
|
326
236
|
[Fact]
|
|
327
237
|
public void KeysAreComparedOrdinallySoThePlanDoesNotDependOnTheMachine()
|
|
328
238
|
{
|
|
@@ -341,11 +251,6 @@ public sealed class DeltaIngestTests
|
|
|
341
251
|
Assert.Equal(["politica-troca"], plan.Removed);
|
|
342
252
|
}
|
|
343
253
|
|
|
344
|
-
/// <summary>
|
|
345
|
-
/// O plano nao le o corpo do item: um documento cuja chave e hash nao mudaram e
|
|
346
|
-
/// <c>Unchanged</c> ainda que o objeto seja outra instancia. E o que permite ao acervo
|
|
347
|
-
/// reprocessar so o que mudou de verdade.
|
|
348
|
-
/// </summary>
|
|
349
254
|
[Fact]
|
|
350
255
|
public void EqualityIsDecidedByKeyAndHashNotByInstance()
|
|
351
256
|
{
|
|
@@ -357,7 +262,6 @@ public sealed class DeltaIngestTests
|
|
|
357
262
|
Assert.True(corpus.Ingest(reconstructed).IsNoOp);
|
|
358
263
|
}
|
|
359
264
|
|
|
360
|
-
/// <summary>A remessa e enumerada uma unica vez — importa quando ela vem de um stream.</summary>
|
|
361
265
|
[Fact]
|
|
362
266
|
public void TheShipmentIsEnumeratedExactlyOnce()
|
|
363
267
|
{
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs
CHANGED
|
@@ -4,35 +4,11 @@ using Xunit;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Tests.Evals;
|
|
6
6
|
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// A REGUA, testada contra a resposta ERRADA.
|
|
9
|
-
/// </summary>
|
|
10
|
-
/// <remarks>
|
|
11
|
-
/// <para>
|
|
12
|
-
/// Este arquivo nasceu de um achado do Gate 3: das seis <see cref="EvalAssertionKind"/>,
|
|
13
|
-
/// afrouxar CINCO para sempre-passar nao matava um unico teste. A suite continuava
|
|
14
|
-
/// "152 total, 0 falhou" com o motor de assercao praticamente inerte — e duas das cinco
|
|
15
|
-
/// (<c>NoPii</c> e <c>JsonHasKey</c>) sao usadas pelo dataset commitado, ou seja, os quatro
|
|
16
|
-
/// cenarios exemplares dependiam de reguas que nunca haviam sido provadas.
|
|
17
|
-
/// </para>
|
|
18
|
-
/// <para>
|
|
19
|
-
/// A causa e a mesma de todo canario inerte desta base: testava-se que a assercao APROVA o
|
|
20
|
-
/// que deve aprovar, nunca que ela REPROVA o que deve reprovar. Uma regua so separa duas
|
|
21
|
-
/// coisas se for exercitada dos DOIS lados — e a que so viu o lado bom e indistinguivel de
|
|
22
|
-
/// <c>return true</c>.
|
|
23
|
-
/// </para>
|
|
24
|
-
/// <para>
|
|
25
|
-
/// Por isso cada teste aqui carrega o PAR: a resposta que passa e a resposta que deveria
|
|
26
|
-
/// reprovar. Afrouxar qualquer <see cref="EvalAssertionKind"/> mata o teste que a nomeia.
|
|
27
|
-
/// </para>
|
|
28
|
-
/// </remarks>
|
|
29
7
|
public sealed class EvalAssertionTests
|
|
30
8
|
{
|
|
31
|
-
/// <summary>Resposta so de texto — a superficie que as assercoes de texto leem.</summary>
|
|
32
9
|
private static ChatResponse Text(string text)
|
|
33
10
|
=> new(new ChatMessage(ChatRole.Assistant, text));
|
|
34
11
|
|
|
35
|
-
/// <summary>Resposta que escolheu uma ferramenta, com argumentos.</summary>
|
|
36
12
|
private static ChatResponse Tool(string name, params (string Key, object? Value)[] args)
|
|
37
13
|
{
|
|
38
14
|
var arguments = new Dictionary<string, object?>(StringComparer.Ordinal);
|
|
@@ -45,8 +21,6 @@ public sealed class EvalAssertionTests
|
|
|
45
21
|
return new ChatResponse(message);
|
|
46
22
|
}
|
|
47
23
|
|
|
48
|
-
// ---------------------------------------------------------------- Contains
|
|
49
|
-
|
|
50
24
|
[Fact]
|
|
51
25
|
public void ContainsSeparatesTheAnswerThatCarriesTheTextFromTheOneThatDoesNot()
|
|
52
26
|
{
|
|
@@ -68,8 +42,6 @@ public sealed class EvalAssertionTests
|
|
|
68
42
|
Assert.False(assertion.Evaluate(Text("ola")).Passed);
|
|
69
43
|
}
|
|
70
44
|
|
|
71
|
-
// ------------------------------------------------------------- NotContains
|
|
72
|
-
|
|
73
45
|
[Fact]
|
|
74
46
|
public void NotContainsSeparatesTheCleanAnswerFromTheOneThatLeaksTheForbiddenText()
|
|
75
47
|
{
|
|
@@ -82,8 +54,6 @@ public sealed class EvalAssertionTests
|
|
|
82
54
|
Assert.Contains("desconto", reproved.Reason, StringComparison.Ordinal);
|
|
83
55
|
}
|
|
84
56
|
|
|
85
|
-
// -------------------------------------------------------------- JsonHasKey
|
|
86
|
-
|
|
87
57
|
[Fact]
|
|
88
58
|
public void JsonHasKeySeparatesTheObjectThatCarriesTheKeyFromTheOneThatDoesNot()
|
|
89
59
|
{
|
|
@@ -109,15 +79,12 @@ public sealed class EvalAssertionTests
|
|
|
109
79
|
[Fact]
|
|
110
80
|
public void JsonHasKeyReprovesAJsonArrayEvenWhenItParses()
|
|
111
81
|
{
|
|
112
|
-
// Um array
|
|
113
|
-
// aprovaria uma resposta em que a chave nao pode nem existir.
|
|
82
|
+
// Um array e JSON valido: a regua tem de cobrar a chave, nao so o parse.
|
|
114
83
|
var assertion = new EvalAssertion { Name = "tem-total", Kind = EvalAssertionKind.JsonHasKey, Target = "total" };
|
|
115
84
|
|
|
116
85
|
Assert.False(assertion.Evaluate(Text("""[1, 2, 3]""")).Passed);
|
|
117
86
|
}
|
|
118
87
|
|
|
119
|
-
// ------------------------------------------------------------------- NoPii
|
|
120
|
-
|
|
121
88
|
[Theory]
|
|
122
89
|
[InlineData("Entre em contato pelo lucas@example.com quando puder.", "e-mail")]
|
|
123
90
|
[InlineData("O CPF do titular e 123.456.789-01.", "CPF")]
|
|
@@ -139,8 +106,6 @@ public sealed class EvalAssertionTests
|
|
|
139
106
|
Assert.True(assertion.Evaluate(Text("Bom dia! Como posso ajudar voce hoje?")).Passed);
|
|
140
107
|
}
|
|
141
108
|
|
|
142
|
-
// -------------------------------------------------------------- ToolCalled
|
|
143
|
-
|
|
144
109
|
[Fact]
|
|
145
110
|
public void ToolCalledSeparatesTheRightToolFromAnotherToolAndFromNoToolAtAll()
|
|
146
111
|
{
|
|
@@ -155,8 +120,6 @@ public sealed class EvalAssertionTests
|
|
|
155
120
|
Assert.False(assertion.Evaluate(Text("vou escalar para um humano")).Passed);
|
|
156
121
|
}
|
|
157
122
|
|
|
158
|
-
// ------------------------------------------------------------ ToolArgument
|
|
159
|
-
|
|
160
123
|
[Fact]
|
|
161
124
|
public void ToolArgumentSeparatesTheRightArgumentFromTheWrongValueAndTheMissingKey()
|
|
162
125
|
{
|
|
@@ -179,13 +142,9 @@ public sealed class EvalAssertionTests
|
|
|
179
142
|
"ToolArgument aceitou uma chamada sem a CHAVE exigida");
|
|
180
143
|
}
|
|
181
144
|
|
|
182
|
-
// ------------------------------------- o veredito diz QUEM julgou (B2)
|
|
183
|
-
|
|
184
145
|
[Fact]
|
|
185
146
|
public void EveryOutcomeCarriesTheNameAndTheKindOfTheAssertionThatProducedIt()
|
|
186
147
|
{
|
|
187
|
-
// Sem este carimbo, uma falha chega a jusante como contador e o sumario nao consegue
|
|
188
|
-
// nomear nada — foi assim que o eval mordia anonimamente.
|
|
189
148
|
var assertion = new EvalAssertion { Name = "saudacao", Kind = EvalAssertionKind.Contains, Value = "Bom dia" };
|
|
190
149
|
|
|
191
150
|
foreach (var outcome in new[] { assertion.Evaluate(Text("Bom dia")), assertion.Evaluate(Text("nada")) })
|