@marinoscar/kvox-eval 0.0.0-stage → 0.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +21 -0
  3. package/README.md +165 -2
  4. package/dist/ask/index.d.ts +6 -0
  5. package/dist/ask/index.js +28 -0
  6. package/dist/ask/question-schema.d.ts +121 -0
  7. package/dist/ask/question-schema.js +167 -0
  8. package/dist/ask/report.d.ts +88 -0
  9. package/dist/ask/report.js +170 -0
  10. package/dist/ask/scorer.d.ts +104 -0
  11. package/dist/ask/scorer.js +164 -0
  12. package/dist/ask-run/ask-command.d.ts +62 -0
  13. package/dist/ask-run/ask-command.js +254 -0
  14. package/dist/ask-run/host/app.d.ts +65 -0
  15. package/dist/ask-run/host/app.js +146 -0
  16. package/dist/ask-run/host/db.d.ts +38 -0
  17. package/dist/ask-run/host/db.js +126 -0
  18. package/dist/ask-run/host/ports.d.ts +35 -0
  19. package/dist/ask-run/host/ports.js +113 -0
  20. package/dist/ask-run/index.d.ts +13 -0
  21. package/dist/ask-run/index.js +35 -0
  22. package/dist/ask-run/replay.d.ts +128 -0
  23. package/dist/ask-run/replay.js +212 -0
  24. package/dist/ask-run/run-ask-eval.d.ts +125 -0
  25. package/dist/ask-run/run-ask-eval.js +193 -0
  26. package/dist/ask-run/seed-golden-graph.d.ts +64 -0
  27. package/dist/ask-run/seed-golden-graph.js +495 -0
  28. package/dist/cli/index.d.ts +3 -0
  29. package/dist/cli/index.js +12 -0
  30. package/dist/cli/kg-command.d.ts +66 -0
  31. package/dist/cli/kg-command.js +238 -0
  32. package/dist/cli/main.d.ts +18 -0
  33. package/dist/cli/main.js +69 -0
  34. package/dist/fixtures/domains.d.ts +16 -0
  35. package/dist/fixtures/domains.js +33 -0
  36. package/dist/fixtures/fixture-schema.d.ts +323 -0
  37. package/dist/fixtures/fixture-schema.js +199 -0
  38. package/dist/fixtures/index.d.ts +16 -0
  39. package/dist/fixtures/index.js +40 -0
  40. package/dist/fixtures/load.d.ts +73 -0
  41. package/dist/fixtures/load.js +137 -0
  42. package/dist/fixtures/questions.d.ts +15 -0
  43. package/dist/fixtures/questions.js +26 -0
  44. package/dist/index.d.ts +20 -0
  45. package/dist/index.js +36 -0
  46. package/dist/kg/gold-runner.d.ts +13 -0
  47. package/dist/kg/gold-runner.js +57 -0
  48. package/dist/kg/index.d.ts +17 -0
  49. package/dist/kg/index.js +65 -0
  50. package/dist/kg/internals.d.ts +7 -0
  51. package/dist/kg/internals.js +17 -0
  52. package/dist/kg/prediction-schema.d.ts +245 -0
  53. package/dist/kg/prediction-schema.js +102 -0
  54. package/dist/kg/predictions.d.ts +11 -0
  55. package/dist/kg/predictions.js +49 -0
  56. package/dist/kg/report.d.ts +84 -0
  57. package/dist/kg/report.js +125 -0
  58. package/dist/kg/run-kg-eval.d.ts +77 -0
  59. package/dist/kg/run-kg-eval.js +137 -0
  60. package/dist/kg/runner.d.ts +72 -0
  61. package/dist/kg/runner.js +51 -0
  62. package/dist/kg/runners/extract-resolve-runner.d.ts +40 -0
  63. package/dist/kg/runners/extract-resolve-runner.js +199 -0
  64. package/dist/kg/runners/extract-runner.d.ts +65 -0
  65. package/dist/kg/runners/extract-runner.js +243 -0
  66. package/dist/kg/scorer.d.ts +226 -0
  67. package/dist/kg/scorer.js +463 -0
  68. package/dist/kg/targets.d.ts +17 -0
  69. package/dist/kg/targets.js +16 -0
  70. package/dist/kg/text.d.ts +45 -0
  71. package/dist/kg/text.js +100 -0
  72. package/dist/live/adapters.d.ts +16 -0
  73. package/dist/live/adapters.js +58 -0
  74. package/dist/live/env.d.ts +39 -0
  75. package/dist/live/env.js +56 -0
  76. package/dist/live/index.d.ts +6 -0
  77. package/dist/live/index.js +16 -0
  78. package/dist/live/live-ai.d.ts +104 -0
  79. package/dist/live/live-ai.js +157 -0
  80. package/dist/parity/constants.d.ts +79 -0
  81. package/dist/parity/constants.js +104 -0
  82. package/dist/parity/index.d.ts +20 -0
  83. package/dist/parity/index.js +38 -0
  84. package/dist/parity/internals.d.ts +8 -0
  85. package/dist/parity/internals.js +16 -0
  86. package/dist/parity/scenario.d.ts +45 -0
  87. package/dist/parity/scenario.js +245 -0
  88. package/dist/parity/script.d.ts +44 -0
  89. package/dist/parity/script.js +136 -0
  90. package/dist/parity/snapshot.d.ts +43 -0
  91. package/dist/parity/snapshot.js +71 -0
  92. package/dist/real-data/index.d.ts +2 -0
  93. package/dist/real-data/index.js +12 -0
  94. package/dist/real-data/real-data.d.ts +76 -0
  95. package/dist/real-data/real-data.js +156 -0
  96. package/fixtures/kg-golden/README.md +136 -0
  97. package/fixtures/kg-golden/ask-questions.json +458 -0
  98. package/fixtures/kg-golden/meetings/m01-northwind-pilot-kickoff.json +792 -0
  99. package/fixtures/kg-golden/meetings/m02-halden-migration-planning.json +814 -0
  100. package/fixtures/kg-golden/meetings/m03-larkspur-weekly-sync.json +601 -0
  101. package/fixtures/kg-golden/meetings/m04-corvid-intake-discovery.json +690 -0
  102. package/fixtures/kg-golden/meetings/m05-hosting-vendor-selection.json +631 -0
  103. package/fixtures/kg-golden/meetings/m06-northwind-floor-data-review.json +649 -0
  104. package/fixtures/kg-golden/meetings/m07-gullwing-intro-notes.json +428 -0
  105. package/fixtures/kg-golden/meetings/m08-halden-migration-checkpoint.json +672 -0
  106. package/fixtures/kg-golden/meetings/m09-northwind-platform-org-update.json +711 -0
  107. package/fixtures/kg-golden/meetings/m10-corvid-prototype-review.json +623 -0
  108. package/fixtures/kg-golden/meetings/m11-solvane-grid-dashboard-scoping.json +689 -0
  109. package/fixtures/kg-golden/meetings/m12-northwind-pilot-replan.json +681 -0
  110. package/fixtures/kg-golden/meetings/m13-halden-cutover-go-no-go.json +721 -0
  111. package/fixtures/kg-golden/meetings/m14-gullwing-discovery-readout.json +719 -0
  112. package/fixtures/kg-golden/meetings/m15-halden-handover-ravi-leaving.json +716 -0
  113. package/fixtures/kg-golden/meetings/m16-varden-claims-portal-intro.json +704 -0
  114. package/fixtures/kg-golden/meetings/m17-corvid-usability-debrief.json +572 -0
  115. package/fixtures/kg-golden/meetings/m18-hosting-decision-revisited.json +655 -0
  116. package/fixtures/kg-golden/meetings/m19-solvane-integration-kickoff.json +726 -0
  117. package/fixtures/kg-golden/meetings/m20-northwind-go-live-retro.json +673 -0
  118. package/fixtures/kg-golden/meetings/m21-quarterly-account-review.json +449 -0
  119. package/fixtures/kg-golden/meetings/m22-halden-intro-call-2020.json +558 -0
  120. package/fixtures/kg-golden/meetings/m23-gullwing-model-review.json +702 -0
  121. package/fixtures/kg-golden/meetings/m24-varden-discovery-midpoint.json +643 -0
  122. package/fixtures/kg-golden/meetings/m25-corvid-rollout-planning.json +642 -0
  123. package/fixtures/kg-golden/meetings/m26-larkspur-engineering-prep.json +695 -0
  124. package/fixtures/kg-golden/meetings/m27-solvane-phase-one-demo.json +649 -0
  125. package/fixtures/kg-golden/meetings/m28-northwind-building-5-scoping.json +715 -0
  126. package/fixtures/kg-golden/meetings/m29-halden-southern-cutover-review.json +752 -0
  127. package/fixtures/kg-golden/meetings/m30-varden-portal-build-kickoff.json +697 -0
  128. package/fixtures/kg-golden/meetings/m31-gullwing-rollout-renewal.json +681 -0
  129. package/fixtures/kg-golden/meetings/m32-brambleway-weekly-sync.json +472 -0
  130. package/fixtures/kg-golden/meetings/m33-nina-oliver-one-on-one.json +342 -0
  131. package/fixtures/kg-golden/meetings/m34-coffee-with-dana.json +249 -0
  132. package/fixtures/kg-golden/meetings/m35-brambleway-june-sync.json +553 -0
  133. package/fixtures/kg-golden/meetings/m36-copenhagen-conference-planning.json +296 -0
  134. package/host/generated/client.d.ts +1 -0
  135. package/host/generated/client.js +5 -0
  136. package/host/generated/default.d.ts +1 -0
  137. package/host/generated/default.js +5 -0
  138. package/host/generated/edge.d.ts +1 -0
  139. package/host/generated/edge.js +1658 -0
  140. package/host/generated/index-browser.js +1684 -0
  141. package/host/generated/index.d.ts +192335 -0
  142. package/host/generated/index.js +1658 -0
  143. package/host/generated/package.json +139 -0
  144. package/host/generated/query_compiler_fast_bg.js +2 -0
  145. package/host/generated/query_compiler_fast_bg.wasm +0 -0
  146. package/host/generated/query_compiler_fast_bg.wasm-base64.js +2 -0
  147. package/host/generated/runtime/client.d.ts +3433 -0
  148. package/host/generated/runtime/client.js +110 -0
  149. package/host/generated/runtime/index-browser.d.ts +90 -0
  150. package/host/generated/runtime/index-browser.js +6 -0
  151. package/host/generated/runtime/wasm-compiler-edge.js +100 -0
  152. package/host/generated/schema.prisma +6312 -0
  153. package/host/generated/wasm-edge-light-loader.mjs +5 -0
  154. package/host/generated/wasm-worker-loader.mjs +5 -0
  155. package/host/prisma/fragments/kvox.ask.prisma +126 -0
  156. package/host/prisma/fragments/kvox.graph.prisma +1073 -0
  157. package/host/prisma/fragments/kvox.notes.prisma +787 -0
  158. package/host/prisma/fragments/kvox.search.prisma +211 -0
  159. package/host/prisma/fragments/kvox.transcripts.prisma +790 -0
  160. package/host/prisma/kvox.lock +101 -0
  161. package/host/prisma/migrations/20240101000000_initial/migration.sql +297 -0
  162. package/host/prisma/migrations/20240101000001_add_personal_access_tokens/migration.sql +31 -0
  163. package/host/prisma/migrations/20240101000002_add_credentials/migration.sql +20 -0
  164. package/host/prisma/migrations/20240101000003_add_notification_deliveries/migration.sql +27 -0
  165. package/host/prisma/migrations/20240101000004_drop_stale_uuid_defaults/migration.sql +35 -0
  166. package/host/prisma/migrations/20240101000005_add_notifications/migration.sql +22 -0
  167. package/host/prisma/migrations/20240101000006_add_push_subscriptions/migration.sql +25 -0
  168. package/host/prisma/migrations/20240101000007_add_jobs/migration.sql +110 -0
  169. package/host/prisma/migrations/20240101000008_add_worker_nodes/migration.sql +90 -0
  170. package/host/prisma/migrations/20240101000009_add_database_backup_runs/migration.sql +104 -0
  171. package/host/prisma/migrations/20240101000010_add_notification_broadcasts/migration.sql +57 -0
  172. package/host/prisma/migrations/20240101000011_add_backup_run_job_link/migration.sql +87 -0
  173. package/host/prisma/migrations/20240101000012_add_job_node_secrets/migration.sql +61 -0
  174. package/host/prisma/migrations/20240101000013_add_backup_run_pg_dump_version/migration.sql +26 -0
  175. package/host/prisma/migrations/20240101000014_add_job_claim_token/migration.sql +92 -0
  176. package/host/prisma/migrations/20240101000015_add_ai_platform/migration.sql +108 -0
  177. package/host/prisma/migrations/20240101000016_revoke_viewer_ai_use/migration.sql +32 -0
  178. package/host/prisma/migrations/20240101000017_add_user_credentials/migration.sql +29 -0
  179. package/host/prisma/migrations/20240101000018_add_device_session_credential_link/migration.sql +52 -0
  180. package/host/prisma/migrations/20240101000019_add_worker_node_vitals/migration.sql +3 -0
  181. package/host/prisma/migrations/20240101000020_add_job_trace_context/migration.sql +6 -0
  182. package/host/prisma/migrations/20240101000021_add_retention_created_at_indexes/migration.sql +13 -0
  183. package/host/prisma/migrations/20240101000022_add_organizations/migration.sql +142 -0
  184. package/host/prisma/migrations/20240101000023_split_system_org_roles/migration.sql +131 -0
  185. package/host/prisma/migrations/20240101000024_org_scoped_rls/migration.sql +215 -0
  186. package/host/prisma/migrations/20240101000025_add_groups/migration.sql +165 -0
  187. package/host/prisma/migrations/20240101000026_add_grants/migration.sql +117 -0
  188. package/host/prisma/migrations/20240101000027_add_org_settings/migration.sql +49 -0
  189. package/host/prisma/migrations/20240101000028_add_org_credentials/migration.sql +54 -0
  190. package/host/prisma/migrations/20240101000029_add_jobs_org_id/migration.sql +18 -0
  191. package/host/prisma/migrations/20240101000030_add_jobs_org_id_status_index/migration.sql +6 -0
  192. package/host/prisma/migrations/20240101000031_add_broadcast_target_org/migration.sql +21 -0
  193. package/host/prisma/migrations/20240101000032_add_android_app/migration.sql +60 -0
  194. package/host/prisma/migrations/20240101000033_open_job_reason/migration.sql +16 -0
  195. package/host/prisma/migrations/20240101000034_ai_usage_feature/migration.sql +37 -0
  196. package/host/prisma/migrations/20240101000035_add_app_metadata/migration.sql +49 -0
  197. package/host/prisma/migrations/20240101000036_open_sharing_vocabularies/migration.sql +97 -0
  198. package/host/prisma/migrations/20260914130000_kvox_add_transcripts/migration.sql +322 -0
  199. package/host/prisma/migrations/20260914140000_kvox_add_notes/migration.sql +311 -0
  200. package/host/prisma/migrations/20260915120000_kvox_add_search_vectors/migration.sql +150 -0
  201. package/host/prisma/migrations/20260915130000_kvox_add_search_embeddings/migration.sql +362 -0
  202. package/host/prisma/migrations/20260915210000_kvox_add_note_title_source/migration.sql +30 -0
  203. package/host/prisma/migrations/20260923120000_kvox_add_note_generation_context_snapshot/migration.sql +24 -0
  204. package/host/prisma/migrations/20260923130000_kvox_add_user_hidden_note_templates/migration.sql +35 -0
  205. package/host/prisma/migrations/20260923140000_kvox_add_transcript_speaker_identities/migration.sql +6 -0
  206. package/host/prisma/migrations/20260923150000_kvox_transcript_name_checks/migration.sql +95 -0
  207. package/host/prisma/migrations/20260923160000_kvox_add_note_body_format/migration.sql +27 -0
  208. package/host/prisma/migrations/20260923170000_kvox_add_note_version_body_format/migration.sql +23 -0
  209. package/host/prisma/migrations/20260926012548_kvox_add_knowledge_graph/migration.sql +680 -0
  210. package/host/prisma/migrations/20260926030000_kvox_fix_kg_relations_valid_precision_chk/migration.sql +29 -0
  211. package/host/prisma/migrations/20260926030100_kvox_add_transcript_recorded_at/migration.sql +9 -0
  212. package/host/prisma/migrations/20260926030200_kvox_kg_evidence_invariant/migration.sql +60 -0
  213. package/host/prisma/migrations/20260926040000_kvox_add_kg_graph_layouts/migration.sql +30 -0
  214. package/host/prisma/migrations/20260926050000_kvox_add_ask_conversations/migration.sql +99 -0
  215. package/host/prisma/migrations/20260926231512_kvox_add_kg_exports/migration.sql +52 -0
  216. package/host/prisma/migrations/20261016120000_kvox_transcripts_org_id/migration.sql +49 -0
  217. package/host/prisma/platform.lock +319 -0
  218. package/host/prisma/schema/app.kvox.ask.prisma +110 -0
  219. package/host/prisma/schema/app.kvox.graph.prisma +1030 -0
  220. package/host/prisma/schema/app.kvox.notes.prisma +746 -0
  221. package/host/prisma/schema/app.kvox.search.prisma +208 -0
  222. package/host/prisma/schema/app.kvox.transcripts.prisma +726 -0
  223. package/host/prisma/schema/platform.ai.prisma +419 -0
  224. package/host/prisma/schema/platform.android-app.prisma +46 -0
  225. package/host/prisma/schema/platform.base.prisma +15 -0
  226. package/host/prisma/schema/platform.credentials.prisma +156 -0
  227. package/host/prisma/schema/platform.db-backup.prisma +359 -0
  228. package/host/prisma/schema/platform.identity.prisma +638 -0
  229. package/host/prisma/schema/platform.jobs.prisma +824 -0
  230. package/host/prisma/schema/platform.notifications.prisma +641 -0
  231. package/host/prisma/schema/platform.settings.prisma +53 -0
  232. package/host/prisma/schema/platform.sharing.prisma +208 -0
  233. package/host/prisma/schema/platform.storage.prisma +118 -0
  234. package/package.json +110 -4
  235. package/parity/requests.ask.json +2681 -0
  236. package/parity/requests.m01.json +1554 -0
  237. package/parity/requests.m02.json +1554 -0
  238. package/parity/requests.m03.json +1554 -0
  239. package/parity/requests.m04.json +1554 -0
  240. package/parity/requests.m05.json +1554 -0
  241. package/parity/requests.m06.json +1554 -0
  242. package/parity/requests.m07.json +1554 -0
  243. package/parity/requests.m08.json +1554 -0
  244. package/parity/requests.m09.json +1554 -0
  245. package/parity/requests.m10.json +1554 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # @marinoscar/kvox-eval
