@geraldmaron/construct 1.2.3 → 1.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.
Files changed (504) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +235 -209
  3. package/bin/construct-postinstall.mjs +0 -21
  4. package/commands/work/optimize-prompts.md +1 -1
  5. package/examples/distribution/sources/adr.md +2 -2
  6. package/examples/seed-observations/decisions.md +1 -1
  7. package/lib/artifact-loop-core.mjs +412 -0
  8. package/lib/artifact-manifest.mjs +6 -0
  9. package/lib/artifact-release-gate.mjs +115 -49
  10. package/lib/audit-rules.mjs +2 -4
  11. package/lib/audit-skills.mjs +32 -20
  12. package/lib/audit-specialists.mjs +44 -26
  13. package/lib/auto-docs.mjs +6 -5
  14. package/lib/brand-prose.mjs +3 -3
  15. package/lib/brand-tokens.mjs +2 -2
  16. package/lib/certification/artifact-fixtures.mjs +4 -1
  17. package/lib/certification/demo-parity.mjs +6 -32
  18. package/lib/certification/real-llm-scenarios.mjs +58 -13
  19. package/lib/certification/role-cards.mjs +7 -6
  20. package/lib/certification/role-overlays.mjs +4 -4
  21. package/lib/certification/runner.mjs +9 -2
  22. package/lib/certification/skill-inventory.mjs +34 -22
  23. package/lib/certification/skill-scenarios.mjs +21 -8
  24. package/lib/certification/specialist-contracts.mjs +4 -3
  25. package/lib/certification/specialist-scenarios.mjs +7 -6
  26. package/lib/certification/status.mjs +5 -4
  27. package/lib/cli-commands.mjs +55 -54
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +90 -24
  31. package/lib/config/schema.mjs +5 -5
  32. package/lib/config/source-targets.mjs +80 -0
  33. package/lib/contracts/validate.mjs +119 -51
  34. package/lib/decisions/golden.mjs +3 -2
  35. package/lib/decisions/registry.mjs +11 -6
  36. package/lib/demo-project.mjs +188 -0
  37. package/lib/demo-recording.mjs +9 -34
  38. package/lib/demo-script.mjs +2 -2
  39. package/lib/demo-surface.mjs +13 -131
  40. package/lib/demo-tour-renderer.mjs +92 -0
  41. package/lib/demo.mjs +69 -54
  42. package/lib/diagram-export.mjs +63 -14
  43. package/lib/doctor/source-checkout.mjs +3 -4
  44. package/lib/doctor/watchers/consistency.mjs +90 -22
  45. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  46. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  47. package/lib/doctor/watchers/service-health.mjs +4 -32
  48. package/lib/document-export.mjs +73 -9
  49. package/lib/document-extract.mjs +10 -198
  50. package/lib/embed/artifact.mjs +2 -3
  51. package/lib/embed/cli.mjs +1 -1
  52. package/lib/embed/config.mjs +1 -2
  53. package/lib/embed/customer-profiles.mjs +1 -1
  54. package/lib/embed/daemon.mjs +5 -6
  55. package/lib/embed/demand-fetch.mjs +114 -2
  56. package/lib/embed/docs-lifecycle.mjs +9 -5
  57. package/lib/embed/intake-metrics.mjs +2 -2
  58. package/lib/embed/notifications.mjs +2 -3
  59. package/lib/embed/recommendation-store.mjs +25 -22
  60. package/lib/embed/role-framing.mjs +4 -9
  61. package/lib/embedded-contract/capability.mjs +4 -2
  62. package/lib/embedded-contract/contract-version.mjs +1 -1
  63. package/lib/env-config.mjs +2 -2
  64. package/lib/flavors/loader.mjs +21 -19
  65. package/lib/graph/build-from-registry.mjs +31 -7
  66. package/lib/graph/staleness.mjs +1 -1
  67. package/lib/headhunt.mjs +82 -35
  68. package/lib/hooks/agent-tracker.mjs +2 -2
  69. package/lib/hooks/config-protection.mjs +1 -1
  70. package/lib/hooks/registry-sync.mjs +3 -3
  71. package/lib/host-capabilities.mjs +5 -5
  72. package/lib/improvement/controller.mjs +2 -2
  73. package/lib/init-unified.mjs +19 -21
  74. package/lib/intake/classify.mjs +26 -0
  75. package/lib/intake/daemon.mjs +2 -3
  76. package/lib/intake/prepare.mjs +10 -3
  77. package/lib/intake/session-prelude.mjs +1 -1
  78. package/lib/intake/tables/creative.mjs +9 -9
  79. package/lib/intake/tables/operations.mjs +4 -4
  80. package/lib/intake/tables/research.mjs +2 -2
  81. package/lib/integrations/intake-integrations.mjs +9 -10
  82. package/lib/knowledge/research-store.mjs +2 -2
  83. package/lib/mcp/server.mjs +104 -61
  84. package/lib/mcp/tool-budget.mjs +1 -46
  85. package/lib/mcp/tool-recovery.mjs +44 -0
  86. package/lib/mcp/tools/artifact-author.mjs +36 -0
  87. package/lib/mcp/tools/find-tool.mjs +86 -0
  88. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  89. package/lib/mcp/tools/project.mjs +4 -3
  90. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  91. package/lib/mcp/tools/skills.mjs +39 -22
  92. package/lib/mcp/tools/telemetry.mjs +2 -1
  93. package/lib/mcp/tools/workflow.mjs +1 -1
  94. package/lib/mcp-platform-config.mjs +7 -5
  95. package/lib/migrations/index.mjs +4 -2
  96. package/lib/migrations/v2-unified-registry.mjs +35 -0
  97. package/lib/model-router.mjs +203 -36
  98. package/lib/models/catalog.mjs +25 -9
  99. package/lib/models/execution-capability-profile.mjs +3 -3
  100. package/lib/models/execution-policy.mjs +7 -6
  101. package/lib/models/provider-poll.mjs +18 -4
  102. package/lib/opencode-config.mjs +56 -1
  103. package/lib/opencode-runtime-plugin.mjs +13 -10
  104. package/lib/oracle/artifact-gate.mjs +14 -4
  105. package/lib/oracle/cli.mjs +2 -0
  106. package/lib/oracle/dispatch.mjs +18 -1
  107. package/lib/oracle/execute.mjs +39 -7
  108. package/lib/oracle/index.mjs +1 -1
  109. package/lib/oracle/org-graph.mjs +10 -5
  110. package/lib/oracle/policy.mjs +6 -0
  111. package/lib/oracle/read-model.mjs +103 -6
  112. package/lib/oracle/reconcile.mjs +31 -4
  113. package/lib/oracle/remediation-dispatch.mjs +53 -0
  114. package/lib/oracle/routing.mjs +5 -0
  115. package/lib/oracle/synthesize.mjs +80 -3
  116. package/lib/orchestration/routing-tables.mjs +44 -10
  117. package/lib/orchestration/runtime.mjs +2 -0
  118. package/lib/orchestration/worker.mjs +1 -1
  119. package/lib/orchestration-policy.mjs +351 -62
  120. package/lib/overrides/resolver.mjs +1 -12
  121. package/lib/parity.mjs +37 -11
  122. package/lib/policy/engine.mjs +80 -15
  123. package/lib/prompt-composer.js +51 -15
  124. package/lib/prompt-metadata.mjs +3 -2
  125. package/lib/providers/connection-probe.mjs +58 -0
  126. package/lib/providers/copilot-auth.mjs +1 -1
  127. package/lib/providers/credential-bootstrap.mjs +1 -1
  128. package/lib/providers/op-run.mjs +3 -4
  129. package/lib/providers/secret-resolver.mjs +31 -0
  130. package/lib/publish-tooling.mjs +3 -11
  131. package/lib/publish.mjs +12 -29
  132. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  133. package/lib/reflect.mjs +2 -2
  134. package/lib/registry/agent-manifest.mjs +190 -0
  135. package/lib/registry/assemble.mjs +133 -0
  136. package/lib/registry/catalog.mjs +171 -0
  137. package/lib/registry/cli.mjs +590 -6
  138. package/lib/registry/consolidation.mjs +5 -4
  139. package/lib/registry/docs-sync.mjs +67 -0
  140. package/lib/registry/loader.mjs +147 -0
  141. package/lib/registry/org-io.mjs +76 -0
  142. package/lib/registry/retired-paths.mjs +71 -0
  143. package/lib/registry/surface-map.mjs +5 -7
  144. package/lib/registry/validator.mjs +438 -0
  145. package/lib/research-execution-policy.mjs +173 -0
  146. package/lib/roles/catalog.mjs +4 -9
  147. package/lib/roles/fence.mjs +107 -1
  148. package/lib/roles/flavor-bindings.mjs +68 -0
  149. package/lib/roles/gateway.mjs +140 -29
  150. package/lib/roles/manifest.mjs +22 -13
  151. package/lib/roles/router.mjs +1 -1
  152. package/lib/scopes/enrich.mjs +67 -0
  153. package/lib/scopes/hooks.mjs +12 -0
  154. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  155. package/lib/scopes/loader.mjs +104 -0
  156. package/lib/scopes/rebrand.mjs +32 -0
  157. package/lib/scopes/research-profile.mjs +16 -0
  158. package/lib/scopes/teams.mjs +98 -0
  159. package/lib/service-manager.mjs +1 -117
  160. package/lib/setup-prompts.mjs +1 -1
  161. package/lib/setup.mjs +6 -26
  162. package/lib/skills/composition-graph.mjs +98 -0
  163. package/lib/skills/router.mjs +80 -0
  164. package/lib/specialist-contracts.mjs +17 -18
  165. package/lib/specialists/postconditions.mjs +2 -2
  166. package/lib/specialists/prompt-schema.mjs +6 -5
  167. package/lib/specialists/roster.mjs +7 -12
  168. package/lib/specialists/schema.mjs +9 -6
  169. package/lib/status.mjs +25 -90
  170. package/lib/storage/file-lock.mjs +6 -3
  171. package/lib/telemetry/skill-calls.mjs +1 -1
  172. package/lib/templates/doc-presentation.mjs +63 -0
  173. package/lib/templates/visual-requirements.mjs +35 -2
  174. package/lib/term-format.mjs +107 -2
  175. package/lib/test-env-setup.mjs +21 -0
  176. package/lib/ui/components.mjs +42 -0
  177. package/lib/ui/glyphs.mjs +58 -0
  178. package/lib/ui/links.mjs +115 -0
  179. package/lib/ui/theme.mjs +108 -0
  180. package/lib/uninstall/uninstall.mjs +2 -1
  181. package/lib/validator.mjs +45 -8
  182. package/lib/validators/skill-effectiveness.mjs +174 -0
  183. package/lib/validators/skills.mjs +1 -1
  184. package/package.json +12 -8
  185. package/personas/construct.md +12 -3
  186. package/platforms/claude/CLAUDE.md +1 -1
  187. package/rules/common/beads-hygiene.md +52 -0
  188. package/rules/common/neurodivergent-output.md +4 -7
  189. package/rules/common/research.md +2 -0
  190. package/scripts/sync-specialists.mjs +260 -49
  191. package/skills/ai/prompt-optimizer.md +3 -3
  192. package/skills/brand/output-vibe.md +48 -0
  193. package/skills/docs/adr-workflow.md +3 -0
  194. package/skills/docs/backlog-proposal-workflow.md +3 -0
  195. package/skills/docs/codebase-research-workflow.md +5 -2
  196. package/skills/docs/customer-profile-workflow.md +3 -0
  197. package/skills/docs/document-ingest-workflow.md +3 -0
  198. package/skills/docs/evidence-ingest-workflow.md +4 -1
  199. package/skills/docs/init-project.md +1 -1
  200. package/skills/docs/prd-workflow.md +1 -1
  201. package/skills/docs/prfaq-workflow.md +3 -0
  202. package/skills/docs/product-intelligence-workflow.md +4 -1
  203. package/skills/docs/product-signal-workflow.md +3 -0
  204. package/skills/docs/research-workflow.md +13 -6
  205. package/skills/docs/runbook-workflow.md +3 -0
  206. package/skills/docs/strategy-workflow.md +3 -0
  207. package/skills/docs/user-research-workflow.md +6 -3
  208. package/skills/operating/orchestration-reference.md +1 -1
  209. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  210. package/skills/roles/architect.ai-systems.md +1 -1
  211. package/skills/roles/architect.data.md +1 -1
  212. package/skills/roles/architect.enterprise.md +1 -1
  213. package/skills/roles/architect.integration.md +1 -1
  214. package/skills/roles/architect.md +1 -1
  215. package/skills/roles/architect.platform.md +1 -1
  216. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  217. package/skills/roles/data-analyst.experiment.md +1 -1
  218. package/skills/roles/data-analyst.md +1 -1
  219. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  220. package/skills/roles/data-analyst.product.md +1 -1
  221. package/skills/roles/data-analyst.telemetry.md +1 -1
  222. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  223. package/skills/roles/data-engineer.pipeline.md +2 -2
  224. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  225. package/skills/roles/data-engineer.warehouse.md +2 -2
  226. package/skills/roles/debugger.md +1 -1
  227. package/skills/roles/designer.accessibility.md +1 -1
  228. package/skills/roles/designer.md +1 -1
  229. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  230. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  231. package/skills/roles/engineer.md +3 -7
  232. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  233. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  234. package/skills/roles/{operator.md → operations.md} +2 -5
  235. package/skills/roles/orchestrator.md +1 -1
  236. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  237. package/skills/roles/product-manager.ai-product.md +1 -1
  238. package/skills/roles/product-manager.enterprise.md +1 -1
  239. package/skills/roles/product-manager.growth.md +1 -2
  240. package/skills/roles/product-manager.md +1 -2
  241. package/skills/roles/product-manager.platform.md +1 -1
  242. package/skills/roles/product-manager.product.md +1 -1
  243. package/skills/roles/qa.ai-eval.md +1 -1
  244. package/skills/roles/qa.api-contract.md +1 -1
  245. package/skills/roles/qa.data-pipeline.md +1 -1
  246. package/skills/roles/qa.md +1 -1
  247. package/skills/roles/qa.web-ui.md +1 -1
  248. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  249. package/skills/roles/researcher.md +1 -1
  250. package/skills/roles/reviewer.md +1 -1
  251. package/skills/roles/security.ai.md +1 -1
  252. package/skills/roles/security.appsec.md +1 -1
  253. package/skills/roles/security.cloud.md +1 -1
  254. package/skills/roles/security.legal-compliance.md +1 -1
  255. package/skills/roles/security.md +1 -1
  256. package/skills/roles/security.privacy.md +1 -1
  257. package/skills/roles/security.supply-chain.md +1 -1
  258. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  259. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  260. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  261. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  262. package/skills/routing.json +18 -0
  263. package/skills/routing.md +1 -1
  264. package/specialists/artifact-manifest.json +2 -1
  265. package/specialists/audit-enrichments.json +14 -14
  266. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  267. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  268. package/specialists/org/contracts/any-to-debugger.json +38 -0
  269. package/specialists/org/contracts/any-to-designer.json +33 -0
  270. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  271. package/specialists/org/contracts/any-to-explorer.json +39 -0
  272. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  273. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  274. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  275. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  276. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  277. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  278. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  279. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  280. package/specialists/org/contracts/architect-to-operations.json +45 -0
  281. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  282. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  283. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  284. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  285. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  286. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  287. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  288. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  289. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  290. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  291. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  292. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  293. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  294. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  295. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  296. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  297. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  298. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  299. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  300. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  301. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  302. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  303. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  304. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  305. package/specialists/org/contracts/user-to-construct.json +30 -0
  306. package/specialists/org/groups/engineering-group.json +43 -0
  307. package/specialists/org/groups/governance-group.json +38 -0
  308. package/specialists/org/groups/operations-group.json +41 -0
  309. package/specialists/org/groups/product-group.json +46 -0
  310. package/specialists/org/groups/quality-group.json +42 -0
  311. package/specialists/org/groups/strategy-group.json +41 -0
  312. package/specialists/org/policies/action-approval.json +13 -0
  313. package/specialists/org/policies/agents-routing.json +13 -0
  314. package/specialists/org/policies/architecture.json +20 -0
  315. package/specialists/org/policies/bash-safety.json +13 -0
  316. package/specialists/org/policies/beads-hygiene.json +13 -0
  317. package/specialists/org/policies/bootstrap-state.json +13 -0
  318. package/specialists/org/policies/code-review.json +13 -0
  319. package/specialists/org/policies/coding-style.json +13 -0
  320. package/specialists/org/policies/comments.json +13 -0
  321. package/specialists/org/policies/commit-approval.json +13 -0
  322. package/specialists/org/policies/contract-preconditions.json +13 -0
  323. package/specialists/org/policies/deployment.json +20 -0
  324. package/specialists/org/policies/description.json +14 -0
  325. package/specialists/org/policies/design-approval.json +19 -0
  326. package/specialists/org/policies/doc-ownership.json +13 -0
  327. package/specialists/org/policies/file-path-fence.json +13 -0
  328. package/specialists/org/policies/framing.json +13 -0
  329. package/specialists/org/policies/git-workflow.json +13 -0
  330. package/specialists/org/policies/incident-response.json +15 -0
  331. package/specialists/org/policies/intake-triage.json +15 -0
  332. package/specialists/org/policies/neurodivergent-output.json +13 -0
  333. package/specialists/org/policies/no-fabrication.json +13 -0
  334. package/specialists/org/policies/patterns.json +13 -0
  335. package/specialists/org/policies/quality-gate-approval.json +17 -0
  336. package/specialists/org/policies/release-gates.json +13 -0
  337. package/specialists/org/policies/research-evidence.json +13 -0
  338. package/specialists/org/policies/review-before-change.json +13 -0
  339. package/specialists/org/policies/rollback.json +15 -0
  340. package/specialists/org/policies/scope-change.json +20 -0
  341. package/specialists/org/policies/secret-scan.json +13 -0
  342. package/specialists/org/policies/security-approval.json +17 -0
  343. package/specialists/org/policies/security.json +13 -0
  344. package/specialists/org/policies/session-efficiency.json +13 -0
  345. package/specialists/org/policies/skill-routing.json +13 -0
  346. package/specialists/org/policies/strategic-prioritization.json +17 -0
  347. package/specialists/org/policies/testing.json +13 -0
  348. package/specialists/org/policies/tool-invisibility.json +14 -0
  349. package/specialists/org/scopes/creative.json +54 -0
  350. package/specialists/org/scopes/operations.json +51 -0
  351. package/specialists/org/scopes/research.json +60 -0
  352. package/specialists/org/scopes/rnd.json +81 -0
  353. package/specialists/org/specialists/cx-accessibility.json +69 -0
  354. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  355. package/specialists/org/specialists/cx-architect.json +89 -0
  356. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  357. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  358. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  359. package/specialists/org/specialists/cx-debugger.json +75 -0
  360. package/specialists/org/specialists/cx-designer.json +85 -0
  361. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  362. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  363. package/specialists/org/specialists/cx-engineer.json +106 -0
  364. package/specialists/org/specialists/cx-evaluator.json +69 -0
  365. package/specialists/org/specialists/cx-explorer.json +69 -0
  366. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  367. package/specialists/org/specialists/cx-operations.json +72 -0
  368. package/specialists/org/specialists/cx-oracle.json +46 -0
  369. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  370. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  371. package/specialists/org/specialists/cx-product-manager.json +102 -0
  372. package/specialists/org/specialists/cx-qa.json +79 -0
  373. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  374. package/specialists/org/specialists/cx-release-manager.json +77 -0
  375. package/specialists/org/specialists/cx-researcher.json +82 -0
  376. package/specialists/org/specialists/cx-reviewer.json +71 -0
  377. package/specialists/org/specialists/cx-security.json +91 -0
  378. package/specialists/org/specialists/cx-sre.json +94 -0
  379. package/specialists/org/specialists/cx-test-automation.json +69 -0
  380. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  381. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  382. package/specialists/org/teams/accessibility-team.json +39 -0
  383. package/specialists/org/teams/design-team.json +40 -0
  384. package/specialists/org/teams/engineering-team.json +50 -0
  385. package/specialists/org/teams/governance-team.json +41 -0
  386. package/specialists/org/teams/operations-team.json +46 -0
  387. package/specialists/org/teams/product-management-team.json +45 -0
  388. package/specialists/org/teams/quality-team.json +49 -0
  389. package/specialists/org/teams/research-team.json +39 -0
  390. package/specialists/org/teams/strategy-team.json +48 -0
  391. package/specialists/org/teams/ux-research-team.json +39 -0
  392. package/specialists/prompts/_shared/validation-contract.md +1 -1
  393. package/specialists/prompts/_team-template.md +10 -0
  394. package/specialists/prompts/cx-accessibility.md +1 -0
  395. package/specialists/prompts/cx-ai-engineer.md +1 -1
  396. package/specialists/prompts/cx-business-strategist.md +1 -1
  397. package/specialists/prompts/cx-data-engineer.md +1 -1
  398. package/specialists/prompts/cx-designer.md +1 -0
  399. package/specialists/prompts/cx-devil-advocate.md +1 -1
  400. package/specialists/prompts/cx-docs-keeper.md +1 -1
  401. package/specialists/prompts/cx-evaluator.md +1 -1
  402. package/specialists/prompts/cx-explorer.md +1 -1
  403. package/specialists/prompts/cx-operations.md +1 -1
  404. package/specialists/prompts/cx-oracle.md +7 -3
  405. package/specialists/prompts/cx-orchestrator.md +17 -1
  406. package/specialists/prompts/cx-platform-engineer.md +1 -1
  407. package/specialists/prompts/cx-product-manager.md +2 -0
  408. package/specialists/prompts/cx-release-manager.md +1 -1
  409. package/specialists/prompts/cx-researcher.md +7 -4
  410. package/specialists/prompts/cx-sre.md +1 -1
  411. package/specialists/prompts/cx-test-automation.md +2 -2
  412. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  413. package/specialists/prompts/cx-ux-researcher.md +2 -1
  414. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  415. package/templates/demos/scripts/capability-contract.json +25 -0
  416. package/templates/demos/scripts/intake-triage.json +25 -0
  417. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  418. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  419. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  420. package/templates/demos/tapes/capability-contract.tape +39 -0
  421. package/templates/demos/tapes/intake-triage.tape +39 -0
  422. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  423. package/templates/distribution/construct-brand.typ +23 -18
  424. package/templates/distribution/construct-decision.typ +1 -1
  425. package/templates/distribution/construct-pdf.typ +1 -1
  426. package/templates/distribution/construct-prd.typ +1 -1
  427. package/templates/distribution/construct-research.typ +1 -1
  428. package/templates/distribution/mermaid-puppeteer.json +8 -0
  429. package/templates/docs/persona-artifact.md +1 -1
  430. package/templates/docs/prd.md +6 -0
  431. package/apps/chat/engine/ai-sdk-agent.mjs +0 -183
  432. package/apps/chat/engine/loop-driver.mjs +0 -211
  433. package/apps/chat/engine/models.mjs +0 -122
  434. package/apps/chat/engine/provider-adapters.mjs +0 -171
  435. package/apps/chat/engine/tools/permission.mjs +0 -54
  436. package/apps/chat/engine/tools/primitives.mjs +0 -180
  437. package/apps/chat/engine/tools/registry.mjs +0 -122
  438. package/apps/chat/engine/turn-controls.mjs +0 -70
  439. package/lib/boundary.mjs +0 -127
  440. package/lib/certification/dashboard-api.mjs +0 -71
  441. package/lib/chat/cli.mjs +0 -333
  442. package/lib/chat/command-suggest.mjs +0 -161
  443. package/lib/chat/commands.mjs +0 -215
  444. package/lib/chat/config.mjs +0 -142
  445. package/lib/chat/context-compactor.mjs +0 -250
  446. package/lib/chat/context-continuation.mjs +0 -253
  447. package/lib/chat/continuation-source.mjs +0 -58
  448. package/lib/chat/demo-guide.mjs +0 -61
  449. package/lib/chat/design-tokens.mjs +0 -91
  450. package/lib/chat/desktop-binary.mjs +0 -81
  451. package/lib/chat/desktop-build.mjs +0 -130
  452. package/lib/chat/desktop-launcher.mjs +0 -133
  453. package/lib/chat/evidence.mjs +0 -145
  454. package/lib/chat/export.mjs +0 -74
  455. package/lib/chat/harness/driver.mjs +0 -91
  456. package/lib/chat/list-picker.mjs +0 -112
  457. package/lib/chat/model-picker.mjs +0 -356
  458. package/lib/chat/openrouter-fallback.mjs +0 -151
  459. package/lib/chat/permission-prompt.mjs +0 -33
  460. package/lib/chat/picker-catalog.mjs +0 -45
  461. package/lib/chat/policy-telemetry.mjs +0 -34
  462. package/lib/chat/present.mjs +0 -246
  463. package/lib/chat/session-context.mjs +0 -39
  464. package/lib/chat/session-persist.mjs +0 -73
  465. package/lib/chat/session-restore.mjs +0 -71
  466. package/lib/chat/session-settings.mjs +0 -53
  467. package/lib/chat/system-prompt.mjs +0 -52
  468. package/lib/chat/transparency.mjs +0 -93
  469. package/lib/chat/tui/color-scheme.mjs +0 -42
  470. package/lib/chat/tui/markdown.mjs +0 -123
  471. package/lib/chat/tui/presentation.mjs +0 -100
  472. package/lib/chat/tui/render.mjs +0 -500
  473. package/lib/chat/tui/turn-block.mjs +0 -284
  474. package/lib/chat/tui/turn-present.mjs +0 -18
  475. package/lib/chat/tui/turn-state.mjs +0 -88
  476. package/lib/chat/tui/usage.mjs +0 -122
  477. package/lib/chat/web-commands.mjs +0 -146
  478. package/lib/chat/web-launcher.mjs +0 -63
  479. package/lib/chat/web-picker-keys.mjs +0 -46
  480. package/lib/chat/web-session.mjs +0 -159
  481. package/lib/config/alias.mjs +0 -57
  482. package/lib/dashboard-demo.mjs +0 -71
  483. package/lib/dashboard-static.mjs +0 -175
  484. package/lib/install/desktop-binary-download.mjs +0 -88
  485. package/lib/profiles/loader.mjs +0 -123
  486. package/lib/profiles/rebrand.mjs +0 -46
  487. package/lib/server/auth.mjs +0 -169
  488. package/lib/server/chat-loop.mjs +0 -622
  489. package/lib/server/chat.mjs +0 -336
  490. package/lib/server/cors.mjs +0 -77
  491. package/lib/server/csrf.mjs +0 -103
  492. package/lib/server/demo-preview.mjs +0 -63
  493. package/lib/server/index.mjs +0 -2641
  494. package/lib/server/insights.mjs +0 -780
  495. package/lib/server/langfuse-login.mjs +0 -58
  496. package/lib/server/rate-limit.mjs +0 -93
  497. package/lib/server/telemetry-login.mjs +0 -100
  498. package/lib/server/webhook.mjs +0 -512
  499. package/specialists/contracts.json +0 -1032
  500. package/specialists/contracts.schema.json +0 -83
  501. package/specialists/policy-inventory.json +0 -188
  502. package/specialists/registry.json +0 -1412
  503. package/specialists/role-manifests.json +0 -217
  504. package/specialists/teams.json +0 -94
