@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
@@ -4,16 +4,6 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests.Evals;
6
6
 
7
- /// <summary>
8
- /// Miss de cache sem credencial REPROVA, nomeado. Jamais <c>skip</c>.
9
- /// </summary>
10
- /// <remarks>
11
- /// A tentacao do <c>skip</c> e real ("nao da para avaliar, entao nao avalio") e recria
12
- /// exatamente o buraco que este harness fechou duas vezes: gate verde sobre verificacao que
13
- /// nao aconteceu. E ha razao positiva: em CI, com cache commitada, um miss significa UMA
14
- /// coisa so — o prompt ou o modelo mudou e ninguem regravou. O miss e o veredito, nao o
15
- /// obstaculo.
16
- /// </remarks>
17
7
  public sealed class EvalCacheMissTests
18
8
  {
19
9
  private static async Task<EvalWorkspace> SeededAsync(params string[] scenarioNames)
@@ -41,21 +31,16 @@ public sealed class EvalCacheMissTests
41
31
  {
42
32
  using var ws = await SeededAsync("a-primeiro");
43
33
 
44
- // Cenario NOVO, que a cache nunca viu — a situacao literal de "mudou o prompt e
45
- // ninguem regravou". O prefixo "b-" e deliberado: os cenarios rodam em ordem ordinal
46
- // de caminho, e o teste abaixo depende de o cenario BOM ter rodado antes do miss.
34
+ // Prefixo "b-": os cenarios rodam em ordem ordinal de caminho, e o miss tem de vir depois do cenario bom.
47
35
  ws.WriteScenario("triagem", "acme", "b-novo", "pergunta inedita", """[ { "kind": "contains", "value": "resposta" } ]""");
48
36
 
49
37
  await using var offline = EvalFixture.Create(ws.Options(EvalMode.Offline));
50
38
  var error = await Assert.ThrowsAsync<EvalCacheMissException>(
51
39
  () => offline.RunAllAsync(TestContext.Current.CancellationToken));
52
40
 
53
- // O cenario que faltou, e nao "algum cenario".
54
41
  Assert.Equal("triagem.acme.b-novo", error.ScenarioName);
55
42
  Assert.Contains("eval-cache-miss", error.Message, StringComparison.Ordinal);
56
43
 
57
- // P3: a mensagem carrega o comando de regravacao e o arquivo de credencial esperado.
58
- // Um "cache miss" seco mandaria o leitor procurar os dois.
59
44
  Assert.Contains(EvalCacheMissException.RefreshCommand, error.Message, StringComparison.Ordinal);
60
45
  Assert.Contains(".env.evals", error.Message, StringComparison.Ordinal);
61
46
  Assert.Contains(ws.CacheRoot, error.Message, StringComparison.Ordinal);
@@ -67,33 +52,22 @@ public sealed class EvalCacheMissTests
67
52
  using var ws = await SeededAsync("a-primeiro");
68
53
  ws.WriteScenario("triagem", "acme", "b-novo", "pergunta inedita", """[ { "kind": "contains", "value": "resposta" } ]""");
69
54
 
70
- // O sumario da SEMEADURA, byte a byte. Ele e a linha de base: o que este teste cobra
71
- // e que o run que morreu por miss nao escreva NADA por cima dele.
72
55
  var summaryBeforeTheMiss = File.ReadAllBytes(ws.SummaryPath);
73
56
 
74
57
  await using var offline = EvalFixture.Create(ws.Options(EvalMode.Offline));
75
58
  await Assert.ThrowsAsync<EvalCacheMissException>(
76
59
  () => offline.RunAllAsync(TestContext.Current.CancellationToken));
77
60
 
78
- // Nao ha caminho pelo qual o cenario ausente apareca como avaliado: a contagem so tem
79
- // o que de fato resolveu, e o sumario NAO foi reescrito. Um sumario dizendo
80
- // "1 cenario, 1 passou, cache warm" aqui seria exatamente a mentira que a excecao
81
- // existe para impedir — e o CLI, que le o ARQUIVO, acreditaria nela.
82
61
  Assert.Single(offline.Results);
83
62
  Assert.Equal("triagem.acme.a-primeiro", offline.Results[0].Scenario);
84
63
  Assert.Equal(summaryBeforeTheMiss, File.ReadAllBytes(ws.SummaryPath));
85
64
 
86
- // E o contador do cliente interno registra a tentativa: a arvore de cache nao
87
- // respondeu, e isso e um FATO medido, nao uma inferencia.
88
65
  Assert.Equal(1, offline.Offline!.Invocations);
89
66
  }
90
67
 
91
68
  [Fact]
92
69
  public async Task AFailedAssertionMakesTheScenarioREDWithoutAnyCacheMiss()
93
70
  {
94
- // A outra metade do contrato: reprovar por conteudo e diferente de reprovar por
95
- // cache. Uma so mensagem para os dois casos mandaria o leitor procurar no lugar
96
- // errado metade das vezes.
97
71
  using var ws = new EvalWorkspace();
98
72
  ws.WriteScenario("triagem", "acme", "saudacao", "bom dia", """[ { "kind": "contains", "value": "jamais aparece" } ]""");
99
73
 
@@ -130,9 +104,6 @@ public sealed class EvalCacheMissTests
130
104
  await using var offline = EvalFixture.Create(ws.Options(EvalMode.Offline));
131
105
  var summary = await offline.RunAllAsync(TestContext.Current.CancellationToken);
132
106
 
133
- // Um cenario que nao cobra nada nao pode contar como cobertura. Deixa-lo verde e o
134
- // mesmo defeito de um teste vazio — pior aqui, porque ele apareceria na contagem
135
- // como se tivesse verificado algo.
136
107
  Assert.Equal(1, summary.Failed);
137
108
  Assert.Equal(0, summary.Passed);
138
109
  }
@@ -4,14 +4,6 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests.Evals;
6
6
 
7
- /// <summary>
8
- /// O carimbo: por que a arvore de cache precisa dizer sob quais versoes foi gravada.
9
- /// </summary>
10
- /// <remarks>
11
- /// A funcao de hash que produz a chave nao promete estabilidade entre versoes da biblioteca.
12
- /// Sem carimbo, subir o pin chega como N misses inexplicaveis num dia em que ninguem mexeu
13
- /// em nada; com carimbo, chega como uma frase que nomeia as DUAS versoes.
14
- /// </remarks>
15
7
  public sealed class EvalCacheStampTests
16
8
  {
17
9
  private static readonly FakeClock Today = new(new DateTimeOffset(2026, 9, 8, 12, 0, 0, TimeSpan.Zero));
@@ -51,8 +43,6 @@ public sealed class EvalCacheStampTests
51
43
  using var ws = new EvalWorkspace();
52
44
  Stamp(ws.CacheRoot);
53
45
 
54
- // Ler de VOLTA do disco, e nao comparar o objeto com ele mesmo: o carimbo e um
55
- // artefato, e um teste que so olhasse a instancia em memoria seria um espelho.
56
46
  var read = EvalCacheStamp.Read(ws.CacheRoot);
57
47
 
58
48
  Assert.NotNull(read);
@@ -72,7 +62,6 @@ public sealed class EvalCacheStampTests
72
62
 
73
63
  Assert.Equal(EvalStampVerdict.PinMismatch, check.Verdict);
74
64
  Assert.Contains("eval-cache-stale-pin", check.Reason, StringComparison.Ordinal);
75
- // Dizer so uma das versoes obriga o leitor a ir buscar a outra.
76
65
  Assert.Contains("gravada 10.9.0", check.Reason, StringComparison.Ordinal);
77
66
  Assert.Contains("pin 10.11.0", check.Reason, StringComparison.Ordinal);
78
67
  }
@@ -89,9 +78,6 @@ public sealed class EvalCacheStampTests
89
78
  Assert.Equal(EvalStampVerdict.Missing, missing.Verdict);
90
79
  Assert.Contains("eval-cache-unstamped", missing.Reason, StringComparison.Ordinal);
91
80
 
92
- // As duas situacoes pedem acoes diferentes: "regrave" versus "regrave PORQUE o
93
- // pacote mudou". Um veredito unico devolveria ao leitor a pergunta que o carimbo
94
- // existia para responder.
95
81
  Assert.NotEqual(missing.Verdict, divergent.Verdict);
96
82
  Assert.DoesNotContain("eval-cache-stale-pin", missing.Reason, StringComparison.Ordinal);
97
83
  }
