@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/MediaAliasIsNotAnAgentTests.cs
CHANGED
|
@@ -5,21 +5,6 @@ using Xunit;
|
|
|
5
5
|
|
|
6
6
|
namespace Morph.AiKit.Tests;
|
|
7
7
|
|
|
8
|
-
/// <summary>
|
|
9
|
-
/// A companheira obrigatoria de <c>AgentApi.Images</c>/<c>AgentApi.Videos</c> existirem:
|
|
10
|
-
/// <b>um alias de midia nao vira agente</b>.
|
|
11
|
-
/// </summary>
|
|
12
|
-
/// <remarks>
|
|
13
|
-
/// <para>
|
|
14
|
-
/// Um registry so e um vocabulario so de alias e a decisao (<c>decisions.md</c> D3) — e
|
|
15
|
-
/// justamente por isso a recusa precisa existir. Sem ela, um alias de imagem passado por
|
|
16
|
-
/// engano viraria um agente de chat apontando para um modelo que nao fala chat: a falha
|
|
17
|
-
/// apareceria no primeiro turno, com uma mensagem do provider que nao diz o que aconteceu.
|
|
18
|
-
/// </para>
|
|
19
|
-
/// <para>
|
|
20
|
-
/// Enum novo sem recusa e uma armadilha nova. Este arquivo e o que impede que ela volte.
|
|
21
|
-
/// </para>
|
|
22
|
-
/// </remarks>
|
|
23
8
|
public sealed class MediaAliasIsNotAnAgentTests
|
|
24
9
|
{
|
|
25
10
|
private const string MediaAliasDocument = """
|
|
@@ -52,7 +37,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
52
37
|
private static AgentSpec Spec(string alias, AgentApi? api = null)
|
|
53
38
|
=> new() { Alias = alias, Instructions = "voce e um agente de teste", Api = api };
|
|
54
39
|
|
|
55
|
-
/// <summary>O registry ACEITA <c>images</c> e <c>videos</c> — um vocabulario so de alias.</summary>
|
|
56
40
|
[Theory]
|
|
57
41
|
[InlineData("imagem", AgentApi.Images)]
|
|
58
42
|
[InlineData("video", AgentApi.Videos)]
|
|
@@ -63,7 +47,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
63
47
|
Assert.Equal(expected, registry.Resolve(alias).Api);
|
|
64
48
|
}
|
|
65
49
|
|
|
66
|
-
/// <summary>E a fabrica RECUSA, na construcao, com erro nomeado.</summary>
|
|
67
50
|
[Theory]
|
|
68
51
|
[InlineData("imagem")]
|
|
69
52
|
[InlineData("video")]
|
|
@@ -78,10 +61,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
78
61
|
Assert.Contains("EnqueueAsync", ex.Message, StringComparison.Ordinal);
|
|
79
62
|
}
|
|
80
63
|
|
|
81
|
-
/// <summary>
|
|
82
|
-
/// A recusa tambem cobre o caminho pela SPEC: um alias de conversa com
|
|
83
|
-
/// <c>Api = Images</c> na spec e a mesma confusao, pela outra porta.
|
|
84
|
-
/// </summary>
|
|
85
64
|
[Fact]
|
|
86
65
|
public void TheFactoryRefusesAMediaProtocolDeclaredOnTheSpec()
|
|
87
66
|
{
|
|
@@ -93,7 +72,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
93
72
|
Assert.Equal(AgentFactoryError.MediaAliasIsNotAnAgent, ex.Reason);
|
|
94
73
|
}
|
|
95
74
|
|
|
96
|
-
/// <summary>Alias de conversa continua passando — a recusa nova nao pode ser um bloqueio geral.</summary>
|
|
97
75
|
[Fact]
|
|
98
76
|
public void AChatAliasStillBuildsAnAgent()
|
|
99
77
|
{
|
|
@@ -103,7 +81,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
103
81
|
Assert.NotNull(factory.Create(Spec("texto")));
|
|
104
82
|
}
|
|
105
83
|
|
|
106
|
-
/// <summary>O predicado mora num lugar so — duas copias e como as duas discordam.</summary>
|
|
107
84
|
[Theory]
|
|
108
85
|
[InlineData(AgentApi.Responses, false)]
|
|
109
86
|
[InlineData(AgentApi.Chat, false)]
|
|
@@ -112,7 +89,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
112
89
|
public void IsMediaAnswersForEveryProtocol(AgentApi api, bool expected)
|
|
113
90
|
=> Assert.Equal(expected, api.IsMedia());
|
|
114
91
|
|
|
115
|
-
/// <summary>Um <c>api</c> inventado continua sendo recusado na carga do documento.</summary>
|
|
116
92
|
[Fact]
|
|
117
93
|
public void AnUnknownApiIsStillRefusedWithTheFullListOfValidValues()
|
|
118
94
|
{
|
|
@@ -4,37 +4,12 @@ using Xunit;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Tests;
|
|
6
6
|
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// Canários da MARCAÇÃO — a quarta afirmação da rubrica `media-claims`, e a última que este
|
|
9
|
-
/// kit ainda prendia só por prosa.
|
|
10
|
-
/// </summary>
|
|
11
|
-
/// <remarks>
|
|
12
|
-
/// <para>
|
|
13
|
-
/// <b>A assimetria que estes testes fecham, medida:</b> a regra do turno de conversa estava
|
|
14
|
-
/// presa por TIPO (`internal` + id de job em vez de bytes) e provada por mutação; custo e
|
|
15
|
-
/// revisão humana, por ESTADO. A marcação — que é a obrigação legal, o art. 50 do AI Act que
|
|
16
|
-
/// motivou a issue — estava escrita em `MediaJobState.cs` ("publicado, com a marcacao
|
|
17
|
-
/// aplicada") e em `MediaRequest.cs` ("marcacao visivel obrigatoria"), e um `grep` por label,
|
|
18
|
-
/// watermark ou disclosure no kit inteiro voltava <b>vazio</b>. Zero mecanismo.
|
|
19
|
-
/// </para>
|
|
20
|
-
/// <para>
|
|
21
|
-
/// Uma rubrica que cobra de terceiros uma prova que o próprio exemplar não dá é o defeito que
|
|
22
|
-
/// esta epic catalogou. Estes testes são o inverso literal dele.
|
|
23
|
-
/// </para>
|
|
24
|
-
/// <para>
|
|
25
|
-
/// <b>Fronteira honesta:</b> nada aqui prova que o rótulo chegou aos olhos de alguém — a
|
|
26
|
-
/// renderização é do aplicativo consumidor, e nenhum tipo em C# alcança um pixel. O que se
|
|
27
|
-
/// prova é que a publicação <b>não acontece</b> sem a decisão registrada, com texto e
|
|
28
|
-
/// superfície nomeados.
|
|
29
|
-
/// </para>
|
|
30
|
-
/// </remarks>
|
|
31
7
|
public sealed class MediaDisclosureTests
|
|
32
8
|
{
|
|
33
9
|
private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
|
|
34
10
|
|
|
35
11
|
private static MediaDisclosure Label => MediaDisclosure.Create("Imagem gerada por IA", "legenda", T0);
|
|
36
12
|
|
|
37
|
-
/// <summary>Um job aprovado, na finalidade pedida.</summary>
|
|
38
13
|
private static MediaJob Approved(MediaPurpose purpose)
|
|
39
14
|
{
|
|
40
15
|
var job = MediaJob.Create(
|
|
@@ -50,11 +25,6 @@ public sealed class MediaDisclosureTests
|
|
|
50
25
|
return job.Approve(MediaReview.ApprovedBy("lucas", T0)).Value;
|
|
51
26
|
}
|
|
52
27
|
|
|
53
|
-
// ---------------------------------------------------------------------------------
|
|
54
|
-
// A política, por finalidade — dado, não comentário
|
|
55
|
-
// ---------------------------------------------------------------------------------
|
|
56
|
-
|
|
57
|
-
/// <summary>Catálogo e marketing exigem marcação; rascunho não chega a publicar.</summary>
|
|
58
28
|
[Theory]
|
|
59
29
|
[InlineData(MediaPurpose.CatalogPhoto, true)]
|
|
60
30
|
[InlineData(MediaPurpose.Marketing, true)]
|
|
@@ -62,7 +32,6 @@ public sealed class MediaDisclosureTests
|
|
|
62
32
|
public void RequiresDisclosureIsDeclaredPerPurpose(MediaPurpose purpose, bool expected)
|
|
63
33
|
=> Assert.Equal(expected, purpose.RequiresDisclosure());
|
|
64
34
|
|
|
65
|
-
/// <summary>Rascunho é a única finalidade não publicável.</summary>
|
|
66
35
|
[Theory]
|
|
67
36
|
[InlineData(MediaPurpose.CatalogPhoto, true)]
|
|
68
37
|
[InlineData(MediaPurpose.Marketing, true)]
|
|
@@ -70,14 +39,6 @@ public sealed class MediaDisclosureTests
|
|
|
70
39
|
public void OnlyDraftIsUnpublishable(MediaPurpose purpose, bool expected)
|
|
71
40
|
=> Assert.Equal(expected, purpose.IsPublishable());
|
|
72
41
|
|
|
73
|
-
// ---------------------------------------------------------------------------------
|
|
74
|
-
// A porta: publicar sem marcação NÃO é um caminho
|
|
75
|
-
// ---------------------------------------------------------------------------------
|
|
76
|
-
|
|
77
|
-
/// <summary>
|
|
78
|
-
/// O caso central. Um job aprovado, de catálogo, sem marcação: <b>não publica</b>, e a
|
|
79
|
-
/// razão nomeia o que falta.
|
|
80
|
-
/// </summary>
|
|
81
42
|
[Theory]
|
|
82
43
|
[InlineData(MediaPurpose.CatalogPhoto)]
|
|
83
44
|
[InlineData(MediaPurpose.Marketing)]
|
|
@@ -93,7 +54,6 @@ public sealed class MediaDisclosureTests
|
|
|
93
54
|
Assert.Contains("marcacao", result.Error.Message, StringComparison.OrdinalIgnoreCase);
|
|
94
55
|
}
|
|
95
56
|
|
|
96
|
-
/// <summary>Com a marcação, publica — e ela fica GRAVADA no job, não descartada.</summary>
|
|
97
57
|
[Fact]
|
|
98
58
|
public void PublishSucceedsWithADisclosureAndRecordsIt()
|
|
99
59
|
{
|
|
@@ -106,10 +66,6 @@ public sealed class MediaDisclosureTests
|
|
|
106
66
|
Assert.Equal(T0, published.Value.Disclosure.AppliedAt);
|
|
107
67
|
}
|
|
108
68
|
|
|
109
|
-
/// <summary>
|
|
110
|
-
/// Rascunho não publica, <b>nem com marcação e nem estando aprovado</b>. Era uma frase
|
|
111
|
-
/// num comentário XML; agora é uma recusa.
|
|
112
|
-
/// </summary>
|
|
113
69
|
[Fact]
|
|
114
70
|
public void ADraftNeverPublishesEvenApprovedAndLabelled()
|
|
115
71
|
{
|
|
@@ -119,11 +75,6 @@ public sealed class MediaDisclosureTests
|
|
|
119
75
|
Assert.Contains("nao publica", result.Error!.Message, StringComparison.OrdinalIgnoreCase);
|
|
120
76
|
}
|
|
121
77
|
|
|
122
|
-
/// <summary>
|
|
123
|
-
/// A ordem das portas importa: o estado vem antes da marcação. Um job NÃO aprovado, sem
|
|
124
|
-
/// marcação, tem de reclamar da revisão humana — mandar consertar a marcação de algo que
|
|
125
|
-
/// nem foi revisado manda consertar a coisa errada.
|
|
126
|
-
/// </summary>
|
|
127
78
|
[Fact]
|
|
128
79
|
public void TheStateGateIsReportedBeforeTheDisclosureGate()
|
|
129
80
|
{
|
|
@@ -142,29 +93,6 @@ public sealed class MediaDisclosureTests
|
|
|
142
93
|
Assert.DoesNotContain("marcacao", result.Error.Message, StringComparison.OrdinalIgnoreCase);
|
|
143
94
|
}
|
|
144
95
|
|
|
145
|
-
// ---------------------------------------------------------------------------------
|
|
146
|
-
// A marcação vazia é o campo preenchido "para passar"
|
|
147
|
-
// ---------------------------------------------------------------------------------
|
|
148
|
-
|
|
149
|
-
/// <summary>
|
|
150
|
-
/// <b>A construção não tem outra porta.</b> Este é o teste que o nome do anterior prometia
|
|
151
|
-
/// e não entregava.
|
|
152
|
-
/// </summary>
|
|
153
|
-
/// <remarks>
|
|
154
|
-
/// <para>
|
|
155
|
-
/// A primeira versão de <see cref="MediaDisclosure"/> era um record POSICIONAL, e record
|
|
156
|
-
/// posicional gera construtor <b>público</b>. A validação vivia só em
|
|
157
|
-
/// <see cref="MediaDisclosure.Create"/>, então um consumidor externo fazia
|
|
158
|
-
/// <c>new MediaDisclosure("", "", at)</c> e publicava com rótulo vazio — medido:
|
|
159
|
-
/// <c>IsSuccess=True State=Published</c>. O canário de então exercitava a FÁBRICA e passava
|
|
160
|
-
/// verde o tempo todo: media a copia, nao o objeto.
|
|
161
|
-
/// </para>
|
|
162
|
-
/// <para>
|
|
163
|
-
/// Por isso a asserção é por REFLEXÃO sobre o tipo. Ela não pode ser escrita como
|
|
164
|
-
/// <c>new MediaDisclosure(...)</c> — hoje isso nem compila, e é esse o ponto: uma asserção
|
|
165
|
-
/// que só existe enquanto o defeito existe não é canário.
|
|
166
|
-
/// </para>
|
|
167
|
-
/// </remarks>
|
|
168
96
|
[Fact]
|
|
169
97
|
public void TheTypeExposesNoPublicConstructor()
|
|
170
98
|
{
|
|
@@ -179,7 +107,6 @@ public sealed class MediaDisclosureTests
|
|
|
179
107
|
"MediaReview. Encontrados: " + string.Join(" | ", publicCtors.Select(c => c.ToString())));
|
|
180
108
|
}
|
|
181
109
|
|
|
182
|
-
/// <summary>Texto ou superfície em branco é recusado pela única porta que existe.</summary>
|
|
183
110
|
[Theory]
|
|
184
111
|
[InlineData("", "legenda")]
|
|
185
112
|
[InlineData(" ", "legenda")]
|
|
@@ -188,10 +115,6 @@ public sealed class MediaDisclosureTests
|
|
|
188
115
|
public void ABlankDisclosureIsRefusedByTheOnlyFactory(string text, string surface)
|
|
189
116
|
=> Assert.Throws<ArgumentException>(() => MediaDisclosure.Create(text, surface, T0));
|
|
190
117
|
|
|
191
|
-
/// <summary>
|
|
192
|
-
/// A superfície é obrigatória junto do texto: "marcado" sem dizer ONDE não responde se
|
|
193
|
-
/// alguém consegue ver.
|
|
194
|
-
/// </summary>
|
|
195
118
|
[Fact]
|
|
196
119
|
public void TheDisclosureCarriesBothTheTextAndWhereItAppears()
|
|
197
120
|
{
|
|
@@ -201,7 +124,6 @@ public sealed class MediaDisclosureTests
|
|
|
201
124
|
Assert.Equal("sobreposicao", disclosure.Surface);
|
|
202
125
|
}
|
|
203
126
|
|
|
204
|
-
/// <summary>Uma publicação recusada não muda o job — o registro continua imutável.</summary>
|
|
205
127
|
[Fact]
|
|
206
128
|
public void ARefusedPublishLeavesTheJobUntouched()
|
|
207
129
|
{
|
|
@@ -5,15 +5,6 @@ using Xunit;
|
|
|
5
5
|
|
|
6
6
|
namespace Morph.AiKit.Tests;
|
|
7
7
|
|
|
8
|
-
/// <summary>
|
|
9
|
-
/// Canarios do enqueue e do worker. <b>Nenhum deles toca API paga</b> — o unico ponto que
|
|
10
|
-
/// gastaria dinheiro e o <see cref="FakeMediaProvider"/>, e a suite conta as chamadas dele.
|
|
11
|
-
/// </summary>
|
|
12
|
-
/// <remarks>
|
|
13
|
-
/// As afirmacoes aqui sao as tres que o dinheiro cobra: um retry nao paga de novo, um
|
|
14
|
-
/// estouro de teto recusa <b>antes</b> da chamada, e um bloqueio de moderacao vai para
|
|
15
|
-
/// <c>RejectedByPolicy</c> em vez de virar retry.
|
|
16
|
-
/// </remarks>
|
|
17
8
|
public sealed class MediaGeneratorTests
|
|
18
9
|
{
|
|
19
10
|
private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
|
|
@@ -42,11 +33,6 @@ public sealed class MediaGeneratorTests
|
|
|
42
33
|
jobs, store, provider);
|
|
43
34
|
}
|
|
44
35
|
|
|
45
|
-
// ---------------------------------------------------------------------------------
|
|
46
|
-
// Enqueue: id de job, nunca bytes
|
|
47
|
-
// ---------------------------------------------------------------------------------
|
|
48
|
-
|
|
49
|
-
/// <summary>Um pedido valido vira job em <c>Pending</c>, e o retorno e um id.</summary>
|
|
50
36
|
[Fact]
|
|
51
37
|
public async Task EnqueueReturnsAJobIdAndTheJobStartsPending()
|
|
52
38
|
{
|
|
@@ -58,11 +44,9 @@ public sealed class MediaGeneratorTests
|
|
|
58
44
|
var job = await generator.GetAsync(enqueued.Value, Ct);
|
|
59
45
|
Assert.Equal(MediaJobState.Pending, job.Value.State);
|
|
60
46
|
|
|
61
|
-
// Nada foi gasto: o enqueue NAO chama provider.
|
|
62
47
|
Assert.Equal(0, provider.Calls);
|
|
63
48
|
}
|
|
64
49
|
|
|
65
|
-
/// <summary>Pedido sem prompt e recusa nomeada, nao excecao.</summary>
|
|
66
50
|
[Fact]
|
|
67
51
|
public async Task AnInvalidRequestIsRefusedByName()
|
|
68
52
|
{
|
|
@@ -75,7 +59,6 @@ public sealed class MediaGeneratorTests
|
|
|
75
59
|
Assert.Equal(MediaErrorCode.InvalidRequest, result.Error!.Code);
|
|
76
60
|
}
|
|
77
61
|
|
|
78
|
-
/// <summary>Alias desconhecido: recusa, e nenhum job criado.</summary>
|
|
79
62
|
[Fact]
|
|
80
63
|
public async Task AnUnknownAliasIsRefused()
|
|
81
64
|
{
|
|
@@ -88,14 +71,6 @@ public sealed class MediaGeneratorTests
|
|
|
88
71
|
Assert.Equal(0, jobs.Saves);
|
|
89
72
|
}
|
|
90
73
|
|
|
91
|
-
// ---------------------------------------------------------------------------------
|
|
92
|
-
// Idempotencia: o retry nao paga de novo
|
|
93
|
-
// ---------------------------------------------------------------------------------
|
|
94
|
-
|
|
95
|
-
/// <summary>
|
|
96
|
-
/// O MESMO pedido, enfileirado duas vezes, devolve o MESMO job. E o que faz um retry de
|
|
97
|
-
/// fila, de worker reiniciado ou de webhook duplicado custar zero.
|
|
98
|
-
/// </summary>
|
|
99
74
|
[Fact]
|
|
100
75
|
public async Task TheSameRequestEnqueuedTwiceReusesTheSameJob()
|
|
101
76
|
{
|
|
@@ -108,7 +83,6 @@ public sealed class MediaGeneratorTests
|
|
|
108
83
|
Assert.Equal(1, jobs.Saves);
|
|
109
84
|
}
|
|
110
85
|
|
|
111
|
-
/// <summary>Pedido diferente, job diferente — a dedupe nao pode engolir trabalho novo.</summary>
|
|
112
86
|
[Fact]
|
|
113
87
|
public async Task ADifferentRequestGetsItsOwnJob()
|
|
114
88
|
{
|
|
@@ -121,14 +95,6 @@ public sealed class MediaGeneratorTests
|
|
|
121
95
|
Assert.NotEqual(first.Value, second.Value);
|
|
122
96
|
}
|
|
123
97
|
|
|
124
|
-
// ---------------------------------------------------------------------------------
|
|
125
|
-
// Teto de custo: recusa ANTES da chamada
|
|
126
|
-
// ---------------------------------------------------------------------------------
|
|
127
|
-
|
|
128
|
-
/// <summary>
|
|
129
|
-
/// O teto e confrontado com o custo estimado, e a recusa acontece no enqueue — antes de
|
|
130
|
-
/// existir qualquer chamada. Depois dela, o dinheiro ja saiu.
|
|
131
|
-
/// </summary>
|
|
132
98
|
[Fact]
|
|
133
99
|
public async Task ABudgetOverrunIsRefusedAtEnqueueBeforeAnyCall()
|
|
134
100
|
{
|
|
@@ -145,7 +111,6 @@ public sealed class MediaGeneratorTests
|
|
|
145
111
|
Assert.Equal([0.211m], budget.Seen);
|
|
146
112
|
}
|
|
147
113
|
|
|
148
|
-
/// <summary>O custo estimado que chega ao teto e o da tarifa, na quantidade pedida.</summary>
|
|
149
114
|
[Fact]
|
|
150
115
|
public async Task TheEstimateSentToTheBudgetIsTariffTimesCount()
|
|
151
116
|
{
|
|
@@ -157,11 +122,6 @@ public sealed class MediaGeneratorTests
|
|
|
157
122
|
Assert.Equal([0.024m], budget.Seen);
|
|
158
123
|
}
|
|
159
124
|
|
|
160
|
-
// ---------------------------------------------------------------------------------
|
|
161
|
-
// Worker: estados, storage e a distincao que vale dinheiro
|
|
162
|
-
// ---------------------------------------------------------------------------------
|
|
163
|
-
|
|
164
|
-
/// <summary>Caminho feliz: os bytes vao para o storage ANTES de o job virar Generated.</summary>
|
|
165
125
|
[Fact]
|
|
166
126
|
public async Task TheWorkerStoresTheBytesAndThenMarksGenerated()
|
|
167
127
|
{
|
|
@@ -178,7 +138,6 @@ public sealed class MediaGeneratorTests
|
|
|
178
138
|
Assert.EndsWith(".png", ran.Value.RelativePath, StringComparison.Ordinal);
|
|
179
139
|
}
|
|
180
140
|
|
|
181
|
-
/// <summary>A proveniencia e gravada com o modelo, o prompt reescrito e o custo estimado.</summary>
|
|
182
141
|
[Fact]
|
|
183
142
|
public async Task TheWorkerRecordsProvenanceWithTheEstimatedCost()
|
|
184
143
|
{
|
|
@@ -197,10 +156,6 @@ public sealed class MediaGeneratorTests
|
|
|
197
156
|
Assert.True(provenance.C2paPreserved);
|
|
198
157
|
}
|
|
199
158
|
|
|
200
|
-
/// <summary>
|
|
201
|
-
/// Bloqueio de moderacao vira <c>RejectedByPolicy</c>, terminal. Um erro tecnico vira
|
|
202
|
-
/// <c>Failed</c>, retryable. Colapsar os dois e como um laco de retry vira fatura.
|
|
203
|
-
/// </summary>
|
|
204
159
|
[Theory]
|
|
205
160
|
[InlineData(MediaErrorCode.PolicyBlocked, MediaJobState.RejectedByPolicy, false)]
|
|
206
161
|
[InlineData(MediaErrorCode.ProviderFailure, MediaJobState.Failed, true)]
|
|
@@ -218,7 +173,6 @@ public sealed class MediaGeneratorTests
|
|
|
218
173
|
Assert.Equal(0, store.Blobs.Writes);
|
|
219
174
|
}
|
|
220
175
|
|
|
221
|
-
/// <summary>Job inexistente e recusa nomeada, nunca uma excecao de referencia nula.</summary>
|
|
222
176
|
[Fact]
|
|
223
177
|
public async Task RunningAnUnknownJobIsRefusedByName()
|
|
224
178
|
{
|
|
@@ -230,10 +184,6 @@ public sealed class MediaGeneratorTests
|
|
|
230
184
|
Assert.Equal(MediaErrorCode.NotFound, ran.Error!.Code);
|
|
231
185
|
}
|
|
232
186
|
|
|
233
|
-
/// <summary>
|
|
234
|
-
/// Um alias de VIDEO pedido como imagem nao vira job: as unidades de cobranca sao
|
|
235
|
-
/// diferentes, e um pedido com a tarifa errada estima o custo errado em silencio.
|
|
236
|
-
/// </summary>
|
|
237
187
|
[Fact]
|
|
238
188
|
public async Task AVideoAliasCannotServeAnImageRequest()
|
|
239
189
|
{
|
|
@@ -246,9 +196,6 @@ public sealed class MediaGeneratorTests
|
|
|
246
196
|
Assert.Equal(MediaErrorCode.InvalidRequest, result.Error!.Code);
|
|
247
197
|
}
|
|
248
198
|
|
|
249
|
-
/// <summary>
|
|
250
|
-
/// Video sem duracao nao tem custo estimavel — e arbitrar segundos e arbitrar dinheiro.
|
|
251
|
-
/// </summary>
|
|
252
199
|
[Fact]
|
|
253
200
|
public async Task AVideoRequestWithoutDurationCannotBePriced()
|
|
254
201
|
{
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs
CHANGED
|
@@ -4,15 +4,6 @@ using Xunit;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Tests;
|
|
6
6
|
|
|
7
|
-
/// <summary>
|
|
8
|
-
/// Canarios de <see cref="MediaIdempotencyKey"/>: a chave que dedupe a CHAMADA PAGA.
|
|
9
|
-
/// </summary>
|
|
10
|
-
/// <remarks>
|
|
11
|
-
/// Duas propriedades, e as duas custam dinheiro quando falham. <b>Estabilidade</b>: o mesmo
|
|
12
|
-
/// pedido tem de dar a mesma chave, senao todo retry paga de novo. <b>Sensibilidade</b>:
|
|
13
|
-
/// qualquer campo participante que mude tem de mudar a chave, senao um pedido reusa o
|
|
14
|
-
/// artefato pago de OUTRO — pior que pagar duas vezes.
|
|
15
|
-
/// </remarks>
|
|
16
7
|
public sealed class MediaIdempotencyKeyTests
|
|
17
8
|
{
|
|
18
9
|
private static MediaRequest Request(
|
|
@@ -29,32 +20,18 @@ public sealed class MediaIdempotencyKeyTests
|
|
|
29
20
|
private static string KeyOf(MediaRequest request)
|
|
30
21
|
=> MediaIdempotencyKey.From(request, "openai", "gpt-image-2").Value;
|
|
31
22
|
|
|
32
|
-
// ---------------------------------------------------------------------------------
|
|
33
|
-
// Estabilidade
|
|
34
|
-
// ---------------------------------------------------------------------------------
|
|
35
|
-
|
|
36
|
-
/// <summary>Duas construcoes do mesmo pedido dao a mesma chave.</summary>
|
|
37
23
|
[Fact]
|
|
38
24
|
public void TheSameRequestAlwaysGivesTheSameKey()
|
|
39
25
|
=> Assert.Equal(KeyOf(Request()), KeyOf(Request()));
|
|
40
26
|
|
|
41
|
-
/// <summary>
|
|
42
|
-
/// Espaco em branco a mais nao muda o pedido, entao nao muda a chave. E o que impede um
|
|
43
|
-
/// prompt re-serializado com quebra de linha diferente de virar outra fatura.
|
|
44
|
-
/// </summary>
|
|
45
27
|
[Fact]
|
|
46
28
|
public void WhitespaceIsNormalisedAway()
|
|
47
29
|
=> Assert.Equal(KeyOf(Request("um sofa verde")), KeyOf(Request(" um sofa\n verde ")));
|
|
48
30
|
|
|
49
|
-
/// <summary>
|
|
50
|
-
/// A caixa NAO e normalizada, e e deliberado: num prompt ela muda o resultado (nome de
|
|
51
|
-
/// marca, texto literal que vai aparecer na imagem).
|
|
52
|
-
/// </summary>
|
|
53
31
|
[Fact]
|
|
54
32
|
public void CaseIsPreservedBecauseItChangesThePicture()
|
|
55
33
|
=> Assert.NotEqual(KeyOf(Request("um sofa VERDE")), KeyOf(Request("um sofa verde")));
|
|
56
34
|
|
|
57
|
-
/// <summary>A chave e um sha256 em hex minusculo: 64 digitos.</summary>
|
|
58
35
|
[Fact]
|
|
59
36
|
public void TheKeyIsASha256InLowercaseHex()
|
|
60
37
|
{
|
|
@@ -64,58 +41,43 @@ public sealed class MediaIdempotencyKeyTests
|
|
|
64
41
|
Assert.Matches("^[0-9a-f]{64}$", key);
|
|
65
42
|
}
|
|
66
43
|
|
|
67
|
-
// ---------------------------------------------------------------------------------
|
|
68
|
-
// Sensibilidade — uma rodada POR CAMPO participante
|
|
69
|
-
// ---------------------------------------------------------------------------------
|
|
70
|
-
|
|
71
|
-
/// <summary>Prompt diferente, chave diferente.</summary>
|
|
72
44
|
[Fact]
|
|
73
45
|
public void ADifferentPromptChangesTheKey()
|
|
74
46
|
=> Assert.NotEqual(KeyOf(Request("um sofa azul")), KeyOf(Request()));
|
|
75
47
|
|
|
76
|
-
/// <summary>Modelo diferente, chave diferente — o alias pode ser repontado.</summary>
|
|
77
48
|
[Fact]
|
|
78
49
|
public void ADifferentModelChangesTheKey()
|
|
79
50
|
=> Assert.NotEqual(
|
|
80
51
|
MediaIdempotencyKey.From(Request(), "openai", "gpt-image-2").Value,
|
|
81
52
|
MediaIdempotencyKey.From(Request(), "openai", "outro-modelo").Value);
|
|
82
53
|
|
|
83
|
-
/// <summary>Provider diferente, chave diferente.</summary>
|
|
84
54
|
[Fact]
|
|
85
55
|
public void ADifferentProviderChangesTheKey()
|
|
86
56
|
=> Assert.NotEqual(
|
|
87
57
|
MediaIdempotencyKey.From(Request(), "openai", "m").Value,
|
|
88
58
|
MediaIdempotencyKey.From(Request(), "google", "m").Value);
|
|
89
59
|
|
|
90
|
-
/// <summary>
|
|
91
|
-
/// <b>Trocar a foto de referencia E outro pedido</b>, mesmo com o prompt identico. Por
|
|
92
|
-
/// isso o sha256 de CADA referencia participa, e nao a contagem delas.
|
|
93
|
-
/// </summary>
|
|
94
60
|
[Fact]
|
|
95
61
|
public void ADifferentReferenceChangesTheKey()
|
|
96
62
|
=> Assert.NotEqual(
|
|
97
63
|
KeyOf(Request(references: ["foto-a"])),
|
|
98
64
|
KeyOf(Request(references: ["foto-b"])));
|
|
99
65
|
|
|
100
|
-
/// <summary>Mesma quantidade de referencias, conteudo trocado de ordem: outro pedido.</summary>
|
|
101
66
|
[Fact]
|
|
102
67
|
public void TheOrderOfReferencesIsPartOfTheRequest()
|
|
103
68
|
=> Assert.NotEqual(
|
|
104
69
|
KeyOf(Request(references: ["a", "b"])),
|
|
105
70
|
KeyOf(Request(references: ["b", "a"])));
|
|
106
71
|
|
|
107
|
-
/// <summary>Sem referencia e com referencia sao pedidos diferentes.</summary>
|
|
108
72
|
[Fact]
|
|
109
73
|
public void AddingAReferenceChangesTheKey()
|
|
110
74
|
=> Assert.NotEqual(KeyOf(Request()), KeyOf(Request(references: ["foto-a"])));
|
|
111
75
|
|
|
112
|
-
/// <summary>Cada opcao participante, uma rodada.</summary>
|
|
113
76
|
[Theory]
|
|
114
77
|
[MemberData(nameof(OptionVariations))]
|
|
115
78
|
public void EveryParticipatingOptionChangesTheKey(MediaOptions variation)
|
|
116
79
|
=> Assert.NotEqual(KeyOf(Request()), KeyOf(Request(options: variation)));
|
|
117
80
|
|
|
118
|
-
/// <summary>As variacoes de opcao exercitadas, uma por campo canonico.</summary>
|
|
119
81
|
public static TheoryData<MediaOptions> OptionVariations() =>
|
|
120
82
|
[
|
|
121
83
|
new MediaOptions { Quality = MediaQuality.High },
|
|
@@ -126,18 +88,12 @@ public sealed class MediaIdempotencyKeyTests
|
|
|
126
88
|
new MediaOptions { AspectRatio = "9:16" },
|
|
127
89
|
];
|
|
128
90
|
|
|
129
|
-
/// <summary>
|
|
130
|
-
/// O separador e um caractere de CONTROLE, e nao <c>|</c>. Um prompt que contivesse o
|
|
131
|
-
/// separador visivel emendaria campos e faria dois pedidos diferentes colidirem — que
|
|
132
|
-
/// e o defeito que faz um pedido reusar o artefato pago do outro.
|
|
133
|
-
/// </summary>
|
|
134
91
|
[Fact]
|
|
135
92
|
public void APromptFullOfPipesDoesNotCollideWithAnotherRequest()
|
|
136
93
|
=> Assert.NotEqual(
|
|
137
94
|
KeyOf(Request("gpt-image-2|Image|um sofa")),
|
|
138
95
|
KeyOf(Request("um sofa")));
|
|
139
96
|
|
|
140
|
-
/// <summary>Normalizacao de prompt e pura e conferivel sozinha.</summary>
|
|
141
97
|
[Theory]
|
|
142
98
|
[InlineData(" a b ", "a b")]
|
|
143
99
|
[InlineData("a\n\nb", "a b")]
|
|
@@ -3,23 +3,10 @@ using Xunit;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Tests;
|
|
5
5
|
|
|
6
|
-
/// <summary>
|
|
7
|
-
/// Canarios da maquina de estados de <see cref="MediaJob"/>.
|
|
8
|
-
/// </summary>
|
|
9
|
-
/// <remarks>
|
|
10
|
-
/// Tres afirmacoes carregam o standard <c>ai-agents/media-pipeline.md</c> inteiro, e cada
|
|
11
|
-
/// uma e o inverso literal de um defeito caro:
|
|
12
|
-
/// <list type="number">
|
|
13
|
-
/// <item><c>Publish</c> falha fora de <c>Approved</c> — publicar sem revisao humana.</item>
|
|
14
|
-
/// <item><c>Approved</c> so existe por <c>Approve(revisor)</c> — "aprovado" sem quem.</item>
|
|
15
|
-
/// <item><c>RejectedByPolicy</c> nao e retryable — retry que vira fatura.</item>
|
|
16
|
-
/// </list>
|
|
17
|
-
/// </remarks>
|
|
18
6
|
public sealed class MediaJobStateTests
|
|
19
7
|
{
|
|
20
8
|
private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
|
|
21
9
|
|
|
22
|
-
/// <summary>Uma marcacao valida, para os casos que nao estao medindo a marcacao.</summary>
|
|
23
10
|
private static MediaDisclosure Label => MediaDisclosure.Create("Imagem gerada por IA", "legenda", T0);
|
|
24
11
|
|
|
25
12
|
private static MediaJob Pending() => MediaJob.Create(
|
|
@@ -37,14 +24,6 @@ public sealed class MediaJobStateTests
|
|
|
37
24
|
.Value;
|
|
38
25
|
}
|
|
39
26
|
|
|
40
|
-
// ---------------------------------------------------------------------------------
|
|
41
|
-
// 1. Publish so a partir de Approved
|
|
42
|
-
// ---------------------------------------------------------------------------------
|
|
43
|
-
|
|
44
|
-
/// <summary>
|
|
45
|
-
/// O atalho que alguem sempre quer: publicar direto de <c>Generated</c>. Ele nao
|
|
46
|
-
/// existe, e a mensagem diz por que.
|
|
47
|
-
/// </summary>
|
|
48
27
|
[Fact]
|
|
49
28
|
public void PublishFailsFromGeneratedBecauseNobodyReviewedIt()
|
|
50
29
|
{
|
|
@@ -55,7 +34,6 @@ public sealed class MediaJobStateTests
|
|
|
55
34
|
Assert.Contains("Approved", result.Error.Message, StringComparison.Ordinal);
|
|
56
35
|
}
|
|
57
36
|
|
|
58
|
-
/// <summary>Publish falha a partir de TODO estado que nao seja Approved — nao so de Generated.</summary>
|
|
59
37
|
[Theory]
|
|
60
38
|
[InlineData(MediaJobState.Pending)]
|
|
61
39
|
[InlineData(MediaJobState.Generating)]
|
|
@@ -71,7 +49,6 @@ public sealed class MediaJobStateTests
|
|
|
71
49
|
Assert.False(job.Publish(Label, T0).IsSuccess);
|
|
72
50
|
}
|
|
73
51
|
|
|
74
|
-
/// <summary>E, com a revisao humana no lugar, publica.</summary>
|
|
75
52
|
[Fact]
|
|
76
53
|
public void PublishSucceedsFromApproved()
|
|
77
54
|
{
|
|
@@ -84,11 +61,6 @@ public sealed class MediaJobStateTests
|
|
|
84
61
|
Assert.Equal(T0, published.Value.PublishedAt);
|
|
85
62
|
}
|
|
86
63
|
|
|
87
|
-
// ---------------------------------------------------------------------------------
|
|
88
|
-
// 2. Approved so por Approve(revisor)
|
|
89
|
-
// ---------------------------------------------------------------------------------
|
|
90
|
-
|
|
91
|
-
/// <summary>A aprovacao carrega quem e quando — sem isso e um campo booleano.</summary>
|
|
92
64
|
[Fact]
|
|
93
65
|
public void ApproveStampsTheReviewerAndTheTime()
|
|
94
66
|
{
|
|
@@ -100,15 +72,10 @@ public sealed class MediaJobStateTests
|
|
|
100
72
|
Assert.Equal("cor confere", approved.Review.Note);
|
|
101
73
|
}
|
|
102
74
|
|
|
103
|
-
/// <summary>Revisor em branco nao carimba nada, e a recusa e na construcao da revisao.</summary>
|
|
104
75
|
[Fact]
|
|
105
76
|
public void AnAnonymousReviewIsRefusedAtConstruction()
|
|
106
77
|
=> Assert.Throws<ArgumentException>(() => MediaReview.ApprovedBy(" ", T0));
|
|
107
78
|
|
|
108
|
-
/// <summary>
|
|
109
|
-
/// Aprovar com um veredito de REJEICAO seria registrar o oposto do que o humano
|
|
110
|
-
/// decidiu. O tipo recusa.
|
|
111
|
-
/// </summary>
|
|
112
79
|
[Fact]
|
|
113
80
|
public void ApproveRefusesAReviewThatSaysRejected()
|
|
114
81
|
{
|
|
@@ -118,19 +85,10 @@ public sealed class MediaJobStateTests
|
|
|
118
85
|
Assert.Equal(MediaErrorCode.InvalidTransition, result.Error!.Code);
|
|
119
86
|
}
|
|
120
87
|
|
|
121
|
-
/// <summary>Aprovar antes de existirem bytes nao e transicao.</summary>
|
|
122
88
|
[Fact]
|
|
123
89
|
public void ApproveFailsBeforeTheBytesExist()
|
|
124
90
|
=> Assert.False(Pending().Approve(MediaReview.ApprovedBy("lucas", T0)).IsSuccess);
|
|
125
91
|
|
|
126
|
-
// ---------------------------------------------------------------------------------
|
|
127
|
-
// 3. RejectedByPolicy nao e retryable; Failed e
|
|
128
|
-
// ---------------------------------------------------------------------------------
|
|
129
|
-
|
|
130
|
-
/// <summary>
|
|
131
|
-
/// A distincao que vale dinheiro. Bloqueio de moderacao e resposta definitiva; erro
|
|
132
|
-
/// tecnico e tentativa perdida.
|
|
133
|
-
/// </summary>
|
|
134
92
|
[Fact]
|
|
135
93
|
public void PolicyBlockIsTerminalAndTechnicalFailureIsNot()
|
|
136
94
|
{
|
|
@@ -146,7 +104,6 @@ public sealed class MediaJobStateTests
|
|
|
146
104
|
Assert.True(failed.StartGenerating().IsSuccess);
|
|
147
105
|
}
|
|
148
106
|
|
|
149
|
-
/// <summary>Um job em <c>Generated</c> carrega hash, caminho e proveniencia — os tres juntos.</summary>
|
|
150
107
|
[Fact]
|
|
151
108
|
public void GeneratedCarriesHashPathAndProvenanceTogether()
|
|
152
109
|
{
|
|
@@ -158,7 +115,6 @@ public sealed class MediaJobStateTests
|
|
|
158
115
|
Assert.Equal("gpt-image-2", job.Provenance!.ModelId);
|
|
159
116
|
}
|
|
160
117
|
|
|
161
|
-
/// <summary>Uma transicao recusada NAO muda o job: o registro e imutavel.</summary>
|
|
162
118
|
[Fact]
|
|
163
119
|
public void ARefusedTransitionLeavesTheJobUntouched()
|
|
164
120
|
{
|