@mutagent/helix 0.1.0-alpha.14

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 (357) hide show
  1. package/.claude/skills/mutagent-helix/SKILL.md +113 -0
  2. package/.claude/skills/mutagent-helix/scripts/cli/codex-transcode.ts +143 -0
  3. package/.claude/skills/mutagent-helix/scripts/cli/doctor.ts +111 -0
  4. package/.claude/skills/mutagent-helix/scripts/cli/init.ts +382 -0
  5. package/.claude/skills/mutagent-helix/scripts/cli/run.sh +61 -0
  6. package/CLAUDE.md +193 -0
  7. package/LICENSE +201 -0
  8. package/NOTICE +22 -0
  9. package/README.md +209 -0
  10. package/assets/adl-loop.svg +162 -0
  11. package/assets/system-status.svg +422 -0
  12. package/bin/mutagent-cli.mjs +9950 -0
  13. package/bin/mutagent-helix.mjs +78 -0
  14. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/CLAUDE.md +28 -0
  15. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/SKILL.md +138 -0
  16. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/assets/templates/agentspec.yaml.tpl +247 -0
  17. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/references/frameworks/doc-pins.md +53 -0
  18. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/references/principles.md +91 -0
  19. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/references/requirements.yaml +295 -0
  20. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/references/workflows/orchestrator-protocol.md +302 -0
  21. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/cli/doctor.ts +116 -0
  22. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/cli/init.ts +230 -0
  23. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/cli/run.sh +60 -0
  24. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/config/resolve-spec-dir.ts +199 -0
  25. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/contract/agentspec.schema.ts +455 -0
  26. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/setup/detect.ts +67 -0
  27. package/mutagent-agentspec/.claude/skills/mutagent-agentspec/scripts/validate/validate-spec.ts +93 -0
  28. package/mutagent-agentspec/LICENSE +201 -0
  29. package/mutagent-agentspec/NOTICE +19 -0
  30. package/mutagent-agentspec/README.md +44 -0
  31. package/mutagent-builder/.claude/skills/mutagent-builder/SKILL.md +100 -0
  32. package/mutagent-builder/.claude/skills/mutagent-builder/assets/agents/ai-architect.md +312 -0
  33. package/mutagent-builder/.claude/skills/mutagent-builder/assets/agents/ai-engineer.md +357 -0
  34. package/mutagent-builder/.claude/skills/mutagent-builder/assets/templates/build-report.md.tpl +39 -0
  35. package/mutagent-builder/.claude/skills/mutagent-builder/references/principles.md +31 -0
  36. package/mutagent-builder/.claude/skills/mutagent-builder/references/workflows/build-protocol.md +61 -0
  37. package/mutagent-builder/.claude/skills/mutagent-builder/references/workflows/improve-handoff.md +23 -0
  38. package/mutagent-builder/.claude/skills/mutagent-builder/scripts/cli/run.sh +60 -0
  39. package/mutagent-builder/.claude/skills/mutagent-builder/scripts/handoff/validate-build-input.ts +72 -0
  40. package/mutagent-builder/.claude/skills/mutagent-builder/scripts/sync-spec/check-sync-spec.ts +499 -0
  41. package/mutagent-builder/.claude/skills/mutagent-builder/scripts/verify/spec-impl-coverage.ts +182 -0
  42. package/mutagent-builder/CLAUDE.md +25 -0
  43. package/mutagent-builder/README.md +26 -0
  44. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/README.md +192 -0
  45. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/SKILL.md +352 -0
  46. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/agents/ai-architect.md +256 -0
  47. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/agents/ai-engineer.md +344 -0
  48. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/agents/diagnostics-analyzer.md +576 -0
  49. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/agents/discovery.md +425 -0
  50. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/audit.json.tpl +24 -0
  51. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/audit.md.tpl +49 -0
  52. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/config.yaml.tpl +100 -0
  53. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/pr-body.md.tpl +56 -0
  54. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/report.html.tpl +327 -0
  55. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/README.md +27 -0
  56. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/iter-N-handover.md.tpl +125 -0
  57. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/self-diagnosis-contract.v0.1.0.yaml.tpl +234 -0
  58. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/spec.yaml.tpl +152 -0
  59. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/team.yaml.tpl +127 -0
  60. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/templates/shared/wave-N-dashboard.html.tpl +1712 -0
  61. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.1-nl-query.md +59 -0
  62. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.2-tier0-progress.md +58 -0
  63. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.3-analysis-progress.md +44 -0
  64. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.4-html-report.md +61 -0
  65. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.5-rca-detail.md +69 -0
  66. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/diagnostics/WF-2.6-stale-warning.md +53 -0
  67. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.1-welcome.md +60 -0
  68. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.2-source-platform-picker.md +41 -0
  69. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.3-source-auth.md +75 -0
  70. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.4-target-picker.md +48 -0
  71. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.5-ask-tool.md +43 -0
  72. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.6-config-review.md +57 -0
  73. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.7-filter-defaults.md +52 -0
  74. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/assets/wireframes/onboarding/WF-1.8-first-run.md +59 -0
  75. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/examples/sample-findings.json +92 -0
  76. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/examples/sample-meta.json +47 -0
  77. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/adapter-strategy.md +75 -0
  78. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/config-migration.md +84 -0
  79. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/config.md +159 -0
  80. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/filter-search-matrix.md +66 -0
  81. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/harness-knowledge.md +22 -0
  82. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/memory-format.md +78 -0
  83. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/onboarding-decisions.yaml +187 -0
  84. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/operation-inventory.md +59 -0
  85. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/overview.md +167 -0
  86. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/reference.md +119 -0
  87. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/script-index.md +101 -0
  88. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-adapter-contract.md +93 -0
  89. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/claude-code-transcripts.md +66 -0
  90. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/codex-transcripts.md +78 -0
  91. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/codex.md +145 -0
  92. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/install-paths.md +57 -0
  93. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/langfuse.md +178 -0
  94. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/local-jsonl.md +55 -0
  95. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/openobserve.md +102 -0
  96. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/source-platforms/otel.md +63 -0
  97. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/cloud-rest.md +84 -0
  98. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-claude.md +65 -0
  99. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-cloud-agent-sdk.md +92 -0
  100. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-codex.md +72 -0
  101. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-cursor.md +108 -0
  102. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-mastra.md +77 -0
  103. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/target-platforms/local-opencode.md +70 -0
  104. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/apply-pr-comment-format.md +82 -0
  105. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/diagnostics.md +35 -0
  106. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/handover-contract.md +247 -0
  107. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/onboarding.md +300 -0
  108. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/orchestrator-protocol.md +1861 -0
  109. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/rca.md +331 -0
  110. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/rendering-anatomy.md +314 -0
  111. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/schedule-prep.md +100 -0
  112. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/scope-model.md +128 -0
  113. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/references/workflows/verification-methodology.md +174 -0
  114. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/aggregate/sort-findings.ts +0 -0
  115. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/awareness/blind-spots.ts +167 -0
  116. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/awareness/llm-sample.ts +681 -0
  117. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/doctor.ts +215 -0
  118. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/init.ts +1013 -0
  119. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/install-agents.ts +350 -0
  120. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/run.sh +61 -0
  121. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/cli/shared-templates.ts +79 -0
  122. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/config/load.ts +392 -0
  123. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/config/resolve-credential.ts +120 -0
  124. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/config/schema.ts +641 -0
  125. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/config/validate.ts +134 -0
  126. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/context/build-diagnosis-context.ts +417 -0
  127. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/context/load-context.ts +105 -0
  128. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/contract/types.ts +166 -0
  129. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/enrich/build-render-input.ts +1964 -0
  130. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/enrich/corpus-severity.ts +172 -0
  131. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/enrich/dismissal-match.ts +270 -0
  132. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/enrich/rank-remedies.ts +75 -0
  133. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/hooks/install-hooks.sh +46 -0
  134. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/hooks/pre-push.sh +78 -0
  135. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/invocation/parse-brief.ts +188 -0
  136. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/library/match.ts +114 -0
  137. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/library/paths.ts +95 -0
  138. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/library/store.ts +535 -0
  139. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/library/types.ts +181 -0
  140. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/lint/template-inline-js.ts +267 -0
  141. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/memory/append.ts +188 -0
  142. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/memory/read.ts +218 -0
  143. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/platforms/entity-context.ts +686 -0
  144. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/read-unitf.ts +267 -0
  145. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/trace.ts +1078 -0
  146. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/unitf-adapter.ts +0 -0
  147. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/normalize/unitf-types.ts +213 -0
  148. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/report/persist-selections.ts +174 -0
  149. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/report/render.ts +3254 -0
  150. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/run/diagnose.ts +64 -0
  151. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/run/session.ts +167 -0
  152. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/sample/caps.ts +345 -0
  153. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/sample/deep-read-gate.ts +424 -0
  154. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/sample/representative.ts +584 -0
  155. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/scan/objection.ts +224 -0
  156. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/scan/trajectory.ts +489 -0
  157. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/self-diagnostics/dispatch.ts +135 -0
  158. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/self-diagnostics/probe.ts +170 -0
  159. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/setup/detect.ts +307 -0
  160. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/setup/ensure-cli.ts +315 -0
  161. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/setup/reconfigure.ts +77 -0
  162. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/setup/verify-agents.ts +207 -0
  163. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/slicer.ts +166 -0
  164. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/claude-code.ts +17 -0
  165. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/codex.ts +11 -0
  166. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/langfuse.ts +17 -0
  167. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/local-jsonl.ts +12 -0
  168. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/adapters/otel.ts +12 -0
  169. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/index.ts +54 -0
  170. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/registry.ts +45 -0
  171. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/source/types.ts +86 -0
  172. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/stale-detector.ts +74 -0
  173. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/test/render-playwright.cli.ts +477 -0
  174. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/tier0/claude-code.ts +133 -0
  175. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/tier0/langfuse.ts +143 -0
  176. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/tier0-scan.ts +423 -0
  177. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/completeness-check.ts +436 -0
  178. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/doc-refs.ts +190 -0
  179. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/finalize-gate.ts +417 -0
  180. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/findings-contract.ts +628 -0
  181. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/render-js-syntax.ts +188 -0
  182. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/report-checklist.yaml +217 -0
  183. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/trace-shape.ts +262 -0
  184. package/mutagent-diagnostics/.claude/skills/mutagent-diagnostics/scripts/validate/wave6-checklist.ts +326 -0
  185. package/mutagent-diagnostics/CLAUDE.md +41 -0
  186. package/mutagent-diagnostics/LICENSE +201 -0
  187. package/mutagent-diagnostics/NOTICE +18 -0
  188. package/mutagent-diagnostics/README.md +343 -0
  189. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/SKILL.md +548 -0
  190. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/agents/audit-executor.md +169 -0
  191. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/agents/dataset-builder.md +160 -0
  192. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/agents/discovery.md +425 -0
  193. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/agents/evaluator.md +1108 -0
  194. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/brand/theme.css +213 -0
  195. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/assets/brand/wordmark.html +9 -0
  196. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/context-flow-lens.md +85 -0
  197. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/data-lens.md +47 -0
  198. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/decision-lens.md +48 -0
  199. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/methodology-critic-lens.md +53 -0
  200. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/lenses/trajectory-lens.md +44 -0
  201. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/build-review-interface.md +83 -0
  202. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/edd-loop.md +134 -0
  203. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/error-analysis.md +113 -0
  204. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/eval-audit.md +154 -0
  205. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/eval-stage.md +168 -0
  206. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/generate-synthetic-data.md +81 -0
  207. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/grounded-adjudication.md +221 -0
  208. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/memory-format.md +65 -0
  209. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/methodology.md +201 -0
  210. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/operation-inventory.md +197 -0
  211. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/validate-evaluator.md +125 -0
  212. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/workflows/orchestrator-protocol.md +300 -0
  213. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/references/write-judge-prompt.md +123 -0
  214. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/behavior-tree.schema.yaml +73 -0
  215. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/dataset.schema.yaml +66 -0
  216. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/edd-change-request.schema.yaml +114 -0
  217. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/eval-matrix.schema.yaml +74 -0
  218. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/flow-graph.schema.yaml +69 -0
  219. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/flow-profile.schema.yaml +49 -0
  220. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/methodology-review.schema.yaml +40 -0
  221. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/schemas/scorecard.schema.yaml +85 -0
  222. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/agent-dispatch.ts +0 -0
  223. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/aggregate-discover.ts +543 -0
  224. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/artifact-paths.ts +115 -0
  225. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/assemble-scorecard.ts +172 -0
  226. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/build-dataset.ts +186 -0
  227. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/build-evals.ts +93 -0
  228. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/build-review-ui.ts +393 -0
  229. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/check-method-router.ts +170 -0
  230. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/aggregate.ts +112 -0
  231. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/audit-run.ts +182 -0
  232. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/doctor.ts +211 -0
  233. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/dogfood.ts +133 -0
  234. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/init.ts +601 -0
  235. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/methodology-review.ts +122 -0
  236. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/prep.ts +295 -0
  237. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/profile-subject.ts +165 -0
  238. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/run.sh +56 -0
  239. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cli/variance-check.ts +105 -0
  240. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/code-eval.ts +248 -0
  241. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/code-quality-verdict.ts +285 -0
  242. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/codegen-evals.ts +173 -0
  243. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cold-start-project.ts +151 -0
  244. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/cold-start-sampler.ts +267 -0
  245. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/config/load.ts +343 -0
  246. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/config/schema.ts +380 -0
  247. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/agentspec-evals.ts +85 -0
  248. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/dataset.ts +149 -0
  249. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/eval-engine.ts +118 -0
  250. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/eval-matrix.ts +577 -0
  251. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/eval-types.ts +1074 -0
  252. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/flow-graph.ts +194 -0
  253. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/types.ts +303 -0
  254. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/contracts/validation.ts +193 -0
  255. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/derive-dataset.ts +152 -0
  256. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/determine-outcome.ts +219 -0
  257. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/diff-discriminate.ts +160 -0
  258. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/discover-criteria.ts +348 -0
  259. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/edd/change-request.ts +232 -0
  260. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/edd/edd-types.ts +210 -0
  261. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/edd/variance-gate.ts +186 -0
  262. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/emit-completeness.ts +111 -0
  263. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/eval-engine.ts +160 -0
  264. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/evaluate.ts +333 -0
  265. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/flow-graph.ts +230 -0
  266. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/judge-prompt-template.ts +253 -0
  267. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/judge-provider.ts +135 -0
  268. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/lint-grounding.ts +229 -0
  269. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/lint-uniformity.ts +168 -0
  270. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/living-suite.ts +109 -0
  271. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/load-bundle.ts +203 -0
  272. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/load-profile-vocab.ts +64 -0
  273. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/load-profile.ts +106 -0
  274. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/mask.ts +138 -0
  275. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/materialize-dataset.ts +113 -0
  276. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/matrix-judge.ts +750 -0
  277. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/memory/append.ts +215 -0
  278. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/memory/read.ts +168 -0
  279. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/persist-eval-criteria.ts +232 -0
  280. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/prep-tasks.ts +125 -0
  281. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/profile-subject.ts +310 -0
  282. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/publish-report.ts +131 -0
  283. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/read-unitf-traces.ts +81 -0
  284. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/render-build-cards.ts +195 -0
  285. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/render-discover-report.ts +1640 -0
  286. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/render-eval-report.ts +3838 -0
  287. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/render-report.ts +212 -0
  288. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/resolve-credential.ts +110 -0
  289. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/resolve-ref.ts +98 -0
  290. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/result-verify.ts +129 -0
  291. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/route-failures.ts +330 -0
  292. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/run-deterministic.ts +367 -0
  293. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/run-evaluate.ts +719 -0
  294. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/run-judge.ts +155 -0
  295. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/run-pipeline.ts +175 -0
  296. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/sample-traces.ts +210 -0
  297. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/self-audit.ts +387 -0
  298. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/source-map.ts +106 -0
  299. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/subject-profile.ts +134 -0
  300. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/substrate.ts +162 -0
  301. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/sync-eval-criteria.ts +244 -0
  302. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/ui-slots.ts +119 -0
  303. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/unitf-to-evaltrace.ts +284 -0
  304. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/validate-judge.ts +358 -0
  305. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/scripts/variance-compare.ts +177 -0
  306. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/behavior-tree.yaml +140 -0
  307. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/eval-matrix.yaml +1270 -0
  308. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/subjects/mutagent-diagnostics/methodology-review.yaml +105 -0
  309. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/workflows/audit.workflow.js +82 -0
  310. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/workflows/data-leak.workflow.js +236 -0
  311. package/mutagent-evaluator/.claude/skills/mutagent-evaluator/workflows/variance.workflow.js +163 -0
  312. package/mutagent-evaluator/CLAUDE.md +45 -0
  313. package/mutagent-evaluator/LICENSE +201 -0
  314. package/mutagent-evaluator/NOTICE +23 -0
  315. package/mutagent-evaluator/README.md +88 -0
  316. package/mutagent-optimize/.claude/skills/mutagent-optimize/SKILL.md +287 -0
  317. package/mutagent-optimize/.claude/skills/mutagent-optimize/scripts/contracts/amend-request.ts +503 -0
  318. package/mutagent-optimize/.claude/skills/mutagent-optimize/scripts/improve-loop-run.ts +304 -0
  319. package/mutagent-optimize/.claude/skills/mutagent-optimize/scripts/loop-state-cli.ts +300 -0
  320. package/mutagent-optimize/.claude/skills/mutagent-optimize/scripts/loop-state.ts +440 -0
  321. package/mutagent-optimize/CLAUDE.md +49 -0
  322. package/mutagent-optimize/README.md +26 -0
  323. package/mutagent-orchestrator/CLAUDE.md +102 -0
  324. package/mutagent-orchestrator/README.md +20 -0
  325. package/mutagent-orchestrator/assets/agents/discovery.md +425 -0
  326. package/mutagent-orchestrator/assets/agents/dogfood-monitor.md +266 -0
  327. package/mutagent-orchestrator/assets/agents/monitor.md +258 -0
  328. package/mutagent-orchestrator/orchestrator.md +782 -0
  329. package/mutagent-orchestrator/references/MONITORING.md +209 -0
  330. package/mutagent-orchestrator/references/config-fields.md +132 -0
  331. package/mutagent-orchestrator/references/config-migration.md +132 -0
  332. package/mutagent-orchestrator/references/index-registry.md +92 -0
  333. package/mutagent-orchestrator/routing.yaml +391 -0
  334. package/mutagent-orchestrator/scripts/config-schema.ts +1010 -0
  335. package/mutagent-orchestrator/scripts/dispatch.ts +439 -0
  336. package/mutagent-orchestrator/scripts/dogfood/extract-feedback.ts +203 -0
  337. package/mutagent-orchestrator/scripts/dogfood/reconstruct-trajectory.ts +327 -0
  338. package/mutagent-orchestrator/scripts/dogfood/render-dogfood-report.ts +347 -0
  339. package/mutagent-orchestrator/scripts/dogfood/resolve-subagents.ts +160 -0
  340. package/mutagent-orchestrator/scripts/dogfood/run-report.ts +264 -0
  341. package/mutagent-orchestrator/scripts/dogfood/slack-thread.ts +106 -0
  342. package/mutagent-orchestrator/scripts/dogfood/types.ts +257 -0
  343. package/mutagent-orchestrator/scripts/eval-routing.ts +753 -0
  344. package/mutagent-orchestrator/scripts/gate.ts +219 -0
  345. package/mutagent-orchestrator/scripts/handover-contract.ts +362 -0
  346. package/mutagent-orchestrator/scripts/index/build-index.ts +351 -0
  347. package/mutagent-orchestrator/scripts/index/render-index.ts +110 -0
  348. package/mutagent-orchestrator/scripts/monitor/slack-notify.ts +111 -0
  349. package/mutagent-orchestrator/scripts/monitor/triggers.ts +162 -0
  350. package/mutagent-orchestrator/scripts/onboarding-check.ts +369 -0
  351. package/mutagent-orchestrator/scripts/render-roster.ts +308 -0
  352. package/mutagent-orchestrator/scripts/resolve-credential.ts +185 -0
  353. package/mutagent-orchestrator/scripts/resolve-paths.ts +114 -0
  354. package/mutagent-orchestrator/scripts/slack/format.ts +63 -0
  355. package/mutagent-orchestrator/scripts/slack/post.ts +115 -0
  356. package/mutagent-orchestrator/scripts/sync-index.ts +311 -0
  357. package/package.json +64 -0
