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,199 +0,0 @@
1
- /**
2
- * Atomic profile adoption for review config chain.
3
- *
4
- * # What this module is
5
- *
6
- * A policy module that enforces **atomic profile ownership** when merging
7
- * project `.onto/config.yml` over global `~/.onto/config.yml`:
8
- *
9
- * - Provider-coupled fields (`llm`, `main_llm`, `lens_agent_teams_mode`)
10
- * belong to ONE
11
- * source only. Either the project owns the whole profile, or the global
12
- * does — never a frankenstein merge.
13
- * - Orthogonal fields (output_language, domains, review_mode, etc.) remain
14
- * free to merge field-by-field because they carry no cross-provider
15
- * semantics.
16
- *
17
- * # How it relates
18
- *
19
- * `adoptProfile()` is called from `resolveConfigChain()`. It returns the
20
- * adopted profile fields (one source, or empty when neither side declared
21
- * any). `resolveConfigChain()` is the single integration seat for review
22
- * callers.
23
- *
24
- * The atomic-ownership principle is preserved: `extractProfileFields`
25
- * still transfers PROFILE_FIELDS as a group, so frankenstein merges
26
- * remain impossible.
27
- */
28
- // ---------------------------------------------------------------------------
29
- // Profile vs orthogonal field partitioning
30
- // ---------------------------------------------------------------------------
31
- /**
32
- * Top-level OntoConfig keys whose semantics are coupled to the provider choice.
33
- * A single source owns the entire set per adoption cycle.
34
- */
35
- export const PROFILE_FIELDS = new Set([
36
- "llm",
37
- "main_llm",
38
- "lens_agent_teams_mode",
39
- ]);
40
- /**
41
- * Orthogonal fields (output language, domain scope, listing limits, etc.)
42
- * continue to merge last-wins across home → project. These do not encode
43
- * provider-coupled semantics.
44
- */
45
- function isOrthogonalField(key) {
46
- return !PROFILE_FIELDS.has(key);
47
- }
48
- /**
49
- * SSOT predicate for "does this config declare a non-empty `review:` axis
50
- * block?". This is consumed only by `claimsProfileOwnership`
51
- * below (the atomic-adoption ownership signal).
52
- *
53
- * A block counts as declared when it is a non-null object with at least
54
- * one key. An empty `review: {}` does not count.
55
- *
56
- * Accepts either a typed `OntoConfig` or a raw `Record<string, unknown>`
57
- * for compatibility with callers that read YAML before typing.
58
- */
59
- export function hasReviewBlock(config) {
60
- if (config === undefined || config === null)
61
- return false;
62
- const review = config.review;
63
- if (typeof review !== "object" || review === null)
64
- return false;
65
- return Object.keys(review).length > 0;
66
- }
67
- // ---------------------------------------------------------------------------
68
- // Profile field presence
69
- // ---------------------------------------------------------------------------
70
- /**
71
- * Does this config declare ANY PROFILE_FIELDS value? Used by `adoptProfile`
72
- * to decide which side owns the profile slice. Returns true when at least
73
- * one profile field is set to a non-empty value.
74
- */
75
- export function hasAnyProfileField(config) {
76
- for (const field of PROFILE_FIELDS) {
77
- const value = config[field];
78
- if (value === undefined || value === null)
79
- continue;
80
- if (typeof value === "object" && !Array.isArray(value)) {
81
- if (Object.keys(value).length === 0)
82
- continue;
83
- }
84
- return true;
85
- }
86
- return false;
87
- }
88
- // ---------------------------------------------------------------------------
89
- // Profile extraction
90
- // ---------------------------------------------------------------------------
91
- /**
92
- * Extract only profile-scoped fields from a config. Orthogonal fields are
93
- * dropped — this is the "profile slice" that adoption transfers atomically.
94
- */
95
- export function extractProfileFields(config) {
96
- const out = {};
97
- for (const field of PROFILE_FIELDS) {
98
- const value = config[field];
99
- if (value !== undefined && value !== null) {
100
- out[field] = value;
101
- }
102
- }
103
- return out;
104
- }
105
- /**
106
- * Does this config claim profile ownership? A side claims ownership when it
107
- * declares either any PROFILE_FIELDS value OR a non-empty `review:` axis
108
- * block. The axis block is orthogonal-merged (not part of the profile slice
109
- * itself), but its presence signals "I opted in to declaring this review
110
- * setup" — which is strong enough to commit that side to owning whatever
111
- * profile fields accompany it.
112
- */
113
- function claimsProfileOwnership(config) {
114
- return hasAnyProfileField(config) || hasReviewBlock(config);
115
- }
116
- /**
117
- * Adopt exactly one profile atomically.
118
- *
119
- * # Decision table
120
- *
121
- * sameRoot → home/project are the same file, treat as global
122
- * project claims ownership → adopt project
123
- * project does not, home claims → adopt home
124
- * neither claims → empty profile, source="none"
125
- *
126
- * # What "claims ownership" means
127
- *
128
- * Either a non-empty `review:` axis block OR any PROFILE_FIELDS value.
129
- * See `claimsProfileOwnership` above.
130
- *
131
- * # Atomic ownership invariant
132
- *
133
- * When `source === "project"`, home's profile fields never appear in
134
- * `profile`. When `source === "global"`, project's profile fields never
135
- * appear. `extractProfileFields` is the enforcement point — it takes
136
- * from one source only. Frankenstein merges remain impossible.
137
- */
138
- export function adoptProfile(args) {
139
- if (args.sameRoot) {
140
- // Single-root case: home === project, so home's profile IS project's.
141
- // Report as "global" for traceability (the home path is the canonical
142
- // path for same-root runs).
143
- if (claimsProfileOwnership(args.home)) {
144
- return {
145
- profile: extractProfileFields(args.home),
146
- source: "global",
147
- source_path: args.homePath,
148
- };
149
- }
150
- return { profile: {}, source: "none", source_path: null };
151
- }
152
- if (claimsProfileOwnership(args.project)) {
153
- return {
154
- profile: extractProfileFields(args.project),
155
- source: "project",
156
- source_path: args.projectPath,
157
- };
158
- }
159
- if (claimsProfileOwnership(args.home)) {
160
- return {
161
- profile: extractProfileFields(args.home),
162
- source: "global",
163
- source_path: args.homePath,
164
- };
165
- }
166
- return { profile: {}, source: "none", source_path: null };
167
- }
168
- // ---------------------------------------------------------------------------
169
- // Orthogonal merge utility (used by resolveConfigChain)
170
- // ---------------------------------------------------------------------------
171
- /**
172
- * Merge orthogonal (non-profile) fields from home and project with last-wins
173
- * semantics. `excluded_names` uses union merge.
174
- */
175
- export function mergeOrthogonalFields(home, project) {
176
- const merged = {};
177
- for (const [key, value] of Object.entries(home)) {
178
- if (!isOrthogonalField(key) || value === undefined || value === null)
179
- continue;
180
- merged[key] = value;
181
- }
182
- for (const [key, value] of Object.entries(project)) {
183
- if (!isOrthogonalField(key) || value === undefined || value === null)
184
- continue;
185
- if (key === "excluded_names") {
186
- const homeNames = Array.isArray(home.excluded_names)
187
- ? home.excluded_names
188
- : [];
189
- const projectNames = Array.isArray(value) ? value : [];
190
- merged[key] = [
191
- ...new Set([...homeNames, ...projectNames]),
192
- ];
193
- }
194
- else {
195
- merged[key] = value;
196
- }
197
- }
198
- return merged;
199
- }
@@ -1,233 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { adoptProfile, extractProfileFields, hasAnyProfileField, hasReviewBlock, mergeOrthogonalFields, } from "./config-profile.js";
3
- // ---------------------------------------------------------------------------
4
- // Atomic profile adoption invariants (post-P9.4, 2026-04-21).
5
- // ---------------------------------------------------------------------------
6
- //
7
- // These tests assert two invariants:
8
- //
9
- // (1) Ownership is atomic — one source owns the full profile slice.
10
- // No frankenstein merges where profile fields from different
11
- // sources coexist in the adopted result.
12
- //
13
- // (2) The adoption layer does NOT decide whether a profile is "runnable".
14
- // Empty profiles and single-side-only profiles both return cleanly;
15
- // the topology resolver's universal `main_native` degrade owns the
16
- // "no viable host" fail-fast (P9.3).
17
- // ---------------------------------------------------------------------------
18
- // ─── hasAnyProfileField ───
19
- describe("hasAnyProfileField", () => {
20
- it("empty config → false", () => {
21
- expect(hasAnyProfileField({})).toBe(false);
22
- });
23
- it("only orthogonal fields → false", () => {
24
- expect(hasAnyProfileField({
25
- output_language: "ko",
26
- review_mode: "full",
27
- domains: ["se"],
28
- })).toBe(false);
29
- });
30
- it("per-provider block set → true", () => {
31
- expect(hasAnyProfileField({ codex: { model: "gpt-5.4" } })).toBe(true);
32
- });
33
- it("top-level model set → true", () => {
34
- expect(hasAnyProfileField({ model: "gpt-5.4" })).toBe(true);
35
- });
36
- it("empty per-provider block (e.g. `codex: {}`) → false", () => {
37
- // YAML authors who wrote `codex:` without a body should not flip
38
- // profile ownership.
39
- expect(hasAnyProfileField({ codex: {} })).toBe(false);
40
- });
41
- });
42
- // ─── hasReviewBlock (surviving legacy-bypass SSOT) ───
43
- describe("hasReviewBlock", () => {
44
- it("undefined / empty → false", () => {
45
- expect(hasReviewBlock(undefined)).toBe(false);
46
- expect(hasReviewBlock({})).toBe(false);
47
- });
48
- it("review block with one key → true", () => {
49
- expect(hasReviewBlock({
50
- review: { subagent: { provider: "main-native" } },
51
- })).toBe(true);
52
- });
53
- it("empty `review: {}` → false (prevents accidental opt-in)", () => {
54
- expect(hasReviewBlock({ review: {} })).toBe(false);
55
- });
56
- });
57
- // ─── extractProfileFields ───
58
- describe("extractProfileFields", () => {
59
- it("returns only profile fields, not orthogonal", () => {
60
- const cfg = {
61
- anthropic: { model: "claude-haiku-4-5" },
62
- reasoning_effort: "high",
63
- output_language: "ko", // orthogonal
64
- domains: ["se"], // orthogonal
65
- review_mode: "full", // orthogonal
66
- };
67
- const extracted = extractProfileFields(cfg);
68
- expect(extracted.anthropic).toEqual({ model: "claude-haiku-4-5" });
69
- expect(extracted.reasoning_effort).toBe("high");
70
- expect(extracted.output_language).toBeUndefined();
71
- expect(extracted.domains).toBeUndefined();
72
- expect(extracted.review_mode).toBeUndefined();
73
- });
74
- });
75
- // ─── adoptProfile — decision table (post-P9.4) ───
76
- const HOME_PATH = "/home/.onto/config.yml";
77
- const PROJECT_PATH = "/project/.onto/config.yml";
78
- function buildArgs(home, project) {
79
- return { home, project, homePath: HOME_PATH, projectPath: PROJECT_PATH, sameRoot: false };
80
- }
81
- describe("adoptProfile — decision branches", () => {
82
- it("project has profile → source=project", () => {
83
- const adoption = adoptProfile(buildArgs({
84
- review: { subagent: { provider: "codex", model_id: "gpt-5.4" } },
85
- codex: { model: "gpt-5.4" },
86
- reasoning_effort: "high",
87
- }, {
88
- review: { subagent: { provider: "main-native" } },
89
- anthropic: { model: "claude-haiku-4-5" },
90
- }));
91
- expect(adoption.source).toBe("project");
92
- expect(adoption.source_path).toBe(PROJECT_PATH);
93
- expect(adoption.profile.anthropic).toEqual({ model: "claude-haiku-4-5" });
94
- // Atomic ownership invariant: home's codex-flavored fields must NOT appear.
95
- expect(adoption.profile.reasoning_effort).toBeUndefined();
96
- expect(adoption.profile.codex).toBeUndefined();
97
- });
98
- it("project has ONLY review block (no profile fields) → source=project (ownership claimed)", () => {
99
- // P9.4 invariant: a non-empty `review:` axis block alone signals
100
- // profile ownership even without any PROFILE_FIELDS. This preserves
101
- // B-5 class behaviour where a project declares `review:` + empty
102
- // `codex: {}` and expects the codex namespace slot to survive
103
- // adoption (belonging to the project, not inherited from home).
104
- const adoption = adoptProfile(buildArgs({
105
- review: { subagent: { provider: "main-native" } },
106
- anthropic: { model: "claude-haiku-4-5" },
107
- }, {
108
- review: { subagent: { provider: "codex", model_id: "gpt-5.4" } },
109
- codex: {}, // empty namespace — not counted by hasAnyProfileField,
110
- // but the review block still claims ownership.
111
- }));
112
- expect(adoption.source).toBe("project");
113
- // Home's anthropic namespace MUST NOT leak into the adopted profile.
114
- expect(adoption.profile.anthropic).toBeUndefined();
115
- });
116
- it("project has profile without review block → still source=project", () => {
117
- // P9.4 change: a project with only a `codex:` namespace (no `review:`
118
- // axis block) now owns the profile. Previously this was treated as
119
- // "touched-but-incomplete" and deferred to home.
120
- const adoption = adoptProfile(buildArgs({
121
- review: { subagent: { provider: "main-native" } },
122
- anthropic: { model: "claude-haiku-4-5" },
123
- }, { codex: { model: "gpt-5.4" } /* no review block */ }));
124
- expect(adoption.source).toBe("project");
125
- expect(adoption.profile.codex).toEqual({ model: "gpt-5.4" });
126
- expect(adoption.profile.anthropic).toBeUndefined();
127
- });
128
- it("project has nothing, home has ONLY review block (no profile fields) → source=global", () => {
129
- // Symmetric to the project-review-block-only case. `claimsProfileOwnership`
130
- // must recognize a non-empty review block on the home side as an
131
- // ownership claim, so home config authors can stage review settings
132
- // without declaring a provider namespace.
133
- const adoption = adoptProfile(buildArgs({
134
- review: { subagent: { provider: "codex", model_id: "gpt-5.4" } },
135
- // No PROFILE_FIELDS — only the review block.
136
- }, {}));
137
- expect(adoption.source).toBe("global");
138
- expect(adoption.source_path).toBe(HOME_PATH);
139
- // Adopted profile is empty (no PROFILE_FIELDS in home), but
140
- // ownership was still claimed so we don't fall to source=none.
141
- expect(adoption.profile).toEqual({});
142
- });
143
- it("project has no profile fields, home has → source=global", () => {
144
- const adoption = adoptProfile(buildArgs({
145
- review: { subagent: { provider: "codex", model_id: "gpt-5.4" } },
146
- codex: { model: "gpt-5.4" },
147
- }, {}));
148
- expect(adoption.source).toBe("global");
149
- expect(adoption.source_path).toBe(HOME_PATH);
150
- expect(adoption.profile.codex).toEqual({ model: "gpt-5.4" });
151
- });
152
- it("project has no profile, home has only orthogonal fields → source=none", () => {
153
- const adoption = adoptProfile(buildArgs({ output_language: "ko" /* orthogonal only */ }, {}));
154
- expect(adoption.source).toBe("none");
155
- expect(adoption.source_path).toBeNull();
156
- expect(adoption.profile).toEqual({});
157
- });
158
- it("both absent → source=none (no throw — resolver handles it)", () => {
159
- const adoption = adoptProfile(buildArgs({}, {}));
160
- expect(adoption.source).toBe("none");
161
- expect(adoption.profile).toEqual({});
162
- });
163
- it("sameRoot + home has profile → source=global", () => {
164
- const adoption = adoptProfile({
165
- home: {
166
- review: { subagent: { provider: "codex", model_id: "gpt-5.4" } },
167
- codex: { model: "gpt-5.4" },
168
- },
169
- // sameRoot: project is the same as home, so same object content.
170
- project: {
171
- review: { subagent: { provider: "codex", model_id: "gpt-5.4" } },
172
- codex: { model: "gpt-5.4" },
173
- },
174
- homePath: HOME_PATH,
175
- projectPath: PROJECT_PATH,
176
- sameRoot: true,
177
- });
178
- expect(adoption.source).toBe("global");
179
- expect(adoption.source_path).toBe(HOME_PATH);
180
- expect(adoption.profile.codex).toEqual({ model: "gpt-5.4" });
181
- });
182
- it("sameRoot + no profile → source=none", () => {
183
- const adoption = adoptProfile({
184
- home: {},
185
- project: {},
186
- homePath: HOME_PATH,
187
- projectPath: PROJECT_PATH,
188
- sameRoot: true,
189
- });
190
- expect(adoption.source).toBe("none");
191
- expect(adoption.profile).toEqual({});
192
- });
193
- });
194
- // ─── mergeOrthogonalFields ───
195
- describe("mergeOrthogonalFields", () => {
196
- it("last-wins for scalars (project over home)", () => {
197
- const merged = mergeOrthogonalFields({ output_language: "en", review_mode: "core-axis" }, { output_language: "ko" });
198
- expect(merged.output_language).toBe("ko");
199
- expect(merged.review_mode).toBe("core-axis");
200
- });
201
- it("union-merges excluded_names", () => {
202
- const merged = mergeOrthogonalFields({ excluded_names: ["node_modules", ".git"] }, { excluded_names: [".git", "dist"] });
203
- expect(merged.excluded_names).toEqual(expect.arrayContaining(["node_modules", ".git", "dist"]));
204
- expect(merged.excluded_names?.length).toBe(3);
205
- });
206
- it("drops profile fields entirely", () => {
207
- const merged = mergeOrthogonalFields({ codex: { model: "gpt-5.4" }, output_language: "ko" }, { anthropic: { model: "claude-haiku-4-5" }, review_mode: "full" });
208
- expect(merged.codex).toBeUndefined();
209
- expect(merged.anthropic).toBeUndefined();
210
- expect(merged.output_language).toBe("ko");
211
- expect(merged.review_mode).toBe("full");
212
- });
213
- it("passes review block through as orthogonal", () => {
214
- const merged = mergeOrthogonalFields({ review: { subagent: { provider: "codex", model_id: "gpt-5.4" } } }, { review: { subagent: { provider: "main-native" } } });
215
- // Last-wins: project overrides home.
216
- expect(merged.review).toEqual({ subagent: { provider: "main-native" } });
217
- });
218
- });
219
- // ─── Regression: migrated Review UX Redesign config ───
220
- describe("regression — migrated Review UX Redesign config", () => {
221
- it("project declares review block + codex block → project owns", () => {
222
- const adoption = adoptProfile(buildArgs({}, // empty global
223
- {
224
- review: {
225
- subagent: { provider: "codex", model_id: "gpt-5.4", effort: "medium" },
226
- },
227
- codex: { model: "gpt-5.4", effort: "medium" },
228
- review_mode: "core-axis",
229
- }));
230
- expect(adoption.source).toBe("project");
231
- expect(adoption.profile.codex).toEqual({ model: "gpt-5.4", effort: "medium" });
232
- });
233
- });
@@ -1,186 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
- import { detectHostRuntime, detectHostRuntimeCategory, detectHostCapabilities, detectClaudeCodeEnvSignal, detectCodexEnvSignal, isClaudeCodeHost, isCodexHost, ENV_ONTO_HOST_RUNTIME, } from "./host-detection.js";
3
- const HOST_ENV_VARS = [
4
- ENV_ONTO_HOST_RUNTIME,
5
- "CLAUDECODE",
6
- "CLAUDE_PROJECT_DIR",
7
- "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS",
8
- "CODEX_THREAD_ID",
9
- "CODEX_CI",
10
- "ANTHROPIC_API_KEY",
11
- "OPENAI_API_KEY",
12
- "LITELLM_BASE_URL",
13
- ];
14
- let savedEnv = {};
15
- function savePotentiallyConflictingEnv() {
16
- savedEnv = {};
17
- for (const key of HOST_ENV_VARS) {
18
- savedEnv[key] = process.env[key];
19
- delete process.env[key];
20
- }
21
- // Make PATH effectively empty so codex binary detection cannot
22
- // accidentally trigger on the test runner's shell PATH.
23
- savedEnv.PATH = process.env.PATH;
24
- process.env.PATH = "";
25
- }
26
- function restoreEnv() {
27
- for (const [key, value] of Object.entries(savedEnv)) {
28
- if (value === undefined) {
29
- delete process.env[key];
30
- }
31
- else {
32
- process.env[key] = value;
33
- }
34
- }
35
- }
36
- beforeEach(() => {
37
- savePotentiallyConflictingEnv();
38
- });
39
- afterEach(() => {
40
- restoreEnv();
41
- });
42
- describe("detectHostRuntime — priority order", () => {
43
- it("Priority 1: ONTO_HOST_RUNTIME env override wins over all signals", () => {
44
- process.env[ENV_ONTO_HOST_RUNTIME] = "standalone";
45
- process.env.CLAUDECODE = "1";
46
- process.env.CODEX_THREAD_ID = "abc";
47
- const result = detectHostRuntime({ host_runtime: "claude" });
48
- expect(result.hostRuntime).toBe("standalone");
49
- expect(result.detectionSource).toBe("env_override");
50
- });
51
- it("Priority 2: config.host_runtime wins over env signals (no env override)", () => {
52
- process.env.CLAUDECODE = "1";
53
- process.env.CODEX_THREAD_ID = "abc";
54
- const result = detectHostRuntime({ host_runtime: "codex" });
55
- expect(result.hostRuntime).toBe("codex");
56
- expect(result.detectionSource).toBe("config_override");
57
- });
58
- it("Priority 3: Claude env signal → claude when no override", () => {
59
- process.env.CLAUDECODE = "1";
60
- const result = detectHostRuntime({});
61
- expect(result.hostRuntime).toBe("claude");
62
- expect(result.detectionSource).toBe("claude_env_signal");
63
- });
64
- it("Priority 4: Codex env signal → codex when no Claude signal", () => {
65
- process.env.CODEX_THREAD_ID = "thread-123";
66
- const result = detectHostRuntime({});
67
- expect(result.hostRuntime).toBe("codex");
68
- expect(result.detectionSource).toBe("codex_env_signal");
69
- });
70
- it("Priority 6: standalone default when no signals", () => {
71
- const result = detectHostRuntime({});
72
- expect(result.hostRuntime).toBe("standalone");
73
- expect(result.detectionSource).toBe("standalone_default");
74
- });
75
- it("ONTO_HOST_RUNTIME accepts case-insensitive values", () => {
76
- process.env[ENV_ONTO_HOST_RUNTIME] = " CLAUDE ";
77
- const result = detectHostRuntime({});
78
- expect(result.hostRuntime).toBe("claude");
79
- expect(result.detectionSource).toBe("env_override");
80
- });
81
- it("ONTO_HOST_RUNTIME with invalid value is ignored, falls through to next priority", () => {
82
- process.env[ENV_ONTO_HOST_RUNTIME] = "invalid_host";
83
- process.env.CLAUDECODE = "1";
84
- const result = detectHostRuntime({});
85
- expect(result.hostRuntime).toBe("claude");
86
- expect(result.detectionSource).toBe("claude_env_signal");
87
- });
88
- });
89
- describe("detectHostRuntime — Claude env signal variants", () => {
90
- it("CLAUDECODE alone triggers claude", () => {
91
- process.env.CLAUDECODE = "1";
92
- expect(detectHostRuntimeCategory({})).toBe("claude");
93
- });
94
- it("CLAUDE_PROJECT_DIR alone triggers claude", () => {
95
- process.env.CLAUDE_PROJECT_DIR = "/some/project";
96
- expect(detectHostRuntimeCategory({})).toBe("claude");
97
- });
98
- it("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS alone triggers claude", () => {
99
- process.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = "1";
100
- expect(detectHostRuntimeCategory({})).toBe("claude");
101
- });
102
- it("Claude takes priority over codex when both env signals present", () => {
103
- process.env.CLAUDECODE = "1";
104
- process.env.CODEX_THREAD_ID = "thread-x";
105
- expect(detectHostRuntimeCategory({})).toBe("claude");
106
- });
107
- });
108
- describe("detectHostCapabilities — capability matrix", () => {
109
- it("claude host: hasTeamCreate + hasAgentSpawn = true", () => {
110
- const caps = detectHostCapabilities("claude");
111
- expect(caps.hasTeamCreate).toBe(true);
112
- expect(caps.hasAgentSpawn).toBe(true);
113
- });
114
- it("codex host: hasTeamCreate + hasAgentSpawn = false", () => {
115
- const caps = detectHostCapabilities("codex");
116
- expect(caps.hasTeamCreate).toBe(false);
117
- expect(caps.hasAgentSpawn).toBe(false);
118
- });
119
- it("standalone host: hasTeamCreate + hasAgentSpawn = false", () => {
120
- const caps = detectHostCapabilities("standalone");
121
- expect(caps.hasTeamCreate).toBe(false);
122
- expect(caps.hasAgentSpawn).toBe(false);
123
- });
124
- it("LLM provider capabilities are environmental, independent of host", () => {
125
- process.env.ANTHROPIC_API_KEY = "sk-ant-test";
126
- process.env.LITELLM_BASE_URL = "http://localhost:4000/v1";
127
- const claudeCaps = detectHostCapabilities("claude");
128
- const standaloneCaps = detectHostCapabilities("standalone");
129
- expect(claudeCaps.hasAnthropicApiKey).toBe(true);
130
- expect(claudeCaps.hasLiteLlmEndpoint).toBe(true);
131
- expect(standaloneCaps.hasAnthropicApiKey).toBe(true);
132
- expect(standaloneCaps.hasLiteLlmEndpoint).toBe(true);
133
- });
134
- it("Cross-host LLM combo: Claude main + LiteLLM subagent (capability indicates feasible)", () => {
135
- process.env.CLAUDECODE = "1";
136
- process.env.LITELLM_BASE_URL = "http://localhost:4000/v1";
137
- const result = detectHostRuntime({});
138
- expect(result.hostRuntime).toBe("claude");
139
- expect(result.capabilities.hasTeamCreate).toBe(true); // main can orchestrate
140
- expect(result.capabilities.hasLiteLlmEndpoint).toBe(true); // subagent can use LiteLLM
141
- });
142
- });
143
- describe("Low-level signal detectors", () => {
144
- it("detectClaudeCodeEnvSignal: returns false when no Claude env vars", () => {
145
- expect(detectClaudeCodeEnvSignal()).toBe(false);
146
- });
147
- it("detectClaudeCodeEnvSignal: returns true on any of the 3 signals", () => {
148
- process.env.CLAUDECODE = "1";
149
- expect(detectClaudeCodeEnvSignal()).toBe(true);
150
- delete process.env.CLAUDECODE;
151
- process.env.CLAUDE_PROJECT_DIR = "/x";
152
- expect(detectClaudeCodeEnvSignal()).toBe(true);
153
- });
154
- it("detectCodexEnvSignal: returns false when no codex env", () => {
155
- expect(detectCodexEnvSignal()).toBe(false);
156
- });
157
- it("detectCodexEnvSignal: returns true on CODEX_THREAD_ID", () => {
158
- process.env.CODEX_THREAD_ID = "x";
159
- expect(detectCodexEnvSignal()).toBe(true);
160
- });
161
- it("detectCodexEnvSignal: returns true on CODEX_CI", () => {
162
- process.env.CODEX_CI = "1";
163
- expect(detectCodexEnvSignal()).toBe(true);
164
- });
165
- });
166
- describe("Backward-compat shims", () => {
167
- it("isClaudeCodeHost returns true when CLAUDECODE is set", () => {
168
- process.env.CLAUDECODE = "1";
169
- expect(isClaudeCodeHost({})).toBe(true);
170
- });
171
- it("isClaudeCodeHost returns false in standalone mode", () => {
172
- expect(isClaudeCodeHost({})).toBe(false);
173
- });
174
- it("isCodexHost returns true when CODEX_THREAD_ID is set", () => {
175
- process.env.CODEX_THREAD_ID = "x";
176
- expect(isCodexHost({})).toBe(true);
177
- });
178
- it("isCodexHost returns false in standalone mode", () => {
179
- expect(isCodexHost({})).toBe(false);
180
- });
181
- it("env override beats env signal in shim", () => {
182
- process.env[ENV_ONTO_HOST_RUNTIME] = "standalone";
183
- process.env.CLAUDECODE = "1";
184
- expect(isClaudeCodeHost({})).toBe(false);
185
- });
186
- });
@@ -1,65 +0,0 @@
1
- import fs from "node:fs";
2
- import os from "node:os";
3
- import path from "node:path";
4
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
- import { __resetInstallationPathCacheForTesting, resolveInstallationPath, } from "./installation-paths.js";
6
- function makeTempInstallRoot() {
7
- return fs.mkdtempSync(path.join(os.tmpdir(), "onto-install-paths-"));
8
- }
9
- describe("resolveInstallationPath — canonical-only resolver (Phase 7)", () => {
10
- const tmpRoots = [];
11
- beforeEach(() => {
12
- __resetInstallationPathCacheForTesting();
13
- });
14
- afterEach(() => {
15
- __resetInstallationPathCacheForTesting();
16
- for (const root of tmpRoots) {
17
- try {
18
- fs.rmSync(root, { recursive: true, force: true });
19
- }
20
- catch {
21
- // ignore
22
- }
23
- }
24
- tmpRoots.length = 0;
25
- });
26
- it("returns .onto/{kind}/ when the canonical layout exists", () => {
27
- const installRoot = makeTempInstallRoot();
28
- tmpRoots.push(installRoot);
29
- const canonical = path.join(installRoot, ".onto", "authority");
30
- fs.mkdirSync(canonical, { recursive: true });
31
- expect(resolveInstallationPath("authority", installRoot)).toBe(canonical);
32
- });
33
- it("throws when the canonical layout is missing", () => {
34
- const installRoot = makeTempInstallRoot();
35
- tmpRoots.push(installRoot);
36
- expect(() => resolveInstallationPath("commands", installRoot)).toThrow(/\.onto\/commands\/ not found/);
37
- });
38
- it("ignores a bare top-level dir that matches the kind name (pre-Phase-6 legacy layout)", () => {
39
- // A project still on the pre-migration layout (e.g., legacy `authority/`
40
- // at repo root) must NOT be recognized by this resolver in Phase 7+.
41
- // Error message should nudge the operator toward the migration script.
42
- const installRoot = makeTempInstallRoot();
43
- tmpRoots.push(installRoot);
44
- fs.mkdirSync(path.join(installRoot, "authority"), { recursive: true });
45
- expect(() => resolveInstallationPath("authority", installRoot)).toThrow(/\.onto\/authority\/ not found/);
46
- expect(() => resolveInstallationPath("authority", installRoot)).toThrow(/repo-layout-migration-replace\.py/);
47
- });
48
- it("caches resolved paths (same kind / installRoot returns identical string)", () => {
49
- const installRoot = makeTempInstallRoot();
50
- tmpRoots.push(installRoot);
51
- fs.mkdirSync(path.join(installRoot, ".onto", "domains"), { recursive: true });
52
- const first = resolveInstallationPath("domains", installRoot);
53
- const second = resolveInstallationPath("domains", installRoot);
54
- expect(first).toBe(second);
55
- });
56
- it("cache reset helper allows swapping fixtures across tests", () => {
57
- const installRoot = makeTempInstallRoot();
58
- tmpRoots.push(installRoot);
59
- fs.mkdirSync(path.join(installRoot, ".onto", "roles"), { recursive: true });
60
- resolveInstallationPath("roles", installRoot);
61
- __resetInstallationPathCacheForTesting();
62
- // Second resolve must succeed after a cache flush.
63
- expect(resolveInstallationPath("roles", installRoot)).toBe(path.join(installRoot, ".onto", "roles"));
64
- });
65
- });