@@ -1,91 +0,0 @@
1
- /**
2
- * lib/chat/harness/driver.mjs — normalized harness driver contract for the
3
- * construct terminal chat surface.
4
- *
5
- * A driver runs the agent loop and normalizes its streaming output into one event
6
- * model, so the transparency engine and the TUI never depend on how the loop is
7
- * produced. ADR-0041: Construct's owned loop (apps/chat/engine) implements this
8
- * contract; the union stays the stable seam between the engine and the renderers.
9
- * Every driver exposes the same shape:
10
- * start() opens a session and returns { sessionId, capabilities }
11
- * prompt(text, opts) async-iterable of normalized events for one turn; opts may
12
- * carry { model, sandbox, permissionMode } when the host supports them
13
- * cancel() aborts the active turn
14
- * stop() tears down the host process / connection
15
- * listModels() optional: returns [{ id, label, ... }] when the host can enumerate models
16
- *
17
- * Normalized event union consumed by the rest of the surface:
18
- * { type: 'thinking', text, messageId } agent internal reasoning chunk
19
- * { type: 'text', text, messageId } agent message chunk
20
- * { type: 'plan', entries } planned path / task entries
21
- * { type: 'tool_call', id, title, kind, status }
22
- * { type: 'tool_update', id, status, content }
23
- * { type: 'usage', tokens, cost, context, model }
24
- * tokens: { input, output, cacheRead, cacheWrite, reasoning, total }
25
- * cost: { amount, currency } context: { used, size }
26
- * every field optional — adapters emit only what the host
27
- * reports (no fabricated splits)
28
- * { type: 'specialist', role, status, detail } construct orchestration overlay
29
- * { type: 'permission', requestId, toolCall, options }
30
- * { type: 'notice', level, code, message } surface-visible advisory (e.g. context-pressure)
31
- * { type: 'context_continuation', packet, compacted } persisted continuation packet (construct-6zga.1.10)
32
- * { type: 'error', message }
33
- * { type: 'done', stopReason }
34
- *
35
- * Keeping this union stable is what lets ACP agents and per-host adapters be
36
- * swapped without touching the transparency or rendering layers.
37
- */
38
-
39
- export const EVENT_TYPES = Object.freeze({
40
- THINKING: 'thinking',
41
- TEXT: 'text',
42
- PLAN: 'plan',
43
- TOOL_CALL: 'tool_call',
44
- TOOL_UPDATE: 'tool_update',
45
- USAGE: 'usage',
46
- SPECIALIST: 'specialist',
47
- PERMISSION: 'permission',
48
- NOTICE: 'notice',
49
- CONTEXT_CONTINUATION: 'context_continuation',
50
- ERROR: 'error',
51
- DONE: 'done',
52
- });
53
-
54
- // A single-producer/single-consumer queue that is also an async iterable. The
55
- // line-router pushes normalized events as they arrive; the turn loop awaits them
56
- // until the producer closes the queue on stop reason or error.
57
-
58
- export class AsyncEventQueue {
59
- constructor() {
60
- this.buffer = [];
61
- this.waiters = [];
62
- this.closed = false;
63
- }
64
-
65
- push(value) {
66
- if (this.closed) return;
67
- const waiter = this.waiters.shift();
68
- if (waiter) waiter({ value, done: false });
69
- else this.buffer.push(value);
70
- }
71
-
72
- close() {
73
- if (this.closed) return;
74
- this.closed = true;
75
- while (this.waiters.length) this.waiters.shift()({ value: undefined, done: true });
76
- }
77
-
78
- [Symbol.asyncIterator]() {
79
- return {
80
- next: () => {
81
- if (this.buffer.length) return Promise.resolve({ value: this.buffer.shift(), done: false });
82
- if (this.closed) return Promise.resolve({ value: undefined, done: true });
83
- return new Promise((resolve) => this.waiters.push(resolve));
84
- },
85
- return: () => {
86
- this.close();
87
- return Promise.resolve({ value: undefined, done: true });
88
- },
89
- };
90
- }
91
- }
@@ -1,112 +0,0 @@
1
- /**
2
- * lib/chat/list-picker.mjs — searchable list picker for construct chat Ink.
3
- *
4
- * Pure state/helpers for arrow-key overlays: type-to-filter, windowed viewport,
5
- * and selection index clamping for model, setting, and permission pickers.
6
- */
7
-
8
- export function filterPickerItems(items = [], query = '') {
9
- const q = String(query || '').trim().toLowerCase();
10
- if (!q) return items;
11
- return items.filter((item) => {
12
- const hay = [item.id, item.label, item.detail, item.tag]
13
- .filter(Boolean)
14
- .join(' ')
15
- .toLowerCase();
16
- return hay.includes(q);
17
- });
18
- }
19
-
20
- export function pickerStartIndex(items, selectedId, idKey = 'id') {
21
- if (!items?.length) return 0;
22
- const idx = items.findIndex((item) => item[idKey] === selectedId);
23
- if (idx >= 0 && !items[idx]?.disabled) return idx;
24
- const firstEnabled = items.findIndex((item) => !item.disabled);
25
- if (firstEnabled >= 0) return firstEnabled;
26
- return idx >= 0 ? idx : 0;
27
- }
28
-
29
- export function windowPickerItems(items, index, windowSize = 12) {
30
- if (!items?.length) return { items: [], offset: 0 };
31
- const size = Math.max(5, windowSize);
32
- let start = Math.max(0, index - Math.floor(size / 2));
33
- if (start + size > items.length) start = Math.max(0, items.length - size);
34
- return { items: items.slice(start, start + size), offset: start };
35
- }
36
-
37
- export function clampPickerIndex(state) {
38
- const visible = filterPickerItems(state.items, state.query);
39
- const max = Math.max(0, visible.length - 1);
40
- return { ...state, index: Math.min(state.index, max) };
41
- }
42
-
43
- /**
44
- * @param {{
45
- * kind?: string,
46
- * title?: string,
47
- * items?: Array<Record<string, unknown>>,
48
- * selectedId?: string | null,
49
- * context?: string | null,
50
- * query?: string,
51
- * }} [opts]
52
- */
53
- export function createListPickerState({
54
- kind, title, items, selectedId = null, context = null, query = '',
55
- } = {}) {
56
- const visible = filterPickerItems(items, query);
57
- return {
58
- kind,
59
- title,
60
- items,
61
- query,
62
- index: pickerStartIndex(visible, selectedId),
63
- context,
64
- };
65
- }
66
-
67
- export function getPickerVisibleItems(state) {
68
- return filterPickerItems(state?.items || [], state?.query || '');
69
- }
70
-
71
- export function getPickerSelectedItem(state) {
72
- const visible = getPickerVisibleItems(state);
73
- return visible[state?.index ?? 0] || null;
74
- }
75
-
76
- export function movePickerIndex(state, delta) {
77
- const visible = getPickerVisibleItems(state);
78
- if (!visible.length) return state;
79
- let idx = state.index ?? 0;
80
- for (let step = 0; step < visible.length; step++) {
81
- idx = Math.max(0, Math.min(visible.length - 1, idx + delta));
82
- if (!visible[idx]?.disabled) return { ...state, index: idx };
83
- if ((delta < 0 && idx === 0) || (delta > 0 && idx === visible.length - 1)) break;
84
- }
85
- return { ...state, index: idx };
86
- }
87
-
88
- export function appendPickerQuery(state, char) {
89
- if (!char) return state;
90
- return clampPickerIndex({ ...state, query: `${state.query || ''}${char}`, index: 0 });
91
- }
92
-
93
- export function backspacePickerQuery(state) {
94
- return clampPickerIndex({ ...state, query: (state.query || '').slice(0, -1), index: 0 });
95
- }
96
-
97
- export function reducePickerKey(state, { char, key } = {}) {
98
- if (!state) return { state: null, action: 'none' };
99
- if (key?.escape) return { state: null, action: 'cancel' };
100
- if (key?.return) return { state, action: 'commit' };
101
- if (key?.upArrow) return { state: movePickerIndex(state, -1), action: 'none' };
102
- if (key?.downArrow) return { state: movePickerIndex(state, 1), action: 'none' };
103
- if (key?.backspace || key?.delete) return { state: backspacePickerQuery(state), action: 'none' };
104
- if (char && !key?.ctrl && !key?.meta) return { state: appendPickerQuery(state, char), action: 'none' };
105
- return { state, action: 'none' };
106
- }
107
-
108
- export function pickerViewport(state, windowSize = 14) {
109
- const visible = getPickerVisibleItems(state);
110
- const clamped = clampPickerIndex({ ...state, items: visible });
111
- return windowPickerItems(visible, clamped.index, windowSize);
112
- }
@@ -1,356 +0,0 @@
1
- /**
2
- * lib/chat/model-picker.mjs — model list data for the construct chat picker.
3
- *
4
- * Curates a short list: free-router auto-pick, live OpenRouter free models,
5
- * configured provider tier defaults, and the active session model. Persistence
6
- * reuses saveChatConfig from lib/chat/config.mjs.
7
- */
8
-
9
- import { saveChatConfig } from './config.mjs';
10
- import { hasAnySecret } from '../providers/secret-resolver.mjs';
11
- import { getProviderModelCatalog, isChatModelAvailable } from '../model-router.mjs';
12
- import {
13
- pollConfiguredProviders,
14
- readProviderCatalogCache,
15
- } from '../models/provider-poll.mjs';
16
- import { resolveFreeOpenRouterModel } from '../../apps/chat/engine/models.mjs';
17
- import { filterPickerItems, pickerStartIndex, windowPickerItems } from './list-picker.mjs';
18
-
19
- export { filterPickerItems, pickerStartIndex, windowPickerItems };
20
-
21
- export const FREE_ROUTER_ITEM_ID = '__free_router__';
22
-
23
- export function sortModelsForPicker(models = []) {
24
- return [...models].sort(
25
- (a, b) => Number(Boolean(b.action === 'free-router')) - Number(Boolean(a.action === 'free-router'))
26
- || Number(Boolean(b.isFree)) - Number(Boolean(a.isFree))
27
- || Number(b.suitable !== false) - Number(a.suitable !== false)
28
- || Number(Boolean(b.isProviderDefault)) - Number(Boolean(a.isProviderDefault))
29
- || String(a.label || a.id).localeCompare(String(b.label || b.id)),
30
- );
31
- }
32
-
33
- function shortModelLabel(id) {
34
- if (!id) return id;
35
- const name = id.replace(/^openrouter\//, '');
36
- const slash = name.lastIndexOf('/');
37
- return slash >= 0 ? name.slice(slash + 1) : name;
38
- }
39
-
40
- function catalogItem(model) {
41
- const id = model.id;
42
- const isFree = id.includes(':free') || model.isFree === true;
43
- return {
44
- id,
45
- label: model.label || (isFree ? shortModelLabel(id) : id),
46
- tag: model.action === 'free-router' ? 'router' : isFree ? 'free' : model.local ? 'local' : model.tier || null,
47
- detail: model.detail || model.name || (model.configured === false ? 'not configured' : null),
48
- isFree,
49
- action: model.action || null,
50
- suitable: model.suitable,
51
- disabled: model.disabled === true,
52
- };
53
- }
54
-
55
- export function configuredTierPickerItems({ env = process.env } = {}) {
56
- const { providers } = getProviderModelCatalog({ env });
57
- const items = [];
58
- const seen = new Set();
59
- for (const provider of providers) {
60
- if (!provider.configured) continue;
61
- for (const tier of ['reasoning', 'standard', 'fast']) {
62
- const id = provider.tiers?.[tier];
63
- if (!id || seen.has(id)) continue;
64
- seen.add(id);
65
- const availability = isChatModelAvailable(id, { env });
66
- const notPulled = availability.reason === 'model_not_pulled';
67
- items.push(catalogItem({
68
- id,
69
- label: id,
70
- name: `${provider.label} · ${tier}`,
71
- tier,
72
- configured: availability.ok,
73
- suitable: availability.ok,
74
- isProviderDefault: tier === 'standard',
75
- disabled: !availability.ok,
76
- detail: notPulled
77
- ? `not installed — run ollama pull ${availability.nativeModel || id.replace(/^ollama\//, '')}`
78
- : availability.ok ? null : availability.reason?.replace(/_/g, ' ') || 'unavailable',
79
- }));
80
- }
81
- }
82
- return { items, seen };
83
- }
84
-
85
- function capabilityBadges(model) {
86
- const badges = [];
87
- if (model.reasoning) badges.push('reasoning');
88
- if (model.vision) badges.push('vision');
89
- if (model.tools) badges.push('tools');
90
- return badges;
91
- }
92
-
93
- function priceLabel(pricing) {
94
- if (!pricing) return null;
95
- if (pricing.input === 0 && pricing.output === 0) return null;
96
- const fmt = (n) => (n >= 1 ? n.toFixed(2) : n.toFixed(3));
97
- return `$${fmt(pricing.input)} in · $${fmt(pricing.output)} out / 1M`;
98
- }
99
-
100
- function contextLabel(context) {
101
- if (!context) return null;
102
- if (context >= 1_000_000) return `${(context / 1_000_000).toFixed(1).replace(/\.0$/, '')}M ctx`;
103
- if (context >= 1000) return `${Math.round(context / 1000)}K ctx`;
104
- return `${context} ctx`;
105
- }
106
-
107
- function pickerItemFromModel(model, groupLabel) {
108
- if (model.source === 'hint' || model.disabled) {
109
- return {
110
- id: model.id,
111
- label: model.label || model.id,
112
- group: groupLabel,
113
- tag: null,
114
- badges: [],
115
- detail: null,
116
- isFree: false,
117
- suitable: false,
118
- disabled: true,
119
- source: model.source || 'hint',
120
- };
121
- }
122
- const badges = capabilityBadges(model);
123
- const price = priceLabel(model.pricing);
124
- const detailParts = [];
125
- if (model.free) detailParts.push(model.provider === 'ollama' ? 'free · runs locally' : 'free');
126
- else if (price) detailParts.push(price);
127
- const ctx = contextLabel(model.context);
128
- if (ctx) detailParts.push(ctx);
129
- if (model.source === 'cached') detailParts.push('cached — provider offline');
130
-
131
- // The OpenRouter free router answers from whatever free model is available, so
132
- // quality and behavior vary turn to turn — say so before it is selected.
133
- if (/^openrouter\/openrouter\/(free|auto)\b/.test(String(model.id))) {
134
- detailParts.push('variable quality — routes to a random free model');
135
- }
136
- return {
137
- id: model.id,
138
- label: model.label || shortModelLabel(model.id),
139
- group: groupLabel,
140
- tag: model.free ? 'free' : null,
141
- badges,
142
- price: model.free ? null : price,
143
- context: model.context || null,
144
- detail: detailParts.join(' · ') || null,
145
- isFree: Boolean(model.free),
146
- suitable: true,
147
- disabled: false,
148
- source: model.source || 'live',
149
- };
150
- }
151
-
152
- // One picker payload: the free-router auto-pick on top, then every configured
153
- // provider's live catalog grouped under its label. Polling is best-effort; on a
154
- // total failure the cached groups (or nothing beyond free-router) are used so the
155
- // picker still opens.
156
- export async function loadModelPickerItems(_driver, { env = process.env, cwd = process.cwd(), currentModel = null, modelMode = 'pinned', pollProviders = pollConfiguredProviders } = {}) {
157
- const items = [];
158
- const seen = new Set();
159
-
160
- const hasOpenRouter = hasAnySecret(['OPENROUTER_API_KEY', 'OPEN_ROUTER_API_KEY'], { env });
161
- items.push({
162
- id: FREE_ROUTER_ITEM_ID,
163
- label: 'OpenRouter free router — auto-pick best standard model',
164
- group: 'Smart routing',
165
- tag: 'router',
166
- badges: [],
167
- detail: hasOpenRouter
168
- ? 'auto-picks a free model + retries on failure; not pinned — quality varies'
169
- : 'needs OPENROUTER_API_KEY in ~/.construct/config.env',
170
- action: 'free-router',
171
- configured: hasOpenRouter,
172
- disabled: !hasOpenRouter,
173
- });
174
- seen.add(FREE_ROUTER_ITEM_ID);
175
-
176
- let groups = [];
177
- try {
178
- groups = await pollProviders({ env, cwd });
179
- } catch {
180
- groups = readProviderCatalogCache() || [];
181
- }
182
-
183
- for (const group of groups) {
184
- const sorted = [...group.models].sort(
185
- (a, b) => Number(Boolean(b.free)) - Number(Boolean(a.free))
186
- || String(a.label || a.id).localeCompare(String(b.label || b.id)),
187
- );
188
- for (const model of sorted) {
189
- if (seen.has(model.id)) continue;
190
-
191
- // The chat loop always dispatches tools, so a model the provider reports as
192
- // tool-incapable can only ever error out — drop it from the picker. Only
193
- // filter when the provider actually told us (toolsKnown); an unknown
194
- // capability (e.g. a local Ollama tag) stays selectable.
195
- if (!model.disabled && model.source !== 'hint' && model.toolsKnown === true && !model.tools) {
196
- seen.add(model.id);
197
- continue;
198
- }
199
-
200
- seen.add(model.id);
201
- items.push(pickerItemFromModel(model, group.label));
202
- }
203
- }
204
-
205
- if (currentModel && modelMode !== 'free-router' && !seen.has(currentModel)) {
206
- seen.add(currentModel);
207
- items.push(pickerItemFromModel(
208
- { id: currentModel, label: currentModel, free: false, source: 'live' },
209
- 'Current session',
210
- ));
211
- }
212
-
213
- return items;
214
- }
215
-
216
- export async function resolveModelPickerSelection(item, { env = process.env } = {}) {
217
- if (!item) return null;
218
- if (item.action === 'free-router' || item.id === FREE_ROUTER_ITEM_ID) {
219
- const modelId = await resolveFreeOpenRouterModel({ env, tier: 'standard' });
220
- return modelId ? { mode: 'free-router', modelId } : null;
221
- }
222
- return { mode: 'pinned', modelId: item.id };
223
- }
224
-
225
- export function commitPickerModel(session, selection, { cwd, hostId = 'construct', layers = null } = {}) {
226
- const normalized = typeof selection === 'string'
227
- ? { mode: 'pinned', modelId: selection }
228
- : selection;
229
- if (!normalized?.modelId && normalized?.mode !== 'free-router') return null;
230
-
231
- session.modelMode = normalized.mode || 'pinned';
232
- session.model = normalized.modelId;
233
- session.savedModel = session.modelMode === 'pinned' ? normalized.modelId : null;
234
- session.modelNotice = null;
235
-
236
- try {
237
- saveChatConfig({
238
- host: hostId,
239
- model: session.modelMode === 'pinned' ? session.model : null,
240
- modelMode: session.modelMode,
241
- layers: layers || session.layers,
242
- thinking: (layers || session.layers)?.thinking,
243
- permissionMode: session.permissionMode,
244
- sandbox: session.sandbox,
245
- ui: session.ui,
246
- }, { cwd });
247
- } catch { /* persistence is best-effort */ }
248
- return normalized;
249
- }
250
-
251
- export function pickerSelectedId(session) {
252
- if (session?.modelMode === 'free-router') return FREE_ROUTER_ITEM_ID;
253
- return session?.savedModel || session?.model || null;
254
- }
255
-
256
- export function formatModelHeader(session) {
257
- if (session?.modelMode === 'free-router') {
258
- const slug = session.model ? session.model.replace(/^openrouter\//, '') : '(resolving…)';
259
- return { label: `free router → ${slug}`, isRouter: true };
260
- }
261
- const active = session?.model || '(no model)';
262
- const saved = session?.savedModel;
263
- if (saved && saved !== session?.model) {
264
- return { label: active, savedPin: saved, isRouter: false };
265
- }
266
- return { label: active, isRouter: false };
267
- }
268
-
269
- function formatPickerLine(item, colors, { selected = false } = {}) {
270
- const marker = selected ? `${colors.green}●${colors.reset}` : ' ';
271
- const tag = item.tag ? `${colors.dim} [${item.tag}]${colors.reset}` : '';
272
- const badges = item.badges?.length ? `${colors.dim} ${item.badges.map((b) => `·${b}`).join(' ')}${colors.reset}` : '';
273
- const disabled = item.disabled ? `${colors.dim} (unavailable)${colors.reset}` : '';
274
- const detail = item.detail ? `\n ${colors.dim}${item.detail}${colors.reset}` : '';
275
- return ` ${marker} ${item.label || item.id}${tag}${badges}${disabled}${detail}`;
276
- }
277
-
278
- export async function promptModelPickerTerminal({
279
- output,
280
- colors,
281
- session,
282
- rl = null,
283
- askFn = null,
284
- env = process.env,
285
- cwd = process.cwd(),
286
- hostId = 'construct',
287
- layers = session?.layers,
288
- pollProviders = pollConfiguredProviders,
289
- } = {}) {
290
- const items = await loadModelPickerItems(null, {
291
- env,
292
- cwd,
293
- currentModel: session?.model,
294
- modelMode: session?.modelMode || 'pinned',
295
- pollProviders,
296
- });
297
- if (!items.length) {
298
- output.write(`${colors.dim}no models to pick from — set a provider key in ~/.construct/config.env or use /model <id>${colors.reset}\n`);
299
- return null;
300
- }
301
-
302
- const selectedId = pickerSelectedId(session);
303
- output.write(`${colors.bold}select a model${colors.reset}\n`);
304
- output.write(`${colors.dim}enter a number, or press enter to cancel${colors.reset}\n`);
305
- const groupCounts = new Map();
306
- for (const item of items) {
307
- if (!item.group || item.disabled) continue;
308
- groupCounts.set(item.group, (groupCounts.get(item.group) || 0) + 1);
309
- }
310
- let lastGroup = null;
311
- items.forEach((item, i) => {
312
- if (item.group && item.group !== lastGroup) {
313
- lastGroup = item.group;
314
- const count = groupCounts.get(item.group);
315
- const suffix = count ? ` (${count})` : '';
316
- output.write(`\n${colors.dim}── ${item.group}${suffix} ──${colors.reset}\n`);
317
- }
318
- output.write(`${String(i + 1).padStart(2)}.${formatPickerLine(item, colors, { selected: item.id === selectedId })}\n`);
319
- });
320
-
321
- const prompt = `${colors.green}model #${colors.reset} `;
322
- const answer = rl
323
- ? (await new Promise((resolve) => rl.question(prompt, resolve))).trim()
324
- : askFn
325
- ? String(await askFn(prompt)).trim()
326
- : '';
327
- if (!answer) {
328
- output.write(`${colors.dim}${rl || askFn ? 'cancelled' : 'pick one with /model <id> or use the Ink UI for search'}${colors.reset}\n`);
329
- return null;
330
- }
331
-
332
- const idx = Number(answer) - 1;
333
- if (!Number.isInteger(idx) || idx < 0 || idx >= items.length) {
334
- output.write(`${colors.red}invalid selection${colors.reset}\n`);
335
- return null;
336
- }
337
-
338
- const item = items[idx];
339
- if (item.disabled) {
340
- output.write(`${colors.red}${item.detail || 'model not available'}${colors.reset}\n`);
341
- return null;
342
- }
343
-
344
- const selection = await resolveModelPickerSelection(item, { env });
345
- if (!selection?.modelId && selection?.mode !== 'free-router') {
346
- output.write(`${colors.red}could not resolve model${colors.reset}\n`);
347
- return null;
348
- }
349
-
350
- commitPickerModel(session, selection, { cwd, hostId, layers });
351
- const label = selection.mode === 'free-router'
352
- ? `free-router → ${selection.modelId}`
353
- : selection.modelId;
354
- output.write(`${colors.green}model set:${colors.reset} ${label} ${colors.dim}(saved)${colors.reset}\n`);
355
- return selection;
356
- }