onto-mcp 0.3.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.
- package/.onto/authority/core-lens-registry.yaml +134 -0
- package/.onto/authority/core-lexicon.yaml +1562 -0
- package/.onto/authority/diagnostic-codes.yaml +94 -0
- package/.onto/domains/accounting/competency_qs.md +384 -0
- package/.onto/domains/accounting/concepts.md +186 -0
- package/.onto/domains/accounting/conciseness_rules.md +160 -0
- package/.onto/domains/accounting/dependency_rules.md +239 -0
- package/.onto/domains/accounting/domain_scope.md +213 -0
- package/.onto/domains/accounting/extension_cases.md +416 -0
- package/.onto/domains/accounting/logic_rules.md +226 -0
- package/.onto/domains/accounting/structure_spec.md +298 -0
- package/.onto/domains/accounting-kr/competency_qs.md +562 -0
- package/.onto/domains/accounting-kr/concepts.md +187 -0
- package/.onto/domains/accounting-kr/conciseness_rules.md +125 -0
- package/.onto/domains/accounting-kr/dependency_rules.md +93 -0
- package/.onto/domains/accounting-kr/domain_scope.md +140 -0
- package/.onto/domains/accounting-kr/extension_cases.md +343 -0
- package/.onto/domains/accounting-kr/logic_rules.md +160 -0
- package/.onto/domains/accounting-kr/structure_spec.md +85 -0
- package/.onto/domains/business/competency_qs.md +263 -0
- package/.onto/domains/business/concepts.md +200 -0
- package/.onto/domains/business/conciseness_rules.md +135 -0
- package/.onto/domains/business/dependency_rules.md +113 -0
- package/.onto/domains/business/domain_scope.md +240 -0
- package/.onto/domains/business/extension_cases.md +249 -0
- package/.onto/domains/business/logic_rules.md +134 -0
- package/.onto/domains/business/structure_spec.md +114 -0
- package/.onto/domains/finance/competency_qs.md +362 -0
- package/.onto/domains/finance/concepts.md +194 -0
- package/.onto/domains/finance/conciseness_rules.md +155 -0
- package/.onto/domains/finance/dependency_rules.md +171 -0
- package/.onto/domains/finance/domain_scope.md +215 -0
- package/.onto/domains/finance/extension_cases.md +350 -0
- package/.onto/domains/finance/logic_rules.md +191 -0
- package/.onto/domains/finance/structure_spec.md +182 -0
- package/.onto/domains/llm-native-development/competency_qs.md +430 -0
- package/.onto/domains/llm-native-development/concepts.md +242 -0
- package/.onto/domains/llm-native-development/conciseness_rules.md +163 -0
- package/.onto/domains/llm-native-development/dependency_rules.md +216 -0
- package/.onto/domains/llm-native-development/domain_scope.md +197 -0
- package/.onto/domains/llm-native-development/extension_cases.md +474 -0
- package/.onto/domains/llm-native-development/logic_rules.md +123 -0
- package/.onto/domains/llm-native-development/prompt_interface.md +49 -0
- package/.onto/domains/llm-native-development/structure_spec.md +245 -0
- package/.onto/domains/market-intelligence/competency_qs.md +274 -0
- package/.onto/domains/market-intelligence/concepts.md +233 -0
- package/.onto/domains/market-intelligence/conciseness_rules.md +165 -0
- package/.onto/domains/market-intelligence/dependency_rules.md +197 -0
- package/.onto/domains/market-intelligence/domain_scope.md +231 -0
- package/.onto/domains/market-intelligence/extension_cases.md +425 -0
- package/.onto/domains/market-intelligence/logic_rules.md +247 -0
- package/.onto/domains/market-intelligence/structure_spec.md +209 -0
- package/.onto/domains/ontology/competency_qs.md +394 -0
- package/.onto/domains/ontology/concepts.md +172 -0
- package/.onto/domains/ontology/conciseness_rules.md +134 -0
- package/.onto/domains/ontology/dependency_rules.md +125 -0
- package/.onto/domains/ontology/domain_scope.md +114 -0
- package/.onto/domains/ontology/extension_cases.md +501 -0
- package/.onto/domains/ontology/logic_rules.md +114 -0
- package/.onto/domains/ontology/problem_framing_profile.md +67 -0
- package/.onto/domains/ontology/structure_spec.md +115 -0
- package/.onto/domains/palantir-foundry/RESEARCH_NOTES.md +911 -0
- package/.onto/domains/palantir-foundry/competency_qs.md +191 -0
- package/.onto/domains/palantir-foundry/competitive_comparison.md +329 -0
- package/.onto/domains/palantir-foundry/concepts.md +197 -0
- package/.onto/domains/palantir-foundry/conciseness_rules.md +245 -0
- package/.onto/domains/palantir-foundry/dependency_rules.md +135 -0
- package/.onto/domains/palantir-foundry/domain_scope.md +395 -0
- package/.onto/domains/palantir-foundry/extension_cases.md +210 -0
- package/.onto/domains/palantir-foundry/logic_rules.md +172 -0
- package/.onto/domains/palantir-foundry/structure_spec.md +291 -0
- package/.onto/domains/software-engineering/competency_qs.md +538 -0
- package/.onto/domains/software-engineering/concepts.md +238 -0
- package/.onto/domains/software-engineering/conciseness_rules.md +167 -0
- package/.onto/domains/software-engineering/dependency_rules.md +216 -0
- package/.onto/domains/software-engineering/domain_scope.md +183 -0
- package/.onto/domains/software-engineering/extension_cases.md +551 -0
- package/.onto/domains/software-engineering/logic_rules.md +240 -0
- package/.onto/domains/software-engineering/problem_framing_profile.md +68 -0
- package/.onto/domains/software-engineering/structure_spec.md +185 -0
- package/.onto/domains/ui-design/competency_qs.md +567 -0
- package/.onto/domains/ui-design/concepts.md +194 -0
- package/.onto/domains/ui-design/conciseness_rules.md +190 -0
- package/.onto/domains/ui-design/dependency_rules.md +323 -0
- package/.onto/domains/ui-design/domain_scope.md +340 -0
- package/.onto/domains/ui-design/extension_cases.md +563 -0
- package/.onto/domains/ui-design/logic_rules.md +349 -0
- package/.onto/domains/ui-design/structure_spec.md +252 -0
- package/.onto/domains/visual-design/competency_qs.md +472 -0
- package/.onto/domains/visual-design/concepts.md +147 -0
- package/.onto/domains/visual-design/conciseness_rules.md +186 -0
- package/.onto/domains/visual-design/dependency_rules.md +282 -0
- package/.onto/domains/visual-design/domain_scope.md +290 -0
- package/.onto/domains/visual-design/extension_cases.md +480 -0
- package/.onto/domains/visual-design/logic_rules.md +232 -0
- package/.onto/domains/visual-design/structure_spec.md +213 -0
- package/.onto/principles/llm-native-development-guideline.md +401 -0
- package/.onto/principles/llm-runtime-interface-principles.md +665 -0
- package/.onto/principles/non-specialist-communication-guideline.md +74 -0
- package/.onto/principles/ontology-as-code-guideline.md +243 -0
- package/.onto/principles/ontology-as-code-naming-charter.md +130 -0
- package/.onto/principles/product-locality-principle.md +129 -0
- package/.onto/principles/productization-charter.md +569 -0
- package/.onto/processes/evolve/material-kind-adapter-contract.md +113 -0
- package/.onto/processes/reconstruct/reconstruct-boundary-contract.md +366 -0
- package/.onto/processes/reconstruct/source-profile-contract.md +107 -0
- package/.onto/processes/reconstruct/source-profiles/code.md +72 -0
- package/.onto/processes/reconstruct/source-profiles/database.md +74 -0
- package/.onto/processes/reconstruct/source-profiles/document.md +71 -0
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +79 -0
- package/.onto/processes/review/binding-contract.md +270 -0
- package/.onto/processes/review/execution-preparation-artifacts.md +281 -0
- package/.onto/processes/review/interpretation-contract.md +245 -0
- package/.onto/processes/review/issue-stance-deliberation-contract.md +761 -0
- package/.onto/processes/review/lens-prompt-contract.md +402 -0
- package/.onto/processes/review/lens-registry.md +127 -0
- package/.onto/processes/review/pre-dispatch-contracts.md +428 -0
- package/.onto/processes/review/productized-live-path.md +398 -0
- package/.onto/processes/review/prompt-execution-runner-contract.md +187 -0
- package/.onto/processes/review/record-contract.md +427 -0
- package/.onto/processes/review/record-field-mapping.md +337 -0
- package/.onto/processes/review/review-context-manifest-contract.md +356 -0
- package/.onto/processes/review/review-execution-ux-contract.md +809 -0
- package/.onto/processes/review/review-target-profile-contract.md +259 -0
- package/.onto/processes/review/shared-phenomenon-contract.md +129 -0
- package/.onto/processes/review/synthesize-prompt-contract.md +343 -0
- package/.onto/processes/shared/target-material-kind-contract.md +198 -0
- package/.onto/roles/axiology.md +81 -0
- package/.onto/roles/conciseness.md +36 -0
- package/.onto/roles/coverage.md +34 -0
- package/.onto/roles/dependency.md +37 -0
- package/.onto/roles/evolution.md +35 -0
- package/.onto/roles/logic.md +104 -0
- package/.onto/roles/pragmatics.md +32 -0
- package/.onto/roles/semantics.md +36 -0
- package/.onto/roles/structure.md +33 -0
- package/.onto/roles/synthesize.md +92 -0
- package/AGENTS.md +240 -0
- package/CLAUDE.md +39 -0
- package/README.md +287 -0
- package/bin/onto +92 -0
- package/dist/cli.js +101 -0
- package/dist/core-api/reconstruct-api.js +222 -0
- package/dist/core-api/review-api.js +1271 -0
- package/dist/core-runtime/cli/assemble-review-record.js +431 -0
- package/dist/core-runtime/cli/bootstrap-review-binding.js +186 -0
- package/dist/core-runtime/cli/codex-nested-dispatch.js +226 -0
- package/dist/core-runtime/cli/codex-nested-dispatch.test.js +390 -0
- package/dist/core-runtime/cli/codex-nested-teamlead-executor.js +464 -0
- package/dist/core-runtime/cli/codex-nested-teamlead-executor.test.js +335 -0
- package/dist/core-runtime/cli/codex-review-unit-executor.js +228 -0
- package/dist/core-runtime/cli/complete-review-session.js +64 -0
- package/dist/core-runtime/cli/complexity-assessment.js +153 -0
- package/dist/core-runtime/cli/coordinator-helpers.js +583 -0
- package/dist/core-runtime/cli/coordinator-state-machine-deliberation.test.js +167 -0
- package/dist/core-runtime/cli/coordinator-state-machine.js +794 -0
- package/dist/core-runtime/cli/e2e-codex-multi-agent-fixes.test.js +615 -0
- package/dist/core-runtime/cli/e2e-start-review-session.test.js +312 -0
- package/dist/core-runtime/cli/health.js +44 -0
- package/dist/core-runtime/cli/inline-http-review-unit-executor.js +656 -0
- package/dist/core-runtime/cli/inline-http-review-unit-executor.test.js +567 -0
- package/dist/core-runtime/cli/materialize-review-execution-preparation.js +104 -0
- package/dist/core-runtime/cli/materialize-review-prompt-packets.js +952 -0
- package/dist/core-runtime/cli/migrate-session-roots.js +118 -0
- package/dist/core-runtime/cli/mock-review-unit-executor.js +285 -0
- package/dist/core-runtime/cli/onto-tools.js +369 -0
- package/dist/core-runtime/cli/prepare-review-session.js +272 -0
- package/dist/core-runtime/cli/render-review-final-output.js +350 -0
- package/dist/core-runtime/cli/repo-layout-migration-replace.smoke.test.js +106 -0
- package/dist/core-runtime/cli/review-invoke-auto-resolution.test.js +268 -0
- package/dist/core-runtime/cli/review-invoke-coordinator-topology.test.js +136 -0
- package/dist/core-runtime/cli/review-invoke-resolver-caching.test.js +201 -0
- package/dist/core-runtime/cli/review-invoke-topology-dispatch.test.js +192 -0
- package/dist/core-runtime/cli/review-invoke.js +2030 -0
- package/dist/core-runtime/cli/run-review-prompt-execution.js +2152 -0
- package/dist/core-runtime/cli/session-root-guard.js +168 -0
- package/dist/core-runtime/cli/spawn-watcher.js +173 -0
- package/dist/core-runtime/cli/spawn-watcher.test.js +457 -0
- package/dist/core-runtime/cli/start-review-session.js +68 -0
- package/dist/core-runtime/cli/strip-wrapping-code-fence.js +56 -0
- package/dist/core-runtime/cli/strip-wrapping-code-fence.test.js +79 -0
- package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.js +412 -0
- package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.test.js +351 -0
- package/dist/core-runtime/cli/topology-executor-mapping.js +139 -0
- package/dist/core-runtime/cli/topology-executor-mapping.test.js +173 -0
- package/dist/core-runtime/cli/write-review-interpretation.js +81 -0
- package/dist/core-runtime/config/onto-config-cli.js +278 -0
- package/dist/core-runtime/config/onto-config-key-path.js +288 -0
- package/dist/core-runtime/config/onto-config-key-path.test.js +195 -0
- package/dist/core-runtime/config/onto-config-preview.js +108 -0
- package/dist/core-runtime/config/onto-config-preview.test.js +132 -0
- package/dist/core-runtime/discovery/config-chain.js +118 -0
- package/dist/core-runtime/discovery/config-chain.test.js +103 -0
- package/dist/core-runtime/discovery/config-profile.js +199 -0
- package/dist/core-runtime/discovery/config-profile.test.js +233 -0
- package/dist/core-runtime/discovery/host-detection.js +33 -0
- package/dist/core-runtime/discovery/host-detection.test.js +186 -0
- package/dist/core-runtime/discovery/installation-paths.js +21 -0
- package/dist/core-runtime/discovery/installation-paths.test.js +65 -0
- package/dist/core-runtime/discovery/lens-registry.js +60 -0
- package/dist/core-runtime/discovery/lens-registry.test.js +81 -0
- package/dist/core-runtime/discovery/onto-home.js +71 -0
- package/dist/core-runtime/discovery/path-normalization.js +28 -0
- package/dist/core-runtime/discovery/path-normalization.test.js +22 -0
- package/dist/core-runtime/discovery/plugin-path.js +72 -0
- package/dist/core-runtime/discovery/plugin-path.test.js +95 -0
- package/dist/core-runtime/discovery/project-root.js +47 -0
- package/dist/core-runtime/discovery/settings-chain.js +353 -0
- package/dist/core-runtime/discovery/walk-up.js +17 -0
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.js +344 -0
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.test.js +915 -0
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile.js +564 -0
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile.test.js +708 -0
- package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.js +165 -0
- package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.test.js +227 -0
- package/dist/core-runtime/evolve/adapters/code-product/validators/validate.js +59 -0
- package/dist/core-runtime/evolve/adapters/code-product/validators/validate.test.js +205 -0
- package/dist/core-runtime/evolve/adapters/methodology/adapter.js +16 -0
- package/dist/core-runtime/evolve/adapters/methodology/adapter.test.js +9 -0
- package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.js +298 -0
- package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.test.js +70 -0
- package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.js +46 -0
- package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.test.js +73 -0
- package/dist/core-runtime/evolve/adapters/registry.js +47 -0
- package/dist/core-runtime/evolve/adapters/registry.test.js +67 -0
- package/dist/core-runtime/evolve/cli.js +256 -0
- package/dist/core-runtime/evolve/commands/align.js +194 -0
- package/dist/core-runtime/evolve/commands/align.test.js +82 -0
- package/dist/core-runtime/evolve/commands/apply.js +161 -0
- package/dist/core-runtime/evolve/commands/apply.test.js +138 -0
- package/dist/core-runtime/evolve/commands/close.js +39 -0
- package/dist/core-runtime/evolve/commands/close.test.js +99 -0
- package/dist/core-runtime/evolve/commands/defer.js +40 -0
- package/dist/core-runtime/evolve/commands/defer.test.js +134 -0
- package/dist/core-runtime/evolve/commands/draft.js +323 -0
- package/dist/core-runtime/evolve/commands/draft.test.js +178 -0
- package/dist/core-runtime/evolve/commands/e2e-evolve-full-cycle.test.js +208 -0
- package/dist/core-runtime/evolve/commands/error-messages.js +125 -0
- package/dist/core-runtime/evolve/commands/error-messages.test.js +167 -0
- package/dist/core-runtime/evolve/commands/propose-align.js +222 -0
- package/dist/core-runtime/evolve/commands/propose-align.test.js +136 -0
- package/dist/core-runtime/evolve/commands/reconstruct.js +330 -0
- package/dist/core-runtime/evolve/commands/reconstruct.test.js +278 -0
- package/dist/core-runtime/evolve/commands/shared.js +22 -0
- package/dist/core-runtime/evolve/commands/stale-check.js +103 -0
- package/dist/core-runtime/evolve/commands/stale-check.test.js +84 -0
- package/dist/core-runtime/evolve/commands/start.js +887 -0
- package/dist/core-runtime/evolve/commands/start.test.js +396 -0
- package/dist/core-runtime/evolve/config/project-config.js +99 -0
- package/dist/core-runtime/evolve/config/project-config.test.js +170 -0
- package/dist/core-runtime/evolve/renderers/align-packet.js +280 -0
- package/dist/core-runtime/evolve/renderers/align-packet.test.js +332 -0
- package/dist/core-runtime/evolve/renderers/draft-packet.js +303 -0
- package/dist/core-runtime/evolve/renderers/draft-packet.test.js +377 -0
- package/dist/core-runtime/evolve/renderers/format.js +5 -0
- package/dist/core-runtime/evolve/renderers/scope-md.js +237 -0
- package/dist/core-runtime/evolve/renderers/scope-md.test.js +306 -0
- package/dist/core-runtime/govern/cli.js +369 -0
- package/dist/core-runtime/govern/cli.test.js +314 -0
- package/dist/core-runtime/govern/drift-engine.js +103 -0
- package/dist/core-runtime/govern/drift-engine.test.js +319 -0
- package/dist/core-runtime/govern/promote-principle.js +206 -0
- package/dist/core-runtime/govern/promote-principle.test.js +368 -0
- package/dist/core-runtime/govern/queue.js +81 -0
- package/dist/core-runtime/govern/types.js +16 -0
- package/dist/core-runtime/install/cli.js +530 -0
- package/dist/core-runtime/install/detect.js +128 -0
- package/dist/core-runtime/install/detect.test.js +155 -0
- package/dist/core-runtime/install/gitignore-update.js +74 -0
- package/dist/core-runtime/install/gitignore-update.test.js +64 -0
- package/dist/core-runtime/install/install-integration.test.js +373 -0
- package/dist/core-runtime/install/prompts.js +389 -0
- package/dist/core-runtime/install/prompts.test.js +293 -0
- package/dist/core-runtime/install/types.js +26 -0
- package/dist/core-runtime/install/validation.js +295 -0
- package/dist/core-runtime/install/validation.test.js +313 -0
- package/dist/core-runtime/install/writer.js +254 -0
- package/dist/core-runtime/install/writer.test.js +218 -0
- package/dist/core-runtime/learning/extractor.js +461 -0
- package/dist/core-runtime/learning/feedback.js +179 -0
- package/dist/core-runtime/learning/health-report.js +165 -0
- package/dist/core-runtime/learning/health-report.test.js +169 -0
- package/dist/core-runtime/learning/loader.js +388 -0
- package/dist/core-runtime/learning/loader.test.js +102 -0
- package/dist/core-runtime/learning/promote/apply-state.js +240 -0
- package/dist/core-runtime/learning/promote/audit-obligation.js +195 -0
- package/dist/core-runtime/learning/promote/collector.js +432 -0
- package/dist/core-runtime/learning/promote/degraded-state.js +125 -0
- package/dist/core-runtime/learning/promote/domain-doc-proposer.js +166 -0
- package/dist/core-runtime/learning/promote/e2e-promote.test.js +6385 -0
- package/dist/core-runtime/learning/promote/health-snapshot.js +150 -0
- package/dist/core-runtime/learning/promote/insight-reclassifier.js +544 -0
- package/dist/core-runtime/learning/promote/judgment-auditor.js +517 -0
- package/dist/core-runtime/learning/promote/panel-reviewer.js +1158 -0
- package/dist/core-runtime/learning/promote/promote-executor.js +1675 -0
- package/dist/core-runtime/learning/promote/promoter.js +307 -0
- package/dist/core-runtime/learning/promote/retirement.js +122 -0
- package/dist/core-runtime/learning/promote/types.js +23 -0
- package/dist/core-runtime/learning/prompt-sections.js +51 -0
- package/dist/core-runtime/learning/shared/artifact-registry-init.js +45 -0
- package/dist/core-runtime/learning/shared/artifact-registry.js +254 -0
- package/dist/core-runtime/learning/shared/audit-obligation-kernel.js +73 -0
- package/dist/core-runtime/learning/shared/audit-state.js +99 -0
- package/dist/core-runtime/learning/shared/duplicate-check.js +28 -0
- package/dist/core-runtime/learning/shared/llm-caller.js +831 -0
- package/dist/core-runtime/learning/shared/llm-caller.test.js +601 -0
- package/dist/core-runtime/learning/shared/llm-tool-loop.js +393 -0
- package/dist/core-runtime/learning/shared/mode.js +25 -0
- package/dist/core-runtime/learning/shared/paths.js +84 -0
- package/dist/core-runtime/learning/shared/paths.test.js +79 -0
- package/dist/core-runtime/learning/shared/patterns.js +37 -0
- package/dist/core-runtime/learning/shared/recoverability.js +355 -0
- package/dist/core-runtime/learning/shared/recovery-context.js +374 -0
- package/dist/core-runtime/learning/shared/scope.js +1 -0
- package/dist/core-runtime/learning/shared/semantic-classifier.js +94 -0
- package/dist/core-runtime/learning/shared/specs/apply-execution-state-spec.js +42 -0
- package/dist/core-runtime/learning/shared/specs/audit-state-spec.js +37 -0
- package/dist/core-runtime/learning/shared/specs/backup-metadata-spec.js +39 -0
- package/dist/core-runtime/learning/shared/specs/emergency-log-spec.js +41 -0
- package/dist/core-runtime/learning/shared/specs/layout-version-spec.js +38 -0
- package/dist/core-runtime/learning/shared/specs/promote-decisions-spec.js +43 -0
- package/dist/core-runtime/learning/shared/specs/promote-report-spec.js +113 -0
- package/dist/core-runtime/learning/shared/specs/prune-log-spec.js +36 -0
- package/dist/core-runtime/learning/shared/specs/recovery-resolution-spec.js +48 -0
- package/dist/core-runtime/learning/shared/specs/restore-manifest-spec.js +43 -0
- package/dist/core-runtime/learning/shared/specs/spec-helpers.js +64 -0
- package/dist/core-runtime/learning/usage-tracker.js +190 -0
- package/dist/core-runtime/learning/usage-tracker.test.js +176 -0
- package/dist/core-runtime/llm/llm-caller.js +649 -0
- package/dist/core-runtime/llm/llm-tool-loop.js +401 -0
- package/dist/core-runtime/llm/model-switcher.js +62 -0
- package/dist/core-runtime/logger.js +22 -0
- package/dist/core-runtime/onboard/detect-review-axes.js +122 -0
- package/dist/core-runtime/onboard/detect-review-axes.test.js +127 -0
- package/dist/core-runtime/onboard/write-review-block.js +188 -0
- package/dist/core-runtime/onboard/write-review-block.test.js +240 -0
- package/dist/core-runtime/readers/brownfield-builder.js +150 -0
- package/dist/core-runtime/readers/brownfield-builder.test.js +136 -0
- package/dist/core-runtime/readers/code-chunk-collector.js +53 -0
- package/dist/core-runtime/readers/code-chunk-collector.test.js +136 -0
- package/dist/core-runtime/readers/file-utils.js +240 -0
- package/dist/core-runtime/readers/file-utils.test.js +146 -0
- package/dist/core-runtime/readers/lexicon-citation-check.js +93 -0
- package/dist/core-runtime/readers/lexicon-citation-check.test.js +77 -0
- package/dist/core-runtime/readers/mcp-figma.js +30 -0
- package/dist/core-runtime/readers/mcp-figma.test.js +82 -0
- package/dist/core-runtime/readers/mcp-generic.js +31 -0
- package/dist/core-runtime/readers/mcp-generic.test.js +76 -0
- package/dist/core-runtime/readers/ontology-index.js +148 -0
- package/dist/core-runtime/readers/ontology-index.test.js +245 -0
- package/dist/core-runtime/readers/ontology-query.js +168 -0
- package/dist/core-runtime/readers/ontology-query.test.js +311 -0
- package/dist/core-runtime/readers/ontology-resolve.js +48 -0
- package/dist/core-runtime/readers/ontology-resolve.test.js +48 -0
- package/dist/core-runtime/readers/patterns/index.js +7 -0
- package/dist/core-runtime/readers/review-log.js +213 -0
- package/dist/core-runtime/readers/review-log.test.js +313 -0
- package/dist/core-runtime/readers/scan-local.js +102 -0
- package/dist/core-runtime/readers/scan-local.test.js +102 -0
- package/dist/core-runtime/readers/scan-tarball.js +121 -0
- package/dist/core-runtime/readers/scan-tarball.test.js +283 -0
- package/dist/core-runtime/readers/scan-vault.js +34 -0
- package/dist/core-runtime/readers/scan-vault.test.js +81 -0
- package/dist/core-runtime/readers/types.js +42 -0
- package/dist/core-runtime/readers/types.test.js +94 -0
- package/dist/core-runtime/readers/viewpoint-collectors.js +229 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +1 -0
- package/dist/core-runtime/reconstruct/directive-validation.js +123 -0
- package/dist/core-runtime/reconstruct/materialize-preparation.js +251 -0
- package/dist/core-runtime/reconstruct/record.js +198 -0
- package/dist/core-runtime/reconstruct/run.js +578 -0
- package/dist/core-runtime/reconstruct/seed-candidate-validation.js +356 -0
- package/dist/core-runtime/reconstruct/source-observations.js +62 -0
- package/dist/core-runtime/reconstruct/source-profiles.js +73 -0
- package/dist/core-runtime/release-channel/release-channel.js +90 -0
- package/dist/core-runtime/review/artifact-types.js +13 -0
- package/dist/core-runtime/review/citation-audit.js +204 -0
- package/dist/core-runtime/review/citation-audit.test.js +165 -0
- package/dist/core-runtime/review/controlled-lens-deliberation.js +125 -0
- package/dist/core-runtime/review/execution-plan-resolver.js +247 -0
- package/dist/core-runtime/review/execution-plan-resolver.test.js +243 -0
- package/dist/core-runtime/review/execution-topology-resolver-axis-first.test.js +246 -0
- package/dist/core-runtime/review/execution-topology-resolver.js +401 -0
- package/dist/core-runtime/review/execution-topology-resolver.test.js +315 -0
- package/dist/core-runtime/review/failure-records.js +57 -0
- package/dist/core-runtime/review/inline-context-embedder.js +141 -0
- package/dist/core-runtime/review/inline-context-embedder.test.js +154 -0
- package/dist/core-runtime/review/issue-artifact-runtime.js +859 -0
- package/dist/core-runtime/review/legacy-mode-policy.js +88 -0
- package/dist/core-runtime/review/lens-completion-policy.js +17 -0
- package/dist/core-runtime/review/materializers-effort-persist.test.js +79 -0
- package/dist/core-runtime/review/materializers.js +963 -0
- package/dist/core-runtime/review/ontology-path-classifier.js +179 -0
- package/dist/core-runtime/review/ontology-path-classifier.test.js +216 -0
- package/dist/core-runtime/review/packet-boundary-policy.js +215 -0
- package/dist/core-runtime/review/packet-boundary-policy.test.js +107 -0
- package/dist/core-runtime/review/participating-lens-paths.js +61 -0
- package/dist/core-runtime/review/participating-lens-paths.test.js +73 -0
- package/dist/core-runtime/review/review-artifact-utils.js +287 -0
- package/dist/core-runtime/review/review-config-legacy-translate.js +244 -0
- package/dist/core-runtime/review/review-config-legacy-translate.test.js +161 -0
- package/dist/core-runtime/review/review-config-validator.js +289 -0
- package/dist/core-runtime/review/review-config-validator.test.js +236 -0
- package/dist/core-runtime/review/review-execution-profile.js +193 -0
- package/dist/core-runtime/review/review-execution-route.js +52 -0
- package/dist/core-runtime/review/review-progress-contract.js +123 -0
- package/dist/core-runtime/review/review-record-validation.js +251 -0
- package/dist/core-runtime/review/review-result-classification.js +379 -0
- package/dist/core-runtime/review/review-state-machine.js +39 -0
- package/dist/core-runtime/review/route-visibility.js +125 -0
- package/dist/core-runtime/review/shape-pipeline-audit.test.js +311 -0
- package/dist/core-runtime/review/shape-to-topology-id.js +117 -0
- package/dist/core-runtime/review/shape-to-topology-id.test.js +132 -0
- package/dist/core-runtime/review/topology-shape-derivation.js +155 -0
- package/dist/core-runtime/review/topology-shape-derivation.test.js +195 -0
- package/dist/core-runtime/scope-runtime/constants.js +12 -0
- package/dist/core-runtime/scope-runtime/constraint-pool.js +166 -0
- package/dist/core-runtime/scope-runtime/constraint-pool.test.js +674 -0
- package/dist/core-runtime/scope-runtime/domain-validation-log.js +135 -0
- package/dist/core-runtime/scope-runtime/domain-validation-log.test.js +156 -0
- package/dist/core-runtime/scope-runtime/eval-persistence.js +65 -0
- package/dist/core-runtime/scope-runtime/eval-persistence.test.js +84 -0
- package/dist/core-runtime/scope-runtime/event-pipeline.js +64 -0
- package/dist/core-runtime/scope-runtime/event-pipeline.test.js +450 -0
- package/dist/core-runtime/scope-runtime/event-store.js +39 -0
- package/dist/core-runtime/scope-runtime/event-store.test.js +95 -0
- package/dist/core-runtime/scope-runtime/gate-guard.js +348 -0
- package/dist/core-runtime/scope-runtime/gate-guard.test.js +1047 -0
- package/dist/core-runtime/scope-runtime/hash.js +4 -0
- package/dist/core-runtime/scope-runtime/hash.test.js +33 -0
- package/dist/core-runtime/scope-runtime/id.js +4 -0
- package/dist/core-runtime/scope-runtime/id.test.js +17 -0
- package/dist/core-runtime/scope-runtime/reducer.js +297 -0
- package/dist/core-runtime/scope-runtime/reducer.test.js +759 -0
- package/dist/core-runtime/scope-runtime/scope-manager.js +161 -0
- package/dist/core-runtime/scope-runtime/state-machine.js +309 -0
- package/dist/core-runtime/scope-runtime/state-machine.test.js +704 -0
- package/dist/core-runtime/scope-runtime/types.js +116 -0
- package/dist/core-runtime/scope-runtime/types.test.js +69 -0
- package/dist/core-runtime/target-material-kind.js +256 -0
- package/dist/core-runtime/translate/render-for-user.js +169 -0
- package/dist/core-runtime/translate/render-for-user.test.js +122 -0
- package/dist/mcp/server.js +1011 -0
- package/dist/mcp/tool-schemas.js +93 -0
- package/dist/providers/capability-contract.js +1 -0
- package/package.json +68 -0
- package/settings.example.json +33 -0
|
@@ -0,0 +1,199 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const CODEX_ENV_SIGNALS = ["CODEX_THREAD_ID", "CODEX_CI"];
|
|
5
|
+
/** True when the current process is running under a Codex-owned session. */
|
|
6
|
+
export function detectCodexEnvSignal() {
|
|
7
|
+
for (const name of CODEX_ENV_SIGNALS) {
|
|
8
|
+
if (process.env[name])
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* True when the Codex worker path can be used from this process.
|
|
15
|
+
*
|
|
16
|
+
* Both the executable and an auth file are required. This keeps OAuth review
|
|
17
|
+
* execution fail-loud when the host-bound worker is unavailable.
|
|
18
|
+
*/
|
|
19
|
+
export function detectCodexBinaryAvailable() {
|
|
20
|
+
const pathEnv = process.env.PATH ?? "";
|
|
21
|
+
let codexOnPath = false;
|
|
22
|
+
for (const dir of pathEnv.split(path.delimiter)) {
|
|
23
|
+
if (!dir)
|
|
24
|
+
continue;
|
|
25
|
+
if (fsSync.existsSync(path.join(dir, "codex"))) {
|
|
26
|
+
codexOnPath = true;
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (!codexOnPath)
|
|
31
|
+
return false;
|
|
32
|
+
return fsSync.existsSync(path.join(os.homedir(), ".codex", "auth.json"));
|
|
33
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const NEW_LAYOUT_ROOT = ".onto";
|
|
4
|
+
const cache = new Map();
|
|
5
|
+
export function resolveInstallationPath(kind, installRoot) {
|
|
6
|
+
const cacheKey = `${installRoot}::${kind}`;
|
|
7
|
+
const cached = cache.get(cacheKey);
|
|
8
|
+
if (cached !== undefined)
|
|
9
|
+
return cached;
|
|
10
|
+
const canonicalPath = path.join(installRoot, NEW_LAYOUT_ROOT, kind);
|
|
11
|
+
if (fs.existsSync(canonicalPath)) {
|
|
12
|
+
cache.set(cacheKey, canonicalPath);
|
|
13
|
+
return canonicalPath;
|
|
14
|
+
}
|
|
15
|
+
throw new Error(`[installation-paths] .onto/${kind}/ not found under ${installRoot}. ` +
|
|
16
|
+
`Installation resources must live under the .onto/ layout.`);
|
|
17
|
+
}
|
|
18
|
+
/** Test helper — clears the cache so tests can swap fixture installations. */
|
|
19
|
+
export function __resetInstallationPathCacheForTesting() {
|
|
20
|
+
cache.clear();
|
|
21
|
+
}
|