@@ -0,0 +1,1640 @@
1
+ /**
2
+ * scripts/render-discover-report.ts — SHIPPED `*discover-evals` report renderer.
3
+ * ---------------------------------------------------------------------------
4
+ * The SHIPPED replacement for the EXCLUDED run-local Python `render-carousel.py`.
5
+ * Reads a discover `criteria.json` (FULL `MinedCriterion[]` — the real `*discover-evals`
6
+ * AGGREGATE output, `aggregateDiscover().criteria`) + OPTIONAL companion artifacts
7
+ * (`grounding-check.json`, `triage-summary.json`, `sent-dist.json`, `verdicts/`,
8
+ * `dataset-candidates.json`, a subject `profile`) and emits a self-contained,
9
+ * EVALUATOR-GRADE `report.html` with sticky-nav tabs (mirrors `render-eval-report.ts`):
10
+ *
11
+ * ① Overview — entity card (subject · reconstructed system prompt · tools ·
12
+ * code-access) + a coverage FUNNEL (ingested → triaged → deep-read
13
+ * → criteria mined, drawn with embedded mermaid) + the send-tool
14
+ * distribution + saturation status.
15
+ * ② Criteria — one expandable card per criterion: statement · HOW-TO-DETECT
16
+ * (plain language) · grounding (observed/inferred) · severity ·
17
+ * check-method chip (CODE / JUDGE / HYBRID) · the `codeEval` spec
18
+ * (primitive + params) for CODE/HYBRID criteria · evidence refs
19
+ * {obs,path,value} · typed assumptions · prevalence · raw-JSON
20
+ * toggle · a mutually-exclusive HITL keep/revise/retire control +
21
+ * a notes box. A decisions PANEL collects every choice into a
22
+ * copy-paste TASK DEFINITION (markdown) with a clipboard export.
23
+ * ③ Proof-of-work — the determiner deep-read verdicts (per-trace ✓/✗ + cited reason
24
+ * + confidence) read from `verdicts/`.
25
+ * ④ Dataset — the dataset-candidates (the held-out derivation seed).
26
+ *
27
+ * Brand: rendered from the evaluator's OWN bundled `assets/brand/theme.css` (the
28
+ * unified design-system tokens — dark · SHARP corners · ≥11px font floor; every size
29
+ * steps off the `--fs-*` scale, NO ad-hoc px) + report-component CSS referencing
30
+ * those SAME tokens. Self-contained — no external script/CDN dependency (the coverage
31
+ * funnel is a native segmented stage strip; the Criteria tab is a paged carousel).
32
+ *
33
+ * DETERMINISTIC + null-guarded: the ONLY non-deterministic input is the injected
34
+ * `generatedAt`, so two renders of the same input are BYTE-IDENTICAL after the
35
+ * `mask.ts` generatedAt mask (C-PIN). The HITL control is client-state-only — its
36
+ * default (unchecked radios · empty task-definition) is static, so byte-identity holds.
37
+ * PURE except `renderDiscoverReport`'s brand file reads (theme.css + wordmark.html) —
38
+ * never spawns a process / opens a file.
39
+ */
40
+ import { readFileSync, writeFileSync, readdirSync } from "node:fs";
41
+ import { dirname, join } from "node:path";
42
+ import { fileURLToPath } from "node:url";
43
+ import type { CodeEvalSpec } from "./code-eval.ts";
44
+ import {
45
+ Grounding,
46
+ type DiscoveryAssumption,
47
+ type DiscoveryRef,
48
+ type EvalTrace,
49
+ type MinedCriterion,
50
+ } from "./contracts/eval-types.ts";
51
+
52
+ const HERE = dirname(fileURLToPath(import.meta.url));
53
+ const BRAND_DIR = join(HERE, "..", "assets", "brand");
54
+
55
+ /** HTML-escape (null-guarded — no throw on undefined). Mirrors render-eval-report.ts. */
56
+ export function esc(s: unknown): string {
57
+ return String(s ?? "")
58
+ .replaceAll("&", "&")
59
+ .replaceAll("<", "&lt;")
60
+ .replaceAll(">", "&gt;")
61
+ .replaceAll('"', "&quot;");
62
+ }
63
+
64
+ /** Embed a JSON value into an inline <script> safely (neutralise `</script>`). */
65
+ function jsonForScript(value: unknown): string {
66
+ return JSON.stringify(value ?? null).replaceAll("<", "\\u003c");
67
+ }
68
+
69
+ /**
70
+ * WS-6 — reconstruct the subject's SYSTEM PROMPT from a raw trace. The discover
71
+ * `profile` usually carries no `systemPrompt`, but the agent's static system message
72
+ * rides on every LLM-call observation as the first `role:"system"` message — so it is
73
+ * RECOVERABLE from the trace batch, not confabulated. The discover-side entity hero
74
+ * renders it COLLAPSED when `profile.systemPrompt` is set.
75
+ *
76
+ * VERBATIM MIRROR of `reconstructSystemPrompt` in `render-eval-report.ts` (the canonical
77
+ * WS-6 extractor ws1-eval built). Kept here — rather than imported — so the SELF-CONTAINED
78
+ * discover renderer does not pull the whole eval scoring pipeline (eval-matrix · matrix-judge ·
79
+ * evaluate · route-failures · source-map) in for one pure function. KEEP IN SYNC: any logic
80
+ * change to either copy must be applied to BOTH (do not let the two extractors diverge).
81
+ *
82
+ * Scans, in priority order: (1) each observation's `input` — a bare messages ARRAY
83
+ * OR a `{messages:[…]}` wrapper; (2) the trace-level `input` in those same two shapes.
84
+ * Returns the FIRST `role:"system"` message's text. Returns `undefined` when no system
85
+ * message exists — the caller then leaves the prompt UNAVAILABLE (NEVER fabricated).
86
+ * PURE, read-only over the trace.
87
+ */
88
+ export function reconstructSystemPrompt(t: EvalTrace): string | undefined {
89
+ // content → text: a message content is a string OR an array of {type,text} parts.
90
+ const contentText = (content: unknown): string => {
91
+ if (typeof content === "string") return content;
92
+ if (Array.isArray(content)) {
93
+ return content
94
+ .map((part) =>
95
+ part !== null && typeof part === "object"
96
+ ? String((part as { text?: unknown }).text ?? "")
97
+ : typeof part === "string"
98
+ ? part
99
+ : "",
100
+ )
101
+ .join("");
102
+ }
103
+ return "";
104
+ };
105
+ // scan a message ARRAY for the first role:"system" → its (non-empty) content text.
106
+ const fromMessages = (msgs: unknown): string | undefined => {
107
+ if (!Array.isArray(msgs)) return undefined;
108
+ for (const m of msgs) {
109
+ if (m !== null && typeof m === "object" && (m as { role?: unknown }).role === "system") {
110
+ const txt = contentText((m as { content?: unknown }).content).trim();
111
+ if (txt.length > 0) return txt;
112
+ }
113
+ }
114
+ return undefined;
115
+ };
116
+ // a container is either a bare messages array OR a {messages:[…]} wrapper.
117
+ const fromContainer = (c: unknown): string | undefined => {
118
+ const direct = fromMessages(c);
119
+ if (direct !== undefined) return direct;
120
+ if (c !== null && typeof c === "object" && !Array.isArray(c)) {
121
+ return fromMessages((c as { messages?: unknown }).messages);
122
+ }
123
+ return undefined;
124
+ };
125
+ for (const o of t.observations ?? []) {
126
+ const hit = fromContainer((o as { input?: unknown }).input);
127
+ if (hit !== undefined) return hit;
128
+ }
129
+ return fromContainer((t as { input?: unknown }).input);
130
+ }
131
+
132
+ /** The OPTIONAL GA `grounding-check.json` summary shape (a subset — null-tolerant). */
133
+ export interface GroundingCheckSummary {
134
+ totalCriteria?: number;
135
+ observed?: number;
136
+ inferred?: number;
137
+ diffResults?: { id: string; decision?: string; grounding?: string; reason?: string }[];
138
+ }
139
+
140
+ /** OPTIONAL subject profile (a subset of the M1 `SubjectProfile`) — drives the entity card. */
141
+ export interface DiscoverProfile {
142
+ identity?: string;
143
+ entityType?: string;
144
+ purpose?: string;
145
+ tools?: string[];
146
+ skill?: string;
147
+ scope?: string;
148
+ /** rendered COLLAPSED in the entity card; never confabulated. */
149
+ systemPrompt?: string;
150
+ harness?: string;
151
+ /** `given` (code access) vs `reconstructed` (from traces) — drives the code-access chip. */
152
+ provenance?: "given" | "reconstructed";
153
+ version?: string;
154
+ inferredFields?: string[];
155
+ }
156
+
157
+ /** OPTIONAL `triage-summary.json` — the cheap-triage census over the WHOLE batch. */
158
+ export interface TriageSummary {
159
+ total?: number;
160
+ sendTool?: string;
161
+ sentDist?: Record<string, number>;
162
+ sendSucc?: Record<string, number>;
163
+ eventTax?: Record<string, number>;
164
+ eventTags?: string[];
165
+ /** reconstructed entity census — tool name → #traces that invoked it (observed call-frequency). */
166
+ toolCensus?: Record<string, number>;
167
+ /** total tool-call volume observed across the batch. */
168
+ sends?: number;
169
+ /** mean observations per trace (observed). */
170
+ obsAvg?: number;
171
+ /** OPTIONAL reliability roll-ups across the batch — the send-health / recovery /
172
+ * outbound-guard signals the gating criteria are mined FROM. Each field optional. */
173
+ totals?: {
174
+ sends?: number;
175
+ failedSends?: number;
176
+ hardFails?: number;
177
+ tracesWithRecovery?: number;
178
+ tracesWithOutboundGuard?: number;
179
+ };
180
+ }
181
+
182
+ /** OPTIONAL `sent-dist.json` — the send/succeed distribution (+ per-trace rows). */
183
+ export interface SentDistSummary {
184
+ dist?: Record<string, number>;
185
+ sendSucc?: Record<string, number>;
186
+ byTrace?: { traceId: string; sent?: string; succ?: string; tools?: number; event?: string }[];
187
+ }
188
+
189
+ /** OPTIONAL determiner deep-read verdict (one per trace; filename = trace id). */
190
+ export interface DeterminerVerdict {
191
+ traceId: string;
192
+ result?: string;
193
+ critique?: string;
194
+ confidence?: number;
195
+ }
196
+
197
+ /** OPTIONAL dataset candidate (the held-out derivation seed). */
198
+ export interface DatasetCandidate {
199
+ id: string;
200
+ tuple?: Record<string, unknown>;
201
+ query?: string;
202
+ source?: string;
203
+ originTraceId?: string;
204
+ /** deterministic provenance — which EV-052 selector nominated it ("data link"). */
205
+ selectedBy?: string;
206
+ /** LLM-authored "why this is high-value as a held-out test" (the judgment). */
207
+ rationale?: string;
208
+ }
209
+
210
+ export interface DiscoverReportInput {
211
+ subject: { name: string; source?: string };
212
+ criteria: MinedCriterion[];
213
+ /** OPTIONAL GA summary — drives the grounding strip + per-card diff note. */
214
+ grounding?: GroundingCheckSummary | null;
215
+ /** OPTIONAL subject profile — drives the Overview entity card. */
216
+ profile?: DiscoverProfile | null;
217
+ /** OPTIONAL cheap-triage census — drives the coverage funnel + send distribution. */
218
+ triage?: TriageSummary | null;
219
+ /** OPTIONAL send/succeed distribution — supplements the funnel. */
220
+ sentDist?: SentDistSummary | null;
221
+ /** OPTIONAL determiner verdicts — drive the Proof-of-work tab. */
222
+ verdicts?: DeterminerVerdict[] | null;
223
+ /** OPTIONAL dataset candidates — drive the Dataset tab. */
224
+ dataset?: DatasetCandidate[] | null;
225
+ /** the ISO timestamp stamped into the header (masked by mask.ts for C-PIN). */
226
+ generatedAt: string;
227
+ batchId?: string;
228
+ }
229
+
230
+ // ── severity ordering (CRIT → LOW) for a deterministic, severity-first sort ──
231
+ const SEVERITY_RANK: Record<string, number> = { CRIT: 0, HIGH: 1, MED: 2, LOW: 3 };
232
+
233
+ /** Stable severity-then-id ordering (DETERMINISTIC — no clock, no input order dep). */
234
+ export function sortCriteria(criteria: MinedCriterion[]): MinedCriterion[] {
235
+ return [...criteria].sort((a, b) => {
236
+ const sa = SEVERITY_RANK[a.metadata.severity] ?? 9;
237
+ const sb = SEVERITY_RANK[b.metadata.severity] ?? 9;
238
+ if (sa !== sb) return sa - sb;
239
+ return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
240
+ });
241
+ }
242
+
243
+ function refRow(r: DiscoveryRef): string {
244
+ return (
245
+ `<div class="ref"><span class="ref-obs">${esc(r.obs)}</span>` +
246
+ `<span class="ref-path">${esc(r.path)}</span>` +
247
+ `<span class="ref-val">${esc(r.value)}</span></div>`
248
+ );
249
+ }
250
+
251
+ function assumptionRow(a: DiscoveryAssumption, cid?: string, idx?: number): string {
252
+ const kind = a.kind ? `<span class="asm-kind">${esc(a.kind)}</span>` : "";
253
+ // The carousel's 2nd HITL axis: adjudicate each surfaced assumption independently.
254
+ const cal =
255
+ cid !== undefined && idx !== undefined
256
+ ? `<span class="asm-cal" data-asm="${esc(cid)}:${idx}">` +
257
+ `<label><input type="radio" name="asm-${esc(cid)}-${idx}" value="verify">verify</label>` +
258
+ `<label><input type="radio" name="asm-${esc(cid)}-${idx}" value="eliminate">eliminate</label>` +
259
+ `</span>`
260
+ : "";
261
+ return (
262
+ `<li class="asm"><span class="asm-status asm-${esc(a.status)}">${esc(a.status)}</span>` +
263
+ `${kind}<span class="asm-text">${esc(a.text)}</span>${cal}</li>`
264
+ );
265
+ }
266
+
267
+ /** Map the 3-value `check_method` router to its substrate LABEL (CODE / JUDGE / HYBRID). */
268
+ function checkMethodLabel(cm: string): string {
269
+ if (cm === "deterministic") return "CODE";
270
+ if (cm === "hybrid") return "HYBRID";
271
+ return "JUDGE";
272
+ }
273
+
274
+ /** A one-line, plain-language description of a `codeEval` primitive (the deterministic check). */
275
+ function codeEvalPlain(spec: CodeEvalSpec): string {
276
+ switch (spec.primitive) {
277
+ case "presence":
278
+ return `field \`${spec.field}\` must be present and non-empty.`;
279
+ case "string-equality":
280
+ return `field \`${spec.field}\` must equal \`${spec.expected}\`${spec.caseInsensitive ? " (case-insensitive)" : ""}.`;
281
+ case "format-validity":
282
+ return `field \`${spec.field}\` must match the pattern \`${spec.pattern}\`.`;
283
+ case "schema-conformance":
284
+ return `field \`${spec.field}\` must contain the keys: ${spec.requiredKeys.join(", ")}.`;
285
+ case "ref-integrity":
286
+ return `every value from \`${spec.producer}\` must re-appear in \`${spec.consumer}\`.`;
287
+ case "recovery-after-failure":
288
+ return `if \`${spec.failField}\` == \`${spec.failEquals}\` on a step, a recovery tool (${spec.recoveryTools.join(" | ")}) must fire AFTER it — else a silent drop.`;
289
+ case "tool-output-failure":
290
+ return `tool \`${spec.tool}\` output at \`${spec.successPath}\` must not signal failure (false).`;
291
+ default:
292
+ return "deterministic code-check.";
293
+ }
294
+ }
295
+
296
+ /** Render the executable `codeEval` spec — the primitive + its params (deterministic check). PURE. */
297
+ export function renderCodeEvalSpec(spec: CodeEvalSpec): string {
298
+ const params = Object.entries(spec)
299
+ .filter(([k]) => k !== "primitive")
300
+ .map(([k, v]) => {
301
+ const val = Array.isArray(v) ? v.join(", ") : String(v);
302
+ return `<div class="ce-param"><span class="ce-k">${esc(k)}</span><span class="ce-v">${esc(val)}</span></div>`;
303
+ })
304
+ .join("");
305
+ return (
306
+ `<div class="ce-block">` +
307
+ `<div class="ce-h">deterministic check · <span class="ce-prim">${esc(spec.primitive)}</span></div>` +
308
+ `<div class="ce-plain">${esc(codeEvalPlain(spec))}</div>` +
309
+ `<div class="ce-params">${params}</div>` +
310
+ `</div>`
311
+ );
312
+ }
313
+
314
+ /** HOW-TO-DETECT — plain language, derived from check_method + codeEval + judge inputs. */
315
+ function howToDetect(c: MinedCriterion): string {
316
+ const cm = c.metadata.check_method;
317
+ const inputs = (c.metadata.judge_inputs ?? c.judgeInputs ?? []).join(", ") || "the output";
318
+ if (c.codeEval) {
319
+ const lead = cm === "hybrid" ? "Code pre-filter, then an LLM judge confirms — " : "Deterministic code-check — ";
320
+ return `${lead}${codeEvalPlain(c.codeEval)}`;
321
+ }
322
+ if (cm === "hybrid") {
323
+ return `Code pre-filter then an LLM judge reads ${inputs} and decides pass/fail against the statement.`;
324
+ }
325
+ if (cm === "deterministic") {
326
+ return `Deterministic check over ${inputs} (codeEval spec pending — see notice below).`;
327
+ }
328
+ return `An LLM judge reads ${inputs} and decides pass/fail against the statement (critique-before-verdict).`;
329
+ }
330
+
331
+ /** One criterion → one expandable card. PURE. */
332
+ export function renderCriterionCard(
333
+ c: MinedCriterion,
334
+ diff?: { decision?: string; reason?: string },
335
+ ): string {
336
+ const md = c.metadata;
337
+ const dr = c.discovery;
338
+ const ev = dr.evidence;
339
+ const grounded = ev.grounding === Grounding.Observed;
340
+ const cm = md.check_method;
341
+ const needsCode = cm === "deterministic" || cm === "hybrid";
342
+ const refsHtml =
343
+ ev.refs.length > 0
344
+ ? `<div class="refs">${ev.refs.map(refRow).join("")}</div>`
345
+ : `<div class="refs-na">no structured refs</div>`;
346
+ const asmHtml =
347
+ dr.assumptions.length > 0
348
+ ? `<ul class="asms">${dr.assumptions.map((a, i) => assumptionRow(a, c.id, i)).join("")}</ul>`
349
+ : `<div class="asms-na">no recorded assumptions</div>`;
350
+ const diffNote =
351
+ diff !== undefined
352
+ ? `<div class="diffnote diff-${esc(diff.decision ?? "")}"><span class="dn-k">GA-11</span> ` +
353
+ `<span class="dn-d">${esc(diff.decision ?? "")}</span> ` +
354
+ `<span class="dn-r">${esc(diff.reason ?? "")}</span></div>`
355
+ : "";
356
+ const codeEvalHtml = c.codeEval
357
+ ? renderCodeEvalSpec(c.codeEval)
358
+ : needsCode
359
+ ? `<div class="ce-missing">⚠ check-method is ${esc(checkMethodLabel(cm))} but no <code>codeEval</code> spec is attached (producer gap — the deterministic check is not yet wired).</div>`
360
+ : "";
361
+ const rawJson = esc(JSON.stringify(c, null, 2));
362
+ const cid = esc(c.id);
363
+
364
+ return `<article class="card sev-${esc(md.severity)}" data-grounding="${esc(ev.grounding)}" data-severity="${esc(md.severity)}" data-dimension="${esc(md.dimension)}">
365
+ <header class="card-h">
366
+ <span class="cid">${cid}</span>
367
+ <span class="sev sev-${esc(md.severity)}">${esc(md.severity)}</span>
368
+ <span class="grounding gr-${esc(ev.grounding)}">${esc(ev.grounding)}</span>
369
+ <span class="cmeth cm-${esc(cm)}">${esc(checkMethodLabel(cm))}</span>
370
+ </header>
371
+ <p class="statement">${esc(c.statement)}</p>
372
+ <div class="howto"><span class="howto-k">how to detect</span> <span class="howto-v">${esc(howToDetect(c))}</span></div>
373
+ <div class="meta-row">
374
+ <span class="chip"><span class="ck">level</span> ${esc(md.level)}</span>
375
+ <span class="chip"><span class="ck">class</span> ${esc(md.dimension)}</span>
376
+ <span class="chip"><span class="ck">check</span> ${esc(md.check_method)}</span>
377
+ <span class="chip"><span class="ck">flag</span> ${esc(md.flag)}</span>
378
+ <span class="chip"><span class="ck">prevalence</span> ${esc(ev.prevalence)}</span>
379
+ </div>
380
+ ${diffNote}
381
+ ${codeEvalHtml}
382
+ <div class="ev-block">
383
+ <div class="ev-h">grounding: ${grounded ? "OBSERVED" : esc(ev.grounding).toUpperCase()} · why: ${esc(dr.why_problem)}</div>
384
+ ${refsHtml}
385
+ </div>
386
+ <div class="asm-block">
387
+ <div class="asm-h">typed assumptions</div>
388
+ ${asmHtml}
389
+ </div>
390
+ ${
391
+ dr.targets || dr.reasoning
392
+ ? `<div class="prov-block">
393
+ ${dr.targets ? `<div class="prov-row"><span class="prov-k">targets</span><span class="prov-v">${esc(dr.targets)}</span></div>` : ""}
394
+ ${dr.reasoning ? `<div class="prov-row"><span class="prov-k">why&nbsp;mined</span><span class="prov-v">${esc(dr.reasoning)}</span></div>` : ""}
395
+ </div>`
396
+ : ""
397
+ }
398
+ <div class="hitl" data-hitl="${cid}">
399
+ <div class="hitl-h">decision</div>
400
+ <div class="hitl-opts">
401
+ <label class="hitl-opt keep"><input type="radio" name="hitl-${cid}" value="keep"><span>keep</span></label>
402
+ <label class="hitl-opt revise"><input type="radio" name="hitl-${cid}" value="revise"><span>revise</span></label>
403
+ <label class="hitl-opt retire"><input type="radio" name="hitl-${cid}" value="retire"><span>retire</span></label>
404
+ </div>
405
+ <textarea class="hitl-note" data-hitl-note="${cid}" rows="2" placeholder="notes — what to change / why retire…"></textarea>
406
+ </div>
407
+ <details class="raw"><summary>raw JSON</summary><pre class="raw-pre">${rawJson}</pre></details>
408
+ </article>`;
409
+ }
410
+
411
+ // ════════════════════════════════════════════════════════════════════════════
412
+ // HITL — the copy-paste TASK DEFINITION (markdown). PURE + mirrored in the client
413
+ // script (the live in-browser builder produces byte-identical text). Tested directly.
414
+ // ════════════════════════════════════════════════════════════════════════════
415
+
416
+ export interface DecisionRow {
417
+ id: string;
418
+ decision: "keep" | "revise" | "retire";
419
+ note?: string;
420
+ severity?: string;
421
+ checkMethod?: string;
422
+ statement?: string;
423
+ }
424
+
425
+ const DECISION_ACTION: Record<DecisionRow["decision"], string> = {
426
+ keep: "KEEP — promote this criterion into the suite as-is.",
427
+ revise: "REVISE — edit the statement/detection before promoting; see note.",
428
+ retire: "RETIRE — drop this criterion from the suite; see note.",
429
+ };
430
+
431
+ /** Build the operator copy-paste TASK DEFINITION markdown from the picked decisions. PURE. */
432
+ export function buildDecisionMarkdown(args: {
433
+ subject: string;
434
+ generatedAt: string;
435
+ decisions: DecisionRow[];
436
+ }): string {
437
+ const head =
438
+ `# Discover — HITL Task Definition\n` +
439
+ `- subject: ${args.subject}\n` +
440
+ `- generated: ${args.generatedAt}\n` +
441
+ `- decisions: ${args.decisions.length}\n`;
442
+ if (args.decisions.length === 0) {
443
+ return `${head}\n_No decisions yet — pick keep/revise/retire on a criterion._\n`;
444
+ }
445
+ const blocks = args.decisions.map((d) => {
446
+ const note = d.note && d.note.trim().length > 0 ? d.note.trim() : "(none)";
447
+ return (
448
+ `## ${d.id} — ${d.decision.toUpperCase()}\n` +
449
+ `- severity: ${d.severity ?? "—"}\n` +
450
+ `- check-method: ${d.checkMethod ?? "—"}\n` +
451
+ `- statement: ${d.statement ?? "—"}\n` +
452
+ `- note: ${note}\n` +
453
+ `- action: ${DECISION_ACTION[d.decision]}\n`
454
+ );
455
+ });
456
+ return `${head}\n${blocks.join("\n")}`;
457
+ }
458
+
459
+ /** A small grounding-summary strip (rendered iff a grounding-check is supplied). */
460
+ // ── Overview tab ─────────────────────────────────────────────────────────────
461
+
462
+ function entityCard(
463
+ subject: { name: string; source?: string },
464
+ profile: DiscoverProfile | null | undefined,
465
+ triage: TriageSummary | null | undefined,
466
+ ): string {
467
+ const p = profile ?? {};
468
+ const t = triage ?? {};
469
+ const codeAccess = p.provenance === "given";
470
+ const accessChip = codeAccess
471
+ ? `<span class="acc acc-given">code access ✓</span>`
472
+ : `<span class="acc acc-recon">reconstructed (no code access)</span>`;
473
+
474
+ // ── tools: a GIVEN profile.tools manifest first; else the triage TOOL CENSUS
475
+ // (reconstructed observed call-frequency across the whole batch — the entity
476
+ // signal that almost always EXISTS even when no profile was captured). ──
477
+ const census = t.toolCensus && Object.keys(t.toolCensus).length > 0 ? t.toolCensus : null;
478
+ const sendToolName = t.sendTool;
479
+ const toolChip = (name: string, n?: number): string =>
480
+ `<span class="tool-chip${name === sendToolName ? " tool-send" : ""}">${esc(name)}${n !== undefined ? ` <span class="tool-n">${esc(n)}</span>` : ""}</span>`;
481
+ let toolsHtml: string;
482
+ let toolsLabel = "tools";
483
+ if (p.tools && p.tools.length > 0) {
484
+ toolsHtml = `<div class="tools">${p.tools.map((tn) => toolChip(tn)).join("")}</div>`;
485
+ } else if (census) {
486
+ toolsHtml = `<div class="tools">${Object.entries(census)
487
+ .sort((a, b) => b[1] - a[1])
488
+ .map(([tn, n]) => toolChip(tn, n))
489
+ .join("")}</div>`;
490
+ toolsLabel = "tools · observed call-frequency (reconstructed)";
491
+ } else if (sendToolName) {
492
+ toolsHtml = `<div class="tools">${toolChip(sendToolName)}</div>`;
493
+ } else {
494
+ toolsHtml = `<div class="na">tools not captured</div>`;
495
+ }
496
+
497
+ // ── reconstructed stat tiles from the triage census ──
498
+ const ss = t.sendSucc;
499
+ const succRate = (() => {
500
+ if (!ss) return null;
501
+ const ok = ss.succeeded ?? ss["true"] ?? 0;
502
+ const bad = ss.failed ?? ss["false"] ?? 0;
503
+ const tot = ok + bad;
504
+ return tot > 0 ? Math.round((ok / tot) * 100) : null;
505
+ })();
506
+ const statTiles: Array<[string, string]> = [];
507
+ if (t.total !== undefined) statTiles.push(["traces", String(t.total)]);
508
+ if (census) statTiles.push(["tools", String(Object.keys(census).length)]);
509
+ if (t.sends !== undefined) statTiles.push(["tool calls", String(t.sends)]);
510
+ if (t.obsAvg !== undefined) statTiles.push(["obs / trace", String(t.obsAvg)]);
511
+ if (succRate !== null) statTiles.push(["send ok", succRate + "%"]);
512
+ if (t.eventTax) statTiles.push(["event kinds", String(Object.keys(t.eventTax).length)]);
513
+ const statsHtml =
514
+ statTiles.length > 0
515
+ ? `<div class="ec-stats">${statTiles.map(([l, v]) => `<div class="ecs"><div class="ecs-v">${esc(v)}</div><div class="ecs-l">${esc(l)}</div></div>`).join("")}</div>`
516
+ : "";
517
+
518
+ // ── event taxonomy chips (what kinds of inputs the agent handled) ──
519
+ const evHtml =
520
+ t.eventTax && Object.keys(t.eventTax).length > 0
521
+ ? `<div class="ec-sp-h">event taxonomy</div><div class="tools">${Object.entries(t.eventTax)
522
+ .sort((a, b) => b[1] - a[1])
523
+ .map(([k, n]) => `<span class="tool-chip">${esc(k)} <span class="tool-n">${esc(n)}</span></span>`)
524
+ .join("")}</div>`
525
+ : "";
526
+
527
+ const facts = [
528
+ ["entity", p.entityType],
529
+ ["purpose", p.purpose],
530
+ ["scope", p.scope],
531
+ ["harness", p.harness],
532
+ ["skill", p.skill],
533
+ ["version", p.version],
534
+ ]
535
+ .filter(([, v]) => v !== undefined && v !== null && String(v).length > 0)
536
+ .map(([k, v]) => `<div class="ec-c"><div class="ec-k">${esc(k)}</div><div class="ec-v">${esc(v)}</div></div>`)
537
+ .join("");
538
+
539
+ const sysPromptHtml =
540
+ p.systemPrompt && p.systemPrompt.trim().length > 0
541
+ ? `<div class="ec-sp-h">system prompt</div><details class="sysprompt"><summary>reconstructed system prompt · ${p.systemPrompt.length} chars</summary><pre class="sp-pre">${esc(p.systemPrompt)}</pre></details>`
542
+ : "";
543
+
544
+ const honesty = !codeAccess
545
+ ? `<div class="ec-honesty">⊙ reconstructed from ${t.total !== undefined ? esc(t.total) + " " : ""}traces — the tool census &amp; event taxonomy are <b>observed</b> call-frequencies, not a declared manifest; no source / system-prompt access.${p.inferredFields && p.inferredFields.length ? " inferred: " + p.inferredFields.map(esc).join(", ") : ""}</div>`
546
+ : "";
547
+
548
+ const head = `<header class="entity-h">
549
+ <span class="ent-name">${esc(p.identity ?? subject.name)}</span>
550
+ ${accessChip}
551
+ ${subject.source ? `<span class="ent-src">${esc(subject.source)}</span>` : ""}
552
+ </header>`;
553
+
554
+ // bare fallback ONLY when there is genuinely NOTHING (no profile, no census, no triage).
555
+ const hasAnything = Boolean(
556
+ facts || (p.tools && p.tools.length) || census || sendToolName || statTiles.length > 0 || p.systemPrompt,
557
+ );
558
+ if (!hasAnything) {
559
+ return `<section class="entity">
560
+ ${head}
561
+ <div class="na">trace-only subject — no tool / event census available in this batch</div>
562
+ </section>`;
563
+ }
564
+
565
+ return `<section class="entity">
566
+ ${head}
567
+ ${statsHtml}
568
+ ${facts ? `<div class="ec-grid">${facts}</div>` : ""}
569
+ <div class="ec-tools-h">${esc(toolsLabel)}</div>
570
+ ${toolsHtml}
571
+ ${evHtml}
572
+ ${sysPromptHtml}
573
+ ${honesty}
574
+ </section>`;
575
+ }
576
+
577
+ function coverageFunnel(input: DiscoverReportInput): string {
578
+ const ingested = input.triage?.total ?? input.sentDist?.byTrace?.length;
579
+ const triaged = input.triage?.total;
580
+ const deepRead = input.verdicts?.length;
581
+ const mined = input.criteria.length;
582
+ const seg = (label: string, v: number | undefined): string =>
583
+ `<div class="fseg"><div class="fseg-v">${v === undefined ? "—" : esc(v)}</div><div class="fseg-l">${esc(label)}</div></div>`;
584
+ const arrow = `<div class="farrow">→</div>`;
585
+ const v = input.verdicts ?? [];
586
+ let pass = 0,
587
+ fail = 0,
588
+ other = 0;
589
+ for (const r of v) {
590
+ if (r.result === "pass") pass++;
591
+ else if (r.result === "fail") fail++;
592
+ else other++;
593
+ }
594
+ const pills =
595
+ v.length > 0
596
+ ? `<div class="fpills"><span class="fpill ok">${pass} pass</span><span class="fpill bad">${fail} fail</span>${other > 0 ? `<span class="fpill">${other} uncertain</span>` : ""}</div>`
597
+ : "";
598
+ return `<div class="funnel2">
599
+ <div class="panel-h">coverage funnel</div>
600
+ <div class="fstages">${seg("ingested", ingested)}${arrow}${seg("triaged", triaged)}${arrow}${seg("deep-read", deepRead)}${arrow}${seg("criteria", mined)}</div>
601
+ ${pills}
602
+ </div>`;
603
+ }
604
+
605
+ function sendDistribution(input: DiscoverReportInput): string {
606
+ const dist = input.sentDist?.dist ?? input.triage?.sentDist;
607
+ const succ = input.sentDist?.sendSucc ?? input.triage?.sendSucc;
608
+ const tax = input.triage?.eventTax;
609
+ if (!dist && !succ && !tax) return "";
610
+ const kv = (obj: Record<string, number> | undefined): string =>
611
+ obj
612
+ ? Object.entries(obj)
613
+ .map(([k, v]) => `<div class="dd"><span class="dd-k">${esc(k)}</span><span class="dd-v">${esc(v)}</span></div>`)
614
+ .join("")
615
+ : `<div class="na">—</div>`;
616
+ return `<div class="send-dist">
617
+ <div class="panel-h">send-tool distribution</div>
618
+ <div class="dist-grid">
619
+ <div class="dist-col"><div class="dist-col-h">sent</div>${kv(dist)}</div>
620
+ <div class="dist-col"><div class="dist-col-h">send succeeded</div>${kv(succ)}</div>
621
+ ${tax ? `<div class="dist-col"><div class="dist-col-h">event taxonomy</div>${kv(tax)}</div>` : ""}
622
+ </div>
623
+ </div>`;
624
+ }
625
+
626
+ function saturationStrip(input: DiscoverReportInput): string {
627
+ const v = input.verdicts ?? [];
628
+ let pass = 0;
629
+ let fail = 0;
630
+ let other = 0;
631
+ for (const r of v) {
632
+ if (r.result === "pass") pass++;
633
+ else if (r.result === "fail") fail++;
634
+ else other++;
635
+ }
636
+ const observed = input.criteria.filter((c) => c.discovery.evidence.grounding === Grounding.Observed).length;
637
+ return `<div class="sat">
638
+ <span class="sat-k">saturation</span>
639
+ <span class="sat-pill">${input.criteria.length} criteria mined</span>
640
+ <span class="sat-pill gr-observed">${observed} observed</span>
641
+ ${v.length > 0 ? `<span class="sat-pill">${v.length} deep-read</span><span class="sat-pill ok">${pass} pass</span><span class="sat-pill bad">${fail} fail</span>${other > 0 ? `<span class="sat-pill">${other} uncertain</span>` : ""}` : ""}
642
+ </div>`;
643
+ }
644
+
645
+ /**
646
+ * The Overview HEADLINE — the decision summary an operator reads first: a one-line
647
+ * yield/risk lede + scannable big-stat tiles. Mirrors the eval report's lead-with-the-
648
+ * verdict treatment (parity gap closed). DERIVED purely from criteria (+ optional
649
+ * verdicts/triage) — invents nothing.
650
+ */
651
+ function overviewHeadline(input: DiscoverReportInput): string {
652
+ const crit = input.criteria;
653
+ const total = crit.length;
654
+ const sub = { code: 0, hybrid: 0, judge: 0 };
655
+ const sev: Record<string, number> = { CRIT: 0, HIGH: 0, MED: 0, LOW: 0 };
656
+ for (const c of crit) {
657
+ const cm = c.metadata.check_method;
658
+ if (cm === "deterministic") sub.code++;
659
+ else if (cm === "hybrid") sub.hybrid++;
660
+ else sub.judge++;
661
+ sev[c.metadata.severity] = (sev[c.metadata.severity] ?? 0) + 1;
662
+ }
663
+ const observed = crit.filter((c) => c.discovery.evidence.grounding === Grounding.Observed).length;
664
+ const highPlus = (sev.CRIT ?? 0) + (sev.HIGH ?? 0);
665
+ const runnable = sub.code + sub.hybrid; // tier-0 executable (0 judge tokens)
666
+ const v = input.verdicts ?? [];
667
+ let drFail = 0;
668
+ for (const r of v) if (r.result === "fail") drFail++;
669
+
670
+ const tiles: Array<[string, string, string]> = [
671
+ ["criteria mined", String(total), ""],
672
+ ["code-eval", String(runnable), "ok"],
673
+ ["judge-eval", String(sub.judge), ""],
674
+ ["HIGH / CRIT", String(highPlus), highPlus > 0 ? "warn" : ""],
675
+ ["deep-read fails", v.length > 0 ? String(drFail) : "—", drFail > 0 ? "fail" : ""],
676
+ ["observed", `${observed}/${total}`, "ok"],
677
+ ];
678
+ const tilesHtml = tiles
679
+ .map(([l, val, kind]) => `<div class="s${kind ? " " + kind : ""}"><div class="v">${esc(val)}</div><div class="l">${esc(l)}</div></div>`)
680
+ .join("");
681
+
682
+ const riskCls = drFail > 0 ? "fail" : "skip";
683
+ const lede =
684
+ `Discovery mined <strong>${total}</strong> actionable criteria` +
685
+ (input.triage?.total ? ` over <strong>${esc(input.triage.total)}</strong> traces` : "") +
686
+ `: <strong>${runnable}</strong> run deterministically (code/hybrid — 0 judge tokens), <strong>${sub.judge}</strong> via grounded judge. ` +
687
+ `<strong>${highPlus}</strong> are HIGH/CRIT severity` +
688
+ (v.length > 0
689
+ ? `; deep-read of <strong>${v.length}</strong> surfaced <strong>${drFail}</strong> observed failure${drFail === 1 ? "" : "s"}.`
690
+ : ".");
691
+
692
+ return (
693
+ `<div class="verdict ${riskCls}">` +
694
+ `<div style="font-size:var(--fs-lg);font-weight:700;color:var(--fg-strong);margin-bottom:4px">Discovery Yield</div>` +
695
+ `<p style="color:var(--fg);margin:0">${lede}</p></div>` +
696
+ `<div class="big-stat">${tilesHtml}</div>`
697
+ );
698
+ }
699
+
700
+ /** severity × substrate breakdown — the keep/retire decision table (CODE/HYBRID/JUDGE per severity). */
701
+ function severitySubstrateTable(input: DiscoverReportInput): string {
702
+ const order = ["CRIT", "HIGH", "MED", "LOW"];
703
+ const subOf = (cm: string): "CODE" | "HYBRID" | "JUDGE" =>
704
+ cm === "deterministic" ? "CODE" : cm === "hybrid" ? "HYBRID" : "JUDGE";
705
+ const grid: Record<string, { CODE: number; HYBRID: number; JUDGE: number }> = {};
706
+ for (const s of order) grid[s] = { CODE: 0, HYBRID: 0, JUDGE: 0 };
707
+ for (const c of input.criteria) {
708
+ const g = grid[c.metadata.severity] ?? (grid[c.metadata.severity] = { CODE: 0, HYBRID: 0, JUDGE: 0 });
709
+ g[subOf(c.metadata.check_method)]++;
710
+ }
711
+ const rows = order
712
+ .filter((s) => grid[s] && grid[s].CODE + grid[s].HYBRID + grid[s].JUDGE > 0)
713
+ .map((s) => {
714
+ const g = grid[s];
715
+ const tot = g.CODE + g.HYBRID + g.JUDGE;
716
+ return `<tr><td><span class="sev sev-${s}">${s}</span></td><td>${g.CODE || "·"}</td><td>${g.HYBRID || "·"}</td><td>${g.JUDGE || "·"}</td><td><strong>${tot}</strong></td></tr>`;
717
+ })
718
+ .join("");
719
+ return `<div class="panel-h">severity × substrate</div>
720
+ <table><thead><tr><th>severity</th><th>CODE</th><th>HYBRID</th><th>JUDGE</th><th>total</th></tr></thead><tbody>${rows}</tbody></table>`;
721
+ }
722
+
723
+ /**
724
+ * The CHIP LEGEND — an explicit, compact key for EVERY vocabulary the report uses
725
+ * (grounding · substrate · severity · the HITL decision states) so a reader never has
726
+ * to infer what a chip means. Mirrors the eval report's `verdictLegend` / `.vlegend`
727
+ * treatment (bordered surf-2 block · cyan left-accent · ▾ header · key+gloss rows).
728
+ * The chips REUSE the same classes the cards render (`.grounding` · `.cmeth` · `.sev`)
729
+ * so the legend is a true visual key, not a re-description. PURE.
730
+ */
731
+ function discoverLegend(): string {
732
+ const group = (title: string, rows: string): string =>
733
+ `<div class="dl-g"><div class="dl-gh">${esc(title)}</div>${rows}</div>`;
734
+ const item = (chip: string, v: string): string =>
735
+ `<div class="dl-item">${chip}<span class="dl-v">${esc(v)}</span></div>`;
736
+
737
+ const grounding = group(
738
+ "grounding",
739
+ item(
740
+ `<span class="grounding gr-observed">observed</span>`,
741
+ "backed by concrete trace evidence — structured refs{obs,path,value} pin it to a real value.",
742
+ ) +
743
+ item(
744
+ `<span class="grounding gr-inferred">inferred</span>`,
745
+ "reasoned from the batch but not pinned to a specific observed value — a hypothesis to verify.",
746
+ ),
747
+ );
748
+
749
+ const substrate = group(
750
+ "substrate · how it's checked",
751
+ item(`<span class="cmeth cm-deterministic">CODE</span>`, "deterministic code-check — runs at tier-0, 0 judge tokens.") +
752
+ item(`<span class="cmeth cm-hybrid">HYBRID</span>`, "code pre-filter, then an LLM judge confirms the pass/fail.") +
753
+ item(`<span class="cmeth cm-llm-judge">JUDGE</span>`, "an LLM judge reads the output and decides pass/fail (critique-before-verdict)."),
754
+ );
755
+
756
+ const severity = group(
757
+ "severity",
758
+ item(`<span class="sev sev-CRIT">CRIT</span>`, "gating — a failure blocks certification of the run.") +
759
+ item(`<span class="sev sev-HIGH">HIGH</span>`, "gating — a failure blocks certification of the run.") +
760
+ item(`<span class="sev sev-MED">MED</span>`, "non-gating — tracked, but does not block on its own.") +
761
+ item(`<span class="sev sev-LOW">LOW</span>`, "non-gating — minor / advisory signal."),
762
+ );
763
+
764
+ const hitl = group(
765
+ "HITL decision · your review",
766
+ item(`<span class="dl-k keep">keep</span>`, "promote this criterion into the suite as-is.") +
767
+ item(`<span class="dl-k revise">revise</span>`, "edit the statement / detection before promoting.") +
768
+ item(`<span class="dl-k retire">retire</span>`, "drop this criterion from the suite."),
769
+ );
770
+
771
+ const foot =
772
+ `<div class="dl-foot"><span class="dl-fk">codeEval primitives</span> ` +
773
+ `presence · string-equality · format-validity · schema-conformance · ref-integrity · ` +
774
+ `recovery-after-failure · tool-output-failure — each CODE / HYBRID card shows the exact primitive + its params.</div>`;
775
+
776
+ return (
777
+ `<div class="dlegend"><div class="dl-h">▾ legend — what every chip &amp; state means</div>` +
778
+ `<div class="dl-groups">${grounding}${substrate}${severity}${hitl}</div>${foot}</div>`
779
+ );
780
+ }
781
+
782
+ /**
783
+ * Reliability CENSUS — the send-health / recovery / outbound-guard roll-ups from the
784
+ * triage `totals` block. Surfaced because the gating criteria (send-failure-recovery,
785
+ * outbound-guard-discipline, send-delivery-success) are mined from EXACTLY these
786
+ * signals — leaving them out left real artifact data invisible. Rendered iff
787
+ * `triage.totals` carries at least one field. DERIVED — invents nothing; honest skip
788
+ * when the block is absent. PURE.
789
+ */
790
+ function reliabilityTotals(input: DiscoverReportInput): string {
791
+ const t = input.triage?.totals;
792
+ if (!t) return "";
793
+ const tiles: Array<[string, number | undefined, string]> = [
794
+ ["sends", t.sends, ""],
795
+ ["failed sends", t.failedSends, (t.failedSends ?? 0) > 0 ? "warn" : ""],
796
+ ["hard fails", t.hardFails, (t.hardFails ?? 0) > 0 ? "fail" : ""],
797
+ ["traces w/ recovery", t.tracesWithRecovery, "ok"],
798
+ ["traces w/ outbound guard", t.tracesWithOutboundGuard, ""],
799
+ ].filter(([, v]) => v !== undefined) as Array<[string, number, string]>;
800
+ if (tiles.length === 0) return "";
801
+ const cells = tiles
802
+ .map(([l, v, k]) => `<div class="rt${k ? " " + k : ""}"><div class="rt-v">${esc(String(v))}</div><div class="rt-l">${esc(l)}</div></div>`)
803
+ .join("");
804
+ return `<div class="reliab">
805
+ <div class="panel-h">reliability census · the signals the gating criteria were mined from</div>
806
+ <div class="rt-grid">${cells}</div>
807
+ </div>`;
808
+ }
809
+
810
+ /** Section header for a tab — h2 title (themed) + a one-line orientation description. */
811
+ function tabHead(num: string, title: string, desc: string): string {
812
+ return `<h2>${num} ${esc(title)}</h2>\n <div class="tabdesc">${esc(desc)}</div>`;
813
+ }
814
+
815
+ function overviewTab(input: DiscoverReportInput): string {
816
+ // Entity-on-top arrangement (parity with render-eval-report's overviewTab, where
817
+ // `entityHero` renders FIRST under the tab header): the subject hero anchors the
818
+ // page, THEN the discovery-yield headline + severity mix + funnel.
819
+ return `<section id="t1" class="panel active">
820
+ ${tabHead("①", "Overview", "What discovery found — yield, severity mix, and the trace coverage funnel. Start here.")}
821
+ ${entityCard(input.subject, input.profile, input.triage)}
822
+ ${overviewHeadline(input)}
823
+ ${severitySubstrateTable(input)}
824
+ ${discoverLegend()}
825
+ ${saturationStrip(input)}
826
+ ${coverageFunnel(input)}
827
+ ${reliabilityTotals(input)}
828
+ ${sendDistribution(input)}
829
+ <div class="note"><span class="tag">★ METHODOLOGY</span>Criteria are mined from a failure-weighted deep-read to saturation. Code/hybrid evals run deterministically (0 judge tokens); judge evals carry structured <code>refs{obs,path,value}</code>. Review &amp; HITL each criterion below — your picks persist in this browser and export as a task definition.</div>
830
+ </section>`;
831
+ }
832
+
833
+ // ── Criteria tab ─────────────────────────────────────────────────────────────
834
+
835
+ function criteriaTab(input: DiscoverReportInput): string {
836
+ const sorted = sortCriteria(input.criteria);
837
+ const diffById = new Map<string, { decision?: string; reason?: string }>();
838
+ for (const d of input.grounding?.diffResults ?? []) {
839
+ diffById.set(d.id, { decision: d.decision, reason: d.reason });
840
+ }
841
+ const cardsHtml =
842
+ sorted.length > 0
843
+ ? `<div class="cards" id="crit-cards">${sorted.map((c) => renderCriterionCard(c, diffById.get(c.id))).join("")}</div>`
844
+ : `<div class="empty">no discovered criteria in this batch</div>`;
845
+
846
+ // Paged carousel nav — one eval at a time (‹ › / dots / keyboard), enabled by JS;
847
+ // without JS the cards render as a plain stack (graceful degrade).
848
+ const carNav =
849
+ sorted.length > 1
850
+ ? `<div class="cariar" id="car-nav">
851
+ <button class="car-btn" id="car-prev" type="button">‹ prev</button>
852
+ <button class="car-btn" id="car-next" type="button">next ›</button>
853
+ <span class="car-count" id="car-count">1 / ${sorted.length}</span>
854
+ <span class="car-hint">← → to navigate · dots tint when decided</span>
855
+ <div class="car-dots" id="car-dots">${sorted.map((c, i) => `<button class="car-dot${i === 0 ? " on" : ""}" data-idx="${i}" data-cid="${esc(c.id)}" title="${esc(c.id)}" type="button"></button>`).join("")}</div>
856
+ </div>`
857
+ : "";
858
+
859
+ return `<section id="t2" class="panel">
860
+ ${tabHead("②", "Criteria", "The mined evals — reviewed one at a time, carousel-style. Adjudicate each from an AI-engineer / domain-expert lens (keep · revise · retire, plus verify/eliminate per assumption); picks persist and export as a task definition.")}
861
+ ${discoverLegend()}
862
+ <div class="hitl-panel">
863
+ <div class="panel-h">HITL · keep / revise / retire — collected task definition</div>
864
+ <div class="hitl-bar">
865
+ <span id="hitl-count" class="hitl-count">0 decisions</span>
866
+ <button id="hitl-copy" class="hitl-copy" type="button">copy task definition</button>
867
+ <span id="hitl-copied" class="hitl-copied"></span>
868
+ </div>
869
+ <pre id="hitl-md" class="hitl-md"></pre>
870
+ </div>
871
+ ${carNav}
872
+ ${cardsHtml}
873
+ </section>`;
874
+ }
875
+
876
+ // ── Proof-of-work tab ────────────────────────────────────────────────────────
877
+
878
+ function proofTab(input: DiscoverReportInput): string {
879
+ const v = input.verdicts ?? [];
880
+ const proofHead = tabHead("③", "Proof-of-work", "The per-trace deep-read verdicts the criteria were mined from — the evidence trail (✗ failures first).");
881
+ if (v.length === 0) {
882
+ return `<section id="t3" class="panel">\n ${proofHead}\n <div class="empty">no determiner verdicts supplied</div></section>`;
883
+ }
884
+ const ordered = [...v].sort((a, b) => {
885
+ const ra = a.result === "fail" ? 0 : a.result === "pass" ? 2 : 1;
886
+ const rb = b.result === "fail" ? 0 : b.result === "pass" ? 2 : 1;
887
+ if (ra !== rb) return ra - rb;
888
+ return a.traceId < b.traceId ? -1 : a.traceId > b.traceId ? 1 : 0;
889
+ });
890
+ const rows = ordered
891
+ .map((r) => {
892
+ const ok = r.result === "pass";
893
+ const mark = ok ? "✓" : r.result === "fail" ? "✗" : "•";
894
+ const conf = typeof r.confidence === "number" ? `<span class="pv-conf">conf ${r.confidence.toFixed(2)}</span>` : "";
895
+ return `<article class="pcard res-${esc(r.result ?? "na")}">
896
+ <header class="pc-h"><span class="pv-mark">${mark}</span><span class="pv-tid">${esc(r.traceId)}</span><span class="pv-res">${esc(r.result ?? "—")}</span>${conf}</header>
897
+ <div class="pv-crit">${esc(r.critique ?? "")}</div>
898
+ </article>`;
899
+ })
900
+ .join("");
901
+ return `<section id="t3" class="panel">
902
+ ${proofHead}
903
+ <div class="panel-h">proof-of-work · determiner deep-read verdicts (${v.length})</div>
904
+ <div class="pcards">${rows}</div>
905
+ </section>`;
906
+ }
907
+
908
+ // ── Dataset tab ──────────────────────────────────────────────────────────────
909
+
910
+ function datasetTab(input: DiscoverReportInput): string {
911
+ const d = input.dataset ?? [];
912
+ const dsHead = tabHead("④", "Dataset", "Failure & uncertain traces distilled into held-out dataset candidates for the eval suite.");
913
+ if (d.length === 0) {
914
+ return `<section id="t4" class="panel">\n ${dsHead}\n <div class="empty">no dataset candidates supplied</div></section>`;
915
+ }
916
+ const rows = d
917
+ .map((c) => {
918
+ const tuple = c.tuple
919
+ ? Object.entries(c.tuple)
920
+ .map(([k, v]) => `<span class="dc-tup"><span class="dc-tk">${esc(k)}</span>${esc(String(v))}</span>`)
921
+ .join("")
922
+ : "";
923
+ const why = (c.rationale ?? "").trim();
924
+ const whyBlock = why
925
+ ? `<div class="dc-why"><span class="dc-why-k">why high-value</span><span class="dc-why-v">${esc(why)}</span></div>`
926
+ : `<div class="dc-why dc-why-pending"><span class="dc-why-k">why high-value</span><span class="dc-why-v">— rationale not authored at selection (held-out cases must carry a written justification)</span></div>`;
927
+ const prov = (c.selectedBy ?? "").trim();
928
+ const provBlock = prov
929
+ ? `<div class="dc-prov"><span class="dc-prov-k">selected by</span><span class="dc-prov-v">${esc(prov)}</span></div>`
930
+ : "";
931
+ return `<article class="dcard">
932
+ <header class="dc-h"><span class="dc-id">${esc(c.id)}</span>${tuple}${c.source ? `<span class="dc-src">${esc(c.source)}</span>` : ""}${c.originTraceId ? `<span class="dc-origin">${esc(c.originTraceId)}</span>` : ""}</header>
933
+ ${whyBlock}
934
+ ${provBlock}
935
+ <details class="dc-q"><summary>query · ${(c.query ?? "").length} chars</summary><pre class="dc-pre">${esc(c.query ?? "")}</pre></details>
936
+ </article>`;
937
+ })
938
+ .join("");
939
+ return `<section id="t4" class="panel">
940
+ ${dsHead}
941
+ <div class="panel-h">dataset candidates · held-out derivation seed (${d.length})</div>
942
+ <div class="dcards">${rows}</div>
943
+ </section>`;
944
+ }
945
+
946
+ // ── client script (tab wiring + HITL builder) ────────────────────────────────
947
+
948
+ function clientScript(meta: { subject: string; generatedAt: string }, criteriaMeta: Record<string, { severity: string; checkMethod: string; statement: string }>): string {
949
+ return `
950
+ (function(){
951
+ var CM=${jsonForScript(criteriaMeta)};
952
+ var SUBJECT=${jsonForScript(meta.subject)};
953
+ var GEN=${jsonForScript(meta.generatedAt)};
954
+ var ACTION={keep:"KEEP — promote this criterion into the suite as-is.",revise:"REVISE — edit the statement/detection before promoting; see note.",retire:"RETIRE — drop this criterion from the suite; see note."};
955
+ function wireTabs(){
956
+ var btns=document.querySelectorAll('nav.tabs button');
957
+ var panels=document.querySelectorAll('main .panel');
958
+ btns.forEach(function(b){b.addEventListener('click',function(){
959
+ var key=b.getAttribute('data-tab');
960
+ btns.forEach(function(x){x.classList.remove('active');});
961
+ panels.forEach(function(p){p.classList.remove('active');});
962
+ b.classList.add('active');
963
+ var p=document.getElementById(key);if(p)p.classList.add('active');
964
+ });});
965
+ }
966
+ function asmPicksFor(id){
967
+ var out=[];
968
+ document.querySelectorAll('.asm-cal input[name^="asm-'+id+'-"]:checked').forEach(function(r){
969
+ var m=r.name.match(/-(\\d+)$/);out.push('#'+(m?m[1]:'?')+':'+r.value);
970
+ });
971
+ return out;
972
+ }
973
+ function markDots(){
974
+ document.querySelectorAll('#car-dots .car-dot').forEach(function(dt){
975
+ var cid=dt.getAttribute('data-cid');
976
+ var picked=cid&&document.querySelector('input[name="hitl-'+cid+'"]:checked');
977
+ dt.classList.toggle('dec',!!picked);
978
+ });
979
+ }
980
+ function buildMd(){
981
+ var ids=Object.keys(CM).sort();
982
+ var decisions=[];
983
+ ids.forEach(function(id){
984
+ var picked=document.querySelector('input[name="hitl-'+id.replace(/"/g,'')+'"]:checked');
985
+ if(!picked)return;
986
+ var noteEl=document.querySelector('textarea[data-hitl-note="'+id.replace(/"/g,'')+'"]');
987
+ decisions.push({id:id,decision:picked.value,note:noteEl?noteEl.value:'',meta:CM[id],asm:asmPicksFor(id)});
988
+ });
989
+ var head='# Discover — HITL Task Definition\\n- subject: '+SUBJECT+'\\n- generated: '+GEN+'\\n- decisions: '+decisions.length+'\\n';
990
+ var md;
991
+ if(decisions.length===0){md=head+'\\n_No decisions yet — pick keep/revise/retire on a criterion._\\n';}
992
+ else{
993
+ var blocks=decisions.map(function(d){
994
+ var note=(d.note&&d.note.trim().length>0)?d.note.trim():'(none)';
995
+ var m=d.meta||{};
996
+ return '## '+d.id+' — '+d.decision.toUpperCase()+'\\n'+
997
+ '- severity: '+(m.severity||'—')+'\\n'+
998
+ '- check-method: '+(m.checkMethod||'—')+'\\n'+
999
+ '- statement: '+(m.statement||'—')+'\\n'+
1000
+ (d.asm&&d.asm.length?('- assumptions: '+d.asm.join(', ')+'\\n'):'')+
1001
+ '- note: '+note+'\\n'+
1002
+ '- action: '+ACTION[d.decision]+'\\n';
1003
+ });
1004
+ md=head+'\\n'+blocks.join('\\n');
1005
+ }
1006
+ var pre=document.getElementById('hitl-md');if(pre)pre.textContent=md;
1007
+ var cnt=document.getElementById('hitl-count');if(cnt)cnt.textContent=decisions.length+' decision'+(decisions.length===1?'':'s');
1008
+ markDots();
1009
+ return md;
1010
+ }
1011
+ function wireHitl(){
1012
+ var LSKEY='mutagent-discover-hitl:'+SUBJECT+':'+GEN;
1013
+ function saveState(){
1014
+ try{
1015
+ var st={};
1016
+ document.querySelectorAll('.hitl input[type="radio"]:checked').forEach(function(r){
1017
+ var id=r.name.replace(/^hitl-/,'');(st[id]=st[id]||{}).decision=r.value;
1018
+ });
1019
+ document.querySelectorAll('.hitl-note').forEach(function(t){
1020
+ var id=t.getAttribute('data-hitl-note');if(t.value){(st[id]=st[id]||{}).note=t.value;}
1021
+ });
1022
+ document.querySelectorAll('.asm-cal input[type="radio"]:checked').forEach(function(r){
1023
+ var m=r.name.match(/^asm-(.+)-(\\d+)$/);if(!m)return;var id=m[1];(st[id]=st[id]||{});st[id].asm=(st[id].asm||{});st[id].asm[m[2]]=r.value;
1024
+ });
1025
+ localStorage.setItem(LSKEY,JSON.stringify(st));
1026
+ }catch(e){}
1027
+ }
1028
+ function restoreState(){
1029
+ try{
1030
+ var raw=localStorage.getItem(LSKEY);if(!raw)return;var st=JSON.parse(raw);
1031
+ Object.keys(st).forEach(function(id){
1032
+ var d=st[id]||{};
1033
+ if(d.decision){var r=document.querySelector('input[name="hitl-'+id+'"][value="'+d.decision+'"]');if(r)r.checked=true;}
1034
+ if(d.note){var t=document.querySelector('textarea[data-hitl-note="'+id+'"]');if(t)t.value=d.note;}
1035
+ if(d.asm){Object.keys(d.asm).forEach(function(k){var a=document.querySelector('input[name="asm-'+id+'-'+k+'"][value="'+d.asm[k]+'"]');if(a)a.checked=true;});}
1036
+ });
1037
+ }catch(e){}
1038
+ }
1039
+ restoreState();
1040
+ document.querySelectorAll('.hitl input[type="radio"]').forEach(function(r){r.addEventListener('change',function(){saveState();buildMd();});});
1041
+ document.querySelectorAll('.hitl-note').forEach(function(t){t.addEventListener('input',function(){saveState();buildMd();});});
1042
+ document.querySelectorAll('.asm-cal input[type="radio"]').forEach(function(r){r.addEventListener('change',function(){saveState();buildMd();});});
1043
+ var copy=document.getElementById('hitl-copy');
1044
+ if(copy)copy.addEventListener('click',function(){
1045
+ var md=buildMd();
1046
+ var done=function(){var s=document.getElementById('hitl-copied');if(s){s.textContent='copied ✓';setTimeout(function(){s.textContent='';},2000);}};
1047
+ if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(md).then(done,done);}else{done();}
1048
+ });
1049
+ buildMd();
1050
+ }
1051
+ function wireCarousel(){
1052
+ var container=document.getElementById('crit-cards');
1053
+ if(!container)return;
1054
+ var cards=Array.prototype.slice.call(container.querySelectorAll('.card'));
1055
+ if(cards.length<2)return;
1056
+ container.classList.add('carousel-on');
1057
+ var dots=Array.prototype.slice.call(document.querySelectorAll('#car-dots .car-dot'));
1058
+ var cnt=document.getElementById('car-count');
1059
+ var cur=0;
1060
+ function show(i){
1061
+ if(i<0)i=cards.length-1;if(i>=cards.length)i=0;cur=i;
1062
+ cards.forEach(function(c,j){c.classList.toggle('on',j===i);});
1063
+ dots.forEach(function(d,j){if(j===i)d.classList.add('on');else d.classList.remove('on');});
1064
+ if(cnt)cnt.textContent=(i+1)+' / '+cards.length;
1065
+ }
1066
+ var prev=document.getElementById('car-prev'),next=document.getElementById('car-next');
1067
+ if(prev)prev.addEventListener('click',function(){show(cur-1);});
1068
+ if(next)next.addEventListener('click',function(){show(cur+1);});
1069
+ dots.forEach(function(d){d.addEventListener('click',function(){show(parseInt(d.getAttribute('data-idx'),10)||0);});});
1070
+ document.addEventListener('keydown',function(e){
1071
+ var t2=document.querySelector('nav.tabs button[data-tab="t2"]');
1072
+ if(!t2||!t2.classList.contains('active'))return;
1073
+ var tag=(e.target&&e.target.tagName)||'';if(tag==='TEXTAREA'||tag==='INPUT')return;
1074
+ if(e.key==='ArrowLeft'){show(cur-1);}else if(e.key==='ArrowRight'){show(cur+1);}
1075
+ });
1076
+ show(0);
1077
+ }
1078
+ if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',function(){wireTabs();wireHitl();wireCarousel();});}
1079
+ else{wireTabs();wireHitl();wireCarousel();}
1080
+ })();`;
1081
+ }
1082
+
1083
+ const REPORT_CSS = `
1084
+ /* WS-4 — discover-local font-scale bump. The shared brand scale (11/12/13/14/16/19/22)
1085
+ reads too small in this report's dense mono captions/labels/foot. Bump the smallest
1086
+ tiers +2px, taper to +1px at the headings — monotonic (no inversions), sits ~+2px
1087
+ above the evaluator report at the small end, equal at the very top. */
1088
+ :root{--fs-2xs:13px;--fs-xs:14px;--fs-sm:15px;--fs-md:16px;--fs-lg:17px;--fs-xl:20px;--fs-2xl:23px}
1089
+ main{max-width:1180px;margin:0 auto;padding:14px 18px 64px}
1090
+ /* tabs — match the EVALUATOR / diagnostics unified treatment (underline-active, void
1091
+ background, NO border-box, NO shadow) instead of the old bordered-button box. */
1092
+ nav.tabs{position:sticky;top:0;z-index:20;background:var(--bg);border-bottom:1px solid var(--border);padding:0 12px;margin:0 0 16px;overflow-x:auto;white-space:nowrap}
1093
+ nav.tabs button{font-size:var(--fs-xs);font-weight:500;color:var(--muted);background:none;border:none;border-bottom:3px solid transparent;padding:10px 12px;cursor:pointer;letter-spacing:.01em;font-family:inherit;transition:all .15s}
1094
+ nav.tabs button:hover{color:var(--fg-strong);background:var(--surf)}
1095
+ nav.tabs button.active{color:var(--primary-soft);border-bottom-color:var(--primary);font-weight:600}
1096
+ .panel{display:none}
1097
+ .panel.active{display:block}
1098
+ .panel-h{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.06em;color:var(--cyan);margin:14px 0 8px}
1099
+ .na{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--dim);border:1px dashed var(--border-strong);padding:4px 8px}
1100
+ .gstrip{display:flex;gap:10px;align-items:center;flex-wrap:wrap;padding:9px 13px;border:1px solid var(--border);background:var(--surf-2);margin:0 0 16px}
1101
+ .gstrip .gs-k{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
1102
+ .gstrip .gs-v{font-family:var(--mono);font-size:var(--fs-sm);font-weight:700;color:var(--fg-strong);margin-right:6px}
1103
+ .gr-observed{color:var(--cyan)}
1104
+ .gr-inferred{color:var(--warn)}
1105
+ .gr-hypothesis-pending{color:var(--dim)}
1106
+ /* entity HERO — surface/contrast converged to render-eval-report's .hero/.hero-top
1107
+ * (border-strong frame · solid primary left-accent · surf-2 header band) so the
1108
+ * subject card reads with the SAME contrast as the eval report's entity hero. */
1109
+ .entity{border:1px solid var(--border-strong);border-left:3px solid var(--primary);background:var(--surf);padding:14px 16px;margin:10px 0 14px}
1110
+ .entity-h{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin:-14px -16px 12px;padding:11px 14px;background:var(--surf-2);border-bottom:1px solid var(--border)}
1111
+ .entity-h .ent-name{font-size:var(--fs-lg);font-weight:700;color:var(--fg-strong)}
1112
+ .entity-h .ent-src{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--muted)}
1113
+ .acc{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;padding:2px 8px;border:1px solid var(--border-strong);text-transform:uppercase;letter-spacing:.04em}
1114
+ .acc-given{color:var(--pass)}
1115
+ .acc-recon{color:var(--warn)}
1116
+ .ec-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:8px;margin-bottom:10px}
1117
+ .ec-c{border:1px solid var(--border);background:var(--surf-2);padding:6px 9px}
1118
+ .ec-k{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--muted)}
1119
+ .ec-v{font-size:var(--fs-xs);color:var(--fg);margin-top:2px;line-height:1.5}
1120
+ .ec-tools-h,.ec-sp-h{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.05em;color:var(--cyan);margin:8px 0 5px}
1121
+ .tools{display:flex;gap:5px;flex-wrap:wrap}
1122
+ /* tool-census chips carry a subtle CYAN accent (was flat grey) — mirrors the eval
1123
+ * report's accented entity tool-chips; the send-tool gets a stronger PRIMARY emphasis
1124
+ * so it stands out as the entity's headline action. */
1125
+ .tool-chip{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--cyan);border:1px solid var(--cyan);padding:2px 7px;background:rgba(69,184,204,.08)}
1126
+ .tool-send{color:var(--fg-strong);border-color:var(--primary);background:rgba(126,71,215,.14);font-weight:700}
1127
+ .sysprompt summary,.raw summary,.dc-q summary,.sp-pre{font-family:var(--mono);font-size:var(--fs-2xs)}
1128
+ .sysprompt summary{color:var(--muted);cursor:pointer;padding:4px 0}
1129
+ .sp-pre,.raw-pre,.dc-pre,.pv-crit{white-space:pre-wrap;word-break:break-word}
1130
+ .sp-pre{color:var(--fg);background:var(--surf-2);border:1px solid var(--border);padding:8px;margin-top:5px;max-height:340px;overflow:auto;font-size:var(--fs-2xs)}
1131
+ .sat{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:12px 0}
1132
+ .sat-k{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.05em;color:var(--muted)}
1133
+ .sat-pill{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;padding:2px 8px;border:1px solid var(--border-strong);color:var(--fg)}
1134
+ .sat-pill.ok{color:var(--pass)}
1135
+ .sat-pill.bad{color:var(--fail)}
1136
+ .send-dist{margin:12px 0}
1137
+ .dist-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px}
1138
+ .dist-col{border:1px solid var(--border);background:var(--surf);padding:9px 11px}
1139
+ .dist-col-h{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.05em;color:var(--primary-soft);margin-bottom:6px}
1140
+ .dd{display:flex;justify-content:space-between;font-family:var(--mono);font-size:var(--fs-2xs);padding:2px 0;border-bottom:1px solid var(--border)}
1141
+ .dd-k{color:var(--muted)}
1142
+ .dd-v{color:var(--fg-strong);font-weight:700}
1143
+ .hitl-panel{border:1px solid var(--border);border-left:3px solid var(--cyan);background:var(--surf);padding:12px 14px;margin-bottom:16px;position:sticky;top:46px;z-index:10}
1144
+ .hitl-bar{display:flex;gap:10px;align-items:center;margin:6px 0 8px}
1145
+ .hitl-count{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--muted)}
1146
+ .hitl-copy{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;color:var(--fg-strong);background:var(--surf-2);border:1px solid var(--border-strong);padding:5px 11px;cursor:pointer}
1147
+ .hitl-copy:hover{border-color:var(--cyan);color:var(--cyan)}
1148
+ .hitl-copied{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--pass)}
1149
+ .hitl-md{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--fg);background:var(--surf-2);border:1px solid var(--border);padding:9px;white-space:pre-wrap;word-break:break-word;max-height:240px;overflow:auto;margin:0}
1150
+ .cards{display:grid;grid-template-columns:repeat(auto-fill,minmax(380px,1fr));gap:14px}
1151
+ .card{border:1px solid var(--border);border-left:3px solid var(--border-strong);background:var(--surf);padding:13px 15px}
1152
+ .card.sev-CRIT{border-left-color:var(--fail)}
1153
+ .card.sev-HIGH{border-left-color:var(--warn)}
1154
+ .card.sev-MED{border-left-color:var(--primary-soft)}
1155
+ .card.sev-LOW{border-left-color:var(--border-strong)}
1156
+ .card-h{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:7px}
1157
+ .card-h .cid{font-family:var(--mono);font-size:var(--fs-sm);font-weight:700;color:var(--fg-strong)}
1158
+ .sev{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;padding:2px 7px;border:1px solid var(--border-strong)}
1159
+ .sev-CRIT{color:var(--fail)}
1160
+ .sev-HIGH{color:var(--warn)}
1161
+ .sev-MED{color:var(--primary-soft)}
1162
+ .sev-LOW{color:var(--muted)}
1163
+ .grounding{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;padding:2px 7px;text-transform:uppercase;letter-spacing:.04em;border:1px solid var(--border)}
1164
+ .cmeth{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;padding:2px 7px;border:1px solid var(--border-strong);letter-spacing:.04em}
1165
+ .cm-deterministic{color:var(--pass)}
1166
+ .cm-llm-judge{color:var(--primary-soft)}
1167
+ .cm-hybrid{color:var(--cyan)}
1168
+ .statement{font-size:var(--fs-sm);color:var(--fg);line-height:1.55;margin:0 0 8px}
1169
+ .howto{font-size:var(--fs-xs);line-height:1.5;margin:0 0 9px;border:1px solid var(--border);background:var(--surf-2);padding:6px 9px}
1170
+ .howto-k{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.05em;color:var(--cyan);margin-right:5px}
1171
+ .howto-v{color:var(--fg)}
1172
+ .meta-row{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:9px}
1173
+ .chip{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--fg);border:1px solid var(--border);padding:2px 7px;white-space:nowrap}
1174
+ .chip .ck{color:var(--muted);text-transform:uppercase;letter-spacing:.03em;margin-right:4px}
1175
+ .diffnote{font-family:var(--mono);font-size:var(--fs-2xs);padding:5px 9px;border:1px solid var(--border);background:var(--surf-2);margin-bottom:9px;line-height:1.5}
1176
+ .diffnote .dn-k{font-weight:700;color:var(--cyan)}
1177
+ .diffnote .dn-d{font-weight:700;color:var(--fg-strong);text-transform:uppercase;margin:0 5px}
1178
+ .diffnote .dn-r{color:var(--muted)}
1179
+ .ce-block{border:1px solid var(--border);border-left:2px solid var(--pass);background:var(--surf-2);padding:7px 10px;margin-bottom:9px}
1180
+ .ce-h{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--pass);margin-bottom:4px}
1181
+ .ce-prim{color:var(--fg-strong)}
1182
+ .ce-plain{font-size:var(--fs-xs);color:var(--fg);line-height:1.5;margin-bottom:5px}
1183
+ .ce-params{display:flex;flex-direction:column;gap:3px}
1184
+ .ce-param{display:grid;grid-template-columns:auto 1fr;gap:8px;font-family:var(--mono);font-size:var(--fs-2xs)}
1185
+ .ce-k{color:var(--muted)}
1186
+ .ce-v{color:var(--fg);word-break:break-word}
1187
+ .ce-missing{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--warn);border:1px dashed var(--warn);padding:6px 9px;margin-bottom:9px;line-height:1.5}
1188
+ .ev-block,.asm-block{border-top:1px solid var(--border);padding-top:8px;margin-top:8px}
1189
+ .ev-h,.asm-h{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--cyan);margin-bottom:6px;line-height:1.5}
1190
+ .asm-h{color:var(--primary-soft)}
1191
+ .refs{display:flex;flex-direction:column;gap:4px}
1192
+ .ref{display:grid;grid-template-columns:auto auto 1fr;gap:8px;font-family:var(--mono);font-size:var(--fs-2xs);align-items:baseline}
1193
+ .ref .ref-obs{color:var(--cyan);font-weight:600}
1194
+ .ref .ref-path{color:var(--muted)}
1195
+ .ref .ref-val{color:var(--fg);white-space:pre-wrap;word-break:break-word}
1196
+ .refs-na,.asms-na{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--dim);border:1px dashed var(--border-strong);padding:4px 8px}
1197
+ .asms{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}
1198
+ .asm{display:flex;gap:7px;align-items:baseline;font-size:var(--fs-xs);line-height:1.5}
1199
+ .asm-status{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;padding:1px 6px;border:1px solid var(--border);text-transform:uppercase;white-space:nowrap}
1200
+ .asm-verified{color:var(--pass)}
1201
+ .asm-unverified{color:var(--warn)}
1202
+ .asm-hypothesis{color:var(--muted)}
1203
+ .asm-eliminated{color:var(--dim)}
1204
+ .asm-kind{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--dim);text-transform:uppercase}
1205
+ .asm-text{color:var(--fg)}
1206
+ .hitl{border-top:1px solid var(--border);margin-top:9px;padding-top:8px}
1207
+ .hitl-h{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.05em;color:var(--warn);margin-bottom:5px}
1208
+ .hitl-opts{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px}
1209
+ .hitl-opt{display:inline-flex;gap:5px;align-items:center;font-family:var(--mono);font-size:var(--fs-2xs);color:var(--fg);border:1px solid var(--border-strong);padding:4px 9px;cursor:pointer}
1210
+ .hitl-opt input{margin:0;cursor:pointer}
1211
+ .hitl-opt.keep:hover{border-color:var(--pass)}
1212
+ .hitl-opt.revise:hover{border-color:var(--warn)}
1213
+ .hitl-opt.retire:hover{border-color:var(--fail)}
1214
+ .hitl-note{width:100%;box-sizing:border-box;font-family:var(--mono);font-size:var(--fs-2xs);color:var(--fg);background:var(--surf-2);border:1px solid var(--border);padding:6px;resize:vertical}
1215
+ .raw{margin-top:8px}
1216
+ .raw summary{color:var(--muted);cursor:pointer;padding:3px 0}
1217
+ .raw-pre{font-size:var(--fs-2xs);color:var(--fg);background:var(--surf-2);border:1px solid var(--border);padding:8px;margin-top:5px;max-height:300px;overflow:auto}
1218
+ .pcards,.dcards{display:flex;flex-direction:column;gap:9px}
1219
+ .pcard{border:1px solid var(--border);border-left:3px solid var(--border-strong);background:var(--surf);padding:9px 12px}
1220
+ .pcard.res-pass{border-left-color:var(--pass)}
1221
+ .pcard.res-fail{border-left-color:var(--fail)}
1222
+ .pc-h{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:5px}
1223
+ .pv-mark{font-family:var(--mono);font-size:var(--fs-sm);font-weight:700}
1224
+ .pcard.res-pass .pv-mark{color:var(--pass)}
1225
+ .pcard.res-fail .pv-mark{color:var(--fail)}
1226
+ .pv-tid{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--fg-strong);font-weight:700}
1227
+ .pv-res{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;color:var(--muted)}
1228
+ .pv-conf{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--cyan)}
1229
+ .pv-crit{font-size:var(--fs-xs);color:var(--fg);line-height:1.55}
1230
+ .dcard{border:1px solid var(--border);border-left:3px solid var(--primary-soft);background:var(--surf);padding:9px 12px}
1231
+ .dc-h{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-bottom:4px}
1232
+ .dc-id{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;color:var(--fg-strong)}
1233
+ .dc-tup{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--fg);border:1px solid var(--border);padding:1px 6px}
1234
+ .dc-tk{color:var(--muted);margin-right:4px}
1235
+ .dc-src,.dc-origin{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--muted)}
1236
+ /* WS-5 — explicit "why this held-out item is high-value" (the authored judgment)
1237
+ + the deterministic selector provenance ("data link"). The why is the headline. */
1238
+ .dc-why{display:flex;gap:8px;align-items:baseline;margin:2px 0 6px;padding:7px 9px;background:var(--primary-bg,rgba(126,71,215,.08));border-left:3px solid var(--primary)}
1239
+ .dc-why-k{flex:0 0 auto;font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.05em;color:var(--primary-soft);font-weight:700}
1240
+ .dc-why-v{flex:1 1 auto;font-size:var(--fs-sm);color:var(--fg);line-height:1.5}
1241
+ .dc-why-pending{background:var(--warn-bg);border-left-color:var(--warn)}
1242
+ .dc-why-pending .dc-why-k{color:var(--warn)}
1243
+ .dc-why-pending .dc-why-v{color:var(--muted);font-style:italic}
1244
+ .dc-prov{display:flex;gap:8px;align-items:baseline;margin:0 0 6px}
1245
+ .dc-prov-k{flex:0 0 auto;font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.05em;color:var(--dim)}
1246
+ .dc-prov-v{flex:1 1 auto;font-family:var(--mono);font-size:var(--fs-2xs);color:var(--muted);line-height:1.45}
1247
+ .dc-q summary{color:var(--muted);cursor:pointer}
1248
+ .dc-pre{font-size:var(--fs-2xs);color:var(--fg);background:var(--surf-2);border:1px solid var(--border);padding:8px;margin-top:5px;max-height:260px;overflow:auto}
1249
+ .empty{font-family:var(--mono);font-size:var(--fs-sm);color:var(--dim);border:1px dashed var(--border-strong);padding:16px;text-align:center}
1250
+ /* ── parity upgrade (2026-06-25): per-tab orientation, status-tinted tiles, card provenance, callouts, footer ── */
1251
+ /* tab-orientation banner — converged to the eval report's .tabdesc (full border +
1252
+ * 3px cyan left-accent on a surf-2 surface) for identical contrast. */
1253
+ .tabdesc{font-family:var(--mono);border:1px solid var(--border);border-left:3px solid var(--cyan);background:var(--surf-2);padding:7px 11px;font-size:var(--fs-xs);color:var(--fg);opacity:.92;line-height:1.5;margin:8px 0 12px}
1254
+ /* big-stat status-acuity — every tile gets a left-accent base (neutral tiles =
1255
+ * border-strong), status tiles tint the FULL frame + left-accent. Byte-for-byte the
1256
+ * eval report's .big-stat treatment (render-eval-report REPORT_CSS). */
1257
+ /* ALIGNMENT FIX: theme.css ships .big-stat as content-sized flex (min-width:96px) →
1258
+ * ragged tile widths + drifting heights. Override to an EQUAL-column grid so every
1259
+ * tile is the same width AND height (grid row-stretch). Mirrored in render-eval-report. */
1260
+ .big-stat{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:12px;align-items:start}
1261
+ /* margin:0 — neutralize the global .warn/.crit callout utility (theme.css margin:14px 0)
1262
+ leaking into the .s.warn status-modifier tile (was dropping that one tile +14px). */
1263
+ .big-stat .s{min-width:0;min-height:84px;margin:0;display:flex;flex-direction:column;justify-content:center}
1264
+ .big-stat .s .v{line-height:1.15}
1265
+ .big-stat .s{border-left:4px solid var(--border-strong)}
1266
+ .big-stat .s.ok{border-color:var(--pass);border-left-color:var(--pass);background:var(--pass-bg)}
1267
+ .big-stat .s.ok .v{color:var(--pass)}
1268
+ .big-stat .s.warn{border-color:var(--warn);border-left-color:var(--warn);background:var(--warn-bg)}
1269
+ .big-stat .s.warn .v{color:var(--warn)}
1270
+ .big-stat .s.fail{border-color:var(--fail);border-left-color:var(--fail);background:var(--fail-bg)}
1271
+ .big-stat .s.fail .v{color:var(--fail)}
1272
+ .prov-block{border-top:1px solid var(--border);padding-top:8px;margin-top:8px}
1273
+ .prov-row{display:grid;grid-template-columns:auto 1fr;gap:8px;font-size:var(--fs-xs);line-height:1.5;margin-bottom:4px}
1274
+ .prov-k{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--primary-soft);white-space:nowrap}
1275
+ .prov-v{color:var(--fg);word-break:break-word}
1276
+ .note{border-left:3px solid var(--recommend);background:var(--recommend-bg);padding:7px 11px;margin:14px 0;font-size:var(--fs-xs);line-height:1.55;color:var(--fg)}
1277
+ .note .tag{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;color:var(--recommend);margin-right:6px}
1278
+ .foot{margin-top:26px;color:var(--dim);font-size:var(--fs-2xs);border-top:1px solid var(--border);padding-top:12px;font-family:var(--mono);line-height:1.6}
1279
+ /* ── paged carousel (Criteria tab) — restored from the original render-carousel ── */
1280
+ .cariar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin:10px 0 14px;padding:7px 11px;border:1px solid var(--border);background:var(--surf-2);position:sticky;top:46px;z-index:9}
1281
+ .car-btn{font-family:var(--mono);font-size:var(--fs-sm);font-weight:700;color:var(--fg-strong);background:var(--surf);border:1px solid var(--border-strong);padding:3px 11px;cursor:pointer;line-height:1.2}
1282
+ .car-btn:hover{border-color:var(--cyan);color:var(--cyan)}
1283
+ .car-count{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--muted)}
1284
+ .car-hint{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--dim)}
1285
+ .car-dots{display:flex;gap:5px;flex-wrap:wrap;margin-left:auto}
1286
+ .car-dot{width:10px;height:10px;border:1px solid var(--border-strong);background:transparent;cursor:pointer;padding:0}
1287
+ .car-dot.on{background:var(--primary-soft);border-color:var(--primary-soft)}
1288
+ .car-dot.dec{border-color:var(--pass);box-shadow:inset 0 0 0 2px var(--pass)}
1289
+ .cards.carousel-on{display:block}
1290
+ .cards.carousel-on .card{display:none}
1291
+ .cards.carousel-on .card.on{display:block;animation:fade .18s ease-out}
1292
+ @keyframes fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}
1293
+ .asm-cal{display:inline-flex;gap:4px;margin-left:8px}
1294
+ .asm-cal label{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--muted);border:1px solid var(--border);padding:1px 6px;cursor:pointer;display:inline-flex;gap:3px;align-items:center}
1295
+ .asm-cal label:hover{border-color:var(--cyan);color:var(--cyan)}
1296
+ .asm-cal input{margin:0;cursor:pointer}
1297
+ /* ── segmented coverage funnel (native big-number stages, no external renderer) ── */
1298
+ .funnel2{margin:14px 0}
1299
+ .fstages{display:flex;gap:8px;align-items:stretch;flex-wrap:wrap}
1300
+ .fseg{flex:1 1 110px;border:1px solid var(--border);background:var(--surf-2);padding:10px 12px;text-align:center}
1301
+ .fseg-v{font-family:var(--mono);font-size:var(--fs-2xl);font-weight:700;color:var(--fg-strong);line-height:1.1}
1302
+ .fseg-l{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.05em;color:var(--dim);margin-top:3px}
1303
+ .farrow{display:flex;align-items:center;color:var(--dim);font-size:var(--fs-lg)}
1304
+ .fpills{display:flex;gap:7px;flex-wrap:wrap;margin-top:9px}
1305
+ .fpill{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;padding:2px 9px;border:1px solid var(--border-strong);color:var(--fg)}
1306
+ .fpill.ok{color:var(--pass);border-color:var(--pass)}
1307
+ .fpill.bad{color:var(--fail);border-color:var(--fail)}
1308
+ /* ── entity hero — reconstructed from the triage census ── */
1309
+ .ec-stats{display:flex;gap:8px;flex-wrap:wrap;margin:8px 0 10px}
1310
+ .ecs{background:var(--surf-2);border:1px solid var(--border);padding:7px 12px;min-width:76px}
1311
+ .ecs-v{font-family:var(--mono);font-size:var(--fs-lg);font-weight:700;color:var(--fg-strong);line-height:1.1}
1312
+ .ecs-l{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--dim);margin-top:2px}
1313
+ .tool-n{color:var(--fg-strong);font-weight:700}
1314
+ .ec-honesty{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--muted);border-top:1px dashed var(--border-strong);margin-top:9px;padding-top:7px;line-height:1.5}
1315
+ .ec-honesty b{color:var(--fg)}
1316
+ /* ── chip legend — parity with render-eval-report's verdictLegend (.vlegend): bordered
1317
+ * surf-2 block · cyan left-accent · ▾ header · key-chip + plain-gloss rows. Chips
1318
+ * reuse the SAME .grounding/.cmeth/.sev classes the cards render (true visual key). ── */
1319
+ .dlegend{border:1px solid var(--border);border-left:3px solid var(--cyan);background:var(--surf-2);padding:9px 12px;margin:12px 0}
1320
+ .dlegend .dl-h{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.05em;color:var(--cyan);margin-bottom:7px}
1321
+ .dlegend .dl-groups{display:grid;grid-template-columns:repeat(auto-fit,minmax(252px,1fr));gap:4px 22px}
1322
+ .dlegend .dl-gh{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin:5px 0 3px}
1323
+ .dlegend .dl-item{display:flex;gap:8px;align-items:baseline;margin:3px 0;line-height:1.4}
1324
+ .dlegend .dl-item>:first-child{flex:0 0 auto;min-width:80px;text-align:center}
1325
+ .dlegend .dl-v{font-size:var(--fs-2xs);color:var(--fg);opacity:.9}
1326
+ .dlegend .dl-k{font-family:var(--mono);font-size:var(--fs-2xs);font-weight:700;padding:2px 7px;border:1px solid;letter-spacing:.04em;text-transform:uppercase}
1327
+ .dlegend .dl-k.keep{color:var(--pass);border-color:var(--pass)}
1328
+ .dlegend .dl-k.revise{color:var(--warn);border-color:var(--warn)}
1329
+ .dlegend .dl-k.retire{color:var(--fail);border-color:var(--fail)}
1330
+ .dlegend .dl-foot{font-family:var(--mono);font-size:var(--fs-2xs);color:var(--muted);border-top:1px dashed var(--border-strong);margin-top:8px;padding-top:7px;line-height:1.5}
1331
+ .dlegend .dl-foot .dl-fk{color:var(--primary-soft);text-transform:uppercase;letter-spacing:.04em;margin-right:6px}
1332
+ /* ── reliability census — send-health roll-ups from triage.totals (status-tinted tiles) ── */
1333
+ .reliab{margin:12px 0}
1334
+ .rt-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(132px,1fr));gap:8px}
1335
+ .rt{border:1px solid var(--border);border-top:3px solid var(--border-strong);background:var(--surf);padding:9px 11px;margin:0}
1336
+ .rt-v{font-family:var(--mono);font-size:var(--fs-lg);font-weight:700;color:var(--fg-strong);line-height:1.1}
1337
+ .rt-l{font-family:var(--mono);font-size:var(--fs-2xs);text-transform:uppercase;letter-spacing:.04em;color:var(--dim);margin-top:3px}
1338
+ .rt.ok{border-top-color:var(--pass)}.rt.ok .rt-v{color:var(--pass)}
1339
+ .rt.warn{border-top-color:var(--warn)}.rt.warn .rt-v{color:var(--warn)}
1340
+ .rt.fail{border-top-color:var(--fail)}.rt.fail .rt-v{color:var(--fail)}
1341
+ `;
1342
+
1343
+ /**
1344
+ * Render the full self-contained discover `report.html`. PURE except the brand
1345
+ * file reads. DETERMINISTIC (severity-then-id card order; the only varying input
1346
+ * is `generatedAt`, masked by mask.ts → byte-identical reruns / C-PIN).
1347
+ */
1348
+ export function renderDiscoverReport(input: DiscoverReportInput): string {
1349
+ const theme = readFileSync(join(BRAND_DIR, "theme.css"), "utf8");
1350
+ const wordmark = readFileSync(join(BRAND_DIR, "wordmark.html"), "utf8");
1351
+
1352
+ const headerTitle = "evaluator · Discover Report";
1353
+ const headerMeta =
1354
+ `<span class="mk">subject</span> <span class="mv">${esc(input.subject.name)}</span>` +
1355
+ `<span class="sep">·</span><span class="mk">generated</span> <span class="mv">${esc(input.generatedAt)}</span>` +
1356
+ (input.batchId ? `<span class="sep">·</span><span class="mk">batch</span> <span class="mv">${esc(input.batchId)}</span>` : "");
1357
+ const header = wordmark
1358
+ .replaceAll("{{HEADER_TITLE}}", esc(headerTitle))
1359
+ .replaceAll("{{HEADER_META}}", headerMeta);
1360
+
1361
+ // criterion meta for the client HITL markdown builder (deterministic).
1362
+ const criteriaMeta: Record<string, { severity: string; checkMethod: string; statement: string }> = {};
1363
+ for (const c of sortCriteria(input.criteria)) {
1364
+ criteriaMeta[c.id] = {
1365
+ severity: c.metadata.severity,
1366
+ checkMethod: c.metadata.check_method,
1367
+ statement: c.statement,
1368
+ };
1369
+ }
1370
+
1371
+ const nav = `<nav class="tabs">
1372
+ <button class="active" data-tab="t1">① Overview</button>
1373
+ <button data-tab="t2">② Criteria</button>
1374
+ <button data-tab="t3">③ Proof-of-work</button>
1375
+ <button data-tab="t4">④ Dataset</button>
1376
+ </nav>`;
1377
+
1378
+ return `<!DOCTYPE html>
1379
+ <html lang="en" data-theme="dark">
1380
+ <head>
1381
+ <meta charset="UTF-8">
1382
+ <meta name="viewport" content="width=device-width,initial-scale=1">
1383
+ <title>${esc(headerTitle)} — ${esc(input.subject.name)}</title>
1384
+ <link rel="preconnect" href="https://fonts.googleapis.com">
1385
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
1386
+ <style>
1387
+ ${theme}
1388
+ ${REPORT_CSS}
1389
+ </style>
1390
+ </head>
1391
+ <body>
1392
+ ${header}
1393
+ ${nav}
1394
+ <main>
1395
+ ${overviewTab(input)}
1396
+ ${criteriaTab(input)}
1397
+ ${proofTab(input)}
1398
+ ${datasetTab(input)}
1399
+ <div class="foot">🧬 MutagenT evaluator · *discover-evals · ${esc(input.criteria.length)} criteria · grounded refs{obs,path,value} · typed assumptions · inline HITL keep/revise/retire (persisted in-browser) · pinned judge + temp 0 (C-PIN — byte-identical reruns)</div>
1400
+ </main>
1401
+ <script>${clientScript({ subject: input.subject.name, generatedAt: input.generatedAt }, criteriaMeta)}</script>
1402
+ </body>
1403
+ </html>
1404
+ `;
1405
+ }
1406
+
1407
+ // ── CLI entrypoint — the *discover-evals AGGREGATE path drives this post-aggregate ──
1408
+ //
1409
+ // Usage: bun scripts/render-discover-report.ts <criteria.json> <out.html> [grounding-check.json] [subjectName]
1410
+ // Reads the FULL MinedCriterion[] criteria.json + an OPTIONAL grounding-check.json
1411
+ // and writes the self-contained report.html. Returns the out path. Companion
1412
+ // artifacts (triage-summary.json, sent-dist.json, verdicts/, dataset-candidates.json)
1413
+ // are AUTO-DISCOVERED next to criteria.json when present (best-effort, null-tolerant).
1414
+
1415
+ declare const Bun: { argv: string[] } | undefined;
1416
+
1417
+ export interface WriteDiscoverReportArgs {
1418
+ criteriaPath: string;
1419
+ outPath: string;
1420
+ groundingPath?: string;
1421
+ /** OPTIONAL companion artifacts — read when supplied; absent ⇒ degrade gracefully. */
1422
+ profilePath?: string;
1423
+ triagePath?: string;
1424
+ sentDistPath?: string;
1425
+ verdictsDir?: string;
1426
+ datasetPath?: string;
1427
+ subjectName?: string;
1428
+ subjectSource?: string;
1429
+ generatedAt?: string;
1430
+ }
1431
+
1432
+ export interface DiscoverIo {
1433
+ readFile: (p: string) => string;
1434
+ writeFile: (p: string, s: string) => void;
1435
+ /** OPTIONAL — list a directory (verdicts/). Absent ⇒ the Proof-of-work tab degrades. */
1436
+ readDir?: (p: string) => string[];
1437
+ }
1438
+
1439
+ function safeParse<T>(io: DiscoverIo, path: string | undefined): T | null {
1440
+ if (path === undefined) return null;
1441
+ try {
1442
+ return JSON.parse(io.readFile(path)) as T;
1443
+ } catch {
1444
+ return null;
1445
+ }
1446
+ }
1447
+
1448
+ /**
1449
+ * Read the criteria (+ optional companions) JSON via injected io, render, and write
1450
+ * the report.html to `outPath`. Returns the out path. SHARED by the CLI + the
1451
+ * AGGREGATE wiring (`aggregate-discover.ts`). The fs side-effects live HERE;
1452
+ * `renderDiscoverReport` stays pure-given-input. Only paths that are SUPPLIED are
1453
+ * read (so an injected io exposing just criteria/grounding stays valid).
1454
+ */
1455
+ export function writeDiscoverReportFromFiles(
1456
+ args: WriteDiscoverReportArgs,
1457
+ io: DiscoverIo,
1458
+ ): string {
1459
+ const criteria = JSON.parse(io.readFile(args.criteriaPath)) as MinedCriterion[];
1460
+ const grounding = safeParse<GroundingCheckSummary>(io, args.groundingPath);
1461
+ const profile = safeParse<DiscoverProfile>(io, args.profilePath);
1462
+ const triage = safeParse<TriageSummary>(io, args.triagePath);
1463
+ const sentDist = safeParse<SentDistSummary>(io, args.sentDistPath);
1464
+ const dataset = safeParse<DatasetCandidate[]>(io, args.datasetPath);
1465
+
1466
+ let verdicts: DeterminerVerdict[] | null = null;
1467
+ if (args.verdictsDir !== undefined && io.readDir) {
1468
+ try {
1469
+ const files = io.readDir(args.verdictsDir).filter((f) => f.endsWith(".verdict.json"));
1470
+ verdicts = files
1471
+ .map((f) => {
1472
+ const traceId = f.replace(/\.verdict\.json$/, "");
1473
+ const v = safeParse<Omit<DeterminerVerdict, "traceId">>(io, join(args.verdictsDir as string, f));
1474
+ return v ? { traceId, ...v } : null;
1475
+ })
1476
+ .filter((v): v is DeterminerVerdict => v !== null);
1477
+ } catch {
1478
+ verdicts = null;
1479
+ }
1480
+ }
1481
+
1482
+ const html = renderDiscoverReport({
1483
+ subject: { name: args.subjectName ?? "discover-subject", ...(args.subjectSource ? { source: args.subjectSource } : {}) },
1484
+ criteria: Array.isArray(criteria) ? criteria : [],
1485
+ grounding,
1486
+ profile,
1487
+ triage,
1488
+ sentDist,
1489
+ verdicts,
1490
+ dataset: Array.isArray(dataset) ? dataset : null,
1491
+ generatedAt: args.generatedAt ?? new Date().toISOString(),
1492
+ });
1493
+ io.writeFile(args.outPath, html);
1494
+ return args.outPath;
1495
+ }
1496
+
1497
+ /** One per-trace `triage.json` entry (the cheap signals ingest-triage writes). */
1498
+ interface TriageEntry {
1499
+ eventKind?: string;
1500
+ nObs?: number;
1501
+ sentMessage?: unknown;
1502
+ sendSucceeded?: unknown;
1503
+ nSends?: number;
1504
+ nFailedSends?: number;
1505
+ nHardFails?: number;
1506
+ recoveryPresent?: boolean;
1507
+ hasOutboundGuard?: boolean;
1508
+ toolNames?: string[];
1509
+ }
1510
+
1511
+ /**
1512
+ * Build the `triage-summary.json` CENSUS (the coverage-funnel + entity source) from the
1513
+ * per-trace `triage.json` array. The shipped ingest writes `triage.json` (per-trace
1514
+ * signals) but NOT this batch census, so the funnel/deep-read-fails degrade to em-dash
1515
+ * unless this is produced. PURE — pure aggregation, no clock/random/network.
1516
+ */
1517
+ export function buildTriageSummary(triage: TriageEntry[], sendTool = "sendMessage"): TriageSummary {
1518
+ const inc = (m: Record<string, number>, k: string): void => { m[k] = (m[k] ?? 0) + 1; };
1519
+ const toolCensus: Record<string, number> = {};
1520
+ const sentDist: Record<string, number> = {}, sendSucc: Record<string, number> = {}, eventTax: Record<string, number> = {};
1521
+ let sends = 0, failedSends = 0, hardFails = 0, recovery = 0, guards = 0, obsTotal = 0;
1522
+ for (const t of triage) {
1523
+ for (const nm of new Set(t.toolNames ?? [])) inc(toolCensus, nm);
1524
+ inc(sentDist, t.sentMessage === true ? "true" : t.sentMessage === false ? "false" : "unknown");
1525
+ inc(sendSucc, t.sendSucceeded === true ? "true" : t.sendSucceeded === false ? "false" : "n/a");
1526
+ if (t.eventKind) inc(eventTax, t.eventKind);
1527
+ sends += t.nSends ?? 0; failedSends += t.nFailedSends ?? 0; hardFails += t.nHardFails ?? 0;
1528
+ if (t.recoveryPresent) recovery++; if (t.hasOutboundGuard) guards++; obsTotal += t.nObs ?? 0;
1529
+ }
1530
+ return {
1531
+ total: triage.length,
1532
+ sendTool,
1533
+ sentDist, sendSucc, eventTax,
1534
+ eventTags: Object.keys(eventTax),
1535
+ toolCensus: Object.fromEntries(Object.entries(toolCensus).sort((a, b) => b[1] - a[1])),
1536
+ sends,
1537
+ obsAvg: triage.length ? Math.round((10 * obsTotal) / triage.length) / 10 : 0,
1538
+ totals: { sends, failedSends, hardFails, tracesWithRecovery: recovery, tracesWithOutboundGuard: guards },
1539
+ };
1540
+ }
1541
+
1542
+ /**
1543
+ * SHIPPED discover RUN composer — the analogue of run-evaluate's `writeRunReport`.
1544
+ * Given a run DIR, ensure `triage-summary.json` exists (built from `triage.json` when
1545
+ * absent) then render the FULL report with EVERY companion wired (funnel + Proof-of-work
1546
+ * + Dataset + entity profile). This is the CANONICAL render: it GUARANTEES a complete
1547
+ * report by default rather than the minimal `writeDiscoverReport` degrade-to-em-dash that
1548
+ * a producer-forgot-companions caller hits. Companions absent on disk degrade gracefully
1549
+ * (safeParse → null). Use THIS from the *discover-evals flow, not bare `writeDiscoverReport`.
1550
+ */
1551
+ export function writeDiscoverRunReport(
1552
+ args: { dir: string; subjectName?: string; subjectSource?: string; generatedAt?: string },
1553
+ io: DiscoverIo,
1554
+ ): string {
1555
+ const { dir } = args;
1556
+ const triageSummaryPath = join(dir, "triage-summary.json");
1557
+ // build the census from triage.json when the summary is absent (so the funnel fills).
1558
+ let hasSummary = false;
1559
+ try { io.readFile(triageSummaryPath); hasSummary = true; } catch { hasSummary = false; }
1560
+ if (!hasSummary) {
1561
+ try {
1562
+ const triage = JSON.parse(io.readFile(join(dir, "triage.json"))) as TriageEntry[];
1563
+ if (Array.isArray(triage)) io.writeFile(triageSummaryPath, JSON.stringify(buildTriageSummary(triage), null, 2));
1564
+ } catch { /* no triage.json → funnel degrades gracefully */ }
1565
+ }
1566
+ return writeDiscoverReportFromFiles(
1567
+ {
1568
+ criteriaPath: join(dir, "criteria.json"),
1569
+ outPath: join(dir, "discovery-report.html"),
1570
+ groundingPath: join(dir, "grounding-check.json"),
1571
+ triagePath: triageSummaryPath,
1572
+ verdictsDir: join(dir, "verdicts"),
1573
+ datasetPath: join(dir, "dataset-candidates.json"),
1574
+ profilePath: join(dir, "subject-profile.json"),
1575
+ ...(args.subjectName ? { subjectName: args.subjectName } : {}),
1576
+ ...(args.subjectSource ? { subjectSource: args.subjectSource } : {}),
1577
+ ...(args.generatedAt ? { generatedAt: args.generatedAt } : {}),
1578
+ },
1579
+ io,
1580
+ );
1581
+ }
1582
+
1583
+ function main(): void {
1584
+ const argv = typeof Bun !== "undefined" ? Bun.argv.slice(2) : process.argv.slice(2);
1585
+ const [criteriaPath, outPath, groundingPath, subjectName] = argv;
1586
+ if (!criteriaPath || !outPath) {
1587
+ console.error(
1588
+ "usage: render-discover-report.ts <criteria.json> <out.html> [grounding-check.json] [subjectName]",
1589
+ );
1590
+ process.exit(2);
1591
+ return;
1592
+ }
1593
+ // auto-discover companion artifacts next to criteria.json (best-effort).
1594
+ const dir = dirname(criteriaPath);
1595
+ const sib = (name: string): string => join(dir, name);
1596
+ const exists = (p: string): boolean => {
1597
+ try {
1598
+ readFileSync(p);
1599
+ return true;
1600
+ } catch {
1601
+ return false;
1602
+ }
1603
+ };
1604
+ const verdictsDir = (() => {
1605
+ try {
1606
+ readdirSync(sib("verdicts"));
1607
+ return sib("verdicts");
1608
+ } catch {
1609
+ return undefined;
1610
+ }
1611
+ })();
1612
+
1613
+ const out = writeDiscoverReportFromFiles(
1614
+ {
1615
+ criteriaPath,
1616
+ outPath,
1617
+ ...(groundingPath ? { groundingPath } : exists(sib("grounding-check.json")) ? { groundingPath: sib("grounding-check.json") } : {}),
1618
+ ...(exists(sib("subject-profile.json")) ? { profilePath: sib("subject-profile.json") } : {}),
1619
+ ...(exists(sib("triage-summary.json")) ? { triagePath: sib("triage-summary.json") } : {}),
1620
+ ...(exists(sib("sent-dist.json")) ? { sentDistPath: sib("sent-dist.json") } : {}),
1621
+ ...(verdictsDir ? { verdictsDir } : {}),
1622
+ ...(exists(sib("dataset-candidates.json")) ? { datasetPath: sib("dataset-candidates.json") } : {}),
1623
+ ...(subjectName ? { subjectName } : {}),
1624
+ },
1625
+ {
1626
+ readFile: (p) => readFileSync(p, "utf8"),
1627
+ writeFile: (p, s) => writeFileSync(p, s),
1628
+ readDir: (p) => readdirSync(p),
1629
+ },
1630
+ );
1631
+ console.info(JSON.stringify({ report: out }, null, 2));
1632
+ process.exit(0);
1633
+ }
1634
+
1635
+ const isMain =
1636
+ typeof import.meta !== "undefined" &&
1637
+ (import.meta as unknown as { main?: boolean }).main === true;
1638
+ if (isMain) {
1639
+ main();
1640
+ }