@@ -116,8 +102,6 @@ public sealed class EvalCacheStampTests
116
102
 
117
103
  var check = EvalCacheStamp.Check(ws.CacheRoot, Pin(pinnedAt: "2027-01-01"), Today);
118
104
 
119
- // O carimbo CASA com o pin — e mesmo assim reprova, porque a regua esta adulterada.
120
- // Uma data no futuro renovaria sozinha qualquer janela medida contra ela.
121
105
  Assert.Equal(EvalStampVerdict.FuturePin, check.Verdict);
122
106
  Assert.Contains("eval-pin-future", check.Reason, StringComparison.Ordinal);
123
107
  }
@@ -135,8 +119,6 @@ public sealed class EvalCacheStampTests
135
119
  ["Microsoft.Extensions.AI.Evaluation"] = "10.11.0",
136
120
  });
137
121
 
138
- // A recusa acontece na CONSTRUCAO — antes de qualquer cenario. Nao faz sentido pagar
139
- // o relogio de uma suite para descobrir no fim que a cache nao servia.
140
122
  var error = Assert.Throws<InvalidOperationException>(
141
123
  () => EvalFixture.Create(ws.Options(EvalMode.Offline, pin), timeProvider: Today));
142
124
 
@@ -148,7 +130,7 @@ public sealed class EvalCacheStampTests
148
130
  {
149
131
  using var ws = new EvalWorkspace();
150
132
 
151
- // Cobrar carimbo em modo de escrita tornaria impossivel criar a PRIMEIRA cache.
133
+ // Cobrar carimbo na escrita impediria criar a primeira cache.
152
134
  var fixture = EvalFixture.Create(ws.Options(EvalMode.Refresh), new FakeChatClient());
153
135
 
154
136
  Assert.True(fixture.StampCheck.IsOk);
@@ -4,37 +4,8 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests.Evals;
6
6
 
7
- /// <summary>
8
- /// A camada que NOMEIA a falha, testada pelo conteudo do que ela produz.
9
- /// </summary>
10
- /// <remarks>
11
- /// <para>
12
- /// Este arquivo fecha a setima ocorrencia da mesma classe nesta epic, e a mais ironica: o
13
- /// mecanismo de <c>Failures</c> nasceu para consertar um eval que mordia ANONIMAMENTE, e
14
- /// nasceu ele proprio sem par. As tres partes podiam ser desligadas sem matar um teste —
15
- /// <c>CollectFailures</c> devolvendo lista vazia, o ramo da vacuidade nunca disparando, e o
16
- /// nome da assercao trocado por um literal — e a suite seguia "165 total, 0 falhou".
17
- /// </para>
18
- /// <para>
19
- /// A causa foi uma unica testemunha automatizada: <c>Assert.Empty(summary.Failures)</c> num
20
- /// run VERDE. Essa asserção é verdadeira exatamente quando o mecanismo esta morto — ela nao
21
- /// distingue "nao houve falha" de "falha nenhuma e registrada". Uma lista vazia prova a
22
- /// ausencia de conteudo, jamais a presenca de capacidade.
23
- /// </para>
24
- /// <para>
25
- /// Por isso aqui nao se assere sobre <c>Failures</c> VAZIA: assere-se sobre o CONTEUDO de
26
- /// <c>Failures</c> num run que falha de verdade — o par, a mesma forma que fechou o motor de
27
- /// assercao um andar abaixo.
28
- /// </para>
29
- /// </remarks>
30
7
  public sealed class EvalFailuresTests
31
8
  {
32
- /// <summary>Grava a cache do cenario com o texto dado, e devolve o fixture offline pronto.</summary>
33
- /// <remarks>
34
- /// A cache e semeada pelo <see cref="FakeChatClient"/>, NUNCA por chamada paga: o caminho
35
- /// de escrita exercitado e o da biblioteca, e a leitura offline le exatamente o que aquela
36
- /// escrita produziu.
37
- /// </remarks>
38
9
  private static async Task<EvalRunSummary> RunOfflineAsync(EvalWorkspace ws, params string[] responses)
39
10
  {
40
11
  using var fake = new FakeChatClient
@@ -60,9 +31,6 @@ public sealed class EvalFailuresTests
60
31
  [Fact]
61
32
  public async Task AFailedScenarioIsRegisteredWithScenarioAssertionKindAndReason()
62
33
  {
63
- // O caso central: uma assercao reprova, e a falha chega ao sumario COM NOME. Sem isso
64
- // o veredito viaja como contador — "Expected 0, Actual 1" — e quem le tem de abrir o
65
- // dataset para descobrir o que quebrou.
66
34
  using var ws = new EvalWorkspace();
67
35
  ws.WriteScenario(
68
36
  "triagem", "acme", "saudacao", "bom dia",
@@ -79,7 +47,6 @@ public sealed class EvalFailuresTests
79
47
  Assert.Contains("FALHOU", failure.Reason, StringComparison.Ordinal);
80
48
  Assert.Contains("bom dia", failure.Reason, StringComparison.Ordinal);
81
49
 
82
- // A linha legivel, no formato que a rubrica eval-de-modelo.md cita como exemplo.
83
50
  Assert.Contains(
84
51
  "eval-fail: triagem.acme.saudacao",
85
52
  summary.DescribeFailures(),
@@ -89,9 +56,6 @@ public sealed class EvalFailuresTests
89
56
  [Fact]
90
57
  public async Task TwoReprovedAssertionsInOneScenarioProduceTWOLinesEachNamingItsOwnRule()
91
58
  {
92
- // `Failures` e LISTA, e nao contador, porque as duas linhas dizem coisas diferentes:
93
- // uma resposta pode quebrar em mais de uma regua, e isso separa "o prompt mudou" de
94
- // "o modelo parou de responder". Se o tipo fosse um booleano, esta distincao morreria.
95
59
  using var ws = new EvalWorkspace();
96
60
  ws.WriteScenario(
97
61
  "triagem", "acme", "saudacao", "bom dia",
@@ -110,18 +74,12 @@ public sealed class EvalFailuresTests
110
74
  Assert.Contains(summary.Failures, f => f.Assertion == "cumprimenta" && f.Kind == "Contains");
111
75
  Assert.Contains(summary.Failures, f => f.Assertion == "sem-dado-pessoal" && f.Kind == "NoPii");
112
76
 
113
- // Toda linha nomeia o cenario: uma falha sem cenario obriga a mesma busca que ela
114
- // deveria dispensar.
115
77
  Assert.All(summary.Failures, f => Assert.Equal("triagem.acme.saudacao", f.Scenario));
116
78
  }
117
79
 
118
80
  [Fact]
119
81
  public async Task AScenarioWithNOAssertionsIsCountedANDNamed()
120
82
  {
121
- // O cenario sem assercao nenhuma reprova por VACUIDADE: ele nao cobra nada. Nesse caso
122
- // nao existe outcome reprovado para nomear, e sem um ramo proprio ele seria contado em
123
- // `Failed` e invisivel em `Failures` — um numero sem frase, que e metade exata do
124
- // defeito que este tipo existe para fechar.
125
83
  using var ws = new EvalWorkspace();
126
84
  ws.WriteScenario("triagem", "acme", "vazio", "bom dia", "[]");
127
85
 
@@ -133,16 +91,12 @@ public sealed class EvalFailuresTests
133
91
  Assert.Equal("triagem.acme.vazio", failure.Scenario);
134
92
  Assert.Contains("sem assercao deterministica", failure.Reason, StringComparison.Ordinal);
135
93
 
136
- // Contado E nomeado: o numero e a frase tem de concordar sempre.
137
94
  Assert.Equal(summary.Failed, summary.Failures.Count);
138
95
  }
139
96
 
140
97
  [Fact]
141
98
  public async Task AGreenRunRegistersNoFailureAndSaysSoInWords()
142
99
  {
143
- // A reciproca vive AO LADO dos casos que falham de verdade, nunca sozinha: uma suite
144
- // cuja unica testemunha e `Assert.Empty` num run verde e verdadeira precisamente
145
- // quando o mecanismo esta morto.
146
100
  using var ws = new EvalWorkspace();
147
101
  ws.WriteScenario(
148
102
  "triagem", "acme", "saudacao", "bom dia",
@@ -158,8 +112,6 @@ public sealed class EvalFailuresTests
158
112
  [Fact]
159
113
  public async Task TheNamedFailuresSurviveTheROUNDTRIPThroughTheSummaryFileTheCLIReads()
160
114
  {
161
- // O sumario e um ARQUIVO: quem consome as falhas e o CLI, do outro lado do disco. Uma
162
- // lista que existisse so em memoria seria invisivel exatamente onde precisa ser lida.
163
115
  using var ws = new EvalWorkspace();
164
116
  ws.WriteScenario(
165
117
  "triagem", "acme", "saudacao", "bom dia",
@@ -5,17 +5,6 @@ using Xunit;
5
5
 
6
6
  namespace Morph.AiKit.Tests.Evals;
7
7
 
8
- /// <summary>
9
- /// A prova que decide a feature: <b>com a cache quente, o eval roda sem credencial e sem
10
- /// rede</b>.
11
- /// </summary>
12
- /// <remarks>
13
- /// Essa frase foi derivada de leitura de fonte antes de existir codigo. Aqui ela vira
14
- /// medicao: uma arvore de cache e gravada por um fake, o processo troca o cliente interno
15
- /// por um que RECUSA qualquer chamada, e a suite termina verde. Se o cliente interno fosse
16
- /// tocado uma unica vez, o contador dele deixaria de ser zero — e nenhuma das assercoes
17
- /// abaixo passaria.
18
- /// </remarks>
19
8
  public sealed class EvalFixtureTests
20
9
  {
21
10
  [Fact]
@@ -25,18 +14,12 @@ public sealed class EvalFixtureTests
25
14
  ws.WriteScenario("triagem", "acme", "saudacao", "bom dia", """[ { "kind": "contains", "value": "Ola" } ]""");
26
15
  ws.WriteScenario("triagem", "acme", "handoff", "quero falar com humano", """[ { "kind": "contains", "value": "humano" } ]""");
27
16
 
28
- // 1) GRAVA a arvore contra o fake — nunca contra uma API paga. O caminho de escrita
29
- // exercitado e o da biblioteca, e a chave e computada pela mesma funcao que a
30
- // leitura vai usar.
31
17
  using var fake = new FakeChatClient
32
18
  {
33
19
  MetadataProviderName = "morph-fake",
34
20
  MetadataDefaultModelId = "modelo-exemplar",
35
21
  };
36
- // A ORDEM importa e e a do dataset (ordinal por caminho): "handoff" vem antes de
37
- // "saudacao". Enfileirar na ordem alfabetica errada foi como esta suite descobriu
38
- // que LoadAll ordena — e a ordem estavel e o que faz duas maquinas gravarem a mesma
39
- // arvore de cache.
22
+ // A ordem e a do dataset (ordinal por caminho): "handoff" antes de "saudacao".
40
23
  fake.EnqueueText("Encaminhando para um humano agora.").EnqueueText("Ola! Bom dia.");
41
24
 
42
25
  await using (var refresh = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake))
@@ -50,8 +33,6 @@ public sealed class EvalFixtureTests
50
33
  Assert.Equal(2, fake.CallCount);
51
34
  Assert.True(File.Exists(EvalCacheStamp.PathFor(ws.CacheRoot)), "o refresh tem de deixar o carimbo na raiz da cache");
52
35
 
53
- // 2) LE offline. O cliente interno agora RECUSA: se a cache nao respondesse, este
54
- // bloco lancaria EvalCacheMissException em vez de terminar verde.
55
36
  await using var offline = EvalFixture.Create(ws.Options(EvalMode.Offline));
56
37
  var summary = await offline.RunAllAsync(TestContext.Current.CancellationToken);
57
38
 
@@ -63,8 +44,6 @@ public sealed class EvalFixtureTests
63
44
  Assert.Equal("ok", summary.Stamp);
64
45
  Assert.Equal(0, offline.Offline!.Invocations);
65
46
 
66
- // 3) O fake nao foi tocado de novo: a leitura offline nao passou por ele nem por
67
- // acidente (ele esta noutro processo logico, mas a contagem prova a intencao).
68
47
  Assert.Equal(2, fake.CallCount);
69
48
  }
70
49
 
@@ -84,8 +63,6 @@ public sealed class EvalFixtureTests
84
63
  await using var offline = EvalFixture.Create(ws.Options(EvalMode.Offline));
85
64
  await offline.RunAllAsync(TestContext.Current.CancellationToken);
86
65
 
87
- // Ler de volta o ARQUIVO, e nao o objeto em memoria: o contrato com o CLI e o
88
- // arquivo, e um teste que so olhasse o objeto seria um espelho do gerador.
89
66
  var reread = EvalRunSummary.ReadFrom(ws.SummaryPath);
90
67
  Assert.NotNull(reread);
91
68
  Assert.Equal(1, reread!.Scenarios);
@@ -103,17 +80,12 @@ public sealed class EvalFixtureTests
103
80
  var error = Assert.Throws<ArgumentOutOfRangeException>(() => EvalFixture.Create(options));
104
81
 
105
82
  Assert.Contains("eval-cache-ttl-too-short", error.Message, StringComparison.Ordinal);
106
- // A mensagem tem de dizer POR QUE, e nao so que o numero e pequeno: a expiracao apaga
107
- // o diretorio da chave durante a LEITURA.
108
83
  Assert.Contains("DESTRUTIVA NA LEITURA", error.Message, StringComparison.Ordinal);
109
84
  }
110
85
 
111
86
  [Fact]
112
87
  public void TheLibraryDefaultTimeToLiveWouldExpireACommittedCacheWithinWeeks()
113
88
  {
114
- // MEDIDO, nao lembrado: e este fato que torna o TTL explicito obrigatorio. Se um dia
115
- // a biblioteca subir o default para alem do piso do kit, este teste morre e a
116
- // exigencia pode ser rediscutida com um numero na mao.
117
89
  Assert.True(
118
90
  Defaults.DefaultTimeToLiveForCacheEntries < EvalCacheOptions.MinimumTimeToLive,
119
91
  $"o default da biblioteca e {Defaults.DefaultTimeToLiveForCacheEntries.TotalDays:0} dias, "
@@ -6,20 +6,9 @@ using Xunit;
6
6
 
7
7
  namespace Morph.AiKit.Tests.Evals;
8
8
 
9
- /// <summary>
10
- /// Regravacao, e o invariante barato que protege a premissa de commitabilidade:
11
- /// <b>uma leitura nao escreve um byte na arvore de cache</b>.
12
- /// </summary>
13
9
  public sealed class EvalRefreshTests
14
10
  {
15
- /// <summary>
16
- /// Impressao digital da arvore: caminho relativo mais SHA-256 de cada arquivo.
17
- /// </summary>
18
- /// <remarks>
19
- /// Comparar CONTEUDO, e nao data de modificacao: o que importa e se o proximo
20
- /// <c>git status</c> mostra diff, e o git tambem olha conteudo. Um teste sobre mtime
21
- /// falharia por gravacao identica e passaria por gravacao diferente no mesmo tick.
22
- /// </remarks>
11
+ // Compara conteudo, nao mtime: e o conteudo que o git ve como diff.
23
12
  private static IReadOnlyList<string> Fingerprint(string root)
24
13
  {
25
14
  if (!Directory.Exists(root))
@@ -63,9 +52,6 @@ public sealed class EvalRefreshTests
63
52
  Assert.Equal(3, summary.Passed);
64
53
  }
65
54
 
66
- // Se algum dia a biblioteca passar a tocar disco no caminho de HIT, este canario morde
67
- // no dia em que isso acontecer — e nao meses depois, quando alguem estranhar um diff
68
- // que aparece sozinho em toda rodada.
69
55
  Assert.Equal(before, Fingerprint(ws.CacheRoot));
70
56
  }
71
57
 
@@ -79,9 +65,6 @@ public sealed class EvalRefreshTests
79
65
  await using var refresh = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake);
80
66
  await refresh.RunAllAsync(TestContext.Current.CancellationToken);
81
67
 
82
- // Os resultados carregam latencia de cronometro, uso de tokens e o flag de cache hit:
83
- // commita-los seria diff garantido em toda rodada, sem sinal nenhum. Por CONSTRUCAO
84
- // (outra raiz), nunca por um .gitignore dentro de um diretorio commitado.
85
68
  Assert.True(Directory.Exists(ws.ResultsRoot), "o store de resultados tem de existir depois de um run");
86
69
  Assert.NotEmpty(Directory.EnumerateFiles(ws.ResultsRoot, "*", SearchOption.AllDirectories));
87
70
 
@@ -102,8 +85,6 @@ public sealed class EvalRefreshTests
102
85
  await using var refresh = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake);
103
86
  var summary = await refresh.RunAllAsync(TestContext.Current.CancellationToken);
104
87
 
105
- // O contador do cliente real e o que torna "a cache esta fria" uma MEDICAO: a arvore
106
- // mudou e precisa ser commitada, e isso e afirmacao com consequencia.
107
88
  Assert.Equal(3, summary.Misses);
108
89
  Assert.Equal(EvalCacheState.Cold, summary.Cache);
109
90
  Assert.Equal(3, fake.CallCount);
@@ -121,9 +102,6 @@ public sealed class EvalRefreshTests
121
102
  await first.RunAllAsync(TestContext.Current.CancellationToken);
122
103
  }
123
104
 
124
- // O cenario do meio some do dataset. Uma regravacao PARCIAL deixaria a entrada antiga
125
- // dele viva na arvore, e a proxima leitura offline nao teria como saber que ela nao
126
- // corresponde mais a nenhum cenario declarado.
127
105
  File.Delete(Path.Combine(ws.DatasetRoot, "triagem", "acme", "b-segundo.json"));
128
106
 
129
107
  using (var fake = new FakeChatClient { MetadataProviderName = "morph-fake", MetadataDefaultModelId = "modelo-exemplar" })
@@ -144,24 +122,14 @@ public sealed class EvalRefreshTests
144
122
  using var ws = new EvalWorkspace();
145
123
  using var fake = new FakeChatClient { MetadataProviderName = "morph-fake", MetadataDefaultModelId = "modelo-exemplar" };
146
124
 
147
- // A lista vem do KIT, e nao do teste: e ela que da ao pacote .Quality cobertura de
148
- // COMPILACAO em src/ (sem um using real la, o pin daquele pacote teria so contrato de
149
- // versao, e um rename de API nao reprovaria PR nenhuma).
150
125
  var quality = EvalQualityEvaluators.Stable();
151
126
 
152
127
  await using var fixture = EvalFixture.Create(ws.Options(EvalMode.Refresh), fake, quality);
153
128
 
154
- // A superficie existe e COMPILA contra as versoes do pin — que e o que um kit de
155
- // template tem de garantir. O que ela NAO faz e decidir: o veredito de um cenario sai
156
- // das assercoes deterministicas, e uma metrica de juizo entra no sumario como numero.
157
- // Sem isso, a variancia de LLM reprovaria PRs e o no de eval nasceria destinado a ser
158
- // desligado do CI.
159
129
  Assert.Equal(2, quality.Count);
160
130
  Assert.All(quality, evaluator => Assert.NotEmpty(evaluator.EvaluationMetricNames));
161
131
  Assert.Empty(fixture.Summary.Quality);
162
132
 
163
- // Uma lista NOVA a cada chamada: um array compartilhado seria estado global
164
- // disfarcado de conveniencia, e a ReportingConfiguration guarda o que recebe.
165
133
  Assert.NotSame(quality, EvalQualityEvaluators.Stable());
166
134
  }
167
135
 
@@ -172,11 +140,7 @@ public sealed class EvalRefreshTests
172
140
  [InlineData("Microsoft.Extensions.AI.Evaluation.Quality.ToolCallAccuracyEvaluator", true)]
173
141
  public void WhichQualityEvaluatorsAreExperimentalIsMEASURED_NotRemembered(string typeName, bool experimental)
174
142
  {
175
- // O plano descrevia os QUATRO como estaveis. Dois nao sao: eles carregam
176
- // [Experimental("AIEVAL001")] na 10.9.0, e usa-los sob TreatWarningsAsErrors exigiria
177
- // suprimir um diagnostico — uma decisao consciente, nunca um efeito colateral. Se um
178
- // dia a Microsoft estabilizar (ou marcar) algum deles, este teste morre e a escolha e
179
- // rediscutida com o fato na mao, em vez de envelhecer em silencio num comentario.
143
+ // Dois dos quatro avaliadores sao [Experimental("AIEVAL001")] na 10.9.0 e exigiriam supressao sob TreatWarningsAsErrors.
180
144
  var type = typeof(RelevanceEvaluator).Assembly.GetType(typeName);
181
145
  Assert.NotNull(type);
182
146
 
@@ -2,15 +2,6 @@ using Morph.AiKit.Evals;
2
2
 
3
3
  namespace Morph.AiKit.Tests.Evals;
4
4
 
5
- /// <summary>
6
- /// Um diretorio temporario com dataset, cache e resultados — a montagem que todo teste de
7
- /// eval precisa, escrita uma vez.
8
- /// </summary>
9
- /// <remarks>
10
- /// Ele existe para que os testes falem de COMPORTAMENTO e nao de caminhos. E cada instancia
11
- /// tem raiz propria (GUID v7): dois testes rodando lado a lado nao podem disputar a mesma
12
- /// arvore de cache, senao a falha aparece como flake e nao como defeito.
13
- /// </remarks>
14
5
  internal sealed class EvalWorkspace : IDisposable
15
6
  {
16
7
  public EvalWorkspace()
@@ -30,7 +21,6 @@ internal sealed class EvalWorkspace : IDisposable
30
21
 
31
22
  public string SummaryPath => Path.Combine(Root, "summary.json");
32
23
 
33
- /// <summary>O pin exemplar: a data e as versoes sob as quais a arvore diz ter sido gravada.</summary>
34
24
  public static EvalPin Pin { get; } = new(
35
25
  "2026-09-08",
36
26
  new Dictionary<string, string>(StringComparer.Ordinal)
@@ -39,7 +29,6 @@ internal sealed class EvalWorkspace : IDisposable
39
29
  ["Microsoft.Extensions.AI.Evaluation"] = "10.9.0",
40
30
  });
41
31
 
42
- /// <summary>Escreve um cenario no dataset e devolve o caminho do arquivo.</summary>
43
32
  public string WriteScenario(string agent, string tenant, string name, string userText, string assertions)
44
33
  {
45
34
  var dir = Path.Combine(DatasetRoot, agent, tenant);
@@ -54,7 +43,6 @@ internal sealed class EvalWorkspace : IDisposable
54
43
  return file;
55
44
  }
56
45
 
57
- /// <summary>Opcoes prontas para um modo, com TTL longo e o pin exemplar.</summary>
58
46
  public EvalCacheOptions Options(EvalMode mode, EvalPin? pin = null) => new()
59
47
  {
60
48
  CacheRoot = CacheRoot,
@@ -79,8 +67,7 @@ internal sealed class EvalWorkspace : IDisposable
79
67
  }
80
68
  catch (IOException)
81
69
  {
82
- // Um temporario que resiste a remocao nao pode derrubar um teste que ja provou o
83
- // que veio provar.
70
+ // Falha ao apagar o temporario nao deve derrubar um teste que ja provou o que veio provar.
84
71
  }
85
72
  }
86
73
  }
@@ -4,42 +4,14 @@ using Xunit;
4
4
 
5
5
  namespace Morph.AiKit.Tests.Evals;
6
6
 
7
- /// <summary>
8
- /// Os evals DO PROPRIO KIT, contra a arvore de cache COMMITADA — sem credencial, sem rede.
9
- /// </summary>
10
- /// <remarks>
11
- /// <para>
12
- /// Esta classe e o exemplar que um projeto copia. Ela roda com a categoria <c>Eval</c>, le a
13
- /// cache commitada em <c>eval-cache/</c> e o dataset versionado em <c>eval-datasets/</c>, e
14
- /// termina verde com TODAS as variaveis de credencial ausentes. Se algum cenario nao estiver
15
- /// na cache, ela fica VERMELHA nomeando o cenario — nunca <c>skip</c>.
16
- /// </para>
17
- /// <para>
18
- /// <b>A cache foi semeada por FAKE, jamais por chamada paga</b> (ver <see cref="SeedTheCommittedCacheTree"/>).
19
- /// O caminho de ESCRITA exercitado e o da biblioteca e a chave e computada pela mesma funcao
20
- /// que a leitura usa — entao a leitura offline le exatamente o que aquela escrita produziu.
21
- /// A lacuna residual, declarada em voz alta: isto NAO prova que uma cache gravada contra um
22
- /// provider real e legivel offline. O que a mitiga e um fato medido — a chave depende de
23
- /// <c>ProviderName</c> e <c>DefaultModelId</c>, e nao do endpoint, e os dois estao sob nosso
24
- /// controle no cliente offline.
25
- /// </para>
26
- /// </remarks>
27
7
  public sealed class KitEvals
28
8
  {
29
- /// <summary>Identidade sob a qual a arvore commitada foi gravada. Ela ENTRA NA CHAVE.</summary>
9
+ // Provider e Model entram na chave de cache: trocar qualquer um invalida a arvore commitada.
30
10
  private const string Provider = "morph-fake";
31
11
 
32
- /// <summary>Idem. Trocar qualquer um dos dois invalida a arvore inteira.</summary>
33
12
  private const string Model = "morph-kit-exemplar";
34
13
 
35
- /// <summary>
36
- /// O diretorio do PROJETO de teste, achado subindo a partir do assembly.
37
- /// </summary>
38
- /// <remarks>
39
- /// De proposito: a alternativa seria copiar a cache para <c>bin/</c> e ler a copia — e ai
40
- /// o canario de "um run offline nao suja a arvore commitada" seria verdadeiro por
41
- /// vacuidade, porque a arvore commitada nem teria sido tocada.
42
- /// </remarks>
14
+ // Le a arvore commitada no projeto, nao uma copia em bin/: senao o canario de "nao suja a arvore" seria vacuo.
43
15
  private static string ProjectDirectory
44
16
  {
45
17
  get
@@ -59,7 +31,6 @@ public sealed class KitEvals
59
31
 
60
32
  private static string DatasetRoot => Path.Combine(ProjectDirectory, "eval-datasets");
61
33
 
62
- /// <summary>O pin sob o qual a arvore commitada foi gravada — o mesmo que o carimbo carrega.</summary>
63
34
  private static EvalPin Pin => new(
64
35
  "2026-09-08",
65
36
  new Dictionary<string, string>(StringComparer.Ordinal)
@@ -74,8 +45,6 @@ public sealed class KitEvals
74
45
  {
75
46
  CacheRoot = CacheRoot,
76
47
  DatasetRoot = DatasetRoot,
77
- // FORA da arvore commitada: os resultados carregam latencia, uso de tokens e o flag
78
- // de cache hit, e commita-los seria diff garantido em toda rodada.
79
48
  ResultsRoot = Path.Combine(Path.GetTempPath(), "morph-kit-eval-results"),
80
49
  TimeToLive = TimeSpan.FromDays(3650),
81
50
  ProviderName = Provider,
@@ -89,9 +58,6 @@ public sealed class KitEvals
89
58
  [Trait("Category", "Eval")]
90
59
  public async Task TheKitEvalsRunGreenFromTheCommittedCacheWithNoCredentialAtAll()
91
60
  {
92
- // Nenhuma variavel de credencial e lida por nada nesta montagem — o cliente interno
93
- // e o OfflineChatClient, que nao abre socket e nao le ambiente. Se a cache nao
94
- // respondesse, este teste morreria com EvalCacheMissException nomeando o cenario.
95
61
  var summaryPath = Path.Combine(Path.GetTempPath(), $"morph-kit-eval-{Guid.CreateVersion7():N}.json");
96
62
 
97
63
  try
@@ -102,20 +68,13 @@ public sealed class KitEvals
102
68
  Assert.Equal(EvalCacheState.Warm, summary.Cache);
103
69
  Assert.Equal("ok", summary.Stamp);
104
70
 
105
- // NOMEANDO. `Assert.Equal(0, summary.Failed)` produzia "Expected 0, Actual 1" —
106
- // verdadeiro e inutil: nao dizia qual cenario nem qual regua. A rubrica
107
- // eval-de-modelo.md exige, na banda 9-10, derrubar o cenario NOMEANDO-O.
108
71
  Assert.True(summary.Failed == 0, summary.DescribeFailures());
109
72
  Assert.Empty(summary.Failures);
110
73
  Assert.Equal(summary.Scenarios, summary.Passed);
111
74
 
112
- // A prova central, e ela e um CONTADOR: o cliente interno e o unico caminho para
113
- // a rede, e ele nao foi invocado nenhuma vez.
114
75
  Assert.Equal(0, summary.Misses);
115
76
  Assert.Equal(0, fixture.Offline!.Invocations);
116
77
 
117
- // O dataset nao pode encolher em silencio: uma suite de eval que passa a avaliar
118
- // menos cenarios e a mesma classe de defeito que uma baseline de teste caindo.
119
78
  Assert.Equal(4, summary.Scenarios);
120
79
  }
121
80
  finally
@@ -144,17 +103,10 @@ public sealed class KitEvals
144
103
  [Trait("Category", "Eval")]
145
104
  public void TheCommittedCacheCarriesNoABSOLUTEPathOfTheMachineThatWroteIt()
146
105
  {
147
- // A premissa que torna a arvore commitavel: nada dentro dela aponta para o disco de
148
- // quem gravou. Medido, e nao suposto — se um dia a biblioteca passar a gravar o
149
- // storageRootPath dentro das entradas, este canario morde.
150
106
  var root = ProjectDirectory.Replace('\\', '/');
151
107
  var drive = Path.GetPathRoot(ProjectDirectory)?.Replace('\\', '/');
152
108
 
153
- // A raiz so' identifica uma MAQUINA quando e' uma unidade nomeada ("R:/"). Em POSIX
154
- // `GetPathRoot` devolve "/", que nao identifica nada — e exigir que a cache nao
155
- // contenha "/" reprova qualquer arquivo com um path ou uma URL dentro. O canario
156
- // passava no Windows e era ABSURDO no Linux: media "nenhuma barra em lugar nenhum".
157
- // Medido no CI (job ai-kit, ubuntu): 1 falha, `Assert.DoesNotContain() Sub-string found`.
109
+ // Em POSIX GetPathRoot devolve "/", que nao identifica maquina; so unidade nomeada ("R:/") identifica.
158
110
  var namedDrive = drive is { Length: > 1 } && drive.Contains(':');
159
111
 
160
112
  foreach (var file in Directory.EnumerateFiles(CacheRoot, "*", SearchOption.AllDirectories))
@@ -168,21 +120,6 @@ public sealed class KitEvals
168
120
  }
169
121
  }
170
122
 
171
- /// <summary>
172
- /// Regrava a arvore commitada a partir do fake. NAO roda em CI.
173
- /// </summary>
174
- /// <remarks>
175
- /// <para>
176
- /// Ele existe committed, e nao como um script que alguem rodou uma vez e jogou fora,
177
- /// porque a proxima pessoa que subir o pin precisa saber COMO regravar — e uma instrucao
178
- /// em prosa envelhece; um teste executavel nao.
179
- /// </para>
180
- /// <para>
181
- /// A porta e uma variavel de ambiente, e nao um <c>[Fact(Skip=...)]</c> fixo, porque um
182
- /// skip fixo nunca poderia ser exercitado: seria um seeder que ninguem consegue rodar.
183
- /// Rode com <c>MORPH_EVAL_SEED=1 dotnet test</c> e commite a arvore que sair.
184
- /// </para>
185
- /// </remarks>
186
123
  [Fact]
187
124
  public async Task SeedTheCommittedCacheTree()
188
125
  {
@@ -196,9 +133,7 @@ public sealed class KitEvals
196
133
  MetadataDefaultModelId = Model,
197
134
  };
198
135
 
199
- // Uma resposta por cenario, na ORDEM do dataset (ordinal por caminho relativo):
200
- // orcamento/acme/a-resposta-estruturada, triagem/acme/a-saudacao,
201
- // triagem/acme/b-escalonamento, triagem/globex/a-saudacao.
136
+ // Uma resposta por cenario, na ordem ordinal de caminho do dataset.
202
137
  fake.EnqueueText("""{"valor":"R$ 1.200,00","prazo":"5 dias uteis"}""")
203
138
  .EnqueueText("Bom dia! Como posso ajudar voce hoje?")
204
139
  .EnqueueText("Encaminhando voce para um atendente humano agora.")