@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,73 +4,21 @@ using Microsoft.Extensions.AI;
4
4
 
5
5
  namespace Morph.AiKit.Evals;
6
6
 
7
- /// <summary>
8
- /// Um cenario de eval: as mensagens que vao ao modelo e as propriedades que a resposta tem
9
- /// de ter.
10
- /// </summary>
11
- /// <remarks>
12
- /// <para>
13
- /// <b>O dataset e versionado e vive em disco</b>, organizado como
14
- /// <c>{agente}/{tenant}/{cenario}.json</c>. Os dois primeiros segmentos existem porque a
15
- /// pergunta util quase nunca e "o produto regrediu?", e sim "o agente X regrediu para o
16
- /// tenant Y?" — e um dataset plano nao consegue responder isso sem convencao de nome.
17
- /// </para>
18
- /// <para>
19
- /// <b>Dado real de tenant e proibido aqui.</b> A cache guarda mensagens INTEIRAS e e
20
- /// commitada: um segredo que entre no dataset entra no git. O fixture so le cenario deste
21
- /// diretorio versionado — nunca de banco, nunca de variavel de ambiente.
22
- /// </para>
23
- /// </remarks>
24
7
  public sealed record EvalScenario
25
8
  {
26
- /// <summary>Primeiro segmento do caminho no dataset.</summary>
27
9
  public required string Agent { get; init; }
28
10
 
29
- /// <summary>Segundo segmento do caminho no dataset.</summary>
30
11
  public required string Tenant { get; init; }
31
12
 
32
- /// <summary>
33
- /// Nome do arquivo, sem extensao. Vira o <c>scenarioName</c> da cache, e por isso e o
34
- /// que aparece na mensagem de miss.
35
- /// </summary>
36
13
  public required string Name { get; init; }
37
14
 
38
- /// <summary>As mensagens enviadas ao modelo, na ordem.</summary>
39
15
  public required IReadOnlyList<ChatMessage> Messages { get; init; }
40
16
 
41
- /// <summary>As assercoes deterministicas que decidem pass/fail deste cenario.</summary>
42
17
  public IReadOnlyList<EvalAssertion> Assertions { get; init; } = [];
43
18
 
44
- /// <summary>
45
- /// Identidade do cenario dentro da cache: <c>{agente}.{tenant}.{nome}</c>.
46
- /// </summary>
47
- /// <remarks>
48
- /// <para>
49
- /// Qualificado, e nao so <see cref="Name"/>, porque dois tenants tem cenarios com o
50
- /// mesmo nome de proposito ("saudacao", "handoff") — e uma chave de cache que os
51
- /// confundisse devolveria a resposta de um tenant para o outro.
52
- /// </para>
53
- /// <para>
54
- /// <b>O separador e ponto, e nao barra, por uma MEDICAO.</b> A primeira versao usava
55
- /// barra, na suposicao de que a biblioteca aninharia o nome do cenario em diretorios. Ela
56
- /// nao aninha: <c>PathValidation.ValidatePathSegment</c> recusa o nome com
57
- /// <c>ArgumentException: 'scenarioName' contains invalid path characters or directory
58
- /// traversal sequences</c> — o nome do cenario e UM segmento de caminho, sempre. Medido
59
- /// na v10.9.0 contra o SDK .NET 10.
60
- /// </para>
61
- /// </remarks>
19
+ // Ponto, nao barra: a biblioteca exige que o nome do cenario seja UM segmento de caminho (10.9.0).
62
20
  public string CacheKey => $"{Agent}.{Tenant}.{Name}";
63
21
 
64
- /// <summary>
65
- /// Le TODOS os cenarios de um dataset, em ordem estavel (ordinal por caminho).
66
- /// </summary>
67
- /// <remarks>
68
- /// Ordem estavel importa mais do que parece: e ela que faz duas maquinas gravarem a
69
- /// mesma arvore de cache e produzirem o mesmo diff. Uma enumeracao dependente do
70
- /// sistema de arquivos e um diff que muda sozinho.
71
- /// </remarks>
72
- /// <param name="datasetRoot">Raiz do dataset versionado.</param>
73
- /// <exception cref="DirectoryNotFoundException">Quando a raiz nao existe — declarar um dataset ausente e erro de config, nao um dataset vazio.</exception>
74
22
  public static IReadOnlyList<EvalScenario> LoadAll(string datasetRoot)
75
23
  {
76
24
  ArgumentException.ThrowIfNullOrWhiteSpace(datasetRoot);
@@ -85,6 +33,7 @@ public sealed record EvalScenario
85
33
 
86
34
  var files = Directory
87
35
  .EnumerateFiles(datasetRoot, "*.json", SearchOption.AllDirectories)
36
+ // Ordem ordinal: duas maquinas precisam gravar a mesma arvore de cache.
88
37
  .OrderBy(p => p.Replace('\\', '/'), StringComparer.Ordinal)
89
38
  .ToList();
90
39
 
@@ -97,7 +46,6 @@ public sealed record EvalScenario
97
46
  return scenarios;
98
47
  }
99
48
 
100
- /// <summary>Le um cenario, derivando agente e tenant do caminho relativo a raiz.</summary>
101
49
  public static EvalScenario Load(string datasetRoot, string file)
102
50
  {
103
51
  ArgumentException.ThrowIfNullOrWhiteSpace(datasetRoot);
@@ -126,9 +74,6 @@ public sealed record EvalScenario
126
74
  for (var i = 0; i < (dto.Assertions?.Count ?? 0); i++)
127
75
  {
128
76
  var raw = dto.Assertions![i];
129
- // Nome default derivado do indice, e unicidade COBRADA: a biblioteca indexa
130
- // metricas por nome, e um nome repetido faria a segunda assercao sobrescrever a
131
- // primeira — um cenario que parece cobrar duas propriedades cobrando uma.
132
77
  var name = string.IsNullOrWhiteSpace(raw.Name) ? $"{raw.Kind}-{i + 1}" : raw.Name!;
133
78
  if (!used.Add(name))
134
79
  {
@@ -3,128 +3,55 @@ using System.Text.Json.Serialization;
3
3
 
4
4
  namespace Morph.AiKit.Evals;
5
5
 
6
- /// <summary>Estado da arvore de cache nesta execucao, como o sumario o reporta.</summary>
7
6
  public static class EvalCacheState
8
7
  {
9
- /// <summary>Todos os cenarios resolveram pela cache. Nenhuma chamada ao provider.</summary>
10
8
  public const string Warm = "warm";
11
9
 
12
- /// <summary>Ao menos um cenario foi ao provider — a arvore mudou e precisa ser commitada.</summary>
13
10
  public const string Cold = "cold";
14
11
  }
15
12
 
16
- /// <summary>Uma falha de eval, com nome — cenario, assercao e o motivo literal.</summary>
17
- /// <remarks>
18
- /// <para>
19
- /// <b>Por que o sumario carrega isto, e nao so contadores.</b> Medido: degradar a resposta em
20
- /// cache deixava a suite vermelha (correto) com a mensagem <c>Expected 0, Actual 1</c> —
21
- /// verdadeira e inutil. Nada a jusante conseguia dizer QUAL cenario caiu nem por que, porque a
22
- /// informacao morria dentro do fixture.
23
- /// </para>
24
- /// <para>
25
- /// A rubrica <c>eval-de-modelo.md</c> exige, na banda 9-10, "derrubando o cenario e
26
- /// NOMEANDO-O", e da como exemplo <c>eval-fail: triagem.acme.a-saudacao</c>. Sem este tipo a
27
- /// propria rubrica seria inexequivel — o que e pior do que nao ter rubrica, porque parece
28
- /// coberto.
29
- /// </para>
30
- /// </remarks>
31
- /// <param name="Scenario">A chave do cenario (<c>{agente}.{tenant}.{nome}</c>).</param>
32
- /// <param name="Assertion">O nome da assercao que reprovou.</param>
33
- /// <param name="Kind">A regua usada, para distinguir duas assercoes homonimas de tipos distintos.</param>
34
- /// <param name="Reason">O motivo literal produzido pela assercao.</param>
35
13
  public sealed record EvalFailureDetail(
36
14
  [property: JsonPropertyName("scenario")] string Scenario,
37
15
  [property: JsonPropertyName("assertion")] string Assertion,
38
16
  [property: JsonPropertyName("kind")] string Kind,
39
17
  [property: JsonPropertyName("reason")] string Reason)
40
18
  {
41
- /// <summary>Uma linha legivel, no formato que a rubrica cita.</summary>
42
19
  public override string ToString()
43
20
  => $"eval-fail: {Scenario} — {Assertion} ({Kind}): {Reason}";
44
21
  }
45
22
 
46
- /// <summary>
47
- /// O sumario que o fixture escreve no fim da suite, e que o CLI le.
48
- /// </summary>
49
- /// <remarks>
50
- /// <para>
51
- /// <b>Por que ARQUIVO e nao uma linha marcada no stdout.</b> O runner de teste captura a
52
- /// saida de console por teste, e o que chega ao stdout do processo depende da configuracao
53
- /// do runner e do logger. Um contrato que quebra quando alguem liga um logger e um contrato
54
- /// ruim — e a falha seria SILENCIOSA (sumario ausente, contagem desconhecida, no verde pelo
55
- /// codigo de saida). O arquivo tem outra virtude: quem escolhe o caminho e o CLI, entao dois
56
- /// nos de eval no mesmo <c>verify</c> nao colidem.
57
- /// </para>
58
- /// <para>
59
- /// <b>Do outro lado, o leitor trata sumario ausente ou ilegivel como DESCONHECIDO, jamais
60
- /// como zero.</b> Um sumario que nao parseia nao pode fabricar <c>scenarios: 0</c> e
61
- /// transformar uma baseline em queda inventada.
62
- /// </para>
63
- /// </remarks>
64
23
  public sealed record EvalRunSummary
65
24
  {
66
- /// <summary>Quantos cenarios foram avaliados.</summary>
67
25
  [JsonPropertyName("scenarios")]
68
26
  public required int Scenarios { get; init; }
69
27
 
70
- /// <summary>Quantos passaram em TODAS as suas assercoes deterministicas.</summary>
71
28
  [JsonPropertyName("passed")]
72
29
  public required int Passed { get; init; }
73
30
 
74
- /// <summary>Quantos falharam em ao menos uma assercao deterministica.</summary>
75
31
  [JsonPropertyName("failed")]
76
32
  public required int Failed { get; init; }
77
33
 
78
- /// <summary><see cref="EvalCacheState.Warm"/> ou <see cref="EvalCacheState.Cold"/>.</summary>
79
34
  [JsonPropertyName("cache")]
80
35
  public required string Cache { get; init; }
81
36
 
82
- /// <summary>Veredito do carimbo, em minusculas (<c>ok</c>, <c>missing</c>, ...).</summary>
83
37
  [JsonPropertyName("stamp")]
84
38
  public required string Stamp { get; init; }
85
39
 
86
- /// <summary>
87
- /// Quantas vezes a cache falhou e a chamada desceu ao cliente interno. Em modo offline
88
- /// com cache quente e ZERO — e esse zero e a prova de que nenhuma chamada paga foi feita.
89
- /// </summary>
90
40
  [JsonPropertyName("misses")]
91
41
  public required int Misses { get; init; }
92
42
 
93
- /// <summary>O <c>ProviderName</c> usado, o mesmo que entrou na chave.</summary>
94
43
  [JsonPropertyName("provider")]
95
44
  public required string Provider { get; init; }
96
45
 
97
- /// <summary>O <c>DefaultModelId</c> usado, o mesmo que entrou na chave.</summary>
98
46
  [JsonPropertyName("model")]
99
47
  public string? Model { get; init; }
100
48
 
101
- /// <summary>
102
- /// Metricas de JUIZO (avaliadores de qualidade), nome para valor. Reportadas, nunca
103
- /// decisivas: com a cache quente elas sao deterministicas, mas a variancia volta na
104
- /// regravacao, e um gate que dependesse disso nasceria destinado a ser desligado.
105
- /// </summary>
106
49
  [JsonPropertyName("quality")]
107
50
  public IReadOnlyDictionary<string, double> Quality { get; init; } = new Dictionary<string, double>(StringComparer.Ordinal);
108
51
 
109
- /// <summary>
110
- /// As falhas, uma por assercao reprovada, com nome. Vazia num run verde.
111
- /// </summary>
112
- /// <remarks>
113
- /// Lista, e nao um booleano ou um contador, porque um cenario pode quebrar em mais de uma
114
- /// assercao e as duas informam coisas diferentes: uma resposta vazia derruba
115
- /// <c>Contains</c> E <c>JsonHasKey</c>, e saber disso e a diferenca entre "o prompt mudou"
116
- /// e "o modelo parou de responder".
117
- /// </remarks>
118
52
  [JsonPropertyName("failures")]
119
53
  public IReadOnlyList<EvalFailureDetail> Failures { get; init; } = [];
120
54
 
121
- /// <summary>
122
- /// As falhas em texto, uma por linha — a mensagem que um teste ou o CLI mostra.
123
- /// </summary>
124
- /// <remarks>
125
- /// Devolve frase propria quando nao ha falha alguma, em vez de string vazia: uma mensagem
126
- /// de assercao vazia e indistinguivel de uma mensagem que ninguem preencheu.
127
- /// </remarks>
128
55
  public string DescribeFailures()
129
56
  => Failures.Count == 0
130
57
  ? "nenhuma falha de eval registrada"
@@ -132,7 +59,6 @@ public sealed record EvalRunSummary
132
59
 
133
60
  private static readonly JsonSerializerOptions Json = new() { WriteIndented = true };
134
61
 
135
- /// <summary>Grava o sumario. Cria o diretorio se preciso; sobrescreve se ja existe.</summary>
136
62
  public void WriteTo(string path)
137
63
  {
138
64
  ArgumentException.ThrowIfNullOrWhiteSpace(path);
@@ -146,7 +72,6 @@ public sealed record EvalRunSummary
146
72
  File.WriteAllText(path, JsonSerializer.Serialize(this, Json));
147
73
  }
148
74
 
149
- /// <summary>Le um sumario gravado. Devolve nulo para arquivo ausente ou ilegivel — DESCONHECIDO, jamais zero.</summary>
150
75
  public static EvalRunSummary? ReadFrom(string path)
151
76
  {
152
77
  ArgumentException.ThrowIfNullOrWhiteSpace(path);
@@ -2,39 +2,6 @@ using Microsoft.Extensions.AI;
2
2
 
3
3
  namespace Morph.AiKit.Evals;
4
4
 
5
- /// <summary>
6
- /// O <see cref="IChatClient"/> que RECUSA no miss: a peca que torna possivel rodar evals no
7
- /// CI sem uma unica credencial.
8
- /// </summary>
9
- /// <remarks>
10
- /// <para>
11
- /// <b>Onde ele fica.</b> A biblioteca de Evaluation embrulha o cliente que recebe num
12
- /// cliente de cache. No HIT, o cliente interno nunca e invocado. No MISS, a chamada desce
13
- /// para o interno — e o interno, aqui, e este. Logo: com a cache quente ele nao e chamado
14
- /// nenhuma vez, e quando e chamado a resposta certa e "faltou cenario na cache", nao uma
15
- /// chamada de rede.
16
- /// </para>
17
- /// <para>
18
- /// <b>Por que ele replica <c>ProviderName</c> e <c>DefaultModelId</c>.</b> Esses dois
19
- /// campos do <see cref="ChatClientMetadata"/> entram na chave de cache. Se o cliente
20
- /// offline se apresentasse com outro nome, a chave computada na leitura seria outra e TODO
21
- /// cenario daria miss — uma cache quente indistinguivel de uma cache vazia. Eles sao
22
- /// parametros, e nao constantes, exatamente porque tem de reproduzir o run que GRAVOU.
23
- /// </para>
24
- /// <para>
25
- /// <b><see cref="ChatClientMetadata.ProviderUri"/> e deliberadamente nulo.</b> O endpoint
26
- /// NAO entra na chave — medido contra a v10.9.0, contra o que a documentacao afirma. E essa
27
- /// ausencia que permite ao cliente offline nao ter endpoint nenhum: ele nao tem para onde
28
- /// ligar, e nao precisa ter.
29
- /// </para>
30
- /// <para>
31
- /// <b><see cref="Invocations"/> e a prova, e ela e um contador, nao um sniffer.</b> "Zero
32
- /// chamadas de rede" nao se mede sem instrumentar a rede; "zero invocacoes do cliente
33
- /// interno" se mede aqui, e e suficiente porque o cliente interno e o UNICO caminho para a
34
- /// rede nesta montagem. O contador sobe ANTES do lancamento, para que uma tentativa
35
- /// interrompida ainda apareca.
36
- /// </para>
37
- /// </remarks>
38
5
  public sealed class OfflineChatClient : IChatClient
39
6
  {
40
7
  private readonly Lock _gate = new();
@@ -43,18 +10,7 @@ public sealed class OfflineChatClient : IChatClient
43
10
  private readonly string _envFile;
44
11
  private int _invocations;
45
12
 
46
- /// <summary>
47
- /// Cria o cliente offline replicando a identidade do run que gravou a cache.
48
- /// </summary>
49
- /// <param name="providerName">Mesmo <c>ProviderName</c> do run que gravou. Entra na chave.</param>
50
- /// <param name="defaultModelId">Mesmo <c>DefaultModelId</c> do run que gravou. Entra na chave.</param>
51
- /// <param name="cachePath">Raiz da arvore de cache, so para a mensagem de miss.</param>
52
- /// <param name="envFile">Arquivo de credencial esperado, so para a mensagem de miss.</param>
53
- /// <param name="currentScenario">
54
- /// Le o nome do cenario em execucao no momento da chamada. E uma funcao, e nao um
55
- /// valor, porque um fixture roda N cenarios com o MESMO cliente: um nome capturado na
56
- /// construcao nomearia sempre o primeiro.
57
- /// </param>
13
+ // ProviderName e DefaultModelId entram na chave de cache: tem de reproduzir o run que gravou.
58
14
  public OfflineChatClient(
59
15
  string providerName,
60
16
  string? defaultModelId,
@@ -74,36 +30,27 @@ public sealed class OfflineChatClient : IChatClient
74
30
  _currentScenario = currentScenario;
75
31
  }
76
32
 
77
- /// <summary>O <c>ProviderName</c> replicado — o mesmo que gravou a cache.</summary>
78
33
  public string ProviderName { get; }
79
34
 
80
- /// <summary>O <c>DefaultModelId</c> replicado — o mesmo que gravou a cache.</summary>
81
35
  public string? DefaultModelId { get; }
82
36
 
83
- /// <summary>
84
- /// Quantas vezes a cache falhou e a chamada desceu ate aqui. Num run com a cache quente
85
- /// e ZERO, e essa e a prova de que nenhuma chamada paga foi feita.
86
- /// </summary>
87
37
  public int Invocations
88
38
  {
89
39
  get { lock (_gate) { return _invocations; } }
90
40
  }
91
41
 
92
- /// <inheritdoc />
93
42
  public Task<ChatResponse> GetResponseAsync(
94
43
  IEnumerable<ChatMessage> messages,
95
44
  ChatOptions? options = null,
96
45
  CancellationToken cancellationToken = default)
97
46
  => throw Miss();
98
47
 
99
- /// <inheritdoc />
100
48
  public IAsyncEnumerable<ChatResponseUpdate> GetStreamingResponseAsync(
101
49
  IEnumerable<ChatMessage> messages,
102
50
  ChatOptions? options = null,
103
51
  CancellationToken cancellationToken = default)
104
52
  => throw Miss();
105
53
 
106
- /// <inheritdoc />
107
54
  public object? GetService(Type serviceType, object? serviceKey = null)
108
55
  {
109
56
  ArgumentNullException.ThrowIfNull(serviceType);
@@ -115,19 +62,15 @@ public sealed class OfflineChatClient : IChatClient
115
62
 
116
63
  if (serviceType == typeof(ChatClientMetadata))
117
64
  {
118
- // providerUri nulo de proposito: o endpoint nao entra na chave de cache, e um
119
- // cliente que nunca liga para lugar nenhum nao deve anunciar um destino.
65
+ // O endpoint nao entra na chave de cache (medido na 10.9.0), entao o cliente offline nao anuncia nenhum.
120
66
  return new ChatClientMetadata(ProviderName, providerUri: null, DefaultModelId);
121
67
  }
122
68
 
123
69
  return serviceType.IsInstanceOfType(this) ? this : null;
124
70
  }
125
71
 
126
- /// <inheritdoc />
127
72
  public void Dispose()
128
73
  {
129
- // Nada a liberar: este cliente nao abre socket, nao le ambiente e nao guarda estado
130
- // fora do contador. E o ponto.
131
74
  }
132
75
 
133
76
  private EvalCacheMissException Miss()
@@ -7,79 +7,17 @@ using Microsoft.Extensions.Caching.Distributed;
7
7
 
8
8
  namespace Morph.AiKit.Evals;
9
9
 
10
- /// <summary>
11
- /// A cache de respostas em disco do kit, com caminhos CURTOS o bastante para serem
12
- /// commitados e instalados no Windows.
13
- /// </summary>
14
- /// <remarks>
15
- /// <para>
16
- /// <b>Por que não usamos o <c>DiskBasedResponseCacheProvider</c> da biblioteca.</b> Ele nomeia
17
- /// o diretório de cada entrada com o hash inteiro em hexadecimal — <b>96 caracteres</b>. Somado
18
- /// ao resto (<c>cache/{cenário}/{iteração}/</c>, mais o nome do arquivo), o caminho relativo
19
- /// chega a <b>225</b> caracteres; com a raiz deste repositório (73) o absoluto dá <b>298</b>,
20
- /// contra o teto de <b>260</b> do <c>MAX_PATH</c> do Windows.
21
- /// </para>
22
- /// <para>
23
- /// <b>O sintoma foi pior que o erro.</b> O git não recusou barulhento: ele avisou
24
- /// <c>could not open directory ... Filename too long</c> e commitou <b>1 de 9</b> arquivos — só
25
- /// o carimbo. A árvore de cache "commitada" não existia no commit, e o canário de CI
26
- /// (<c>git diff --exit-code</c>) reportava LIMPO porque o git nunca enxergou os arquivos.
27
- /// Um guarda que não consegue observar o objeto reporta verde por construção.
28
- /// </para>
29
- /// <para>
30
- /// <b>O que muda, e só isso:</b> o segmento de hash passa a ser um PREFIXO de
31
- /// <see cref="KeyLength"/> caracteres do mesmo hash. O resto do layout é o da biblioteca —
32
- /// <c>cache/{cenário}/{iteração}/{chave}/{entry.json,contents.data}</c>.
33
- /// </para>
34
- /// <para>
35
- /// <b>Truncar é seguro AQUI por construção, e não por contagem de bits.</b> O
36
- /// <c>entry.json</c> guarda a chave INTEIRA, e a leitura compara: prefixo que colide com outra
37
- /// chave devolve <b>miss</b>, nunca a resposta errada. Um miss é caro (regravar) e visível
38
- /// (o modo offline reprova nomeando o cenário); uma resposta trocada seria um eval verde sobre
39
- /// a resposta de outro cenário — a única falha que este kit não pode ter.
40
- /// </para>
41
- /// <para>
42
- /// <b>Expiração NÃO é destrutiva.</b> A implementação da biblioteca apaga o diretório da chave
43
- /// ao ler uma entrada vencida — numa árvore commitada isso significa `git status` sujo e miss
44
- /// em massa num dia em que ninguém mexeu em nada. Aqui, entrada vencida devolve <c>null</c> e
45
- /// não toca em disco. O piso de TTL de um ano continua valendo como segunda linha de defesa:
46
- /// duas proteções para o mesmo tiro é barato quando o tiro apaga trabalho commitado.
47
- /// </para>
48
- /// <para>
49
- /// <b><see cref="ShortPathResponseCache.Refresh"/> é um no-op deliberado.</b> A validade é
50
- /// absoluta a partir da gravação, e não deslizante. Se <c>Refresh</c> reescrevesse o
51
- /// <c>entry.json</c>, uma LEITURA sujaria a árvore — e o invariante "um run offline não escreve
52
- /// um byte" cairia, junto com o canário que o cobra.
53
- /// </para>
54
- /// </remarks>
10
+ // O DiskBasedResponseCacheProvider da biblioteca nomeia a entrada com o hash inteiro (96 chars) e estoura o MAX_PATH do Windows.
55
11
  public sealed class ShortPathResponseCacheProvider : IEvaluationResponseCacheProvider
56
12
  {
57
- /// <summary>
58
- /// Quantos caracteres do hash nomeiam o diretório da entrada.
59
- /// </summary>
60
- /// <remarks>
61
- /// 16 hexadecimais = 64 bits. O número não precisa ser à prova de colisão sozinho: a
62
- /// verificação da chave inteira no <c>entry.json</c> transforma qualquer colisão num miss.
63
- /// Ele precisa apenas ser curto — e 16 derruba o pior caminho relativo de 225 para 145 (medido).
64
- /// </remarks>
65
13
  public const int KeyLength = 16;
66
14
 
67
- /// <summary>Subdiretório que separa as entradas do carimbo, na raiz da árvore.</summary>
68
- /// <remarks>
69
- /// Mantido (a biblioteca também o usa) porque é ele que torna <see cref="ResetAsync"/>
70
- /// trivialmente seguro: zerar a cache é apagar UM diretório, sem risco de levar o carimbo
71
- /// junto. Custa seis caracteres e remove uma classe inteira de engano.
72
- /// </remarks>
73
15
  public const string EntriesDirectory = "cache";
74
16
 
75
17
  private readonly string _root;
76
18
  private readonly TimeSpan _timeToLive;
77
19
  private readonly TimeProvider _clock;
78
20
 
79
- /// <summary>Cria o provedor sobre uma raiz de cache.</summary>
80
- /// <param name="storageRootPath">Raiz da árvore (a mesma que carrega o carimbo).</param>
81
- /// <param name="timeToLive">Validade absoluta das entradas, contada da gravação.</param>
82
- /// <param name="clock">Relógio injetado — a expiração é uma leitura de relógio.</param>
83
21
  public ShortPathResponseCacheProvider(string storageRootPath, TimeSpan timeToLive, TimeProvider? clock = null)
84
22
  {
85
23
  ArgumentException.ThrowIfNullOrWhiteSpace(storageRootPath);
@@ -89,10 +27,8 @@ public sealed class ShortPathResponseCacheProvider : IEvaluationResponseCachePro
89
27
  _clock = clock ?? TimeProvider.System;
90
28
  }
91
29
 
92
- /// <summary>Onde as entradas vivem, dentro da raiz.</summary>
93
30
  public string EntriesRoot => Path.Combine(_root, EntriesDirectory);
94
31
 
95
- /// <inheritdoc />
96
32
  public ValueTask<IDistributedCache> GetCacheAsync(
97
33
  string scenarioName,
98
34
  string iterationName,
@@ -108,8 +44,6 @@ public sealed class ShortPathResponseCacheProvider : IEvaluationResponseCachePro
108
44
  return ValueTask.FromResult(cache);
109
45
  }
110
46
 
111
- /// <inheritdoc />
112
- /// <remarks>Apaga TODAS as entradas e preserva o carimbo. Raiz ausente é sucesso: não há o que zerar.</remarks>
113
47
  public ValueTask ResetAsync(CancellationToken cancellationToken = default)
114
48
  {
115
49
  cancellationToken.ThrowIfCancellationRequested();
@@ -122,11 +56,6 @@ public sealed class ShortPathResponseCacheProvider : IEvaluationResponseCachePro
122
56
  return ValueTask.CompletedTask;
123
57
  }
124
58
 
125
- /// <inheritdoc />
126
- /// <remarks>
127
- /// Este é o ÚNICO caminho que apaga entrada vencida, e ele só roda quando alguém o chama de
128
- /// propósito. A leitura nunca apaga — ver as observações do tipo.
129
- /// </remarks>
130
59
  public ValueTask DeleteExpiredCacheEntriesAsync(CancellationToken cancellationToken = default)
131
60
  {
132
61
  cancellationToken.ThrowIfCancellationRequested();
@@ -150,16 +79,10 @@ public sealed class ShortPathResponseCacheProvider : IEvaluationResponseCachePro
150
79
  }
151
80
  }
152
81
 
153
- /// <summary>
154
- /// A cache de UM par (cenário, iteração). Ver <see cref="ShortPathResponseCacheProvider"/> para
155
- /// o porquê de ela existir.
156
- /// </summary>
157
82
  internal sealed class ShortPathResponseCache : IDistributedCache
158
83
  {
159
- /// <summary>Metadados da entrada. Nome idêntico ao da biblioteca — o layout é o mesmo.</summary>
160
84
  internal const string EntryFileName = "entry.json";
161
85
 
162
- /// <summary>Payload cru da resposta. Nome idêntico ao da biblioteca.</summary>
163
86
  internal const string ContentsFileName = "contents.data";
164
87
 
165
88
  private static readonly JsonSerializerOptions Json = new() { WriteIndented = true };
@@ -180,16 +103,6 @@ internal sealed class ShortPathResponseCache : IDistributedCache
180
103
  _clock = clock;
181
104
  }
182
105
 
183
- /// <summary>
184
- /// O prefixo do hash que nomeia o diretório da entrada.
185
- /// </summary>
186
- /// <remarks>
187
- /// A chave que a biblioteca entrega já é um hash hexadecimal; pegar o prefixo dela mantém o
188
- /// nome CORRELACIONÁVEL com a chave real (é literalmente o começo dela), o que uma segunda
189
- /// função de hash destruiria. Uma chave que não seja hexadecimal — improvável, mas não
190
- /// impossível se a biblioteca mudar — é reduzida por SHA-256 antes de truncar, para que o
191
- /// nome continue sendo um segmento de caminho válido em qualquer sistema de arquivos.
192
- /// </remarks>
193
106
  internal static string ShortKey(string key)
194
107
  {
195
108
  ArgumentException.ThrowIfNullOrWhiteSpace(key);
@@ -203,7 +116,6 @@ internal sealed class ShortPathResponseCache : IDistributedCache
203
116
  : usable[..ShortPathResponseCacheProvider.KeyLength];
204
117
  }
205
118
 
206
- /// <inheritdoc />
207
119
  public byte[]? Get(string key)
208
120
  {
209
121
  ArgumentException.ThrowIfNullOrWhiteSpace(key);
@@ -218,16 +130,13 @@ internal sealed class ShortPathResponseCache : IDistributedCache
218
130
  return null;
219
131
  }
220
132
 
221
- // A chave INTEIRA decide. O nome do diretório é um prefixo, e um prefixo pode colidir:
222
- // sem esta comparação, a colisão devolveria a resposta de OUTRO cenário — a única falha
223
- // que um eval não pode ter. Com ela, colisão vira miss, que é caro e visível.
133
+ // O diretorio e so um prefixo do hash: a chave inteira decide, e colisao vira miss em vez da resposta de outro cenario.
224
134
  if (!string.Equals(entry.Key, key, StringComparison.Ordinal))
225
135
  {
226
136
  return null;
227
137
  }
228
138
 
229
- // Vencida: devolve miss e NÃO apaga nada. Apagar durante a leitura é o que faz uma
230
- // árvore commitada sumir sozinha e sujar o git status de quem só rodou os testes.
139
+ // Vencida nao apaga, ao contrario da biblioteca: a arvore e commitada e uma leitura nao pode sujar o git status.
231
140
  if (entry.Expiration <= _clock.GetUtcNow())
232
141
  {
233
142
  return null;
@@ -236,14 +145,12 @@ internal sealed class ShortPathResponseCache : IDistributedCache
236
145
  return File.ReadAllBytes(contentsPath);
237
146
  }
238
147
 
239
- /// <inheritdoc />
240
148
  public Task<byte[]?> GetAsync(string key, CancellationToken token = default)
241
149
  {
242
150
  token.ThrowIfCancellationRequested();
243
151
  return Task.FromResult(Get(key));
244
152
  }
245
153
 
246
- /// <inheritdoc />
247
154
  public void Set(string key, byte[] value, DistributedCacheEntryOptions options)
248
155
  {
249
156
  ArgumentException.ThrowIfNullOrWhiteSpace(key);
@@ -263,14 +170,11 @@ internal sealed class ShortPathResponseCache : IDistributedCache
263
170
  };
264
171
 
265
172
  File.WriteAllBytes(Path.Combine(dir, ContentsFileName), value);
266
- // Quebras normalizadas para LF e uma quebra final: estes arquivos são COMMITADOS, e
267
- // CRLF numa máquina com LF noutra é diff que não é diff.
268
173
  File.WriteAllText(
269
174
  Path.Combine(dir, EntryFileName),
270
175
  JsonSerializer.Serialize(entry, Json).ReplaceLineEndings("\n") + "\n");
271
176
  }
272
177
 
273
- /// <inheritdoc />
274
178
  public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
275
179
  {
276
180
  token.ThrowIfCancellationRequested();
@@ -278,18 +182,11 @@ internal sealed class ShortPathResponseCache : IDistributedCache
278
182
  return Task.CompletedTask;
279
183
  }
280
184
 
281
- /// <inheritdoc />
282
- /// <remarks>
283
- /// NO-OP deliberado. A validade é absoluta a partir da gravação, nunca deslizante: se este
284
- /// método reescrevesse o <c>entry.json</c>, uma LEITURA passaria a escrever, e o invariante
285
- /// "um run offline não muda um byte da árvore" — com o canário que o cobra — cairia junto.
286
- /// </remarks>
185
+ // No-op: a validade e absoluta, e reescrever o entry.json faria uma leitura escrever na arvore commitada.
287
186
  public void Refresh(string key) { }
288
187
 
289
- /// <inheritdoc />
290
188
  public Task RefreshAsync(string key, CancellationToken token = default) => Task.CompletedTask;
291
189
 
292
- /// <inheritdoc />
293
190
  public void Remove(string key)
294
191
  {
295
192
  ArgumentException.ThrowIfNullOrWhiteSpace(key);
@@ -301,7 +198,6 @@ internal sealed class ShortPathResponseCache : IDistributedCache
301
198
  }
302
199
  }
303
200
 
304
- /// <inheritdoc />
305
201
  public Task RemoveAsync(string key, CancellationToken token = default)
306
202
  {
307
203
  token.ThrowIfCancellationRequested();
@@ -309,7 +205,6 @@ internal sealed class ShortPathResponseCache : IDistributedCache
309
205
  return Task.CompletedTask;
310
206
  }
311
207
 
312
- /// <summary>Lê o <c>entry.json</c>. Ausente ou ilegível devolve nulo — um miss, nunca uma exceção.</summary>
313
208
  internal static CacheEntry? ReadEntry(string entryPath)
314
209
  {
315
210
  if (!File.Exists(entryPath))
@@ -327,7 +222,6 @@ internal sealed class ShortPathResponseCache : IDistributedCache
327
222
  }
328
223
  }
329
224
 
330
- /// <summary>O que o <c>entry.json</c> carrega. Sem caminho absoluto nenhum: a árvore é commitada.</summary>
331
225
  internal sealed record CacheEntry
332
226
  {
333
227
  [JsonPropertyName("scenarioName")]
@@ -336,7 +230,6 @@ internal sealed class ShortPathResponseCache : IDistributedCache
336
230
  [JsonPropertyName("iterationName")]
337
231
  public required string IterationName { get; init; }
338
232
 
339
- /// <summary>A chave INTEIRA. É ela que torna o prefixo truncado seguro.</summary>
340
233
  [JsonPropertyName("key")]
341
234
  public required string Key { get; init; }
342
235
 
@@ -1,42 +1,8 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>
4
- /// <b>O UNICO ponto de entrada publico da geracao de midia.</b>
5
- /// </summary>
6
- /// <remarks>
7
- /// <para>
8
- /// Leia as assinaturas: nenhuma delas devolve bytes, stream, <c>BinaryData</c> ou URL de
9
- /// provider. <see cref="EnqueueAsync"/> devolve um <see cref="MediaJobId"/> e
10
- /// <see cref="GetAsync"/> devolve o <see cref="MediaJob"/> com o estado dele. Isso e
11
- /// deliberado, e e o mecanismo — nao a explicacao — da regra dura de
12
- /// <c>ai-agents/media-pipeline.md</c>:
13
- /// </para>
14
- /// <para>
15
- /// <b>Nunca gere midia dentro de um turno de conversa.</b> Latencia documentada de ate 2
16
- /// minutos em imagem e de 11 s a 6 min em video, custo por chamada, e zero revisao possivel
17
- /// dentro do turno. O turno enfileira e confirma; a midia volta depois, revisada.
18
- /// </para>
19
- /// <para>
20
- /// O tipo que efetivamente produz bytes (<c>IMediaProvider</c> e os tres adaptadores de
21
- /// provider) e <c>internal</c> ao assembly, com <c>InternalsVisibleTo</c> liberando apenas
22
- /// <c>Morph.AiKit.Tests</c>. Um handler de mensagem no projeto consumidor <b>nao consegue
23
- /// nomear</b> esse tipo: o codigo nao compila. E a diferenca entre "o standard diz para nao
24
- /// fazer" e "o compilador recusa", e ha um teste de acessibilidade por reflexao que a
25
- /// mantem verdadeira.
26
- /// </para>
27
- /// </remarks>
28
3
  public interface IMediaGenerator
29
4
  {
30
- /// <summary>
31
- /// Aceita o pedido e devolve o id do job. <b>Nunca devolve bytes.</b>
32
- /// </summary>
33
- /// <remarks>
34
- /// A chave de idempotencia de PEDIDO e consultada aqui, antes de qualquer chamada paga:
35
- /// um pedido identico a outro ja atendido reusa o job existente em vez de pagar de
36
- /// novo. O teto de custo tambem recusa aqui — depois da chamada, um alerta chega tarde.
37
- /// </remarks>
38
5
  Task<MediaResult<MediaJobId>> EnqueueAsync(MediaRequest request, CancellationToken cancellationToken = default);
39
6
 
40
- /// <summary>O job, com estado, caminho e proveniencia. Job inexistente e recusa nomeada.</summary>
41
7
  Task<MediaResult<MediaJob>> GetAsync(MediaJobId id, CancellationToken cancellationToken = default);
42
8
  }