@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.
Files changed (317) hide show
  1. package/CLAUDE.md +37 -13
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +75 -49
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +6 -5
  8. package/framework/MORPH.md +25 -22
  9. package/framework/agents.json +96 -36
  10. package/framework/docs/CLI.md +54 -17
  11. package/framework/evals/rubrics/code-quality.md +5 -0
  12. package/framework/evals/rubrics/condensed.md +6 -2
  13. package/framework/evals/rubrics/contracts.md +20 -0
  14. package/framework/evals/rubrics/test-coverage.md +17 -6
  15. package/framework/hooks/README.md +9 -3
  16. package/framework/hooks/claude-code/core/graph-refresh-kick.js +52 -0
  17. package/framework/hooks/claude-code/core/telemetry-log.js +172 -87
  18. package/framework/hooks/claude-code/core/validate-completion.js +16 -90
  19. package/framework/hooks/claude-code/core/worktree-context.js +10 -0
  20. package/framework/hooks/shared/command-tokens.js +96 -0
  21. package/framework/hooks/shared/git-exclude.js +44 -0
  22. package/framework/hooks/shared/git-paths.js +52 -0
  23. package/framework/hooks/shared/graph-freshness.js +380 -0
  24. package/framework/hooks/shared/graph-refresh.js +416 -0
  25. package/framework/hooks/shared/morph-cli.js +17 -5
  26. package/framework/hooks/shared/redact-secrets.js +55 -0
  27. package/framework/hooks/shared/telemetry-logger.js +25 -2
  28. package/framework/hooks/shared/transcript-usage.js +80 -0
  29. package/framework/mcp/registry.json +33 -2
  30. package/framework/rubrics/backend.json +2 -2
  31. package/framework/rubrics/default.json +2 -2
  32. package/framework/rubrics/frontend.json +2 -2
  33. package/framework/rules/csharp-standards.md +1 -0
  34. package/framework/rules/infrastructure-standards.md +0 -1
  35. package/framework/rules/morph-workflow.md +10 -4
  36. package/framework/rules/nextjs-standards.md +1 -0
  37. package/framework/schemas/config.schema.json +41 -1
  38. package/framework/schemas/feature.schema.json +5 -1
  39. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  40. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  41. package/framework/skills/core/morph-apply/SKILL.md +40 -11
  42. package/framework/skills/core/morph-brainstorming/SKILL.md +16 -7
  43. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  44. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  45. package/framework/skills/core/morph-implement/SKILL.md +44 -10
  46. package/framework/skills/core/morph-media/SKILL.md +1 -1
  47. package/framework/skills/core/morph-plan/SKILL.md +73 -15
  48. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  49. package/framework/skills/core/morph-review/SKILL.md +51 -13
  50. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  51. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  52. package/framework/skills/core/morph-uiux/SKILL.md +39 -13
  53. package/framework/standards/STANDARDS.json +189 -14
  54. package/framework/standards/ai-agents/context-providers.md +16 -26
  55. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  56. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  57. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  58. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  59. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  60. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  61. package/framework/standards/ai-agents/testing-ai.md +2 -1
  62. package/framework/standards/architecture/code-comments.md +100 -0
  63. package/framework/standards/backend/dotnet/testing.md +22 -0
  64. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  65. package/framework/standards/frontend/admin-app/data-table.md +562 -0
  66. package/framework/standards/frontend/admin-app/feedback.md +236 -0
  67. package/framework/standards/frontend/admin-app/forms.md +232 -0
  68. package/framework/standards/frontend/admin-app/page-structure.md +203 -0
  69. package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
  70. package/framework/standards/frontend/nextjs/components.md +37 -58
  71. package/framework/standards/frontend/nextjs/data-fetching.md +1 -0
  72. package/framework/standards/frontend/nextjs/forms.md +31 -30
  73. package/framework/standards/frontend/nextjs/state-management.md +2 -1
  74. package/framework/standards/frontend/nextjs/testing.md +18 -0
  75. package/framework/standards/integration/mcp/mcp-tools.md +48 -117
  76. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  77. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  78. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  79. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  80. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  81. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  82. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  83. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  84. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  85. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  86. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  87. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  88. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  89. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  90. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  91. package/framework/templates/code/dotnet/test.cs +2 -87
  92. package/framework/templates/code/typescript/contracts.ts +140 -167
  93. package/framework/templates/context/design-system.md +3 -3
  94. package/framework/templates/docs/proposal.md +11 -0
  95. package/framework/templates/docs/spec.md +15 -0
  96. package/framework/templates/docs/ui-mockups.md +115 -98
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  118. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  119. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  120. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  121. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  122. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  123. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  148. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  149. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  150. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  151. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  152. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  153. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  154. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  155. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  156. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  157. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  158. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  159. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  160. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  161. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  162. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  163. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  164. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  165. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  166. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  167. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  168. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  169. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  170. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  171. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  172. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  173. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  174. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  175. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  176. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  180. package/framework/templates/feature/decisions.md +124 -122
  181. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  182. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  183. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  184. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  185. package/framework/templates/integrations/asaas-client.cs +332 -387
  186. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  187. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  188. package/framework/templates/project/validate.js +97 -120
  189. package/package.json +2 -2
  190. package/src/commands/dashboard/dashboard.js +80 -53
  191. package/src/commands/graph/affected.js +4 -3
  192. package/src/commands/graph/build.js +109 -18
  193. package/src/commands/graph/explain.js +8 -5
  194. package/src/commands/graph/status.js +14 -8
  195. package/src/commands/mcp/mcp-setup.js +37 -9
  196. package/src/commands/observability/cost.js +148 -18
  197. package/src/commands/project/doctor.js +85 -15
  198. package/src/commands/project/init.js +6 -3
  199. package/src/commands/project/install-plugin-cmd.js +1 -1
  200. package/src/commands/project/update.js +30 -0
  201. package/src/commands/project/worktree.js +73 -90
  202. package/src/commands/state/advance.js +18 -0
  203. package/src/commands/state/approve.js +15 -0
  204. package/src/commands/state/archive.js +62 -9
  205. package/src/commands/state/finish.js +62 -14
  206. package/src/commands/state/gate-check.js +8 -1
  207. package/src/commands/state/gate-decision.js +8 -4
  208. package/src/commands/state/report-check.js +90 -9
  209. package/src/commands/state/usecase.js +66 -0
  210. package/src/commands/tasks/dag.js +17 -2
  211. package/src/commands/validation/validate-feature.js +6 -1
  212. package/src/commands/validation/verify.js +20 -1
  213. package/src/dashboard/app/App.jsx +80 -16
  214. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  215. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  216. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  217. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  218. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  219. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  220. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  221. package/src/dashboard/app/components/Markdown.jsx +47 -0
  222. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  223. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  224. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  225. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  226. package/src/dashboard/app/hooks/usePref.js +38 -0
  227. package/src/dashboard/app/lib/format.js +69 -0
  228. package/src/dashboard/app/lib/phases.js +56 -0
  229. package/src/dashboard/app/lib/project.js +50 -0
  230. package/src/dashboard/app/pages/Activity.jsx +24 -0
  231. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  232. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  233. package/src/dashboard/app/pages/Features.jsx +400 -0
  234. package/src/dashboard/app/pages/Guide.jsx +146 -0
  235. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  236. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  237. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  238. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  239. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  240. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  241. package/src/dashboard/app/pages/Standards.jsx +407 -332
  242. package/src/dashboard/app/styles/index.css +187 -47
  243. package/src/dashboard/index.html +14 -12
  244. package/src/dashboard/package.json +32 -24
  245. package/src/dashboard/scanner/activity-scanner.js +351 -0
  246. package/src/dashboard/scanner/agents-scanner.js +63 -1
  247. package/src/dashboard/scanner/docs-scanner.js +59 -0
  248. package/src/dashboard/scanner/feature-status.js +10 -0
  249. package/src/dashboard/scanner/features-scanner.js +360 -0
  250. package/src/dashboard/scanner/hooks-scanner.js +8 -1
  251. package/src/dashboard/scanner/index.js +14 -7
  252. package/src/dashboard/scanner/phases-scanner.js +14 -15
  253. package/src/dashboard/scanner/portfolio.js +153 -0
  254. package/src/dashboard/scanner/project-api.js +195 -0
  255. package/src/dashboard/scanner/rules-scanner.js +42 -37
  256. package/src/dashboard/scanner/skills-scanner.js +105 -43
  257. package/src/dashboard/scanner/standards-scanner.js +4 -1
  258. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  259. package/src/lib/ai/comment-stripper.js +101 -4
  260. package/src/lib/detectors/claude-config-detector.js +2 -7
  261. package/src/lib/detectors/index.js +5 -1
  262. package/src/lib/evaluator-report.js +47 -0
  263. package/src/lib/generators/handoff-generator.js +4 -18
  264. package/src/lib/generators/settings-generator.js +208 -209
  265. package/src/lib/git/default-branch.js +52 -0
  266. package/src/lib/git/tree-fingerprint.js +58 -0
  267. package/src/lib/git/tree-state.js +167 -0
  268. package/src/lib/graph/god-nodes.js +12 -3
  269. package/src/lib/graph/graph-health.js +48 -2
  270. package/src/lib/graph/graph-loader.js +99 -25
  271. package/src/lib/graph/graphifyignore.js +145 -28
  272. package/src/lib/graph/sanitize.js +117 -11
  273. package/src/lib/installers/mcp-installer.js +724 -719
  274. package/src/lib/installers/memory-installer.js +1 -2
  275. package/src/lib/projects/registry.js +154 -0
  276. package/src/lib/risk-detector.js +90 -0
  277. package/src/lib/specs/acervo-format.js +70 -0
  278. package/src/lib/specs/acervo-lifecycle.js +264 -0
  279. package/src/lib/specs/adr-index.js +169 -0
  280. package/src/lib/specs/revocations.js +156 -0
  281. package/src/lib/specs/screen-checklist.js +357 -0
  282. package/src/lib/specs/usecase-check.js +20 -16
  283. package/src/lib/specs/usecase-promoter.js +103 -38
  284. package/src/lib/specs/usecase-registry.js +7 -7
  285. package/src/lib/tasks/tasks-json-parser.js +2 -3
  286. package/src/lib/telemetry/metrics.js +386 -0
  287. package/src/lib/telemetry/telemetry-reader.js +161 -51
  288. package/src/lib/telemetry/verify-event.js +79 -0
  289. package/src/lib/validators/code-budget/code-budget-validator.js +200 -0
  290. package/src/lib/validators/code-budget/comment-scanner.js +75 -0
  291. package/src/lib/validators/code-budget/index.js +9 -0
  292. package/src/lib/validators/reuse/reuse-validator.js +4 -2
  293. package/src/lib/validators/reuse/symbol-scanner.js +27 -22
  294. package/src/lib/validators/validation-runner.js +21 -5
  295. package/src/lib/validators/verify-runner.js +11 -0
  296. package/src/lib/worktree/finish-backup.js +739 -0
  297. package/src/lib/worktree/ignored-triage.js +15 -8
  298. package/src/lib/worktree/worktree-audit.js +5 -0
  299. package/src/scripts/install-plugin.js +1 -1
  300. package/src/scripts/setup-infra.js +31 -22
  301. package/src/utils/agents-installer.js +338 -24
  302. package/src/utils/banner.js +1 -1
  303. package/src/utils/hooks-installer.js +75 -15
  304. package/src/commands/index.js +0 -13
  305. package/src/commands/project/checkpoint.js +0 -209
  306. package/src/core/state/phase-state-machine.js +0 -197
  307. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  308. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  309. package/src/dashboard/scanner/squads-scanner.js +0 -13
  310. package/src/dashboard/scanner/state-scanner.js +0 -85
  311. package/src/lib/generators/index.js +0 -10
  312. package/src/lib/hooks/hook-executor.js +0 -167
  313. package/src/lib/validators/checkpoint-persistence.js +0 -39
  314. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  315. package/src/lib/validators/shared/index.js +0 -12
  316. package/src/lib/validators/shared/issue-counter.js +0 -14
  317. 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 DENTRO de cada sessao (uma conversa e uma fila), concorrente ENTRE elas.
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)); // estavel na mesma sessao
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 anterior + o pedido e a resposta desta volta. A volta grava SO a si
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
- /// <summary>
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
- // O agente cria uma sessao efemera para a volta: a guarda de sessao ausente nao e
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); // INSERT
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; // UPDATE
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); // DELETE
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
- // O acervo e o custo nao se mexeram: 3 documentos, os mesmos, 3 embeddings ao todo.
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."), // <- unico campo alterado
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); // nao apendou
207
- Assert.Single(corpus.Rows, d => d.Slug == "prazo-entrega"); // uma so linha
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
  {
@@ -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 parseia como JSON valido: se a regua so perguntasse "parseia?", ela
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")) })