onto-mcp 0.3.2 → 0.4.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 (300) hide show
  1. package/.onto/processes/reconstruct/actionable-ontology-seed-recomposition-design.md +447 -0
  2. package/.onto/processes/reconstruct/foundry-style-ontology-seed-contract.md +934 -0
  3. package/.onto/processes/reconstruct/reconstruct-boundary-contract.md +303 -725
  4. package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +1645 -0
  5. package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +26 -22
  6. package/.onto/processes/reconstruct/source-profile-contract.md +49 -23
  7. package/.onto/processes/reconstruct/source-profiles/code.md +6 -3
  8. package/.onto/processes/reconstruct/source-profiles/database.md +5 -2
  9. package/.onto/processes/reconstruct/source-profiles/document.md +5 -2
  10. package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +5 -4
  11. package/.onto/processes/review/review-execution-ux-contract.md +40 -0
  12. package/.onto/processes/shared/pipeline-execution-ledger-contract.md +26 -10
  13. package/.onto/processes/shared/target-material-kind-contract.md +29 -16
  14. package/AGENTS.md +6 -4
  15. package/README.md +135 -76
  16. package/dist/cli.js +8 -8
  17. package/dist/core-api/reconstruct-api.js +117 -31
  18. package/dist/core-api/review-api.js +47 -0
  19. package/dist/core-runtime/cli/codex-review-unit-executor.js +39 -2
  20. package/dist/core-runtime/cli/complete-review-session.js +2 -2
  21. package/dist/core-runtime/cli/mock-review-unit-executor.js +1 -1
  22. package/dist/core-runtime/cli/review-invoke.js +9 -9
  23. package/dist/core-runtime/cli/run-review-prompt-execution.js +39 -5
  24. package/dist/core-runtime/cli/spawn-watcher.js +266 -47
  25. package/dist/core-runtime/cli/start-review-session.js +3 -3
  26. package/dist/core-runtime/llm/llm-caller.js +11 -0
  27. package/dist/core-runtime/llm/llm-tool-loop.js +2 -0
  28. package/dist/core-runtime/observability/runtime-stream-observation.js +118 -0
  29. package/dist/core-runtime/onboard/cli-host.js +149 -0
  30. package/dist/core-runtime/onboard/host-target.js +22 -0
  31. package/dist/core-runtime/onboard/json-config-host.js +122 -0
  32. package/dist/core-runtime/onboard/path-scan.js +26 -0
  33. package/dist/core-runtime/onboard/prompt.js +51 -0
  34. package/dist/core-runtime/onboard/register.js +207 -0
  35. package/dist/core-runtime/onboard/types.js +27 -0
  36. package/dist/core-runtime/reconstruct/actionable-seed-validation.js +1777 -0
  37. package/dist/core-runtime/reconstruct/artifact-types.js +10 -4
  38. package/dist/core-runtime/reconstruct/contract-registry.js +623 -0
  39. package/dist/core-runtime/reconstruct/domain-id.js +10 -0
  40. package/dist/core-runtime/reconstruct/governing-snapshot.js +716 -0
  41. package/dist/core-runtime/reconstruct/material-profile-validation.js +191 -0
  42. package/dist/core-runtime/reconstruct/materialize-preparation.js +49 -11
  43. package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +269 -79
  44. package/dist/core-runtime/reconstruct/post-seed-validation.js +1194 -51
  45. package/dist/core-runtime/reconstruct/record.js +104 -20
  46. package/dist/core-runtime/reconstruct/run.js +2107 -413
  47. package/dist/core-runtime/reconstruct/seed-claim-projections.js +268 -0
  48. package/dist/core-runtime/reconstruct/source-profiles.js +93 -4
  49. package/dist/core-runtime/reconstruct/terminal-validation.js +807 -0
  50. package/dist/core-runtime/review/review-invocation-runner.js +4 -4
  51. package/dist/mcp/server.js +110 -38
  52. package/dist/mcp/tool-schemas.js +20 -6
  53. package/package.json +8 -17
  54. package/scripts/onto-review-watch.sh +486 -0
  55. package/scripts/onto-runtime-watch.sh +122 -0
  56. package/scripts/postinstall-hint.js +22 -0
  57. package/.onto/processes/reconstruct/top-level-concept-discovery-contract.md +0 -387
  58. package/dist/core-runtime/cli/bootstrap-review-binding.js +0 -186
  59. package/dist/core-runtime/cli/codex-nested-dispatch.test.js +0 -390
  60. package/dist/core-runtime/cli/codex-nested-teamlead-executor.test.js +0 -335
  61. package/dist/core-runtime/cli/coordinator-helpers.js +0 -583
  62. package/dist/core-runtime/cli/coordinator-state-machine-deliberation.test.js +0 -167
  63. package/dist/core-runtime/cli/coordinator-state-machine.js +0 -794
  64. package/dist/core-runtime/cli/e2e-codex-multi-agent-fixes.test.js +0 -615
  65. package/dist/core-runtime/cli/e2e-start-review-session.test.js +0 -312
  66. package/dist/core-runtime/cli/health.js +0 -44
  67. package/dist/core-runtime/cli/inline-http-review-unit-executor.test.js +0 -567
  68. package/dist/core-runtime/cli/materialize-review-execution-preparation.js +0 -104
  69. package/dist/core-runtime/cli/migrate-session-roots.js +0 -118
  70. package/dist/core-runtime/cli/repo-layout-migration-replace.smoke.test.js +0 -106
  71. package/dist/core-runtime/cli/review-invoke-auto-resolution.test.js +0 -268
  72. package/dist/core-runtime/cli/review-invoke-coordinator-topology.test.js +0 -136
  73. package/dist/core-runtime/cli/review-invoke-resolver-caching.test.js +0 -201
  74. package/dist/core-runtime/cli/review-invoke-topology-dispatch.test.js +0 -192
  75. package/dist/core-runtime/cli/session-root-guard.js +0 -168
  76. package/dist/core-runtime/cli/spawn-watcher.test.js +0 -457
  77. package/dist/core-runtime/cli/strip-wrapping-code-fence.test.js +0 -79
  78. package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.js +0 -412
  79. package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.test.js +0 -351
  80. package/dist/core-runtime/cli/topology-executor-mapping.js +0 -139
  81. package/dist/core-runtime/cli/topology-executor-mapping.test.js +0 -173
  82. package/dist/core-runtime/cli/write-review-interpretation.js +0 -81
  83. package/dist/core-runtime/config/onto-config-cli.js +0 -278
  84. package/dist/core-runtime/config/onto-config-key-path.js +0 -288
  85. package/dist/core-runtime/config/onto-config-key-path.test.js +0 -195
  86. package/dist/core-runtime/config/onto-config-preview.js +0 -108
  87. package/dist/core-runtime/config/onto-config-preview.test.js +0 -132
  88. package/dist/core-runtime/discovery/config-chain.js +0 -118
  89. package/dist/core-runtime/discovery/config-chain.test.js +0 -103
  90. package/dist/core-runtime/discovery/config-profile.js +0 -199
  91. package/dist/core-runtime/discovery/config-profile.test.js +0 -233
  92. package/dist/core-runtime/discovery/host-detection.test.js +0 -186
  93. package/dist/core-runtime/discovery/installation-paths.test.js +0 -65
  94. package/dist/core-runtime/discovery/lens-registry.test.js +0 -81
  95. package/dist/core-runtime/discovery/path-normalization.test.js +0 -22
  96. package/dist/core-runtime/discovery/plugin-path.js +0 -72
  97. package/dist/core-runtime/discovery/plugin-path.test.js +0 -95
  98. package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.js +0 -344
  99. package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.test.js +0 -915
  100. package/dist/core-runtime/evolve/adapters/code-product/compile/compile.js +0 -564
  101. package/dist/core-runtime/evolve/adapters/code-product/compile/compile.test.js +0 -708
  102. package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.js +0 -165
  103. package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.test.js +0 -227
  104. package/dist/core-runtime/evolve/adapters/code-product/validators/validate.js +0 -59
  105. package/dist/core-runtime/evolve/adapters/code-product/validators/validate.test.js +0 -205
  106. package/dist/core-runtime/evolve/adapters/methodology/adapter.js +0 -16
  107. package/dist/core-runtime/evolve/adapters/methodology/adapter.test.js +0 -9
  108. package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.js +0 -298
  109. package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.test.js +0 -70
  110. package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.js +0 -46
  111. package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.test.js +0 -73
  112. package/dist/core-runtime/evolve/adapters/registry.js +0 -47
  113. package/dist/core-runtime/evolve/adapters/registry.test.js +0 -67
  114. package/dist/core-runtime/evolve/cli.js +0 -256
  115. package/dist/core-runtime/evolve/commands/align.js +0 -194
  116. package/dist/core-runtime/evolve/commands/align.test.js +0 -82
  117. package/dist/core-runtime/evolve/commands/apply.js +0 -161
  118. package/dist/core-runtime/evolve/commands/apply.test.js +0 -138
  119. package/dist/core-runtime/evolve/commands/close.js +0 -39
  120. package/dist/core-runtime/evolve/commands/close.test.js +0 -99
  121. package/dist/core-runtime/evolve/commands/defer.js +0 -40
  122. package/dist/core-runtime/evolve/commands/defer.test.js +0 -134
  123. package/dist/core-runtime/evolve/commands/draft.js +0 -323
  124. package/dist/core-runtime/evolve/commands/draft.test.js +0 -178
  125. package/dist/core-runtime/evolve/commands/e2e-evolve-full-cycle.test.js +0 -208
  126. package/dist/core-runtime/evolve/commands/error-messages.js +0 -125
  127. package/dist/core-runtime/evolve/commands/error-messages.test.js +0 -167
  128. package/dist/core-runtime/evolve/commands/propose-align.js +0 -222
  129. package/dist/core-runtime/evolve/commands/propose-align.test.js +0 -136
  130. package/dist/core-runtime/evolve/commands/reconstruct.js +0 -330
  131. package/dist/core-runtime/evolve/commands/reconstruct.test.js +0 -278
  132. package/dist/core-runtime/evolve/commands/shared.js +0 -22
  133. package/dist/core-runtime/evolve/commands/stale-check.js +0 -103
  134. package/dist/core-runtime/evolve/commands/stale-check.test.js +0 -84
  135. package/dist/core-runtime/evolve/commands/start.js +0 -887
  136. package/dist/core-runtime/evolve/commands/start.test.js +0 -396
  137. package/dist/core-runtime/evolve/config/project-config.js +0 -99
  138. package/dist/core-runtime/evolve/config/project-config.test.js +0 -170
  139. package/dist/core-runtime/evolve/renderers/align-packet.js +0 -280
  140. package/dist/core-runtime/evolve/renderers/align-packet.test.js +0 -332
  141. package/dist/core-runtime/evolve/renderers/draft-packet.js +0 -303
  142. package/dist/core-runtime/evolve/renderers/draft-packet.test.js +0 -377
  143. package/dist/core-runtime/evolve/renderers/format.js +0 -5
  144. package/dist/core-runtime/evolve/renderers/scope-md.js +0 -237
  145. package/dist/core-runtime/evolve/renderers/scope-md.test.js +0 -306
  146. package/dist/core-runtime/govern/cli.js +0 -369
  147. package/dist/core-runtime/govern/cli.test.js +0 -314
  148. package/dist/core-runtime/govern/drift-engine.js +0 -103
  149. package/dist/core-runtime/govern/drift-engine.test.js +0 -319
  150. package/dist/core-runtime/govern/promote-principle.js +0 -206
  151. package/dist/core-runtime/govern/promote-principle.test.js +0 -368
  152. package/dist/core-runtime/govern/queue.js +0 -81
  153. package/dist/core-runtime/govern/types.js +0 -16
  154. package/dist/core-runtime/install/cli.js +0 -530
  155. package/dist/core-runtime/install/detect.js +0 -128
  156. package/dist/core-runtime/install/detect.test.js +0 -155
  157. package/dist/core-runtime/install/gitignore-update.js +0 -74
  158. package/dist/core-runtime/install/gitignore-update.test.js +0 -64
  159. package/dist/core-runtime/install/install-integration.test.js +0 -373
  160. package/dist/core-runtime/install/prompts.js +0 -389
  161. package/dist/core-runtime/install/prompts.test.js +0 -293
  162. package/dist/core-runtime/install/types.js +0 -26
  163. package/dist/core-runtime/install/validation.js +0 -295
  164. package/dist/core-runtime/install/validation.test.js +0 -313
  165. package/dist/core-runtime/install/writer.js +0 -254
  166. package/dist/core-runtime/install/writer.test.js +0 -218
  167. package/dist/core-runtime/learning/extractor.js +0 -461
  168. package/dist/core-runtime/learning/feedback.js +0 -179
  169. package/dist/core-runtime/learning/health-report.js +0 -165
  170. package/dist/core-runtime/learning/health-report.test.js +0 -169
  171. package/dist/core-runtime/learning/loader.js +0 -388
  172. package/dist/core-runtime/learning/loader.test.js +0 -102
  173. package/dist/core-runtime/learning/promote/apply-state.js +0 -240
  174. package/dist/core-runtime/learning/promote/audit-obligation.js +0 -195
  175. package/dist/core-runtime/learning/promote/collector.js +0 -432
  176. package/dist/core-runtime/learning/promote/degraded-state.js +0 -125
  177. package/dist/core-runtime/learning/promote/domain-doc-proposer.js +0 -166
  178. package/dist/core-runtime/learning/promote/e2e-promote.test.js +0 -6385
  179. package/dist/core-runtime/learning/promote/health-snapshot.js +0 -150
  180. package/dist/core-runtime/learning/promote/insight-reclassifier.js +0 -544
  181. package/dist/core-runtime/learning/promote/judgment-auditor.js +0 -517
  182. package/dist/core-runtime/learning/promote/panel-reviewer.js +0 -1158
  183. package/dist/core-runtime/learning/promote/promote-executor.js +0 -1675
  184. package/dist/core-runtime/learning/promote/promoter.js +0 -307
  185. package/dist/core-runtime/learning/promote/retirement.js +0 -122
  186. package/dist/core-runtime/learning/promote/types.js +0 -23
  187. package/dist/core-runtime/learning/prompt-sections.js +0 -51
  188. package/dist/core-runtime/learning/shared/artifact-registry-init.js +0 -45
  189. package/dist/core-runtime/learning/shared/artifact-registry.js +0 -254
  190. package/dist/core-runtime/learning/shared/audit-obligation-kernel.js +0 -73
  191. package/dist/core-runtime/learning/shared/audit-state.js +0 -99
  192. package/dist/core-runtime/learning/shared/duplicate-check.js +0 -28
  193. package/dist/core-runtime/learning/shared/llm-caller.js +0 -831
  194. package/dist/core-runtime/learning/shared/llm-caller.test.js +0 -601
  195. package/dist/core-runtime/learning/shared/llm-tool-loop.js +0 -393
  196. package/dist/core-runtime/learning/shared/mode.js +0 -25
  197. package/dist/core-runtime/learning/shared/paths.js +0 -84
  198. package/dist/core-runtime/learning/shared/paths.test.js +0 -79
  199. package/dist/core-runtime/learning/shared/patterns.js +0 -37
  200. package/dist/core-runtime/learning/shared/recoverability.js +0 -355
  201. package/dist/core-runtime/learning/shared/recovery-context.js +0 -374
  202. package/dist/core-runtime/learning/shared/scope.js +0 -1
  203. package/dist/core-runtime/learning/shared/semantic-classifier.js +0 -94
  204. package/dist/core-runtime/learning/shared/specs/apply-execution-state-spec.js +0 -42
  205. package/dist/core-runtime/learning/shared/specs/audit-state-spec.js +0 -37
  206. package/dist/core-runtime/learning/shared/specs/backup-metadata-spec.js +0 -39
  207. package/dist/core-runtime/learning/shared/specs/emergency-log-spec.js +0 -41
  208. package/dist/core-runtime/learning/shared/specs/layout-version-spec.js +0 -38
  209. package/dist/core-runtime/learning/shared/specs/promote-decisions-spec.js +0 -43
  210. package/dist/core-runtime/learning/shared/specs/promote-report-spec.js +0 -113
  211. package/dist/core-runtime/learning/shared/specs/prune-log-spec.js +0 -36
  212. package/dist/core-runtime/learning/shared/specs/recovery-resolution-spec.js +0 -48
  213. package/dist/core-runtime/learning/shared/specs/restore-manifest-spec.js +0 -43
  214. package/dist/core-runtime/learning/shared/specs/spec-helpers.js +0 -64
  215. package/dist/core-runtime/learning/usage-tracker.js +0 -190
  216. package/dist/core-runtime/learning/usage-tracker.test.js +0 -176
  217. package/dist/core-runtime/onboard/detect-review-axes.js +0 -122
  218. package/dist/core-runtime/onboard/detect-review-axes.test.js +0 -127
  219. package/dist/core-runtime/onboard/write-review-block.js +0 -188
  220. package/dist/core-runtime/onboard/write-review-block.test.js +0 -240
  221. package/dist/core-runtime/readers/brownfield-builder.js +0 -150
  222. package/dist/core-runtime/readers/brownfield-builder.test.js +0 -136
  223. package/dist/core-runtime/readers/code-chunk-collector.js +0 -53
  224. package/dist/core-runtime/readers/code-chunk-collector.test.js +0 -136
  225. package/dist/core-runtime/readers/file-utils.js +0 -240
  226. package/dist/core-runtime/readers/file-utils.test.js +0 -146
  227. package/dist/core-runtime/readers/lexicon-citation-check.js +0 -93
  228. package/dist/core-runtime/readers/lexicon-citation-check.test.js +0 -77
  229. package/dist/core-runtime/readers/mcp-figma.js +0 -30
  230. package/dist/core-runtime/readers/mcp-figma.test.js +0 -82
  231. package/dist/core-runtime/readers/mcp-generic.js +0 -31
  232. package/dist/core-runtime/readers/mcp-generic.test.js +0 -76
  233. package/dist/core-runtime/readers/ontology-index.js +0 -148
  234. package/dist/core-runtime/readers/ontology-index.test.js +0 -245
  235. package/dist/core-runtime/readers/ontology-query.js +0 -168
  236. package/dist/core-runtime/readers/ontology-query.test.js +0 -311
  237. package/dist/core-runtime/readers/ontology-resolve.js +0 -48
  238. package/dist/core-runtime/readers/ontology-resolve.test.js +0 -48
  239. package/dist/core-runtime/readers/patterns/index.js +0 -7
  240. package/dist/core-runtime/readers/review-log.js +0 -213
  241. package/dist/core-runtime/readers/review-log.test.js +0 -313
  242. package/dist/core-runtime/readers/scan-local.js +0 -102
  243. package/dist/core-runtime/readers/scan-local.test.js +0 -102
  244. package/dist/core-runtime/readers/scan-tarball.js +0 -121
  245. package/dist/core-runtime/readers/scan-tarball.test.js +0 -283
  246. package/dist/core-runtime/readers/scan-vault.js +0 -34
  247. package/dist/core-runtime/readers/scan-vault.test.js +0 -81
  248. package/dist/core-runtime/readers/types.js +0 -42
  249. package/dist/core-runtime/readers/types.test.js +0 -94
  250. package/dist/core-runtime/readers/viewpoint-collectors.js +0 -229
  251. package/dist/core-runtime/reconstruct/seed-candidate-validation.js +0 -385
  252. package/dist/core-runtime/review/citation-audit.test.js +0 -165
  253. package/dist/core-runtime/review/execution-plan-resolver.js +0 -247
  254. package/dist/core-runtime/review/execution-plan-resolver.test.js +0 -243
  255. package/dist/core-runtime/review/execution-topology-resolver-axis-first.test.js +0 -246
  256. package/dist/core-runtime/review/execution-topology-resolver.js +0 -401
  257. package/dist/core-runtime/review/execution-topology-resolver.test.js +0 -315
  258. package/dist/core-runtime/review/inline-context-embedder.test.js +0 -154
  259. package/dist/core-runtime/review/legacy-mode-policy.js +0 -88
  260. package/dist/core-runtime/review/materializers-effort-persist.test.js +0 -79
  261. package/dist/core-runtime/review/ontology-path-classifier.js +0 -179
  262. package/dist/core-runtime/review/ontology-path-classifier.test.js +0 -216
  263. package/dist/core-runtime/review/packet-boundary-policy.test.js +0 -107
  264. package/dist/core-runtime/review/participating-lens-paths.test.js +0 -73
  265. package/dist/core-runtime/review/review-config-legacy-translate.js +0 -244
  266. package/dist/core-runtime/review/review-config-legacy-translate.test.js +0 -161
  267. package/dist/core-runtime/review/review-config-validator.js +0 -289
  268. package/dist/core-runtime/review/review-config-validator.test.js +0 -236
  269. package/dist/core-runtime/review/shape-pipeline-audit.test.js +0 -311
  270. package/dist/core-runtime/review/shape-to-topology-id.js +0 -117
  271. package/dist/core-runtime/review/shape-to-topology-id.test.js +0 -132
  272. package/dist/core-runtime/review/topology-shape-derivation.js +0 -155
  273. package/dist/core-runtime/review/topology-shape-derivation.test.js +0 -195
  274. package/dist/core-runtime/scope-runtime/constants.js +0 -12
  275. package/dist/core-runtime/scope-runtime/constraint-pool.js +0 -166
  276. package/dist/core-runtime/scope-runtime/constraint-pool.test.js +0 -674
  277. package/dist/core-runtime/scope-runtime/domain-validation-log.js +0 -135
  278. package/dist/core-runtime/scope-runtime/domain-validation-log.test.js +0 -156
  279. package/dist/core-runtime/scope-runtime/eval-persistence.js +0 -65
  280. package/dist/core-runtime/scope-runtime/eval-persistence.test.js +0 -84
  281. package/dist/core-runtime/scope-runtime/event-pipeline.js +0 -64
  282. package/dist/core-runtime/scope-runtime/event-pipeline.test.js +0 -450
  283. package/dist/core-runtime/scope-runtime/event-store.js +0 -39
  284. package/dist/core-runtime/scope-runtime/event-store.test.js +0 -95
  285. package/dist/core-runtime/scope-runtime/gate-guard.js +0 -348
  286. package/dist/core-runtime/scope-runtime/gate-guard.test.js +0 -1047
  287. package/dist/core-runtime/scope-runtime/hash.js +0 -4
  288. package/dist/core-runtime/scope-runtime/hash.test.js +0 -33
  289. package/dist/core-runtime/scope-runtime/id.js +0 -4
  290. package/dist/core-runtime/scope-runtime/id.test.js +0 -17
  291. package/dist/core-runtime/scope-runtime/reducer.js +0 -297
  292. package/dist/core-runtime/scope-runtime/reducer.test.js +0 -759
  293. package/dist/core-runtime/scope-runtime/scope-manager.js +0 -161
  294. package/dist/core-runtime/scope-runtime/state-machine.js +0 -309
  295. package/dist/core-runtime/scope-runtime/state-machine.test.js +0 -704
  296. package/dist/core-runtime/scope-runtime/types.js +0 -116
  297. package/dist/core-runtime/scope-runtime/types.test.js +0 -69
  298. package/dist/core-runtime/translate/render-for-user.js +0 -169
  299. package/dist/core-runtime/translate/render-for-user.test.js +0 -122
  300. package/dist/providers/capability-contract.js +0 -1
