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,389 +0,0 @@
1
- /**
2
- * Interactive prompts for `onto install`.
3
- *
4
- * # Module layout
5
- *
6
- * The `PromptIO` interface is the seam between interactive behavior
7
- * and stdin/stdout. Every prompt function takes a `PromptIO` so tests
8
- * can inject a scripted mock that returns predetermined answers —
9
- * no real terminal, no stdin mocking gymnastics.
10
- *
11
- * `createReadlineIo()` is the default production implementation,
12
- * backed by `node:readline/promises`.
13
- *
14
- * # Secret input
15
- *
16
- * `askSecret()` masks input with asterisks on TTY. It closes the
17
- * active readline interface, takes over stdin in raw mode, reads
18
- * character by character echoing `*`, and reopens readline
19
- * afterwards. Non-TTY environments fall back to plain readline
20
- * (masking isn't possible without raw-mode support). See
21
- * `readMaskedFromStdin` below for the recognized key bindings
22
- * (Enter, Ctrl-C, Ctrl-D, Backspace).
23
- *
24
- * # Step functions
25
- *
26
- * One function per interactive step, each accepting `(io, flags,
27
- * detection, priorDecisions)` as needed and returning the resolved
28
- * value. They respect `InstallFlags` first: if the flag is set the
29
- * prompt is skipped; otherwise the user is asked with a sensible
30
- * default derived from `PreflightDetection`.
31
- *
32
- * # Orchestrator
33
- *
34
- * `runInteractivePrompts()` runs all six steps in order and returns
35
- * the fully-resolved `{ decisions, secrets }` pair that writer.ts
36
- * consumes. The non-interactive orchestration path (`install/cli.ts
37
- * #runNonInteractive`) reuses the step functions' flag-respect logic
38
- * — when every flag is pre-populated, the prompts are skipped and
39
- * the mode difference collapses to the flag resolution logic.
40
- */
41
- import readline from "node:readline/promises";
42
- /**
43
- * Read a line from stdin with echo masking (asterisks per keypress).
44
- *
45
- * Takes full control of stdin via raw mode, consuming data events
46
- * directly rather than through readline. The caller must have any
47
- * active readline interface closed before entering this function —
48
- * otherwise readline's own data listener would race with ours and
49
- * eat characters before the mask loop sees them.
50
- *
51
- * Recognized inputs:
52
- * - Enter / newline → resolve with buffered string
53
- * - Ctrl-C (0x03) → reject with cancellation error
54
- * - Ctrl-D (0x04) → resolve with buffered string (EOF)
55
- * - Backspace (0x7f, 0x08) → remove last char + visual backspace
56
- * - Printable chars → append to buffer, echo "*"
57
- * - Other control chars → ignored
58
- */
59
- async function readMaskedFromStdin(prompt) {
60
- process.stdout.write(`${prompt}: `);
61
- return new Promise((resolve, reject) => {
62
- const stdin = process.stdin;
63
- stdin.setRawMode?.(true);
64
- stdin.resume();
65
- stdin.setEncoding("utf8");
66
- let buffer = "";
67
- const cleanup = () => {
68
- stdin.off("data", onData);
69
- stdin.setRawMode?.(false);
70
- stdin.pause();
71
- };
72
- const onData = (chunk) => {
73
- for (const ch of chunk) {
74
- const code = ch.charCodeAt(0);
75
- if (ch === "\n" || ch === "\r") {
76
- cleanup();
77
- process.stdout.write("\n");
78
- resolve(buffer);
79
- return;
80
- }
81
- if (code === 3) {
82
- cleanup();
83
- process.stdout.write("\n");
84
- reject(new Error("Install canceled (Ctrl-C)"));
85
- return;
86
- }
87
- if (code === 4) {
88
- cleanup();
89
- process.stdout.write("\n");
90
- resolve(buffer);
91
- return;
92
- }
93
- if (code === 127 || code === 8) {
94
- if (buffer.length > 0) {
95
- buffer = buffer.slice(0, -1);
96
- // Visually erase the last asterisk.
97
- process.stdout.write("\b \b");
98
- }
99
- continue;
100
- }
101
- if (code < 32)
102
- continue;
103
- buffer += ch;
104
- process.stdout.write("*");
105
- }
106
- };
107
- stdin.on("data", onData);
108
- });
109
- }
110
- /** Default PromptIO backed by `node:readline/promises`. */
111
- export function createReadlineIo() {
112
- let rl = readline.createInterface({
113
- input: process.stdin,
114
- output: process.stdout,
115
- });
116
- const ask = async (prompt, options) => {
117
- const suffix = options?.default !== undefined ? ` [${options.default}]: ` : ": ";
118
- const answer = (await rl.question(`${prompt}${suffix}`)).trim();
119
- if (!answer && options?.default !== undefined)
120
- return options.default;
121
- return answer;
122
- };
123
- const askChoice = async (prompt, choices, options) => {
124
- while (true) {
125
- const hint = choices.join(" | ");
126
- const answer = (await rl.question(`${prompt}\n (${hint})${options?.default ? ` [${options.default}]` : ""}: `)).trim();
127
- const picked = !answer && options?.default ? options.default : answer;
128
- if (choices.includes(picked)) {
129
- return picked;
130
- }
131
- process.stdout.write(` '${picked}' 는 유효한 선택이 아닙니다. 다시 입력해 주세요.\n`);
132
- }
133
- };
134
- const askConfirm = async (prompt, defaultValue) => {
135
- const defaultHint = defaultValue ? "Y/n" : "y/N";
136
- const raw = (await rl.question(`${prompt} [${defaultHint}]: `))
137
- .trim()
138
- .toLowerCase();
139
- if (!raw)
140
- return defaultValue;
141
- if (raw === "y" || raw === "yes")
142
- return true;
143
- if (raw === "n" || raw === "no")
144
- return false;
145
- return defaultValue;
146
- };
147
- /**
148
- * TTY → masked input (asterisks). Non-TTY → fall back to plain
149
- * readline (no masking possible without raw mode).
150
- *
151
- * The TTY path must close + reopen the readline interface because
152
- * both readline and our raw-mode listener bind to `process.stdin`;
153
- * leaving readline open during the masked read makes it race us
154
- * for input bytes. Close before, recreate after — any in-flight
155
- * state (cursor line, completion state) is irrelevant across a
156
- * secret prompt boundary.
157
- */
158
- const askSecret = async (prompt) => {
159
- const stdin = process.stdin;
160
- const canRawMode = Boolean(stdin.isTTY) && typeof stdin.setRawMode === "function";
161
- if (!canRawMode) {
162
- return (await rl.question(`${prompt}: `)).trim();
163
- }
164
- rl.close();
165
- try {
166
- const answer = await readMaskedFromStdin(prompt);
167
- return answer.trim();
168
- }
169
- finally {
170
- rl = readline.createInterface({
171
- input: process.stdin,
172
- output: process.stdout,
173
- });
174
- }
175
- };
176
- const print = (text) => {
177
- process.stdout.write(`${text}\n`);
178
- };
179
- const close = () => {
180
- rl.close();
181
- };
182
- return { ask, askChoice, askConfirm, askSecret, print, close };
183
- }
184
- // ---------------------------------------------------------------------------
185
- // Step 1: profile scope
186
- // ---------------------------------------------------------------------------
187
- export async function stepProfileScope(io, flags) {
188
- if (flags.profileScope)
189
- return flags.profileScope;
190
- return io.askChoice("이 프로파일을 어디에 저장하시겠어요?", ["global", "project"], { default: "project" });
191
- }
192
- // ---------------------------------------------------------------------------
193
- // Step 2: review provider
194
- // ---------------------------------------------------------------------------
195
- const REVIEW_PROVIDERS = [
196
- "main-native",
197
- "codex",
198
- "anthropic",
199
- "openai",
200
- "litellm",
201
- ];
202
- /**
203
- * Derive the suggested default review provider from the pre-flight
204
- * snapshot. Priority favors "least friction" — if the user is
205
- * currently in a Claude Code session with no external keys, main-native
206
- * costs nothing. Otherwise the highest-priority detected credential
207
- * wins.
208
- */
209
- function suggestReviewDefault(detection) {
210
- if (detection.hostIsClaudeCode)
211
- return "main-native";
212
- if (detection.hasCodexBinary && detection.hasCodexAuth)
213
- return "codex";
214
- if (detection.hasAnthropicKey)
215
- return "anthropic";
216
- if (detection.hasOpenAiKey)
217
- return "openai";
218
- if (detection.hasLitellmBaseUrl)
219
- return "litellm";
220
- return "main-native";
221
- }
222
- export async function stepReviewProvider(io, flags, detection) {
223
- if (flags.reviewProvider) {
224
- if (flags.reviewProvider === "main-native" && !detection.hostIsClaudeCode) {
225
- io.print("[warning] main-native는 Claude Code 세션 내에서만 동작합니다. " +
226
- "현재는 Claude Code가 감지되지 않았습니다.");
227
- }
228
- return flags.reviewProvider;
229
- }
230
- const suggested = suggestReviewDefault(detection);
231
- while (true) {
232
- const picked = await io.askChoice("Review (9-lens) 실행에 사용할 provider를 선택하세요.", REVIEW_PROVIDERS, { default: suggested });
233
- if (picked === "main-native" && !detection.hostIsClaudeCode) {
234
- io.print(" [warning] main-native는 Claude Code 세션이 아닐 때 동작하지 " +
235
- "않습니다. 그래도 이 값으로 기록할까요? (npm CLI 사용 시 review가 " +
236
- "실패할 수 있습니다.)");
237
- const proceed = await io.askConfirm("계속", false);
238
- if (!proceed)
239
- continue;
240
- }
241
- return picked;
242
- }
243
- }
244
- // ---------------------------------------------------------------------------
245
- // Step 3: review provider auth
246
- // ---------------------------------------------------------------------------
247
- export async function stepReviewAuth(io, provider, detection) {
248
- switch (provider) {
249
- case "main-native":
250
- return {};
251
- case "codex":
252
- return stepCodexAuth(io, detection);
253
- case "anthropic":
254
- return stepAnthropicAuth(io, detection);
255
- case "openai":
256
- return stepOpenaiAuth(io, detection);
257
- case "litellm":
258
- return stepLitellmAuth(io, detection);
259
- }
260
- }
261
- async function stepCodexAuth(io, detection) {
262
- if (detection.hasCodexBinary && detection.hasCodexAuth) {
263
- io.print(" [ok] codex binary와 ~/.codex/auth.json이 확인됐습니다.");
264
- return {};
265
- }
266
- if (!detection.hasCodexBinary) {
267
- io.print(" [error] codex CLI가 설치돼 있지 않습니다. https://github.com/openai/codex " +
268
- "에서 먼저 설치해 주세요.");
269
- return {};
270
- }
271
- io.print([
272
- " [action] codex 로그인이 필요합니다.",
273
- " 다른 터미널에서 다음을 실행한 뒤 Enter를 눌러 주세요:",
274
- "",
275
- " codex login",
276
- "",
277
- ].join("\n"));
278
- await io.ask("완료되면 Enter", { default: "" });
279
- return {};
280
- }
281
- async function stepAnthropicAuth(io, detection) {
282
- if (detection.hasAnthropicKey) {
283
- const reuse = await io.askConfirm(" ANTHROPIC_API_KEY가 이미 환경에 있습니다. 그대로 사용할까요?", true);
284
- if (reuse)
285
- return {};
286
- }
287
- const key = await io.askSecret(" ANTHROPIC_API_KEY 입력");
288
- return key.trim() ? { anthropicApiKey: key.trim() } : {};
289
- }
290
- async function stepOpenaiAuth(io, detection) {
291
- if (detection.hasOpenAiKey) {
292
- const reuse = await io.askConfirm(" OPENAI_API_KEY가 이미 환경에 있습니다. 그대로 사용할까요?", true);
293
- if (reuse)
294
- return {};
295
- }
296
- const key = await io.askSecret(" OPENAI_API_KEY 입력");
297
- return key.trim() ? { openaiApiKey: key.trim() } : {};
298
- }
299
- async function stepLitellmAuth(io, detection) {
300
- io.print([
301
- " LiteLLM / OpenAI-compatible 엔드포인트를 사용합니다.",
302
- " 로컬에서 띄운 경우 포트를 포함한 전체 URL을 입력하세요.",
303
- " 예) LiteLLM proxy: http://localhost:4000/v1",
304
- " Ollama: http://localhost:11434/v1",
305
- ].join("\n"));
306
- const baseUrl = await io.ask(" Base URL", {
307
- default: detection.litellmBaseUrlValue ?? "http://localhost:4000/v1",
308
- });
309
- const needsAuth = await io.askConfirm(" 엔드포인트에 API key 인증이 필요한가요?", false);
310
- const out = { litellmBaseUrl: baseUrl.trim() };
311
- if (needsAuth) {
312
- const apiKey = await io.askSecret(" LITELLM_API_KEY 입력");
313
- if (apiKey.trim())
314
- out.litellmApiKey = apiKey.trim();
315
- }
316
- return out;
317
- }
318
- // ---------------------------------------------------------------------------
319
- // Step 4: learn provider
320
- // ---------------------------------------------------------------------------
321
- const LEARN_PROVIDERS = [
322
- "codex",
323
- "anthropic",
324
- "openai",
325
- "litellm",
326
- ];
327
- export async function stepLearnProvider(io, flags, reviewProvider) {
328
- if (flags.learnProvider === "same") {
329
- if (reviewProvider === "main-native") {
330
- io.print(" [warning] learn은 main-native를 지원하지 않습니다. " +
331
- "별도 provider를 선택해야 합니다.");
332
- }
333
- else {
334
- return reviewProvider;
335
- }
336
- }
337
- else if (flags.learnProvider) {
338
- return flags.learnProvider;
339
- }
340
- if (reviewProvider !== "main-native") {
341
- const reuse = await io.askConfirm(`Learn (background 작업)도 review와 동일하게 ${reviewProvider}를 사용할까요?`, true);
342
- if (reuse)
343
- return reviewProvider;
344
- }
345
- return io.askChoice("Learn / govern / promote용 provider를 선택하세요 (main-native는 지원되지 않습니다).", LEARN_PROVIDERS, { default: "anthropic" });
346
- }
347
- // ---------------------------------------------------------------------------
348
- // Step 5: learn provider auth (only if distinct from review)
349
- // ---------------------------------------------------------------------------
350
- export async function stepLearnAuth(io, learnProvider, reviewProvider, detection, priorSecrets) {
351
- if (learnProvider === reviewProvider)
352
- return priorSecrets;
353
- const learnSecrets = await stepReviewAuth(io, learnProvider, detection);
354
- return { ...priorSecrets, ...learnSecrets };
355
- }
356
- // ---------------------------------------------------------------------------
357
- // Step 6: output language
358
- // ---------------------------------------------------------------------------
359
- export async function stepOutputLanguage(io, flags) {
360
- if (flags.outputLanguage)
361
- return flags.outputLanguage;
362
- return io.askChoice("출력 언어 (principal-facing)", ["ko", "en"], {
363
- default: "ko",
364
- });
365
- }
366
- /**
367
- * Run all six steps in sequence and return the fully-resolved
368
- * decisions + secrets. Skips any step whose value is already fixed
369
- * via flags.
370
- */
371
- export async function runInteractivePrompts(args) {
372
- const { io, flags, detection } = args;
373
- const profileScope = await stepProfileScope(io, flags);
374
- const reviewProvider = await stepReviewProvider(io, flags, detection);
375
- const reviewSecrets = await stepReviewAuth(io, reviewProvider, detection);
376
- const learnProvider = await stepLearnProvider(io, flags, reviewProvider);
377
- const secrets = await stepLearnAuth(io, learnProvider, reviewProvider, detection, reviewSecrets);
378
- const outputLanguage = await stepOutputLanguage(io, flags);
379
- const decisions = {
380
- profileScope,
381
- reviewProvider,
382
- learnProvider,
383
- outputLanguage,
384
- };
385
- if (secrets.litellmBaseUrl) {
386
- decisions.litellmBaseUrl = secrets.litellmBaseUrl;
387
- }
388
- return { decisions, secrets };
389
- }
@@ -1,293 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { runInteractivePrompts, stepLearnProvider, stepOutputLanguage, stepProfileScope, stepReviewAuth, stepReviewProvider, } from "./prompts.js";
3
- class ScriptedIo {
4
- script;
5
- log = [];
6
- i = 0;
7
- constructor(script) {
8
- this.script = script;
9
- }
10
- next(kind) {
11
- if (this.i >= this.script.length) {
12
- throw new Error(`ScriptedIo: no more scripted answers (requested ${kind}; log=${this.log.join(" | ")})`);
13
- }
14
- const entry = this.script[this.i++];
15
- if (!entry || entry.kind !== kind) {
16
- throw new Error(`ScriptedIo: expected ${kind}, got ${entry?.kind ?? "end-of-script"} at step ${this.i}`);
17
- }
18
- return entry;
19
- }
20
- async ask(prompt) {
21
- this.log.push(`ask:${prompt}`);
22
- return this.next("ask").answer;
23
- }
24
- async askChoice(prompt) {
25
- this.log.push(`askChoice:${prompt}`);
26
- return this.next("askChoice")
27
- .answer;
28
- }
29
- async askConfirm(prompt) {
30
- this.log.push(`askConfirm:${prompt}`);
31
- return this.next("askConfirm")
32
- .answer;
33
- }
34
- async askSecret(prompt) {
35
- this.log.push(`askSecret:${prompt}`);
36
- return this.next("askSecret").answer;
37
- }
38
- print(text) {
39
- this.log.push(`print:${text}`);
40
- }
41
- close() {
42
- this.log.push("close");
43
- }
44
- }
45
- const BLANK_DETECTION = {
46
- existingGlobalConfig: false,
47
- existingProjectConfig: false,
48
- hasAnthropicKey: false,
49
- hasOpenAiKey: false,
50
- hasLitellmBaseUrl: false,
51
- hasCodexBinary: false,
52
- hasCodexAuth: false,
53
- hostIsClaudeCode: false,
54
- };
55
- const EMPTY_FLAGS = {
56
- nonInteractive: false,
57
- reconfigure: false,
58
- skipValidation: false,
59
- dryRun: false,
60
- };
61
- // ---------------------------------------------------------------------------
62
- // Step 1: profile scope
63
- // ---------------------------------------------------------------------------
64
- describe("stepProfileScope", () => {
65
- it("prompts when flag is unset", async () => {
66
- const io = new ScriptedIo([{ kind: "askChoice", answer: "global" }]);
67
- const scope = await stepProfileScope(io, EMPTY_FLAGS);
68
- expect(scope).toBe("global");
69
- });
70
- it("skips prompt when flag is preset", async () => {
71
- const io = new ScriptedIo([]);
72
- const scope = await stepProfileScope(io, {
73
- ...EMPTY_FLAGS,
74
- profileScope: "project",
75
- });
76
- expect(scope).toBe("project");
77
- expect(io.log).toEqual([]);
78
- });
79
- });
80
- // ---------------------------------------------------------------------------
81
- // Step 2: review provider
82
- // ---------------------------------------------------------------------------
83
- describe("stepReviewProvider", () => {
84
- it("accepts flag verbatim", async () => {
85
- const io = new ScriptedIo([]);
86
- const provider = await stepReviewProvider(io, { ...EMPTY_FLAGS, reviewProvider: "codex" }, BLANK_DETECTION);
87
- expect(provider).toBe("codex");
88
- });
89
- it("warns when flag=main-native but host is not Claude Code", async () => {
90
- const io = new ScriptedIo([]);
91
- await stepReviewProvider(io, { ...EMPTY_FLAGS, reviewProvider: "main-native" }, BLANK_DETECTION);
92
- expect(io.log.some((l) => l.includes("main-native"))).toBe(true);
93
- });
94
- it("prompts and requires confirmation for main-native without Claude Code", async () => {
95
- const io = new ScriptedIo([
96
- { kind: "askChoice", answer: "main-native" },
97
- { kind: "askConfirm", answer: false },
98
- { kind: "askChoice", answer: "anthropic" },
99
- ]);
100
- const provider = await stepReviewProvider(io, EMPTY_FLAGS, BLANK_DETECTION);
101
- expect(provider).toBe("anthropic");
102
- });
103
- it("accepts main-native without reprompt when Claude Code is detected", async () => {
104
- const io = new ScriptedIo([{ kind: "askChoice", answer: "main-native" }]);
105
- const provider = await stepReviewProvider(io, EMPTY_FLAGS, {
106
- ...BLANK_DETECTION,
107
- hostIsClaudeCode: true,
108
- });
109
- expect(provider).toBe("main-native");
110
- });
111
- });
112
- // ---------------------------------------------------------------------------
113
- // Step 3: review auth (anthropic / openai / litellm / codex / main-native)
114
- // ---------------------------------------------------------------------------
115
- describe("stepReviewAuth", () => {
116
- it("main-native returns empty secrets with no prompts", async () => {
117
- const io = new ScriptedIo([]);
118
- const secrets = await stepReviewAuth(io, "main-native", BLANK_DETECTION);
119
- expect(secrets).toEqual({});
120
- });
121
- it("anthropic: reuse existing env key when user confirms", async () => {
122
- const io = new ScriptedIo([{ kind: "askConfirm", answer: true }]);
123
- const secrets = await stepReviewAuth(io, "anthropic", {
124
- ...BLANK_DETECTION,
125
- hasAnthropicKey: true,
126
- });
127
- expect(secrets).toEqual({});
128
- });
129
- it("anthropic: prompts for key when none present", async () => {
130
- const io = new ScriptedIo([
131
- { kind: "askSecret", answer: "sk-ant-xyz" },
132
- ]);
133
- const secrets = await stepReviewAuth(io, "anthropic", BLANK_DETECTION);
134
- expect(secrets).toEqual({ anthropicApiKey: "sk-ant-xyz" });
135
- });
136
- it("litellm: captures base_url and optional auth key", async () => {
137
- const io = new ScriptedIo([
138
- { kind: "ask", answer: "http://localhost:11434/v1" },
139
- { kind: "askConfirm", answer: true },
140
- { kind: "askSecret", answer: "sk-proxy-xxx" },
141
- ]);
142
- const secrets = await stepReviewAuth(io, "litellm", BLANK_DETECTION);
143
- expect(secrets).toEqual({
144
- litellmBaseUrl: "http://localhost:11434/v1",
145
- litellmApiKey: "sk-proxy-xxx",
146
- });
147
- });
148
- it("codex: reports ready when binary and auth both present", async () => {
149
- const io = new ScriptedIo([]);
150
- const secrets = await stepReviewAuth(io, "codex", {
151
- ...BLANK_DETECTION,
152
- hasCodexBinary: true,
153
- hasCodexAuth: true,
154
- });
155
- expect(secrets).toEqual({});
156
- expect(io.log.some((l) => l.includes("[ok]"))).toBe(true);
157
- });
158
- it("codex: surfaces install instructions when binary missing", async () => {
159
- const io = new ScriptedIo([]);
160
- await stepReviewAuth(io, "codex", BLANK_DETECTION);
161
- expect(io.log.some((l) => l.includes("codex CLI가 설치"))).toBe(true);
162
- });
163
- it("codex: guides user through `codex login` when auth missing", async () => {
164
- const io = new ScriptedIo([{ kind: "ask", answer: "" }]);
165
- await stepReviewAuth(io, "codex", {
166
- ...BLANK_DETECTION,
167
- hasCodexBinary: true,
168
- });
169
- expect(io.log.some((l) => l.includes("codex login"))).toBe(true);
170
- });
171
- });
172
- // ---------------------------------------------------------------------------
173
- // Step 4: learn provider
174
- // ---------------------------------------------------------------------------
175
- describe("stepLearnProvider", () => {
176
- it("respects learn=same when review is not main-native", async () => {
177
- const io = new ScriptedIo([]);
178
- const learn = await stepLearnProvider(io, { ...EMPTY_FLAGS, learnProvider: "same" }, "anthropic");
179
- expect(learn).toBe("anthropic");
180
- });
181
- it("warns when learn=same but review=main-native (not supported)", async () => {
182
- const io = new ScriptedIo([
183
- { kind: "askChoice", answer: "anthropic" },
184
- ]);
185
- const learn = await stepLearnProvider(io, { ...EMPTY_FLAGS, learnProvider: "same" }, "main-native");
186
- expect(learn).toBe("anthropic");
187
- expect(io.log.some((l) => l.includes("main-native"))).toBe(true);
188
- });
189
- it("offers review provider as reuse default when compatible", async () => {
190
- const io = new ScriptedIo([{ kind: "askConfirm", answer: true }]);
191
- const learn = await stepLearnProvider(io, EMPTY_FLAGS, "anthropic");
192
- expect(learn).toBe("anthropic");
193
- });
194
- it("prompts for separate learn provider when user declines reuse", async () => {
195
- const io = new ScriptedIo([
196
- { kind: "askConfirm", answer: false },
197
- { kind: "askChoice", answer: "codex" },
198
- ]);
199
- const learn = await stepLearnProvider(io, EMPTY_FLAGS, "anthropic");
200
- expect(learn).toBe("codex");
201
- });
202
- });
203
- // ---------------------------------------------------------------------------
204
- // Step 6: output language
205
- // ---------------------------------------------------------------------------
206
- describe("stepOutputLanguage", () => {
207
- it("returns flag value without prompting", async () => {
208
- const io = new ScriptedIo([]);
209
- const lang = await stepOutputLanguage(io, {
210
- ...EMPTY_FLAGS,
211
- outputLanguage: "en",
212
- });
213
- expect(lang).toBe("en");
214
- });
215
- it("prompts when flag absent", async () => {
216
- const io = new ScriptedIo([{ kind: "askChoice", answer: "ko" }]);
217
- const lang = await stepOutputLanguage(io, EMPTY_FLAGS);
218
- expect(lang).toBe("ko");
219
- });
220
- });
221
- // ---------------------------------------------------------------------------
222
- // Full orchestrator
223
- // ---------------------------------------------------------------------------
224
- describe("runInteractivePrompts — end-to-end", () => {
225
- it("main-native review + anthropic learn path", async () => {
226
- const io = new ScriptedIo([
227
- // Step 1: profile scope
228
- { kind: "askChoice", answer: "project" },
229
- // Step 2: review provider
230
- { kind: "askChoice", answer: "main-native" },
231
- // Step 3: no auth for main-native (no prompts)
232
- // Step 4: learn provider (reuse review? → main-native can't, so pick)
233
- { kind: "askChoice", answer: "anthropic" },
234
- // Step 5: learn auth (anthropic, not in env, needs key)
235
- { kind: "askSecret", answer: "sk-ant-xyz" },
236
- // Step 6: output language
237
- { kind: "askChoice", answer: "ko" },
238
- ]);
239
- const result = await runInteractivePrompts({
240
- io,
241
- flags: EMPTY_FLAGS,
242
- detection: { ...BLANK_DETECTION, hostIsClaudeCode: true },
243
- });
244
- expect(result.decisions).toEqual({
245
- profileScope: "project",
246
- reviewProvider: "main-native",
247
- learnProvider: "anthropic",
248
- outputLanguage: "ko",
249
- });
250
- expect(result.secrets).toEqual({ anthropicApiKey: "sk-ant-xyz" });
251
- });
252
- it("anthropic review + same learn provider → single auth prompt", async () => {
253
- const io = new ScriptedIo([
254
- { kind: "askChoice", answer: "project" },
255
- { kind: "askChoice", answer: "anthropic" },
256
- { kind: "askSecret", answer: "sk-ant-1" },
257
- { kind: "askConfirm", answer: true },
258
- { kind: "askChoice", answer: "ko" },
259
- ]);
260
- const result = await runInteractivePrompts({
261
- io,
262
- flags: EMPTY_FLAGS,
263
- detection: BLANK_DETECTION,
264
- });
265
- expect(result.decisions.reviewProvider).toBe("anthropic");
266
- expect(result.decisions.learnProvider).toBe("anthropic");
267
- expect(result.secrets.anthropicApiKey).toBe("sk-ant-1");
268
- });
269
- it("flag-only path skips all prompts (hypothetical non-interactive)", async () => {
270
- const io = new ScriptedIo([]);
271
- const result = await runInteractivePrompts({
272
- io,
273
- flags: {
274
- ...EMPTY_FLAGS,
275
- profileScope: "global",
276
- reviewProvider: "codex",
277
- learnProvider: "codex",
278
- outputLanguage: "en",
279
- },
280
- detection: {
281
- ...BLANK_DETECTION,
282
- hasCodexBinary: true,
283
- hasCodexAuth: true,
284
- },
285
- });
286
- expect(result.decisions).toEqual({
287
- profileScope: "global",
288
- reviewProvider: "codex",
289
- learnProvider: "codex",
290
- outputLanguage: "en",
291
- });
292
- });
293
- });