2
+
3
+ ## 0.1.0-next.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7310f9d: Add the headless Ask evaluation (#608). The new `@marinoscar/kvox-eval/ask-run` subpath (`runAskEval`, `runAskReplay`, `startAskEvalHost`, `seedGoldenGraph`, `runAskCommand`) runs the golden question set through the real `ask.respond` handler on an in-memory PGlite migrated with the platform and domain history, seeding the golden graph through `GraphWriteService`. It never touches a real database. `kvox-eval ask` (`--provider`, `--model`, `--base-url`, `--only`, `--category`, `--out`, `--min-match`, `--replay`) replaces the stub that exited 2; `--replay` needs no key. Exit codes are 0, 1 and 2, and the key is read only from `KVOX_EVAL_OPENAI_API_KEY`.
8
+
9
+ The root entry does not export `ask-run`; import the subpath. The package now ships `host/prisma` and `host/generated`, built by `scripts/prepare-host.mjs`.
10
+
11
+ New required peers: `@electric-sql/pglite` (0.5.10), `@electric-sql/pglite-pgvector` (0.0.11), `@electric-sql/pglite-socket` (0.2.13), `@prisma/client`, `@prisma/adapter-pg`, `pg`, `@nestjs/common`, `@nestjs/core`, `reflect-metadata`, `rxjs`.
12
+
13
+ - c060a77: New package `@marinoscar/kvox-eval`: the KVox knowledge-graph and Ask evaluation (#580), written only against the model port (`KvoxAiPort`, bound with `createKvoxAi` of `@marinoscar/kvox-ai`). It moves in from the web app unchanged in behaviour: the synthetic golden set (`./fixtures`: 36 meetings and the Ask question file, with their schema and loaders), the prediction schema, the scorer, the report and the gold, `extract` and `extract+resolve` runners, `runKgEval`, the pure half of the Ask evaluation (question schema, scorer, report), the real-data path guard, and the `kvox-eval` CLI (`kvox-eval kg`; `kvox-eval ask` is not headless yet). Live runs go through `createDirectRuntime` over the platform's OpenAI and OpenAI-compatible adapters with the key from `KVOX_EVAL_OPENAI_API_KEY` (`KG_EVAL_OPENAI_API_KEY` and `ASK_EVAL_OPENAI_API_KEY` are read for one more release); the web app's private OpenAI client is gone.
14
+ - 8dfebb9: Harness parity (#580). `@marinoscar/kvox-ai/testing` gains request recorders: `createRecordingRuntime` (every runtime round trip, without user, org, job or signal), `createRecordingAdapter` (a scripted provider adapter for `createDirectRuntime` that records what it receives) and `normalizeRecordedValue` (ids, timestamps and request ids numbered by first appearance, nothing dropped). `@marinoscar/kvox-eval/parity` drives notes generation, `kg.extract`, adjudication and three Ask questions through the real package code over any `KvoxAiPort` and ships the request snapshots (`parity/requests.*.json`) every host binding must reproduce; `kg` also exports `runExtraction` and `fixtureToInput`. `@marinoscar/kvox-api/notes` exports `budgetFromTarget` and the `AiTokenCaps` type.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 marinoscar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,166 @@
1
- # Temporary Holding Version
1
+ # @marinoscar/kvox-eval
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The KVox knowledge-graph and Ask evaluation: the synthetic golden set, its schemas, the scorers, the report, the gold and live runners, the headless Ask run and the `kvox-eval` command. Written only against the model port, so every host measures the prompts, schemas and validators the product ships. CommonJS.
4
+
5
+ ## Purpose and scope
6
+
7
+ One package for "how good is extraction (and Ask) on the golden set", shared by the web edition and KVox Desktop. A live run binds the same `KvoxAiPort` every host binds (`createKvoxAi` of `@marinoscar/kvox-ai`, over `createDirectRuntime` and the platform's provider adapters); the extraction pipeline it scores is `@marinoscar/kvox-api`'s own (`graph-extraction` internals), minus the loader and the writer. The package owns no prompt, schema or pipeline of the product.
8
+
9
+ The Ask run is headless (issue #608): `kvox-eval ask` starts an in-memory PGlite, applies the shipped migration SQL in order, seeds the golden graph through `GraphWriteService` and drives the real `ask.respond` handler, with no web app and no real database. `npm run ask:eval --workspace=api` is a thin wrapper over the same command. Exporting one of your own notes as an unlabelled skeleton (`--export-note`) reads the web app's tables and stays in `apps/api`.
10
+
11
+ Status: implemented by issue #580 (moved from `apps/api/scripts/kg-eval/`, `apps/api/scripts/ask-eval/` and `apps/api/test/fixtures/kg-golden/`), unreleased like the other packages. The web app's private OpenAI client (`direct-structured.ts`) is deleted; `npm run kg:eval --workspace=api` is a thin wrapper over `runKgCommand`.
12
+
13
+ Entry points:
14
+
15
+ - `@marinoscar/kvox-eval`: the package root, which re-exports the `kg`, `ask`, `live`, `real-data` and `cli` slices (not `ask-run`: import the subpath below). The root is still slow to import (about 2 s) because the kg runners import `@marinoscar/kvox-api/graph`. Slice READMEs: [kg](src/kg/README.md), [ask](src/ask/README.md), [live](src/live/README.md), [real-data](src/real-data/README.md), [cli](src/cli/README.md).
16
+ - `@marinoscar/kvox-eval/fixtures`: the golden set (`loadGoldenMeetings`, `GOLDEN_MEETINGS_DIR`, `ASK_QUESTIONS_PATH`, `loadGoldenAskQuestions`) and its schema (`goldenFixtureSchema`, `GoldenFixture`). Slice README: [fixtures](src/fixtures/README.md). The data is under [fixtures/kg-golden](fixtures/kg-golden/README.md).
17
+ - `@marinoscar/kvox-eval/ask-run`: the headless Ask evaluation (`runAskEval`, `runAskReplay`, `startAskEvalHost`, `seedGoldenGraph`, `runAskCommand`): an in-memory PGlite, the golden graph seeded through the graph write service, the real `ask.respond` handler. Slice README: [ask-run](src/ask-run/README.md).
18
+ - `@marinoscar/kvox-eval/parity`: the harness-parity scenario and its committed request snapshots (`parity/requests.*.json`), which prove two bindings of the model port send the same provider requests (`runParityScenario`, `createParityScript`, `loadParitySnapshot`). Slice README: [parity](src/parity/README.md).
19
+ - `kvox-eval` (the `bin`, `dist/cli/main.js`): `kvox-eval kg` and `kvox-eval ask`.
20
+
21
+ ## Install and peer dependencies
22
+
23
+ ```bash
24
+ npm install @marinoscar/kvox-eval@next @marinoscar/kvox-ai@next @marinoscar/kvox-api@next @marinoscar/kvox-contract@next @marinoscar/platform-api@0.1.0-next.5 zod \
25
+ @electric-sql/pglite@0.5.10 @electric-sql/pglite-pgvector@0.0.11 @electric-sql/pglite-socket@0.2.13 \
26
+ @prisma/client @prisma/adapter-pg pg @nestjs/common @nestjs/core reflect-metadata rxjs
27
+ ```
28
+
29
+ Every slice is universal, so every peer is required, including those only `ask-run` imports (PGlite, Prisma, Nest):
30
+
31
+ | Package | Range | Needed by |
32
+ |---|---|---|
33
+ | `zod` | `^4.4.3` | `fixtures`, `ask`, `kg` (the fixture, prediction, report and question schemas) |
34
+ | `@marinoscar/kvox-contract` | `>=0.0.0-0 \|\| >=0.1.0-0` | `fixtures`, `kg` (the ontology registry), `ask` (the Ask wire types) |
35
+ | `@marinoscar/kvox-api` | `>=0.0.0-0 \|\| >=0.1.0-0` | `kg` (the extraction pipeline, `graph-extraction` and `graph`), `live` and `cli` (`KvoxAiPort`, `KvoxAiError`) |
36
+ | `@marinoscar/kvox-ai` | `>=0.0.0-0 \|\| >=0.1.0-0` | `live` (`createKvoxAi`, `createDirectRuntime`) |
37
+ | `@marinoscar/platform-api` | `0.1.0-next.5` (exact, the repository's platform pin) | `live` and `cli`: the OpenAI and OpenAI-compatible adapters; `ask-run`: the provider event types of the replay. Only `@marinoscar/platform-api/ai`, as in `@marinoscar/kvox-ai`; an interim peer until the lean adapter package of #578. |
38
+ | `@electric-sql/pglite` | `0.5.10` (exact) | `ask-run`: the in-memory PostgreSQL |
39
+ | `@electric-sql/pglite-pgvector` | `0.0.11` (exact) | `ask-run`: the `vector` extension the migrations create |
40
+ | `@electric-sql/pglite-socket` | `0.2.13` (exact) | `ask-run`: the loopback socket the Prisma client connects to |
41
+ | `@prisma/client` | `^7.8.0` | `ask-run`: the client generated into `host/generated` |
42
+ | `@prisma/adapter-pg` | `^7.8.0` | `ask-run`: the driver adapter over `pg` |
43
+ | `pg` | `^8.23.0` | `ask-run` |
44
+ | `@nestjs/common`, `@nestjs/core` | `^11.1.12` | `ask-run`: the application context of the Ask slices |
45
+ | `reflect-metadata` | `^0.2.2` | `ask-run` |
46
+ | `rxjs` | `^7.8.1` | `ask-run` |
47
+
48
+ `@marinoscar/kvox-api`'s graph slices bring their own peers (Nest, among others). The per-slice data is `packages/kvox-slice-peers.json`; `node scripts/kvox-packages/check-slice-peers.mjs --table` prints it.
49
+
50
+ ## Quick start
51
+
52
+ The harness self-test, and a score of predictions written earlier:
53
+
54
+ ```bash
55
+ kvox-eval kg --predictions gold # 1.000 everywhere, or the scorer has a bug
56
+ kvox-eval kg --predictions ./runs/2026-10-17 --only m01,m07 --json report.json
57
+ ```
58
+
59
+ A live run over the model port (the key comes from the environment only):
60
+
61
+ ```bash
62
+ export KVOX_EVAL_OPENAI_API_KEY=... # never a flag; never printed
63
+ kvox-eval kg --runner extract --provider openai --model gpt-5-mini --out /tmp/kg-run
64
+ kvox-eval kg --runner extract-resolve --provider openai-compatible --base-url http://localhost:8000/v1 --model local-model
65
+ ```
66
+
67
+ The Ask evaluation, headless (in-memory database, golden graph seeded through the graph write service, the real `ask.respond` handler):
68
+
69
+ ```bash
70
+ kvox-eval ask --replay # the scripted self-test: 6 questions and the leak gate; no key, no network
71
+ export KVOX_EVAL_OPENAI_API_KEY=...
72
+ kvox-eval ask --model gpt-4o-mini --out /tmp/ask-run
73
+ kvox-eval ask --only q01,q22 --category negative --min-match 0.7
74
+ kvox-eval ask --provider openai-compatible --base-url http://localhost:8000/v1 --model local-model
75
+ ```
76
+
77
+ Flags: `--provider openai|openai-compatible`, `--model`, `--base-url`, `--only`, `--category`, `--out` (writes `report.json` and `answers.md`; `replay.json` for `--replay`), `--min-match <0..1>`, `--replay`. Exit codes: 0 pass, 1 a gate failed (citation validity below 1, a sensitive-fact leak) or the answer match fell below `--min-match` (or a replay expectation failed), 2 configuration (no key, a refused provider, a bad flag, an unknown question id) or every model call failed. One failed model call marks that turn failed and the run goes on. The seeded graph is 36 meetings, 94 entities, 265 relations, 187 items and 731 evidence rows, 0 refused. Semantic search is off (no embeddings). `--replay` reproduces `apps/api/test/ask/ask-eval.replay.db.spec.ts`.
78
+
79
+ From code:
80
+
81
+ ```ts
82
+ import { createLiveKvoxAi, formatReport, readEvalApiKey, runKgEval } from '@marinoscar/kvox-eval';
83
+ import { loadGoldenMeetings } from '@marinoscar/kvox-eval/fixtures';
84
+
85
+ const gold = await runKgEval({ runner: 'gold' });
86
+ console.log(formatReport(gold));
87
+
88
+ const key = readEvalApiKey(process.env);
89
+ const { ai } = createLiveKvoxAi({ provider: 'openai', model: 'gpt-5-mini', apiKey: key?.apiKey ?? null });
90
+ const live = await runKgEval({ runner: 'extract', ai, model: 'gpt-5-mini', fixtures: ['m01'] });
91
+
92
+ loadGoldenMeetings(['m01', 'm07']); // the fixtures themselves
93
+ ```
94
+
95
+ ```ts
96
+ import { runAskReplay } from '@marinoscar/kvox-eval/ask-run'; // never from the root
97
+
98
+ const replay = await runAskReplay(); // no key; starts and closes its own PGlite
99
+ ```
100
+
101
+ ## Configuration
102
+
103
+ Environment only, read by the `live` slice and the CLI:
104
+
105
+ - `KVOX_EVAL_OPENAI_API_KEY`: the key of a live run (`kg` and `ask`; read only from here, never a flag). `kvox-eval ask --replay` needs none. `KG_EVAL_OPENAI_API_KEY` and `ASK_EVAL_OPENAI_API_KEY` are read for one more release, with a one-line deprecation note on standard error.
106
+ - `KG_EVAL_REAL_DIR`: stands in for `--real-dir`.
107
+
108
+ No settings key and no file. The models a live run uses are the command line's (`--provider`, `--model`, `--base-url`).
109
+
110
+ ## Extension-point catalog
111
+
112
+ - `KgEvalRunner`: one fixture to one prediction. Register a factory in `KG_EVAL_RUNNERS` (a test's stub runner does) and it becomes `--runner <name>`.
113
+
114
+ ## Data
115
+
116
+ The golden set, shipped in the tarball under `fixtures/kg-golden/`: 36 synthetic meetings (`meetings/mNN-<slug>.json`) and the Ask question file (`ask-questions.json`). **Synthetic only, ever**: no real recording, transcript, note, name, company, email address or phone number belongs there (the rule, the format and the coverage the set must keep: [fixtures/kg-golden/README.md](fixtures/kg-golden/README.md); `test/kg-golden-fixtures.spec.ts` asserts them). A fixture's and a question's id is permanent once committed. The package owns no table. The `ask-run` slice ships `host/prisma` and `host/generated` (the schema, migrations and Prisma client), generated at build time by `scripts/prepare-host.mjs`; the in-memory database they run on is created per run and discarded.
117
+
118
+ ## Permissions and settings
119
+
120
+ None. A live run has no signed-in user: its calls carry the user id `kg-eval` and no organization.
121
+
122
+ ## UI
123
+
124
+ None.
125
+
126
+ ## Infra
127
+
128
+ None. A live run reaches the provider's API from the machine it runs on. The Ask run needs no database server: it never touches a real database.
129
+
130
+ ## Observability
131
+
132
+ None: no log, metric or span (the Ask run's Nest context logs errors and warnings only). The CLI prints the report table to standard output and its notes (missing predictions, where predictions were written, deprecations) to standard error.
133
+
134
+ ## Security notes
135
+
136
+ - The API key is read from the environment only, never a flag. It is handed to `createDirectRuntime`'s `keys()` per call and never logged, printed, put in an error or written into a prediction or a report (a prediction carries `model` alone). A failed live call is reported by its `KvoxAiError` class, the platform error code and the HTTP status, never a message body.
137
+ - Real meetings never enter the repository: `--real-dir`, `--out` of a real-data run and `KG_EVAL_REAL_DIR` are refused (exit 3) when they resolve inside the git work tree, symlinks included (`assertOutsideRepo`).
138
+ - The golden set is synthetic; its spec rejects a real-looking email address, a phone number and the product's name.
139
+ - The Ask run uses an in-memory PGlite only: it has no connection string to a real database, so it cannot read or write one. The key never enters that database.
140
+ - Apart from the `ask-run` slice (Prisma, Nest, PGlite), the source imports no Prisma, no `@app/shared`, and of the platform only `@marinoscar/platform-api/ai` (rule `host-neutral` of `npm run check:kvox-slice-peers`).
141
+
142
+ ## Conformance suite
143
+
144
+ None of its own to export. The package's specs (`npm run test:run -w @marinoscar/kvox-eval`) pin the scorer case by case, the golden set's validity and coverage, the extraction runners over a fake port (`createFakeKvoxAi` of `@marinoscar/kvox-api/testing`), the Ask question file and scorer, `runKgEval`, the key aliases, the copilot refusal and the real-data guard. `test/parity.spec.ts` runs the harness-parity scenario over the direct binding and compares it with the shipped `parity/` snapshots (`npm run test:run -w @marinoscar/kvox-eval -- parity`; `KVOX_EVAL_UPDATE_PARITY=1` rewrites them, see [parity](src/parity/README.md)); the web edition's twin is `apps/api/test/kvox/ai-parity.spec.ts`. The gold self-test (`kvox-eval kg --predictions gold`) must print 1.000 everywhere.
145
+
146
+ ## Upgrade notes
147
+
148
+ First release. For code that imported these files from the web app: `apps/api/scripts/kg-eval/{fixture-schema,load,domains}.ts` are `@marinoscar/kvox-eval/fixtures`; `prediction-schema`, `scorer`, `report`, `targets`, `text`, `gold-runner`, `runner` and `runners/*` are the root; `apps/api/scripts/ask-eval/{question-schema,scorer,report}.ts` are the root. `loadPredictionFile` moved from `load.ts` to the root. A runner factory now takes `{ ai }` and a runner no longer receives `apiKey`. `KG_EVAL_API_KEY_ENV` is `KVOX_EVAL_API_KEY_ENV` (`KVOX_EVAL_OPENAI_API_KEY`). `assertOutsideRepo`'s option is `allowInside: <dir>` (the web app keeps its `allowSyntheticRunsDir` wrapper).
149
+
150
+ ## Troubleshooting
151
+
152
+ | Symptom | Fix |
153
+ |---|---|
154
+ | `Cannot find module '@marinoscar/kvox-eval'` in `apps/api` | Build the packages first: `npm run build:kvox-packages`. |
155
+ | `reads its API key from KVOX_EVAL_OPENAI_API_KEY; it is not set` | Export the key in the shell that runs the command; it is never a flag. |
156
+ | `Cannot find module '@electric-sql/pglite'` (or `@prisma/client`, `@nestjs/core`) on `kvox-eval ask` | Install the `ask-run` peers listed above; they are required. |
157
+ | `the 'copilot' provider is not available yet` | The Copilot adapter arrives with #581; use `--provider openai` or `openai-compatible`. |
158
+ | `real meeting data must never be written inside the repository` (exit 3) | Point `--real-dir` / `--out` outside the git work tree. |
159
+
160
+ ## Links
161
+
162
+ - The package standard: [docs/PACKAGES.md](../../docs/PACKAGES.md)
163
+ - The quality bar the report checks: [docs/specs/ontology.md](../../docs/specs/ontology.md) (§6)
164
+ - Consuming the packages from KVox Desktop (which runs the same evaluation over its own binding of the model port): [docs/guides/kvox-desktop.md](../../docs/guides/kvox-desktop.md)
165
+ - The model port: [packages/kvox-api/src/ports/README.md](../kvox-api/src/ports/README.md); its implementation: [packages/kvox-ai/README.md](../kvox-ai/README.md)
166
+ - Dev commands: `npm run build -w @marinoscar/kvox-eval`, `npm run typecheck -w @marinoscar/kvox-eval`, `npm run test:run -w @marinoscar/kvox-eval`, `npm run docs -w @marinoscar/kvox-eval`
@@ -0,0 +1,6 @@
1
+ export { ASK_EVAL_CATEGORIES, AskEvalQuestionLoadError, askEvalExpectedSchema, askEvalQuestionFileSchema, askEvalQuestionSchema, loadAskEvalQuestions, parseAskEvalQuestions, selectAskEvalQuestions, } from './question-schema.js';
2
+ export type { AskEvalCategory, AskEvalExpected, AskEvalQuestion } from './question-schema.js';
3
+ export { askEvalReportSchema, buildAskEvalReport, formatAnswersMarkdown, formatAskEvalReport } from './report.js';
4
+ export type { AskEvalReport } from './report.js';
5
+ export { scoreAnswer, scoreAnswerMatch, scoreCitationValidity, scoreCited, scoreCiteFromHit, scoreEntityRecall, scoreSensitiveLeak, } from './scorer.js';
6
+ export type { AskEvalScore, EvidenceSourceMap } from './scorer.js';
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.scoreSensitiveLeak = exports.scoreEntityRecall = exports.scoreCiteFromHit = exports.scoreCited = exports.scoreCitationValidity = exports.scoreAnswerMatch = exports.scoreAnswer = exports.formatAskEvalReport = exports.formatAnswersMarkdown = exports.buildAskEvalReport = exports.askEvalReportSchema = exports.selectAskEvalQuestions = exports.parseAskEvalQuestions = exports.loadAskEvalQuestions = exports.askEvalQuestionSchema = exports.askEvalQuestionFileSchema = exports.askEvalExpectedSchema = exports.AskEvalQuestionLoadError = exports.ASK_EVAL_CATEGORIES = void 0;
4
+ // The ask slice's public surface (re-exported by the package root): the PURE
5
+ // half of the Ask evaluation. The run itself (seeding a graph, the real
6
+ // `ask.respond` handler) needs the web app's database and stays there.
7
+ var question_schema_js_1 = require("./question-schema.js");
8
+ Object.defineProperty(exports, "ASK_EVAL_CATEGORIES", { enumerable: true, get: function () { return question_schema_js_1.ASK_EVAL_CATEGORIES; } });
9
+ Object.defineProperty(exports, "AskEvalQuestionLoadError", { enumerable: true, get: function () { return question_schema_js_1.AskEvalQuestionLoadError; } });
10
+ Object.defineProperty(exports, "askEvalExpectedSchema", { enumerable: true, get: function () { return question_schema_js_1.askEvalExpectedSchema; } });
11
+ Object.defineProperty(exports, "askEvalQuestionFileSchema", { enumerable: true, get: function () { return question_schema_js_1.askEvalQuestionFileSchema; } });
12
+ Object.defineProperty(exports, "askEvalQuestionSchema", { enumerable: true, get: function () { return question_schema_js_1.askEvalQuestionSchema; } });
13
+ Object.defineProperty(exports, "loadAskEvalQuestions", { enumerable: true, get: function () { return question_schema_js_1.loadAskEvalQuestions; } });
14
+ Object.defineProperty(exports, "parseAskEvalQuestions", { enumerable: true, get: function () { return question_schema_js_1.parseAskEvalQuestions; } });
15
+ Object.defineProperty(exports, "selectAskEvalQuestions", { enumerable: true, get: function () { return question_schema_js_1.selectAskEvalQuestions; } });
16
+ var report_js_1 = require("./report.js");
17
+ Object.defineProperty(exports, "askEvalReportSchema", { enumerable: true, get: function () { return report_js_1.askEvalReportSchema; } });
18
+ Object.defineProperty(exports, "buildAskEvalReport", { enumerable: true, get: function () { return report_js_1.buildAskEvalReport; } });
19
+ Object.defineProperty(exports, "formatAnswersMarkdown", { enumerable: true, get: function () { return report_js_1.formatAnswersMarkdown; } });
20
+ Object.defineProperty(exports, "formatAskEvalReport", { enumerable: true, get: function () { return report_js_1.formatAskEvalReport; } });
21
+ var scorer_js_1 = require("./scorer.js");
22
+ Object.defineProperty(exports, "scoreAnswer", { enumerable: true, get: function () { return scorer_js_1.scoreAnswer; } });
23
+ Object.defineProperty(exports, "scoreAnswerMatch", { enumerable: true, get: function () { return scorer_js_1.scoreAnswerMatch; } });
24
+ Object.defineProperty(exports, "scoreCitationValidity", { enumerable: true, get: function () { return scorer_js_1.scoreCitationValidity; } });
25
+ Object.defineProperty(exports, "scoreCited", { enumerable: true, get: function () { return scorer_js_1.scoreCited; } });
26
+ Object.defineProperty(exports, "scoreCiteFromHit", { enumerable: true, get: function () { return scorer_js_1.scoreCiteFromHit; } });
27
+ Object.defineProperty(exports, "scoreEntityRecall", { enumerable: true, get: function () { return scorer_js_1.scoreEntityRecall; } });
28
+ Object.defineProperty(exports, "scoreSensitiveLeak", { enumerable: true, get: function () { return scorer_js_1.scoreSensitiveLeak; } });
@@ -0,0 +1,121 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Every category a question may be filed under. Coverage counts live in the README.
4
+ *
5
+ * @stability experimental
6
+ */
7
+ export declare const ASK_EVAL_CATEGORIES: readonly ["lookup", "relationship", "temporal", "as_of", "commitments", "decisions", "multi_hop", "negative"];
8
+ /**
9
+ * One of {@link ASK_EVAL_CATEGORIES}.
10
+ *
11
+ * @stability experimental
12
+ */
13
+ export type AskEvalCategory = (typeof ASK_EVAL_CATEGORIES)[number];
14
+ /**
15
+ * What a correct answer to one question must (and must not) contain: cited entities,
16
+ * `answerAll` groups, `answerNone` strings (a sensitive fact's own text belongs here),
17
+ * `citeFrom` sources and the `notFound` rule.
18
+ *
19
+ * @stability experimental
20
+ */
21
+ export declare const askEvalExpectedSchema: z.ZodObject<{
22
+ entities: z.ZodDefault<z.ZodArray<z.ZodString>>;
23
+ answerAll: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodString>>>;
24
+ answerNone: z.ZodDefault<z.ZodArray<z.ZodString>>;
25
+ citeFrom: z.ZodDefault<z.ZodArray<z.ZodString>>;
26
+ notFound: z.ZodDefault<z.ZodBoolean>;
27
+ }, z.core.$strip>;
28
+ /**
29
+ * A parsed {@link askEvalExpectedSchema}.
30
+ *
31
+ * @stability experimental
32
+ */
33
+ export type AskEvalExpected = z.infer<typeof askEvalExpectedSchema>;
34
+ /**
35
+ * One golden Ask question: a permanent `qNN` id, a category, the fixtures it is grounded
36
+ * in (provenance only), an optional scope entity, the question and its expectations.
37
+ *
38
+ * @stability experimental
39
+ */
40
+ export declare const askEvalQuestionSchema: z.ZodObject<{
41
+ id: z.ZodString;
42
+ category: z.ZodEnum<{
43
+ lookup: "lookup";
44
+ relationship: "relationship";
45
+ temporal: "temporal";
46
+ as_of: "as_of";
47
+ commitments: "commitments";
48
+ decisions: "decisions";
49
+ multi_hop: "multi_hop";
50
+ negative: "negative";
51
+ }>;
52
+ fixtures: z.ZodArray<z.ZodString>;
53
+ scopeEntity: z.ZodDefault<z.ZodNullable<z.ZodString>>;
54
+ question: z.ZodString;
55
+ expected: z.ZodObject<{
56
+ entities: z.ZodDefault<z.ZodArray<z.ZodString>>;
57
+ answerAll: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodString>>>;
58
+ answerNone: z.ZodDefault<z.ZodArray<z.ZodString>>;
59
+ citeFrom: z.ZodDefault<z.ZodArray<z.ZodString>>;
60
+ notFound: z.ZodDefault<z.ZodBoolean>;
61
+ }, z.core.$strip>;
62
+ }, z.core.$strip>;
63
+ /**
64
+ * A parsed {@link askEvalQuestionSchema}.
65
+ *
66
+ * @stability experimental
67
+ */
68
+ export type AskEvalQuestion = z.infer<typeof askEvalQuestionSchema>;
69
+ /**
70
+ * The whole committed file: a bare array of questions.
71
+ *
72
+ * @stability experimental
73
+ */
74
+ export declare const askEvalQuestionFileSchema: z.ZodArray<z.ZodObject<{
75
+ id: z.ZodString;
76
+ category: z.ZodEnum<{
77
+ lookup: "lookup";
78
+ relationship: "relationship";
79
+ temporal: "temporal";
80
+ as_of: "as_of";
81
+ commitments: "commitments";
82
+ decisions: "decisions";
83
+ multi_hop: "multi_hop";
84
+ negative: "negative";
85
+ }>;
86
+ fixtures: z.ZodArray<z.ZodString>;
87
+ scopeEntity: z.ZodDefault<z.ZodNullable<z.ZodString>>;
88
+ question: z.ZodString;
89
+ expected: z.ZodObject<{
90
+ entities: z.ZodDefault<z.ZodArray<z.ZodString>>;
91
+ answerAll: z.ZodDefault<z.ZodArray<z.ZodArray<z.ZodString>>>;
92
+ answerNone: z.ZodDefault<z.ZodArray<z.ZodString>>;
93
+ citeFrom: z.ZodDefault<z.ZodArray<z.ZodString>>;
94
+ notFound: z.ZodDefault<z.ZodBoolean>;
95
+ }, z.core.$strip>;
96
+ }, z.core.$strip>>;
97
+ /**
98
+ * The question file cannot be used: not JSON, a schema violation, a duplicate or unknown id.
99
+ *
100
+ * @stability experimental
101
+ */
102
+ export declare class AskEvalQuestionLoadError extends Error {
103
+ }
104
+ /**
105
+ * Parses and validates the question file's raw JSON. Throws on any schema violation.
106
+ *
107
+ * @stability experimental
108
+ */
109
+ export declare function parseAskEvalQuestions(raw: unknown): AskEvalQuestion[];
110
+ /**
111
+ * Loads and validates the committed question file from disk.
112
+ *
113
+ * @stability experimental
114
+ */
115
+ export declare function loadAskEvalQuestions(path: string): AskEvalQuestion[];
116
+ /**
117
+ * `--only q01,q07` / `--category lookup`: both narrow; neither given keeps all.
118
+ *
119
+ * @stability experimental
120
+ */
121
+ export declare function selectAskEvalQuestions(questions: AskEvalQuestion[], only: string[] | null, category: string | null): AskEvalQuestion[];
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ // =============================================================================
3
+ // The Ask golden question set: schema (issue #382, epic #348)
4
+ // =============================================================================
5
+ //
6
+ // One JSON file, `packages/kvox-eval/fixtures/kg-golden/ask-questions.json`, holding
7
+ // every question the Ask-agent evaluation asks against the seeded gold graph
8
+ // (built from #362's synthetic meetings — never real data, per CLAUDE.md's
9
+ // "synthetic fixtures only, ever"). A question's `id` is PERMANENT once
10
+ // committed, exactly like a fixture's `id` (#362's own rule): retire a
11
+ // question by noting it in the README, never by renumbering, so a report is
12
+ // comparable across runs.
13
+ //
14
+ // `fixtures` documents PROVENANCE ONLY — the seeder always loads every fixture
15
+ // under #362's `packages/kvox-eval/fixtures/kg-golden/meetings/`, so a question is
16
+ // never scoped to a subset at seed time. It exists so a reviewer (or a script
17
+ // checking referential integrity, `ask-eval-questions.spec.ts`) can see at a
18
+ // glance which meeting(s) a question's expectations are grounded in.
19
+ // =============================================================================
20
+ Object.defineProperty(exports, "__esModule", { value: true });
21
+ exports.AskEvalQuestionLoadError = exports.askEvalQuestionFileSchema = exports.askEvalQuestionSchema = exports.askEvalExpectedSchema = exports.ASK_EVAL_CATEGORIES = void 0;
22
+ exports.parseAskEvalQuestions = parseAskEvalQuestions;
23
+ exports.loadAskEvalQuestions = loadAskEvalQuestions;
24
+ exports.selectAskEvalQuestions = selectAskEvalQuestions;
25
+ const zod_1 = require("zod");
26
+ /**
27
+ * Every category a question may be filed under. Coverage counts live in the README.
28
+ *
29
+ * @stability experimental
30
+ */
31
+ exports.ASK_EVAL_CATEGORIES = [
32
+ 'lookup',
33
+ 'relationship',
34
+ 'temporal',
35
+ 'as_of',
36
+ 'commitments',
37
+ 'decisions',
38
+ 'multi_hop',
39
+ 'negative',
40
+ ];
41
+ /**
42
+ * What a correct answer to one question must (and must not) contain: cited entities,
43
+ * `answerAll` groups, `answerNone` strings (a sensitive fact's own text belongs here),
44
+ * `citeFrom` sources and the `notFound` rule.
45
+ *
46
+ * @stability experimental
47
+ */
48
+ exports.askEvalExpectedSchema = zod_1.z.object({
49
+ /**
50
+ * Entity labels that must be cited: either directly (`[^entN]`) or as the
51
+ * subject of a cited evidence row (`[^evN]`/a resolved `[^itmN]`/`[^relN]`)
52
+ * whose citation carries this label. Case-insensitive exact match against
53
+ * the citation's own `label` field — never a substring, since a label is a
54
+ * whole name.
55
+ */
56
+ entities: zod_1.z.array(zod_1.z.string()).default([]),
57
+ /**
58
+ * Every inner group must be satisfied by a case-insensitive SUBSTRING match
59
+ * somewhere in the answer text; within a group, any one alternative is
60
+ * enough (synonyms/phrasing variants). `[["Sarah Chen","Sarah"]]` is one
61
+ * group with two acceptable spellings.
62
+ */
63
+ answerAll: zod_1.z.array(zod_1.z.array(zod_1.z.string()).min(1)).default([]),
64
+ /** None of these substrings may appear anywhere in the answer, case-insensitively. */
65
+ answerNone: zod_1.z.array(zod_1.z.string()).default([]),
66
+ /**
67
+ * Segment ids (`"m04-s012"`, matching a fixture's own segment id verbatim)
68
+ * or `"mNN#note"` for a meeting's note. At least one VALID citation in the
69
+ * answer must resolve (through the seeder's `idMap`) to one of these.
70
+ * Empty for a `notFound` question, which cites nothing.
71
+ */
72
+ citeFrom: zod_1.z.array(zod_1.z.string()).default([]),
73
+ /**
74
+ * A negative question: the agent must say it could not find an answer and
75
+ * must cite nothing valid. `answerNone` still applies — this is where a
76
+ * sensitive fact's own text belongs, so a leak is caught even though the
77
+ * question is "answered" with a refusal.
78
+ */
79
+ notFound: zod_1.z.boolean().default(false),
80
+ });
81
+ /**
82
+ * One golden Ask question: a permanent `qNN` id, a category, the fixtures it is grounded
83
+ * in (provenance only), an optional scope entity, the question and its expectations.
84
+ *
85
+ * @stability experimental
86
+ */
87
+ exports.askEvalQuestionSchema = zod_1.z.object({
88
+ id: zod_1.z.string().regex(/^q\d{2,3}$/, 'must look like "q01" or "q123"'),
89
+ category: zod_1.z.enum(exports.ASK_EVAL_CATEGORIES),
90
+ /** Provenance only — see the file header. At least one fixture id (`"m01"`), even for a negative question about an absent entity. */
91
+ fixtures: zod_1.z.array(zod_1.z.string().regex(/^m\d{2,3}$/)).min(1),
92
+ /** The label of the entity to scope the conversation to, or `null` for an unscoped conversation. */
93
+ scopeEntity: zod_1.z.string().nullable().default(null),
94
+ question: zod_1.z.string().min(5),
95
+ expected: exports.askEvalExpectedSchema,
96
+ });
97
+ /**
98
+ * The whole committed file: a bare array of questions.
99
+ *
100
+ * @stability experimental
101
+ */
102
+ exports.askEvalQuestionFileSchema = zod_1.z.array(exports.askEvalQuestionSchema);
103
+ /**
104
+ * The question file cannot be used: not JSON, a schema violation, a duplicate or unknown id.
105
+ *
106
+ * @stability experimental
107
+ */
108
+ class AskEvalQuestionLoadError extends Error {
109
+ }
110
+ exports.AskEvalQuestionLoadError = AskEvalQuestionLoadError;
111
+ /**
112
+ * Parses and validates the question file's raw JSON. Throws on any schema violation.
113
+ *
114
+ * @stability experimental
115
+ */
116
+ function parseAskEvalQuestions(raw) {
117
+ const parsed = exports.askEvalQuestionFileSchema.safeParse(raw);
118
+ if (!parsed.success) {
119
+ const issues = parsed.error.issues
120
+ .slice(0, 8)
121
+ .map((i) => `${i.path.join('.')}: ${i.message}`)
122
+ .join('; ');
123
+ throw new AskEvalQuestionLoadError(`ask-questions.json does not match askEvalQuestionSchema — ${issues}`);
124
+ }
125
+ const seen = new Set();
126
+ for (const q of parsed.data) {
127
+ if (seen.has(q.id))
128
+ throw new AskEvalQuestionLoadError(`duplicate question id ${q.id}`);
129
+ seen.add(q.id);
130
+ }
131
+ return parsed.data;
132
+ }
133
+ /**
134
+ * Loads and validates the committed question file from disk.
135
+ *
136
+ * @stability experimental
137
+ */
138
+ function loadAskEvalQuestions(path) {
139
+ // Lazy so callers that already have the parsed JSON (e.g. a spec that reads
140
+ // it once) never need Node's `fs` in their own import graph.
141
+ const { readFileSync } = require('fs');
142
+ let raw;
143
+ try {
144
+ raw = JSON.parse(readFileSync(path, 'utf8'));
145
+ }
146
+ catch (err) {
147
+ throw new AskEvalQuestionLoadError(`${path}: not valid JSON (${err.message})`);
148
+ }
149
+ return parseAskEvalQuestions(raw);
150
+ }
151
+ /**
152
+ * `--only q01,q07` / `--category lookup`: both narrow; neither given keeps all.
153
+ *
154
+ * @stability experimental
155
+ */
156
+ function selectAskEvalQuestions(questions, only, category) {
157
+ let out = questions;
158
+ if (only && only.length > 0) {
159
+ const unknown = only.filter((id) => !questions.some((q) => q.id === id));
160
+ if (unknown.length > 0)
161
+ throw new AskEvalQuestionLoadError(`unknown question id(s): ${unknown.join(', ')}`);
162
+ out = out.filter((q) => only.includes(q.id));
163
+ }
164
+ if (category)
165
+ out = out.filter((q) => q.category === category);
166
+ return out;
167
+ }
@@ -0,0 +1,88 @@
1
+ import { z } from 'zod';
2
+ import type { AskEvalScore } from './scorer.js';
3
+ /**
4
+ * The Ask evaluation report: the per-category rollup, the overall numbers, the two gates
5
+ * (citation validity 1.000, zero sensitive leaks), the answer-match threshold and every
6
+ * question's score. The JSON written as `report.json`.
7
+ *
8
+ * @stability experimental
9
+ */
10
+ export declare const askEvalReportSchema: z.ZodObject<{
11
+ runAt: z.ZodString;
12
+ model: z.ZodNullable<z.ZodString>;
13
+ questions: z.ZodNumber;
14
+ categories: z.ZodArray<z.ZodObject<{
15
+ category: z.ZodString;
16
+ count: z.ZodNumber;
17
+ answerMatch: z.ZodNumber;
18
+ cited: z.ZodNumber;
19
+ entityRecall: z.ZodNumber;
20
+ meanSteps: z.ZodNumber;
21
+ meanToolCalls: z.ZodNumber;
22
+ meanPromptTokens: z.ZodNumber;
23
+ meanCompletionTokens: z.ZodNumber;
24
+ meanMs: z.ZodNullable<z.ZodNumber>;
25
+ }, z.core.$strip>>;
26
+ overall: z.ZodObject<{
27
+ answerMatch: z.ZodNumber;
28
+ citationValidity: z.ZodNumber;
29
+ sensitiveLeaks: z.ZodNumber;
30
+ }, z.core.$strip>;
31
+ gatesPassed: z.ZodBoolean;
32
+ answerMatchPassed: z.ZodBoolean;
33
+ minMatch: z.ZodNumber;
34
+ perQuestion: z.ZodArray<z.ZodObject<{
35
+ id: z.ZodString;
36
+ category: z.ZodString;
37
+ citationValidity: z.ZodNumber;
38
+ cited: z.ZodBoolean;
39
+ citeFromHit: z.ZodBoolean;
40
+ answerMatch: z.ZodBoolean;
41
+ entityRecall: z.ZodNumber;
42
+ sensitiveLeak: z.ZodBoolean;
43
+ steps: z.ZodNumber;
44
+ toolCalls: z.ZodNumber;
45
+ promptTokens: z.ZodNumber;
46
+ completionTokens: z.ZodNumber;
47
+ ms: z.ZodNullable<z.ZodNumber>;
48
+ notes: z.ZodArray<z.ZodString>;
49
+ }, z.core.$strip>>;
50
+ }, z.core.$strip>;
51
+ /**
52
+ * A parsed {@link askEvalReportSchema}.
53
+ *
54
+ * @stability experimental
55
+ */
56
+ export type AskEvalReport = z.infer<typeof askEvalReportSchema>;
57
+ /**
58
+ * Rolls the per-question scores of one run up into a report.
59
+ *
60
+ * @param scores - one score per answered question.
61
+ * @param meta - the model, the answer-match threshold and (for a test) the run time.
62
+ * @returns the report.
63
+ *
64
+ * @stability experimental
65
+ */
66
+ export declare function buildAskEvalReport(scores: AskEvalScore[], meta: {
67
+ model: string | null;
68
+ minMatch: number;
69
+ runAt?: Date;
70
+ }): AskEvalReport;
71
+ /**
72
+ * The report as the fixed-column table `ask:eval` prints, then the gates and the failing questions.
73
+ *
74
+ * @param r - the report.
75
+ * @returns the table.
76
+ *
77
+ * @stability experimental
78
+ */
79
+ export declare function formatAskEvalReport(r: AskEvalReport): string;
80
+ /**
81
+ * `answers.md`: every question, its answer text and its per-question numbers — for reading, not for scripts.
82
+ *
83
+ * @stability experimental
84
+ */
85
+ export declare function formatAnswersMarkdown(report: AskEvalReport, answers: ReadonlyMap<string, {
86
+ question: string;
87
+ content: string;
88
+ }>): string;