@@ -1,831 +0,0 @@
1
- /**
2
- * Background task (learn/govern/promote) LLM call wrapper.
3
- *
4
- * Canonical provider resolution:
5
- * 1. Caller-explicit: callLlm(..., { provider }) — one provider only.
6
- * 2. `llm.auth=oauth + llm.provider=openai` — Codex worker.
7
- * 3. `llm.auth=api_key` — OpenAI / Anthropic / Grok API key from env.
8
- * 4. `llm.auth=local + llm.provider=lmstudio` — local OpenAI-style endpoint.
9
- *
10
- * Priority 0 (special): ONTO_LLM_MOCK=1 → in-process mock (test only)
11
- *
12
- * Mock provider:
13
- * When ONTO_LLM_MOCK=1 is set, callLlm() routes to an in-process mock that
14
- * pattern-matches the system prompt against known Phase 3 prompts (panel
15
- * review, judgment audit, insight reclassify, domain doc) and returns
16
- * deterministic JSON. This unblocks E2E tests that need to exercise the
17
- * full LLM call path without real API credentials. NEVER ship with this
18
- * env var set in production — there's no real reasoning happening.
19
- *
20
- * Runtime config must reach this module through the canonical `llm` switcher
21
- * or an explicit call-site override. Missing provider/model/credentials fail
22
- * immediately.
23
- */
24
- import crypto from "node:crypto";
25
- import fs from "node:fs";
26
- import path from "node:path";
27
- import os from "node:os";
28
- import { loadCoreLensRegistry } from "../../discovery/lens-registry.js";
29
- import { DEFAULT_GROK_BASE_URL, DEFAULT_LMSTUDIO_BASE_URL, normalizeLlmModelSwitcher, } from "../../llm/model-switcher.js";
30
- /**
31
- * Bridge: OntoConfig + CLI overrides → Partial<LlmCallConfig> that callLlm consumes.
32
- *
33
- * Callers (learning/promote panel-reviewer, promote-executor, judgment-auditor,
34
- * insight-reclassifier, extractor, semantic-classifier) should:
35
- *
36
- * const partial = resolveLearningProviderConfig({ config: ontoConfig, cliOverrides });
37
- * const result = await callLlm(system, user, { ...partial, max_tokens: 2048 });
38
- *
39
- * This replaces the pattern of callers building Partial<LlmCallConfig> ad-hoc, and is
40
- * the canonical seat where OntoConfig translates to provider resolution input.
41
- *
42
- */
43
- export function resolveLearningProviderConfig(args) {
44
- const config = args.config ?? {};
45
- const cli = args.cliOverrides ?? {};
46
- const selection = normalizeLlmModelSwitcher(config.llm);
47
- const provider = cli.provider ?? selection?.provider;
48
- const model_id = cli.model ?? selection?.model_id;
49
- const envBaseUrl = provider === "grok"
50
- ? process.env.GROK_BASE_URL ?? process.env.XAI_BASE_URL
51
- : provider === "lmstudio"
52
- ? process.env.LMSTUDIO_BASE_URL
53
- : undefined;
54
- const base_url = cli.base_url ?? envBaseUrl ?? selection?.base_url;
55
- const reasoning_effort = cli.reasoning_effort ?? selection?.reasoning_effort;
56
- const service_tier = selection?.provider === "codex" ? selection.service_tier : undefined;
57
- const api_key_env = selection?.api_key_env;
58
- const models_per_provider = {};
59
- if (provider && model_id)
60
- models_per_provider[provider] = model_id;
61
- const out = {};
62
- if (provider)
63
- out.provider = provider;
64
- if (model_id)
65
- out.model_id = model_id;
66
- if (base_url)
67
- out.base_url = base_url;
68
- if (reasoning_effort)
69
- out.reasoning_effort = reasoning_effort;
70
- if (service_tier)
71
- out.service_tier = service_tier;
72
- if (api_key_env)
73
- out.api_key_env = api_key_env;
74
- if (Object.keys(models_per_provider).length > 0) {
75
- out.models_per_provider = models_per_provider;
76
- }
77
- return out;
78
- }
79
- // Phase 3 production found 30s too tight for large audit batches (37 items
80
- // could time out then SDK-retry for 90s total). 120s is generous
81
- // enough for ~50-item single-batch audits while still failing fast on real
82
- // network problems.
83
- const DEFAULT_TIMEOUT_MS = Number(process.env.ONTO_LLM_TIMEOUT_MS) || 120_000;
84
- // SDK auto-retry hides failures behind a long stall. We surface failures
85
- // faster (1 retry instead of the default 2) so operators see provider errors
86
- // within ~2× timeout instead of ~3×.
87
- const DEFAULT_MAX_RETRIES = 1;
88
- /**
89
- * Model-call observability — emits STDERR logs for each LLM API call, covering
90
- * (a) pre-call model_id + provider + max_tokens, (b) post-call usage on success,
91
- * (c) full SDK error fields (status / error.type / error.message / request_id)
92
- * on failure. Silent "Connection error." wrapping by review runner no longer
93
- * hides model-not-found / auth / quota / network distinctions.
94
- */
95
- function emitModelCallLog(line) {
96
- process.stderr.write(`[model-call] ${line}\n`);
97
- }
98
- function readCodexAuthState() {
99
- const codexAuthPath = path.join(os.homedir(), ".codex", "auth.json");
100
- if (!fs.existsSync(codexAuthPath)) {
101
- return { chatgptOAuth: false, openaiApiKey: null };
102
- }
103
- try {
104
- const auth = JSON.parse(fs.readFileSync(codexAuthPath, "utf8"));
105
- const oauth = auth.auth_mode === "chatgpt" ||
106
- (auth.tokens && typeof auth.tokens.access_token === "string");
107
- const openaiKey = typeof auth.OPENAI_API_KEY === "string" && auth.OPENAI_API_KEY.length > 0
108
- ? auth.OPENAI_API_KEY
109
- : null;
110
- return { chatgptOAuth: Boolean(oauth), openaiApiKey: openaiKey };
111
- }
112
- catch {
113
- return { chatgptOAuth: false, openaiApiKey: null };
114
- }
115
- }
116
- function readEnvApiKey(envNames) {
117
- for (const envName of envNames) {
118
- const value = process.env[envName];
119
- if (typeof value === "string" && value.length > 0)
120
- return value;
121
- }
122
- return null;
123
- }
124
- function resolveProvider(preferred, configBaseUrl, apiKeyEnv) {
125
- if (preferred === undefined) {
126
- throw new Error(missingProviderSelectionError());
127
- }
128
- if (preferred === "anthropic") {
129
- const apiKey = readEnvApiKey(apiKeyEnv ? [apiKeyEnv] : ["ANTHROPIC_API_KEY"]);
130
- if (apiKey) {
131
- return { provider: "anthropic", apiKey };
132
- }
133
- throw new Error(explicitProviderMissingCredentialError("anthropic"));
134
- }
135
- if (preferred === "openai") {
136
- const envKey = readEnvApiKey(apiKeyEnv ? [apiKeyEnv] : ["OPENAI_API_KEY"]);
137
- if (envKey) {
138
- return { provider: "openai", apiKey: envKey };
139
- }
140
- const codexAuth = readCodexAuthState();
141
- if (codexAuth.openaiApiKey) {
142
- return { provider: "openai", apiKey: codexAuth.openaiApiKey };
143
- }
144
- throw new Error(explicitProviderMissingCredentialError("openai"));
145
- }
146
- if (preferred === "grok") {
147
- const apiKey = readEnvApiKey(apiKeyEnv ? [apiKeyEnv] : ["XAI_API_KEY", "GROK_API_KEY"]);
148
- if (apiKey) {
149
- return {
150
- provider: "grok",
151
- apiKey,
152
- baseUrl: configBaseUrl ?? DEFAULT_GROK_BASE_URL,
153
- };
154
- }
155
- throw new Error(explicitProviderMissingCredentialError("grok"));
156
- }
157
- if (preferred === "lmstudio") {
158
- return {
159
- provider: "lmstudio",
160
- apiKey: "lmstudio-local",
161
- baseUrl: configBaseUrl ?? process.env.LMSTUDIO_BASE_URL ?? DEFAULT_LMSTUDIO_BASE_URL,
162
- };
163
- }
164
- return {
165
- provider: "codex",
166
- apiKey: "codex-oauth",
167
- };
168
- }
169
- function explicitProviderMissingCredentialError(provider) {
170
- const envVar = provider === "anthropic"
171
- ? "ANTHROPIC_API_KEY"
172
- : provider === "openai"
173
- ? "OPENAI_API_KEY"
174
- : "XAI_API_KEY or GROK_API_KEY";
175
- return [
176
- `llm.provider=${provider} 명시적으로 선택되었으나 ${envVar}가 환경변수에 없습니다.`,
177
- ...(provider === "openai"
178
- ? ["(~/.codex/auth.json의 OPENAI_API_KEY 필드도 비어 있거나 없음)"]
179
- : []),
180
- `명시적 provider override를 사용하려면 ${envVar}를 export하세요.`,
181
- "또는 .onto/settings.json 의 llm block 을 현재 credential에 맞게 수정하세요.",
182
- ].join("\n");
183
- }
184
- function missingProviderSelectionError() {
185
- return [
186
- "LLM provider가 지정되지 않았습니다.",
187
- "`.onto/settings.json`에 `llm` 블록을 추가하거나 호출부에서 provider를 명시하세요:",
188
- " llm:",
189
- " auth: oauth | api_key | local",
190
- " provider: openai | anthropic | grok | lmstudio",
191
- " model: <model-id>",
192
- ].join("\n");
193
- }
194
- /**
195
- * Construct a fail-fast error for api-key providers when no model is specified.
196
- * Used by anthropic / openai / grok / lmstudio dispatch branches. codex is exempt because
197
- * the codex CLI picks its own default when `-m` is omitted.
198
- *
199
- * Hardcoded DEFAULT_ANTHROPIC_MODEL / DEFAULT_OPENAI_MODEL constants were removed
200
- * from this module (2026-04-15): model choice is a user decision (cost / quality /
201
- * account constraints) and should not be hardcoded in library code where it can
202
- * go stale or mismatch account permissions.
203
- */
204
- function missingModelError(provider) {
205
- const providerField = provider;
206
- return new Error([
207
- `provider=${provider} 경로는 model 지정이 필요합니다. 하드코딩된 기본 모델은 제거되었습니다.`,
208
- "다음 중 한 가지로 설정하세요:",
209
- " 1. .onto/settings.json 의 `llm.model: <model-id>`",
210
- " 3. 호출부에서 LlmCallConfig.model_id 인자 전달 (런타임 override)",
211
- "(codex provider는 model 미지정 시 codex CLI가 자체 기본값을 사용하므로 이 메시지의 대상이 아닙니다.)",
212
- ].join("\n"));
213
- }
214
- // ---------------------------------------------------------------------------
215
- // Anthropic call
216
- // ---------------------------------------------------------------------------
217
- async function callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens) {
218
- const { default: Anthropic } = await import("@anthropic-ai/sdk");
219
- const client = new Anthropic({
220
- apiKey,
221
- timeout: DEFAULT_TIMEOUT_MS,
222
- maxRetries: DEFAULT_MAX_RETRIES,
223
- });
224
- emitModelCallLog(`anthropic call: model="${modelId}" max_tokens=${maxTokens}`);
225
- let response;
226
- try {
227
- response = await client.messages.create({
228
- model: modelId,
229
- max_tokens: maxTokens,
230
- system: systemPrompt,
231
- messages: [{ role: "user", content: userPrompt }],
232
- });
233
- }
234
- catch (err) {
235
- const e = err;
236
- emitModelCallLog(`anthropic call FAILED: model="${modelId}" status=${e.status ?? "?"} type=${e.error?.type ?? e.name ?? "?"} message="${e.error?.message ?? e.message ?? String(err)}" request_id=${e.request_id ?? "?"}`);
237
- throw err;
238
- }
239
- emitModelCallLog(`anthropic success: model_id=${response.model ?? modelId} input_tokens=${response.usage.input_tokens} output_tokens=${response.usage.output_tokens}`);
240
- const text = response.content
241
- .filter((block) => block.type === "text")
242
- .map((block) => ("text" in block ? block.text : ""))
243
- .join("\n");
244
- return {
245
- text,
246
- input_tokens: response.usage.input_tokens,
247
- output_tokens: response.usage.output_tokens,
248
- model_id: modelId,
249
- effective_base_url: "https://api.anthropic.com",
250
- declared_billing_mode: "per_token",
251
- };
252
- }
253
- // ---------------------------------------------------------------------------
254
- // OpenAI call
255
- // ---------------------------------------------------------------------------
256
- async function callOpenAI(systemPrompt, userPrompt, apiKey, modelId, maxTokens, baseUrl, providerLabel = "openai") {
257
- const { default: OpenAI } = await import("openai");
258
- const client = new OpenAI({
259
- apiKey,
260
- baseURL: baseUrl,
261
- timeout: DEFAULT_TIMEOUT_MS,
262
- maxRetries: DEFAULT_MAX_RETRIES,
263
- });
264
- emitModelCallLog(`${providerLabel} call: model="${modelId}" max_tokens=${maxTokens}${baseUrl ? ` base_url=${baseUrl}` : ""}`);
265
- let response;
266
- try {
267
- response = await client.chat.completions.create({
268
- model: modelId,
269
- max_tokens: maxTokens,
270
- messages: [
271
- { role: "system", content: systemPrompt },
272
- { role: "user", content: userPrompt },
273
- ],
274
- });
275
- }
276
- catch (err) {
277
- const e = err;
278
- emitModelCallLog(`${providerLabel} call FAILED: model="${modelId}" status=${e.status ?? "?"} type=${e.error?.type ?? e.name ?? "?"} message="${e.error?.message ?? e.message ?? String(err)}" request_id=${e.request_id ?? "?"}`);
279
- throw err;
280
- }
281
- emitModelCallLog(`${providerLabel} success: model_id=${response.model ?? modelId} input_tokens=${response.usage?.prompt_tokens ?? 0} output_tokens=${response.usage?.completion_tokens ?? 0}`);
282
- const text = response.choices[0]?.message?.content ?? "";
283
- const defaultBase = providerLabel === "grok"
284
- ? DEFAULT_GROK_BASE_URL
285
- : providerLabel === "lmstudio"
286
- ? DEFAULT_LMSTUDIO_BASE_URL
287
- : "https://api.openai.com/v1";
288
- return {
289
- text,
290
- input_tokens: response.usage?.prompt_tokens ?? 0,
291
- output_tokens: response.usage?.completion_tokens ?? 0,
292
- model_id: modelId,
293
- effective_base_url: baseUrl ?? defaultBase,
294
- declared_billing_mode: providerLabel === "lmstudio" ? "local" : "per_token",
295
- };
296
- }
297
- // ---------------------------------------------------------------------------
298
- // codex CLI call (OAuth subscription path)
299
- // ---------------------------------------------------------------------------
300
- /**
301
- * Invoke `codex exec --ephemeral -` as a Codex worker for a single-turn
302
- * prompt → text response. Uses the host's codex CLI authentication
303
- * (chatgpt OAuth via ~/.codex/auth.json), which routes through chatgpt.com's
304
- * backend — cannot be reached via the OpenAI SDK.
305
- *
306
- * --ephemeral keeps this learning call from persisting a session file
307
- * alongside review sessions. --skip-git-repo-check lets learning run
308
- * from non-repo cwd. No -C/-s/-o: this is single-turn, no agentic scaffold.
309
- */
310
- async function callCodexCli(systemPrompt, userPrompt, modelId, reasoningEffort, serviceTier) {
311
- const { spawn } = await import("node:child_process");
312
- const args = ["exec", "--skip-git-repo-check", "--ephemeral"];
313
- if (modelId)
314
- args.push("-m", modelId);
315
- if (reasoningEffort)
316
- args.push("-c", `model_reasoning_effort="${reasoningEffort}"`);
317
- if (serviceTier)
318
- args.push("-c", `service_tier="${serviceTier}"`);
319
- args.push("-");
320
- const combinedPrompt = `${systemPrompt}\n\n---\n\n${userPrompt}`;
321
- emitModelCallLog(`codex call: model="${modelId ?? "(codex default)"}" effort="${reasoningEffort ?? "(unset)"}" service_tier="${serviceTier ?? "(unset)"}" timeout_ms=${DEFAULT_TIMEOUT_MS}`);
322
- const child = spawn("codex", args, {
323
- stdio: ["pipe", "pipe", "pipe"],
324
- });
325
- let stdout = "";
326
- let stderr = "";
327
- let timedOut = false;
328
- child.stdout.on("data", (chunk) => {
329
- stdout += String(chunk);
330
- });
331
- child.stderr.on("data", (chunk) => {
332
- stderr += String(chunk);
333
- });
334
- child.stdin.write(combinedPrompt);
335
- child.stdin.end();
336
- const timeoutHandle = setTimeout(() => {
337
- timedOut = true;
338
- child.kill("SIGTERM");
339
- }, DEFAULT_TIMEOUT_MS);
340
- const exitCode = await new Promise((resolve, reject) => {
341
- child.on("error", (err) => {
342
- clearTimeout(timeoutHandle);
343
- if (err.code === "ENOENT") {
344
- reject(new Error("codex CLI not found on PATH. Install codex to use the OAuth subscription path: https://github.com/openai/codex"));
345
- }
346
- else {
347
- reject(err);
348
- }
349
- });
350
- child.on("close", (code) => {
351
- clearTimeout(timeoutHandle);
352
- resolve(code ?? 1);
353
- });
354
- });
355
- if (timedOut) {
356
- emitModelCallLog(`codex call FAILED: model="${modelId ?? "(codex default)"}" reason=timeout timeout_ms=${DEFAULT_TIMEOUT_MS}`);
357
- throw new Error(`codex CLI call timed out after ${DEFAULT_TIMEOUT_MS}ms`);
358
- }
359
- if (exitCode !== 0) {
360
- const combined = [stderr.trim(), stdout.trim()]
361
- .filter((m) => m.length > 0)
362
- .join("\n");
363
- emitModelCallLog(`codex call FAILED: model="${modelId ?? "(codex default)"}" exit_code=${exitCode} message="${combined.slice(0, 200).replace(/\n/g, " ")}"`);
364
- // A1: chatgpt account model allowlist rejection — augment with actionable hint.
365
- // codex emits errors like:
366
- // "The 'gpt-4o-mini' model is not supported when using Codex with a ChatGPT account."
367
- // Surface a fix path so users don't have to decode the upstream message.
368
- if (combined.includes("is not supported when using Codex with a ChatGPT account") ||
369
- combined.includes("not supported when using Codex")) {
370
- const requested = modelId ?? "(codex default)";
371
- throw new Error([
372
- combined,
373
- "",
374
- `지정된 모델 "${requested}"이 현재 ChatGPT 계정의 codex allowlist에 없습니다.`,
375
- "다음 중 한 가지로 해결하세요:",
376
- " 1. .onto/settings.json 의 llm.model 값을 현재 계정에서 허용되는 모델로 변경",
377
- " 2. 터미널에서 `codex` 를 직접 실행해 현재 계정에서 선택 가능한 모델 확인",
378
- " 3. `codex login` 으로 API-key 모드로 전환 (per-token 과금, 더 넓은 모델 범위)",
379
- ].join("\n"));
380
- }
381
- throw new Error(combined.length > 0 ? combined : `codex CLI exited with code ${exitCode}`);
382
- }
383
- const text = stdout.trim();
384
- // codex exec does not return usage metadata in stdout; estimate by char count.
385
- // LlmCallResult carries these as approximate; audit may flag via declared_billing_mode=subscription.
386
- const estimateTokens = (s) => Math.max(1, Math.ceil(s.length / 4));
387
- const in_tokens = estimateTokens(combinedPrompt);
388
- const out_tokens = estimateTokens(text);
389
- emitModelCallLog(`codex success: model_id=${modelId ?? "codex-default"} input_tokens~=${in_tokens} output_tokens~=${out_tokens}`);
390
- return {
391
- text,
392
- input_tokens: in_tokens,
393
- output_tokens: out_tokens,
394
- model_id: modelId ?? "codex-default",
395
- effective_base_url: "codex-cli://oauth",
396
- declared_billing_mode: "subscription",
397
- };
398
- }
399
- // ---------------------------------------------------------------------------
400
- // Plan-aware dispatch (Review Recovery PR-1)
401
- // ---------------------------------------------------------------------------
402
- /**
403
- * Dispatch an LLM call using a pre-resolved ExecutionPlan shape. The plan
404
- * carries `provider_identity`, `model_id`, and `base_url`; credentials are
405
- * still read from env (ANTHROPIC_API_KEY / OPENAI_API_KEY / XAI_API_KEY)
406
- * since secrets never enter the plan by design.
407
- *
408
- * Why credentials stay in env:
409
- * The plan is written to session artifacts (`execution-plan.yaml`) for
410
- * reproducibility and audit. Including API keys would leak them; env-sourced
411
- * credentials keep the plan portable while the runtime still has enough to
412
- * authenticate.
413
- */
414
- async function dispatchByPlan(systemPrompt, userPrompt, config) {
415
- const { plan } = config;
416
- const maxTokens = config.max_tokens ?? 1024;
417
- if (plan.provider_identity === "mock") {
418
- return callMockProvider(systemPrompt, userPrompt);
419
- }
420
- if (plan.provider_identity === "claude-code") {
421
- throw new Error("callLlm: ExecutionPlan.provider_identity=claude-code is orchestrator-only; background LLM calls cannot dispatch through the host nested spawn path.");
422
- }
423
- if (plan.provider_identity === "codex") {
424
- const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.codex;
425
- return callCodexCli(systemPrompt, userPrompt, modelId, config.reasoning_effort, config.service_tier);
426
- }
427
- if (plan.provider_identity === "anthropic") {
428
- const apiKey = readEnvApiKey(config.api_key_env ? [config.api_key_env] : ["ANTHROPIC_API_KEY"]);
429
- if (!apiKey) {
430
- throw new Error(explicitProviderMissingCredentialError("anthropic"));
431
- }
432
- const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.anthropic;
433
- if (!modelId)
434
- throw missingModelError("anthropic");
435
- return callAnthropic(systemPrompt, userPrompt, apiKey, modelId, maxTokens);
436
- }
437
- if (plan.provider_identity === "openai") {
438
- const envKey = readEnvApiKey(config.api_key_env ? [config.api_key_env] : ["OPENAI_API_KEY"]);
439
- const codexAuth = readCodexAuthState();
440
- const apiKey = envKey ?? codexAuth.openaiApiKey ?? null;
441
- if (!apiKey) {
442
- throw new Error(explicitProviderMissingCredentialError("openai"));
443
- }
444
- const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.openai;
445
- if (!modelId)
446
- throw missingModelError("openai");
447
- return callOpenAI(systemPrompt, userPrompt, apiKey, modelId, maxTokens);
448
- }
449
- if (plan.provider_identity === "grok") {
450
- const apiKey = readEnvApiKey(config.api_key_env ? [config.api_key_env] : ["XAI_API_KEY", "GROK_API_KEY"]);
451
- if (!apiKey) {
452
- throw new Error(explicitProviderMissingCredentialError("grok"));
453
- }
454
- const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.grok;
455
- if (!modelId)
456
- throw missingModelError("grok");
457
- return callOpenAI(systemPrompt, userPrompt, apiKey, modelId, maxTokens, plan.base_url ?? config.base_url ?? DEFAULT_GROK_BASE_URL, "grok");
458
- }
459
- if (plan.provider_identity === "lmstudio") {
460
- const modelId = config.model_id ?? plan.model_id ?? config.models_per_provider?.lmstudio;
461
- if (!modelId)
462
- throw missingModelError("lmstudio");
463
- return callOpenAI(systemPrompt, userPrompt, "lmstudio-local", modelId, maxTokens, plan.base_url ?? config.base_url ?? process.env.LMSTUDIO_BASE_URL ?? DEFAULT_LMSTUDIO_BASE_URL, "lmstudio");
464
- }
465
- throw new Error(`dispatchByPlan: unexpected provider_identity=${String(plan.provider_identity)}`);
466
- }
467
- // ---------------------------------------------------------------------------
468
- // Public API
469
- // ---------------------------------------------------------------------------
470
- /** Call an LLM through the explicitly selected provider path. */
471
- export async function callLlm(systemPrompt, userPrompt, config) {
472
- // Test-only mock provider — gated by ONTO_LLM_MOCK=1.
473
- if (process.env.ONTO_LLM_MOCK === "1") {
474
- return callMockProvider(systemPrompt, userPrompt);
475
- }
476
- if (config?.plan) {
477
- return dispatchByPlan(systemPrompt, userPrompt, config);
478
- }
479
- if (config?.provider === "codex") {
480
- return callCodexCli(systemPrompt, userPrompt, config.model_id ?? config.models_per_provider?.codex, config.reasoning_effort, config.service_tier);
481
- }
482
- if (config?.provider === "grok") {
483
- const modelId = config.model_id ?? config.models_per_provider?.grok;
484
- if (!modelId)
485
- throw missingModelError("grok");
486
- const apiKey = readEnvApiKey(config.api_key_env ? [config.api_key_env] : ["XAI_API_KEY", "GROK_API_KEY"]);
487
- if (!apiKey)
488
- throw new Error(explicitProviderMissingCredentialError("grok"));
489
- const maxTokens = config.max_tokens ?? 1024;
490
- return callOpenAI(systemPrompt, userPrompt, apiKey, modelId, maxTokens, config.base_url ?? DEFAULT_GROK_BASE_URL, "grok");
491
- }
492
- if (config?.provider === "lmstudio") {
493
- const modelId = config.model_id ?? config.models_per_provider?.lmstudio;
494
- if (!modelId)
495
- throw missingModelError("lmstudio");
496
- const maxTokens = config.max_tokens ?? 1024;
497
- return callOpenAI(systemPrompt, userPrompt, "lmstudio-local", modelId, maxTokens, config.base_url ?? process.env.LMSTUDIO_BASE_URL ?? DEFAULT_LMSTUDIO_BASE_URL, "lmstudio");
498
- }
499
- const resolved = resolveProvider(config?.provider, config?.base_url, config?.api_key_env);
500
- const maxTokens = config?.max_tokens ?? 1024;
501
- const perProviderModel = config?.models_per_provider?.[resolved.provider];
502
- switch (resolved.provider) {
503
- case "codex": {
504
- const modelId = config?.model_id ?? perProviderModel;
505
- return callCodexCli(systemPrompt, userPrompt, modelId, config?.reasoning_effort, config?.service_tier);
506
- }
507
- case "anthropic": {
508
- const modelId = config?.model_id ?? perProviderModel;
509
- if (!modelId)
510
- throw missingModelError("anthropic");
511
- return callAnthropic(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens);
512
- }
513
- case "openai": {
514
- const modelId = config?.model_id ?? perProviderModel;
515
- if (!modelId)
516
- throw missingModelError("openai");
517
- return callOpenAI(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens);
518
- }
519
- case "grok": {
520
- const modelId = config?.model_id ?? perProviderModel;
521
- if (!modelId)
522
- throw missingModelError("grok");
523
- return callOpenAI(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens, resolved.baseUrl ?? DEFAULT_GROK_BASE_URL, "grok");
524
- }
525
- case "lmstudio": {
526
- const modelId = config?.model_id ?? perProviderModel;
527
- if (!modelId)
528
- throw missingModelError("lmstudio");
529
- return callOpenAI(systemPrompt, userPrompt, resolved.apiKey, modelId, maxTokens, resolved.baseUrl ?? DEFAULT_LMSTUDIO_BASE_URL, "lmstudio");
530
- }
531
- }
532
- }
533
- // ---------------------------------------------------------------------------
534
- // Mock provider — test only, gated by ONTO_LLM_MOCK=1
535
- // ---------------------------------------------------------------------------
536
- const MOCK_MODEL_ID = "mock-llm-deterministic";
537
- /**
538
- * Pattern-match the system prompt against known Phase 3 prompt headers and
539
- * return a deterministic JSON response shaped to satisfy each module's
540
- * validator. The matching is intentionally string-prefix based — fragile by
541
- * design so a prompt change forces a test update rather than silently
542
- * accepting drift.
543
- *
544
- * Coverage:
545
- * - Panel reviewer (criteria 1~5)
546
- * - Judgment auditor (audit outcomes)
547
- * - Insight reclassifier (proposed_role)
548
- * - Domain doc proposer Phase B (reflection_form + content)
549
- * - Cross-agent dedup (criterion 6, same-principle test)
550
- * - Phase 2 semantic classifier (decision)
551
- *
552
- * N-1 fix: previously, unknown prompts fell through to a generic "ok" string,
553
- * which made prompt drift a downstream parse failure instead of an immediate
554
- * mock-dispatch failure. Now unknown prompts raise an error so test breakage
555
- * surfaces at the mock layer with the actual prompt prefix in the message.
556
- */
557
- function callMockProvider(systemPrompt, userPrompt) {
558
- let text;
559
- if (systemPrompt.startsWith("You are reviewing promotion candidates")) {
560
- // Panel reviewer — extract candidate_ids from the user prompt and return
561
- // one item per id with all-yes criteria + promote verdict.
562
- const candidateIds = extractCandidateIds(userPrompt);
563
- text = JSON.stringify({
564
- items: candidateIds.map((id) => ({
565
- candidate_id: id,
566
- verdict: "promote",
567
- criteria: [1, 2, 3, 4, 5].map((c) => ({
568
- criterion: c,
569
- judgment: "yes",
570
- reasoning: `mock reasoning for criterion ${c}`,
571
- })),
572
- axis_tag_recommendation: "retain",
573
- axis_tag_note: "mock — keep current tags",
574
- contradiction_resolution: "n/a",
575
- reason: "mock — all criteria passed deterministically",
576
- })),
577
- });
578
- }
579
- else if (systemPrompt.startsWith("You are re-verifying previously promoted [judgment]-type learnings")) {
580
- // Judgment auditor — extract item count and return retain for each.
581
- const count = extractJudgmentItemCount(userPrompt);
582
- text = JSON.stringify({
583
- outcomes: Array.from({ length: count }, (_, i) => ({
584
- item_index: i,
585
- decision: "retain",
586
- reason: "mock — judgment still valid",
587
- modified_content: null,
588
- })),
589
- });
590
- }
591
- else if (systemPrompt.startsWith("You are reclassifying [insight]-tagged learnings")) {
592
- // Insight reclassifier — return foundation as a safe default.
593
- text = JSON.stringify({
594
- proposed_role: "foundation",
595
- reason: "mock — defaulted to foundation",
596
- });
597
- }
598
- else if (systemPrompt.startsWith("You are updating a domain document")) {
599
- // Domain doc proposer Phase B.
600
- text = JSON.stringify({
601
- reflection_form: "add_term",
602
- content: "**Mock Term** — A mock entry produced by the deterministic LLM provider.",
603
- });
604
- }
605
- else if (systemPrompt.startsWith("You are detecting cross-agent principle duplication")) {
606
- // Cross-agent dedup (criterion 6) — extract the first agent from the
607
- // user prompt as primary owner and fabricate a consolidated line. The
608
- // mock happy path always confirms same_principle so tests can exercise
609
- // the structural path.
610
- //
611
- // Negative-path hooks (CG3 + UF3):
612
- // ONTO_LLM_MOCK_DEDUP_BOGUS_OWNER=1
613
- // → return a primary_owner_agent that is NOT in the shortlist so the
614
- // C2 runtime guard in llmConfirmCluster rejects the cluster.
615
- // ONTO_LLM_MOCK_DEDUP_SAME_PRINCIPLE_FALSE=1
616
- // → return same_principle=false so the UF2 metric bucket bumps.
617
- // ONTO_LLM_MOCK_DEDUP_MALFORMED=1
618
- // → emit non-JSON so the malformed_json failure channel fires.
619
- //
620
- // These hooks are test-only and gated on the ONTO_LLM_MOCK=1 envelope
621
- // already checked above; production runs never see them.
622
- if (process.env.ONTO_LLM_MOCK_DEDUP_MALFORMED === "1") {
623
- text = "{this is not valid json at all";
624
- }
625
- else if (process.env.ONTO_LLM_MOCK_DEDUP_SAME_PRINCIPLE_FALSE === "1") {
626
- text = JSON.stringify({
627
- same_principle: false,
628
- primary_owner_agent: null,
629
- primary_owner_reason: "mock — disagreement",
630
- consolidated_principle: "",
631
- representative_cases: [],
632
- consolidated_line: "",
633
- });
634
- }
635
- else {
636
- const firstAgent = extractFirstDedupAgent(userPrompt);
637
- const agentCount = countDedupAgents(userPrompt);
638
- const bogusOwner = process.env.ONTO_LLM_MOCK_DEDUP_BOGUS_OWNER === "1"
639
- ? "offshortlist_ghost_agent"
640
- : firstAgent;
641
- text = JSON.stringify({
642
- same_principle: true,
643
- primary_owner_agent: bogusOwner,
644
- primary_owner_reason: "mock — first listed agent",
645
- consolidated_principle: "Mock consolidated principle produced by the deterministic LLM provider.",
646
- representative_cases: Array.from({ length: Math.min(agentCount, 3) }, (_, i) => `mock case ${i + 1}`),
647
- consolidated_line: "- [fact] [methodology] [foundation] mock consolidated principle " +
648
- "(Representative cases: mock case 1; mock case 2) (source: consolidated from mock-mock-2026-04-09)",
649
- });
650
- }
651
- }
652
- else if (systemPrompt.startsWith("You are a semantic classifier")) {
653
- text = JSON.stringify({
654
- decision: "save",
655
- conflict_kind: null,
656
- matched_existing_line: null,
657
- reason: "mock — no overlap detected",
658
- });
659
- }
660
- else if (systemPrompt.startsWith("You are a review complexity assessor")) {
661
- // Phase 3: Step 1.5 complexity assessment mock — defaults to full review
662
- text = JSON.stringify({
663
- q2_cross_verification_secondary: false,
664
- q2_rationale: "mock — defaulting to full review (cross-verification critical)",
665
- q3_miss_risk_acceptable: false,
666
- q3_rationale: "mock — defaulting to full review (risk not acceptable)",
667
- suggest_core_axis: false,
668
- });
669
- }
670
- else if (systemPrompt.startsWith("You are a review lens selector")) {
671
- // Phase 3: Step 1.5 lens selection mock — default core-axis set.
672
- // SSOT: .onto/authority/core-lens-registry.yaml (v0.2.1: cost-constrained
673
- // Pareto-optimal lenses). Imported at module init (see top of file).
674
- text = JSON.stringify({
675
- selected_lens_ids: loadCoreLensRegistry().core_axis_lens_ids,
676
- rationale: "mock — default core-axis review lens set",
677
- });
678
- }
679
- else if (systemPrompt.startsWith("You are executing a single bounded review unit")) {
680
- // Phase 2 host-decoupling: ts_inline_http review unit executor (lens
681
- // variant). The mock returns a minimal lens-output-shaped markdown so
682
- // executor tests can verify the full call → write → JSON-print path
683
- // without needing a real LLM endpoint. Real lens output comes from a real
684
- // LLM; this mock only exercises the executor wiring.
685
- text = [
686
- "# Mock Lens Output (ts_inline_http executor mock)",
687
- "",
688
- "## Structural Inspection",
689
- "- Mock checklist item: PASS",
690
- "",
691
- "## Findings",
692
- "(none — mock executor)",
693
- "",
694
- "## Newly Learned",
695
- "(none — mock executor)",
696
- "",
697
- "## Applied Learnings",
698
- "(none — mock executor)",
699
- "",
700
- "## Domain Constraints Used",
701
- "[]",
702
- "",
703
- "## Domain Context Assumptions",
704
- '- "Mock executor returned this output for test purposes via ONTO_LLM_MOCK=1."',
705
- "",
706
- ].join("\n");
707
- }
708
- else if (systemPrompt.startsWith("You are the synthesize actor for a 9-lens review")) {
709
- // Phase 3-3: synthesize-variant executor mock. Returns a minimal
710
- // synthesize-shaped markdown with the 8 required sections + YAML
711
- // frontmatter so downstream consumers can verify the structure.
712
- //
713
- // Phase 3-4 A2 negative-path hook: ONTO_LLM_MOCK_SYNTHESIZE_WRAP_FENCE=1
714
- // makes the mock wrap its entire response in a ```yaml fence, simulating
715
- // the 30B-A3B behavior that violates the "Do not wrap" prompt rule. This
716
- // exercises the executor's stripWrappingCodeFence post-processor without
717
- // needing a real LLM call.
718
- //
719
- // Phase 3-4 A5 negative-path hook: ONTO_LLM_MOCK_SYNTHESIZE_FABRICATE=1
720
- // injects a fabricated quote (a phrase that won't appear in any lens
721
- // pool content) into the Disagreement section, simulating the
722
- // hallucination observed in the A3 benchmark. This exercises the
723
- // citation audit layer. Both hooks are test-only and gated on the
724
- // ONTO_LLM_MOCK=1 envelope already checked above.
725
- const disagreementSection = process.env.ONTO_LLM_MOCK_SYNTHESIZE_FABRICATE === "1"
726
- ? 'Axiology said "A fabricated quote that is definitely nowhere in the lens pool for this mock test run".'
727
- : "(none — mock executor)";
728
- const synthesizeBody = [
729
- "---",
730
- "deliberation_status: performed",
731
- "---",
732
- "",
733
- "# Mock Synthesize Output (ts_inline_http executor mock, synthesize variant)",
734
- "",
735
- "## Consensus",
736
- "(none — mock executor)",
737
- "",
738
- "## Conditional Consensus",
739
- "(none — mock executor)",
740
- "",
741
- "## Disagreement",
742
- disagreementSection,
743
- "",
744
- "## Deliberation Decision",
745
- "Mock synthesize consumed the controlled deliberation artifact via ONTO_LLM_MOCK=1.",
746
- "",
747
- "## Unique Finding Tagging",
748
- "(none — mock executor)",
749
- "",
750
- "## Axiology Integration",
751
- "(none — mock executor)",
752
- "",
753
- "## Newly Learned",
754
- "(none — mock executor)",
755
- "",
756
- "## Degraded Lens Failures",
757
- "(none — mock executor)",
758
- "",
759
- ].join("\n");
760
- text =
761
- process.env.ONTO_LLM_MOCK_SYNTHESIZE_WRAP_FENCE === "1"
762
- ? "```yaml\n" + synthesizeBody + "```"
763
- : synthesizeBody;
764
- }
765
- else {
766
- // Unknown prompt → throw with the prefix so tests point at the drifted prompt.
767
- const prefix = systemPrompt.slice(0, 80).replace(/\n/g, " ");
768
- return Promise.reject(new Error(`mock LLM provider: no pattern matched system prompt prefix "${prefix}". ` +
769
- `If this is a new Phase 3 prompt, add a matching branch in callMockProvider. ` +
770
- `If this is an old prompt that changed, update the matching prefix.`));
771
- }
772
- return Promise.resolve({
773
- text,
774
- input_tokens: estimateMockTokens(systemPrompt + userPrompt),
775
- output_tokens: estimateMockTokens(text),
776
- model_id: MOCK_MODEL_ID,
777
- effective_base_url: "mock://deterministic",
778
- declared_billing_mode: "per_token",
779
- });
780
- }
781
- function extractCandidateIds(userPrompt) {
782
- // Panel prompt format: `1. candidate_id=abc123 type=...`
783
- const ids = [];
784
- const re = /candidate_id=([A-Za-z0-9_-]+)/g;
785
- let m;
786
- while ((m = re.exec(userPrompt)) !== null) {
787
- ids.push(m[1]);
788
- }
789
- return ids;
790
- }
791
- function extractJudgmentItemCount(userPrompt) {
792
- const m = userPrompt.match(/Judgment items to re-verify:\s*(\d+)/);
793
- return m ? Number(m[1]) : 0;
794
- }
795
- /**
796
- * Cross-agent dedup user prompt lists items as:
797
- * 1. agent_id=structure
798
- * ...
799
- * 2. agent_id=coverage
800
- * ...
801
- * Pick the first agent_id we see so the mock's primary_owner_agent matches
802
- * the first listed shortlist member. Falls back to "structure" when no
803
- * agent_id appears at all (shouldn't happen in practice).
804
- */
805
- function extractFirstDedupAgent(userPrompt) {
806
- const m = userPrompt.match(/agent_id=([A-Za-z0-9_-]+)/);
807
- return m ? m[1] : "structure";
808
- }
809
- /**
810
- * Count the distinct agent_id references in the cross-agent dedup user prompt
811
- * so the mock can emit a plausible representative_cases list sized to the
812
- * shortlist.
813
- */
814
- function countDedupAgents(userPrompt) {
815
- const ids = new Set();
816
- const re = /agent_id=([A-Za-z0-9_-]+)/g;
817
- let m;
818
- while ((m = re.exec(userPrompt)) !== null) {
819
- ids.add(m[1]);
820
- }
821
- return ids.size;
822
- }
823
- function estimateMockTokens(text) {
824
- return Math.max(1, Math.ceil(text.length / 4));
825
- }
826
- /**
827
- * Compute a stable hash of a prompt string for audit trail.
828
- */
829
- export function hashPrompt(prompt) {
830
- return crypto.createHash("sha256").update(prompt).digest("hex").slice(0, 12);
831
- }