@geraldmaron/construct 1.2.2 → 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 (497) hide show
  1. package/README.md +7 -10
  2. package/bin/construct +286 -220
  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 +56 -57
  28. package/lib/comment-lint.mjs +1 -5
  29. package/lib/completions.mjs +20 -2
  30. package/lib/config/legacy-config-migration.mjs +125 -0
  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/runtime-env.mjs +1 -1
  153. package/lib/scopes/enrich.mjs +67 -0
  154. package/lib/scopes/hooks.mjs +12 -0
  155. package/lib/{profiles → scopes}/lifecycle.mjs +70 -70
  156. package/lib/scopes/loader.mjs +104 -0
  157. package/lib/scopes/rebrand.mjs +32 -0
  158. package/lib/scopes/research-profile.mjs +16 -0
  159. package/lib/scopes/teams.mjs +98 -0
  160. package/lib/service-manager.mjs +1 -117
  161. package/lib/setup-prompts.mjs +1 -1
  162. package/lib/setup.mjs +17 -28
  163. package/lib/skills/composition-graph.mjs +98 -0
  164. package/lib/skills/router.mjs +80 -0
  165. package/lib/specialist-contracts.mjs +17 -18
  166. package/lib/specialists/postconditions.mjs +2 -2
  167. package/lib/specialists/prompt-schema.mjs +6 -5
  168. package/lib/specialists/roster.mjs +7 -12
  169. package/lib/specialists/schema.mjs +9 -6
  170. package/lib/status.mjs +25 -90
  171. package/lib/storage/file-lock.mjs +6 -3
  172. package/lib/telemetry/skill-calls.mjs +1 -1
  173. package/lib/templates/doc-presentation.mjs +63 -0
  174. package/lib/templates/visual-requirements.mjs +35 -2
  175. package/lib/term-format.mjs +107 -2
  176. package/lib/test-env-setup.mjs +21 -0
  177. package/lib/ui/components.mjs +42 -0
  178. package/lib/ui/glyphs.mjs +58 -0
  179. package/lib/ui/links.mjs +115 -0
  180. package/lib/ui/theme.mjs +108 -0
  181. package/lib/uninstall/uninstall.mjs +2 -1
  182. package/lib/validator.mjs +45 -8
  183. package/lib/validators/skill-effectiveness.mjs +174 -0
  184. package/lib/validators/skills.mjs +1 -1
  185. package/package.json +12 -7
  186. package/personas/construct.md +12 -3
  187. package/platforms/claude/CLAUDE.md +1 -1
  188. package/rules/common/beads-hygiene.md +52 -0
  189. package/rules/common/neurodivergent-output.md +4 -7
  190. package/rules/common/research.md +2 -0
  191. package/scripts/sync-specialists.mjs +260 -49
  192. package/skills/ai/prompt-optimizer.md +3 -3
  193. package/skills/brand/output-vibe.md +48 -0
  194. package/skills/docs/adr-workflow.md +3 -0
  195. package/skills/docs/backlog-proposal-workflow.md +3 -0
  196. package/skills/docs/codebase-research-workflow.md +5 -2
  197. package/skills/docs/customer-profile-workflow.md +3 -0
  198. package/skills/docs/document-ingest-workflow.md +3 -0
  199. package/skills/docs/evidence-ingest-workflow.md +4 -1
  200. package/skills/docs/init-project.md +1 -1
  201. package/skills/docs/prd-workflow.md +1 -1
  202. package/skills/docs/prfaq-workflow.md +3 -0
  203. package/skills/docs/product-intelligence-workflow.md +4 -1
  204. package/skills/docs/product-signal-workflow.md +3 -0
  205. package/skills/docs/research-workflow.md +13 -6
  206. package/skills/docs/runbook-workflow.md +3 -0
  207. package/skills/docs/strategy-workflow.md +3 -0
  208. package/skills/docs/user-research-workflow.md +6 -3
  209. package/skills/operating/orchestration-reference.md +1 -1
  210. package/skills/roles/{engineer.ai.md → ai-engineer.md} +2 -2
  211. package/skills/roles/architect.ai-systems.md +1 -1
  212. package/skills/roles/architect.data.md +1 -1
  213. package/skills/roles/architect.enterprise.md +1 -1
  214. package/skills/roles/architect.integration.md +1 -1
  215. package/skills/roles/architect.md +1 -1
  216. package/skills/roles/architect.platform.md +1 -1
  217. package/skills/roles/{product-manager.business-strategy.md → business-strategist.md} +3 -3
  218. package/skills/roles/data-analyst.experiment.md +1 -1
  219. package/skills/roles/data-analyst.md +1 -1
  220. package/skills/roles/data-analyst.product-intelligence.md +1 -1
  221. package/skills/roles/data-analyst.product.md +1 -1
  222. package/skills/roles/data-analyst.telemetry.md +1 -1
  223. package/skills/roles/{engineer.data.md → data-engineer.md} +2 -2
  224. package/skills/roles/data-engineer.pipeline.md +2 -2
  225. package/skills/roles/data-engineer.vector-retrieval.md +2 -2
  226. package/skills/roles/data-engineer.warehouse.md +2 -2
  227. package/skills/roles/debugger.md +1 -1
  228. package/skills/roles/designer.accessibility.md +1 -1
  229. package/skills/roles/designer.md +1 -1
  230. package/skills/roles/{reviewer.devil-advocate.md → devil-advocate.md} +2 -2
  231. package/skills/roles/{operator.docs.md → docs-keeper.md} +3 -3
  232. package/skills/roles/engineer.md +3 -7
  233. package/skills/roles/{reviewer.evaluator.md → evaluator.md} +2 -2
  234. package/skills/roles/{researcher.explorer.md → explorer.md} +2 -2
  235. package/skills/roles/{operator.md → operations.md} +2 -5
  236. package/skills/roles/orchestrator.md +1 -1
  237. package/skills/roles/{engineer.platform.md → platform-engineer.md} +2 -2
  238. package/skills/roles/product-manager.ai-product.md +1 -1
  239. package/skills/roles/product-manager.enterprise.md +1 -1
  240. package/skills/roles/product-manager.growth.md +1 -2
  241. package/skills/roles/product-manager.md +1 -2
  242. package/skills/roles/product-manager.platform.md +1 -1
  243. package/skills/roles/product-manager.product.md +1 -1
  244. package/skills/roles/qa.ai-eval.md +1 -1
  245. package/skills/roles/qa.api-contract.md +1 -1
  246. package/skills/roles/qa.data-pipeline.md +1 -1
  247. package/skills/roles/qa.md +1 -1
  248. package/skills/roles/qa.web-ui.md +1 -1
  249. package/skills/roles/{operator.release.md → release-manager.md} +3 -3
  250. package/skills/roles/researcher.md +1 -1
  251. package/skills/roles/reviewer.md +1 -1
  252. package/skills/roles/security.ai.md +1 -1
  253. package/skills/roles/security.appsec.md +1 -1
  254. package/skills/roles/security.cloud.md +1 -1
  255. package/skills/roles/security.legal-compliance.md +1 -1
  256. package/skills/roles/security.md +1 -1
  257. package/skills/roles/security.privacy.md +1 -1
  258. package/skills/roles/security.supply-chain.md +1 -1
  259. package/skills/roles/{operator.sre.md → sre.md} +3 -3
  260. package/skills/roles/{qa.test-automation.md → test-automation.md} +2 -2
  261. package/skills/roles/{reviewer.trace.md → trace-reviewer.md} +2 -2
  262. package/skills/roles/{researcher.ux.md → ux-researcher.md} +2 -2
  263. package/skills/routing.json +18 -0
  264. package/skills/routing.md +1 -1
  265. package/specialists/artifact-manifest.json +2 -1
  266. package/specialists/audit-enrichments.json +14 -14
  267. package/specialists/org/contracts/accessibility-to-qa.json +37 -0
  268. package/specialists/org/contracts/any-to-business-strategist.json +57 -0
  269. package/specialists/org/contracts/any-to-debugger.json +38 -0
  270. package/specialists/org/contracts/any-to-designer.json +33 -0
  271. package/specialists/org/contracts/any-to-docs-keeper.json +34 -0
  272. package/specialists/org/contracts/any-to-explorer.json +39 -0
  273. package/specialists/org/contracts/any-to-sre-incident.json +33 -0
  274. package/specialists/org/contracts/any-to-trace-reviewer.json +32 -0
  275. package/specialists/org/contracts/architect-to-ai-engineer.json +32 -0
  276. package/specialists/org/contracts/architect-to-data-engineer.json +52 -0
  277. package/specialists/org/contracts/architect-to-devil-advocate.json +38 -0
  278. package/specialists/org/contracts/architect-to-engineer.json +34 -0
  279. package/specialists/org/contracts/architect-to-evaluator.json +59 -0
  280. package/specialists/org/contracts/architect-to-legal-compliance.json +55 -0
  281. package/specialists/org/contracts/architect-to-operations.json +45 -0
  282. package/specialists/org/contracts/architect-to-platform-engineer.json +42 -0
  283. package/specialists/org/contracts/business-strategist-to-product-manager.json +39 -0
  284. package/specialists/org/contracts/construct-to-orchestrator.json +36 -0
  285. package/specialists/org/contracts/construct-to-rd-lead.json +53 -0
  286. package/specialists/org/contracts/data-analyst-to-product-manager.json +43 -0
  287. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +36 -0
  288. package/specialists/org/contracts/designer-to-accessibility.json +36 -0
  289. package/specialists/org/contracts/engineer-to-qa.json +34 -0
  290. package/specialists/org/contracts/engineer-to-reviewer.json +52 -0
  291. package/specialists/org/contracts/explorer-to-engineer.json +38 -0
  292. package/specialists/org/contracts/legal-compliance-to-release-manager.json +43 -0
  293. package/specialists/org/contracts/platform-engineer-to-engineer.json +31 -0
  294. package/specialists/org/contracts/product-manager-to-architect.json +71 -0
  295. package/specialists/org/contracts/product-manager-to-data-analyst.json +49 -0
  296. package/specialists/org/contracts/product-manager-to-ux-researcher.json +51 -0
  297. package/specialists/org/contracts/qa-to-release-manager.json +42 -0
  298. package/specialists/org/contracts/qa-to-test-automation.json +42 -0
  299. package/specialists/org/contracts/rd-lead-to-architect.json +38 -0
  300. package/specialists/org/contracts/researcher-to-architect.json +40 -0
  301. package/specialists/org/contracts/researcher-to-product-manager.json +57 -0
  302. package/specialists/org/contracts/reviewer-to-security.json +41 -0
  303. package/specialists/org/contracts/sre-to-release-manager.json +36 -0
  304. package/specialists/org/contracts/test-automation-to-engineer.json +34 -0
  305. package/specialists/org/contracts/trace-reviewer-to-sre.json +41 -0
  306. package/specialists/org/contracts/user-to-construct.json +30 -0
  307. package/specialists/org/groups/engineering-group.json +43 -0
  308. package/specialists/org/groups/governance-group.json +38 -0
  309. package/specialists/org/groups/operations-group.json +41 -0
  310. package/specialists/org/groups/product-group.json +46 -0
  311. package/specialists/org/groups/quality-group.json +42 -0
  312. package/specialists/org/groups/strategy-group.json +41 -0
  313. package/specialists/org/policies/action-approval.json +13 -0
  314. package/specialists/org/policies/agents-routing.json +13 -0
  315. package/specialists/org/policies/architecture.json +20 -0
  316. package/specialists/org/policies/bash-safety.json +13 -0
  317. package/specialists/org/policies/beads-hygiene.json +13 -0
  318. package/specialists/org/policies/bootstrap-state.json +13 -0
  319. package/specialists/org/policies/code-review.json +13 -0
  320. package/specialists/org/policies/coding-style.json +13 -0
  321. package/specialists/org/policies/comments.json +13 -0
  322. package/specialists/org/policies/commit-approval.json +13 -0
  323. package/specialists/org/policies/contract-preconditions.json +13 -0
  324. package/specialists/org/policies/deployment.json +20 -0
  325. package/specialists/org/policies/description.json +14 -0
  326. package/specialists/org/policies/design-approval.json +19 -0
  327. package/specialists/org/policies/doc-ownership.json +13 -0
  328. package/specialists/org/policies/file-path-fence.json +13 -0
  329. package/specialists/org/policies/framing.json +13 -0
  330. package/specialists/org/policies/git-workflow.json +13 -0
  331. package/specialists/org/policies/incident-response.json +15 -0
  332. package/specialists/org/policies/intake-triage.json +15 -0
  333. package/specialists/org/policies/neurodivergent-output.json +13 -0
  334. package/specialists/org/policies/no-fabrication.json +13 -0
  335. package/specialists/org/policies/patterns.json +13 -0
  336. package/specialists/org/policies/quality-gate-approval.json +17 -0
  337. package/specialists/org/policies/release-gates.json +13 -0
  338. package/specialists/org/policies/research-evidence.json +13 -0
  339. package/specialists/org/policies/review-before-change.json +13 -0
  340. package/specialists/org/policies/rollback.json +15 -0
  341. package/specialists/org/policies/scope-change.json +20 -0
  342. package/specialists/org/policies/secret-scan.json +13 -0
  343. package/specialists/org/policies/security-approval.json +17 -0
  344. package/specialists/org/policies/security.json +13 -0
  345. package/specialists/org/policies/session-efficiency.json +13 -0
  346. package/specialists/org/policies/skill-routing.json +13 -0
  347. package/specialists/org/policies/strategic-prioritization.json +17 -0
  348. package/specialists/org/policies/testing.json +13 -0
  349. package/specialists/org/policies/tool-invisibility.json +14 -0
  350. package/specialists/org/scopes/creative.json +54 -0
  351. package/specialists/org/scopes/operations.json +51 -0
  352. package/specialists/org/scopes/research.json +60 -0
  353. package/specialists/org/scopes/rnd.json +81 -0
  354. package/specialists/org/specialists/cx-accessibility.json +69 -0
  355. package/specialists/org/specialists/cx-ai-engineer.json +75 -0
  356. package/specialists/org/specialists/cx-architect.json +89 -0
  357. package/specialists/org/specialists/cx-business-strategist.json +72 -0
  358. package/specialists/org/specialists/cx-data-analyst.json +68 -0
  359. package/specialists/org/specialists/cx-data-engineer.json +71 -0
  360. package/specialists/org/specialists/cx-debugger.json +75 -0
  361. package/specialists/org/specialists/cx-designer.json +85 -0
  362. package/specialists/org/specialists/cx-devil-advocate.json +71 -0
  363. package/specialists/org/specialists/cx-docs-keeper.json +82 -0
  364. package/specialists/org/specialists/cx-engineer.json +106 -0
  365. package/specialists/org/specialists/cx-evaluator.json +69 -0
  366. package/specialists/org/specialists/cx-explorer.json +69 -0
  367. package/specialists/org/specialists/cx-legal-compliance.json +74 -0
  368. package/specialists/org/specialists/cx-operations.json +72 -0
  369. package/specialists/org/specialists/cx-oracle.json +46 -0
  370. package/specialists/org/specialists/cx-orchestrator.json +81 -0
  371. package/specialists/org/specialists/cx-platform-engineer.json +80 -0
  372. package/specialists/org/specialists/cx-product-manager.json +102 -0
  373. package/specialists/org/specialists/cx-qa.json +79 -0
  374. package/specialists/org/specialists/cx-rd-lead.json +73 -0
  375. package/specialists/org/specialists/cx-release-manager.json +77 -0
  376. package/specialists/org/specialists/cx-researcher.json +82 -0
  377. package/specialists/org/specialists/cx-reviewer.json +71 -0
  378. package/specialists/org/specialists/cx-security.json +91 -0
  379. package/specialists/org/specialists/cx-sre.json +94 -0
  380. package/specialists/org/specialists/cx-test-automation.json +69 -0
  381. package/specialists/org/specialists/cx-trace-reviewer.json +69 -0
  382. package/specialists/org/specialists/cx-ux-researcher.json +68 -0
  383. package/specialists/org/teams/accessibility-team.json +39 -0
  384. package/specialists/org/teams/design-team.json +40 -0
  385. package/specialists/org/teams/engineering-team.json +50 -0
  386. package/specialists/org/teams/governance-team.json +41 -0
  387. package/specialists/org/teams/operations-team.json +46 -0
  388. package/specialists/org/teams/product-management-team.json +45 -0
  389. package/specialists/org/teams/quality-team.json +49 -0
  390. package/specialists/org/teams/research-team.json +39 -0
  391. package/specialists/org/teams/strategy-team.json +48 -0
  392. package/specialists/org/teams/ux-research-team.json +39 -0
  393. package/specialists/prompts/_shared/validation-contract.md +1 -1
  394. package/specialists/prompts/_team-template.md +10 -0
  395. package/specialists/prompts/cx-accessibility.md +1 -0
  396. package/specialists/prompts/cx-ai-engineer.md +1 -1
  397. package/specialists/prompts/cx-business-strategist.md +1 -1
  398. package/specialists/prompts/cx-data-engineer.md +1 -1
  399. package/specialists/prompts/cx-designer.md +1 -0
  400. package/specialists/prompts/cx-devil-advocate.md +1 -1
  401. package/specialists/prompts/cx-docs-keeper.md +1 -1
  402. package/specialists/prompts/cx-evaluator.md +1 -1
  403. package/specialists/prompts/cx-explorer.md +1 -1
  404. package/specialists/prompts/cx-operations.md +1 -1
  405. package/specialists/prompts/cx-oracle.md +7 -3
  406. package/specialists/prompts/cx-orchestrator.md +17 -1
  407. package/specialists/prompts/cx-platform-engineer.md +1 -1
  408. package/specialists/prompts/cx-product-manager.md +2 -0
  409. package/specialists/prompts/cx-release-manager.md +1 -1
  410. package/specialists/prompts/cx-researcher.md +7 -4
  411. package/specialists/prompts/cx-sre.md +1 -1
  412. package/specialists/prompts/cx-test-automation.md +2 -2
  413. package/specialists/prompts/cx-trace-reviewer.md +3 -3
  414. package/specialists/prompts/cx-ux-researcher.md +2 -1
  415. package/templates/demos/scripts/architecture-review-adr.json +30 -0
  416. package/templates/demos/scripts/capability-contract.json +25 -0
  417. package/templates/demos/scripts/intake-triage.json +25 -0
  418. package/templates/demos/scripts/profile-doctor-health.json +25 -0
  419. package/templates/demos/tapes/agentic-platforms-prd.tape +2 -2
  420. package/templates/demos/tapes/architecture-review-adr.tape +44 -0
  421. package/templates/demos/tapes/capability-contract.tape +39 -0
  422. package/templates/demos/tapes/intake-triage.tape +39 -0
  423. package/templates/demos/tapes/profile-doctor-health.tape +39 -0
  424. package/templates/distribution/construct-brand.typ +23 -18
  425. package/templates/distribution/construct-decision.typ +1 -1
  426. package/templates/distribution/construct-pdf.typ +1 -1
  427. package/templates/distribution/construct-prd.typ +1 -1
  428. package/templates/distribution/construct-research.typ +1 -1
  429. package/templates/distribution/mermaid-puppeteer.json +8 -0
  430. package/templates/docs/persona-artifact.md +1 -1
  431. package/templates/docs/prd.md +6 -0
  432. package/lib/boundary.mjs +0 -127
  433. package/lib/certification/dashboard-api.mjs +0 -71
  434. package/lib/chat/cli.mjs +0 -333
  435. package/lib/chat/command-suggest.mjs +0 -161
  436. package/lib/chat/commands.mjs +0 -215
  437. package/lib/chat/config.mjs +0 -142
  438. package/lib/chat/context-compactor.mjs +0 -250
  439. package/lib/chat/context-continuation.mjs +0 -253
  440. package/lib/chat/continuation-source.mjs +0 -58
  441. package/lib/chat/demo-guide.mjs +0 -61
  442. package/lib/chat/design-tokens.mjs +0 -91
  443. package/lib/chat/desktop-binary.mjs +0 -81
  444. package/lib/chat/desktop-build.mjs +0 -130
  445. package/lib/chat/desktop-launcher.mjs +0 -133
  446. package/lib/chat/evidence.mjs +0 -145
  447. package/lib/chat/export.mjs +0 -74
  448. package/lib/chat/harness/driver.mjs +0 -91
  449. package/lib/chat/list-picker.mjs +0 -112
  450. package/lib/chat/model-picker.mjs +0 -356
  451. package/lib/chat/openrouter-fallback.mjs +0 -151
  452. package/lib/chat/permission-prompt.mjs +0 -33
  453. package/lib/chat/picker-catalog.mjs +0 -45
  454. package/lib/chat/policy-telemetry.mjs +0 -34
  455. package/lib/chat/present.mjs +0 -246
  456. package/lib/chat/session-context.mjs +0 -39
  457. package/lib/chat/session-persist.mjs +0 -73
  458. package/lib/chat/session-restore.mjs +0 -71
  459. package/lib/chat/session-settings.mjs +0 -53
  460. package/lib/chat/system-prompt.mjs +0 -52
  461. package/lib/chat/transparency.mjs +0 -93
  462. package/lib/chat/tui/color-scheme.mjs +0 -42
  463. package/lib/chat/tui/markdown.mjs +0 -123
  464. package/lib/chat/tui/presentation.mjs +0 -100
  465. package/lib/chat/tui/render.mjs +0 -500
  466. package/lib/chat/tui/turn-block.mjs +0 -284
  467. package/lib/chat/tui/turn-present.mjs +0 -18
  468. package/lib/chat/tui/turn-state.mjs +0 -88
  469. package/lib/chat/tui/usage.mjs +0 -122
  470. package/lib/chat/web-commands.mjs +0 -146
  471. package/lib/chat/web-launcher.mjs +0 -63
  472. package/lib/chat/web-picker-keys.mjs +0 -46
  473. package/lib/chat/web-session.mjs +0 -159
  474. package/lib/config/alias.mjs +0 -57
  475. package/lib/dashboard-demo.mjs +0 -71
  476. package/lib/dashboard-static.mjs +0 -175
  477. package/lib/install/desktop-binary-download.mjs +0 -88
  478. package/lib/profiles/loader.mjs +0 -123
  479. package/lib/profiles/rebrand.mjs +0 -46
  480. package/lib/server/auth.mjs +0 -169
  481. package/lib/server/chat-loop.mjs +0 -622
  482. package/lib/server/chat.mjs +0 -336
  483. package/lib/server/cors.mjs +0 -77
  484. package/lib/server/csrf.mjs +0 -103
  485. package/lib/server/demo-preview.mjs +0 -63
  486. package/lib/server/index.mjs +0 -2641
  487. package/lib/server/insights.mjs +0 -780
  488. package/lib/server/langfuse-login.mjs +0 -58
  489. package/lib/server/rate-limit.mjs +0 -93
  490. package/lib/server/telemetry-login.mjs +0 -100
  491. package/lib/server/webhook.mjs +0 -512
  492. package/specialists/contracts.json +0 -1032
  493. package/specialists/contracts.schema.json +0 -83
  494. package/specialists/policy-inventory.json +0 -188
  495. package/specialists/registry.json +0 -1412
  496. package/specialists/role-manifests.json +0 -217
  497. package/specialists/teams.json +0 -94
@@ -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
- }
@@ -1,151 +0,0 @@
1
- /**
2
- * lib/chat/openrouter-fallback.mjs — OpenRouter failure parsing and turn-time fallback.
3
- *
4
- * When a free slug returns 404/429 at call time, walks the live poll for the next
5
- * candidate. When an Ollama tag is pinned but not pulled, falls through to the
6
- * next configured provider for the same turn (mirroring session launch resolution).
7
- */
8
-
9
- import { classifyProviderFailure, resolveValidatedChatModel } from '../model-router.mjs';
10
-
11
- const MAX_FALLBACK_ATTEMPTS = 3;
12
-
13
- export function parseOpenRouterError(error) {
14
- const text = typeof error === 'string' ? error : String(error?.message || error || '');
15
- let raw = text;
16
- try {
17
- const jsonMatch = text.match(/\{[\s\S]*\}/);
18
- if (jsonMatch) {
19
- const body = JSON.parse(jsonMatch[0]);
20
- raw = body?.error?.metadata?.raw || body?.error?.message || text;
21
- }
22
- } catch { /* not JSON */ }
23
-
24
- let summary = raw;
25
- const ollamaMissing = raw.match(/model ['"]([^'"]+)['"] not found/i);
26
- if (ollamaMissing) {
27
- const native = ollamaMissing[1];
28
- summary = `Ollama model '${native}' is not installed locally. Pull it with: ollama pull ${native} (or: construct ollama pull ${native})`;
29
- } else if (/rate[- ]?limit|429|temporarily rate-limited/i.test(raw)) {
30
- summary = raw.replace(/^Provider returned error\.?\s*/i, '').trim() || 'rate-limited upstream';
31
- } else if (/unavailable for free|paid version is available/i.test(raw)) {
32
- summary = 'free tier retired — use a different free model or paid slug';
33
- } else if (/Failed after \d+ attempts/i.test(text)) {
34
- summary = raw || 'provider error after retries';
35
- } else if (/OLLAMA_MODEL_NOT_PULLED|not installed locally/i.test(raw)) {
36
- summary = raw;
37
- }
38
-
39
- return { raw, summary, text };
40
- }
41
-
42
- export function ensureFailedModels(session) {
43
- if (!session.failedModels) session.failedModels = new Set();
44
- return session.failedModels;
45
- }
46
-
47
- export function recordFailedModel(session, modelId) {
48
- if (!modelId) return;
49
- ensureFailedModels(session).add(modelId);
50
- }
51
-
52
- export function getExcludeList(session) {
53
- return [...ensureFailedModels(session)];
54
- }
55
-
56
- export function shouldAttemptFreeFallback(session, modelId) {
57
- if (session?.modelMode === 'free-router') return true;
58
- return typeof modelId === 'string' && /:free$/i.test(modelId);
59
- }
60
-
61
- export function isOllamaNotPulledError(error) {
62
- const text = typeof error === 'string' ? error : String(error?.message || error || '');
63
- return error?.code === 'OLLAMA_MODEL_NOT_PULLED' || /not installed locally/i.test(text);
64
- }
65
-
66
- export async function handleOllamaNotPulledFailure({ session, error, env = process.env, currentModel }) {
67
- if (!isOllamaNotPulledError(error)) return null;
68
- if (typeof currentModel !== 'string' || !currentModel.startsWith('ollama/')) return null;
69
-
70
- recordFailedModel(session, currentModel);
71
- const excludeFamilies = typeof currentModel === 'string' && currentModel.startsWith('ollama/')
72
- ? ['ollama']
73
- : [];
74
- const next = resolveValidatedChatModel({ env, requested: null, excludeFamilies });
75
- if (!next?.id || next.id === currentModel) return null;
76
-
77
- const parsed = parseOpenRouterError(error);
78
- return {
79
- modelId: next.id,
80
- notice: `${parsed.summary} Switched to ${next.id} for this turn.`,
81
- };
82
- }
83
-
84
- export async function handleModelFailure(opts) {
85
- const openRouter = await handleOpenRouterFailure(opts);
86
- if (openRouter) return openRouter;
87
- return handleOllamaNotPulledFailure(opts);
88
- }
89
-
90
- export async function handleOpenRouterFailure({ session, error, env = process.env, currentModel }) {
91
- const parsed = parseOpenRouterError(error);
92
- const classified = classifyProviderFailure({ error: { message: parsed.text || parsed.raw } });
93
- if (!classified?.retryable) return null;
94
- if (!shouldAttemptFreeFallback(session, currentModel)) return null;
95
-
96
- recordFailedModel(session, currentModel);
97
- const exclude = getExcludeList(session);
98
- const { resolveFreeOpenRouterModel } = await import('../../apps/chat/engine/models.mjs');
99
- const next = await resolveFreeOpenRouterModel({ env, tier: 'standard', exclude });
100
- if (!next || next === currentModel) return null;
101
-
102
- const short = (id) => id.replace(/^openrouter\//, '');
103
- return {
104
- modelId: next,
105
- notice: `${short(currentModel)} failed (${parsed.summary}). Switched to ${short(next)}. Retry or /model to pick manually.`,
106
- };
107
- }
108
-
109
- export async function runTurnWithFallback({
110
- driver,
111
- text,
112
- session,
113
- layers,
114
- env,
115
- promptOptions = {},
116
- runTurnInto,
117
- onUpdate = () => {},
118
- }) {
119
- let model = session.model;
120
- let lastState = null;
121
- let lastNotice = null;
122
-
123
- for (let attempt = 0; attempt < MAX_FALLBACK_ATTEMPTS; attempt++) {
124
- lastState = await runTurnInto(
125
- driver,
126
- text,
127
- { ...promptOptions, model, turnOverlay: promptOptions.turnOverlay },
128
- { session, layers, onUpdate },
129
- );
130
-
131
- if (!lastState.error) {
132
- return { state: lastState, model, notice: lastNotice };
133
- }
134
-
135
- const fallback = await handleModelFailure({
136
- session,
137
- error: lastState.error,
138
- env,
139
- currentModel: model,
140
- });
141
- if (!fallback) break;
142
-
143
- model = fallback.modelId;
144
- session.model = model;
145
- lastNotice = fallback.notice;
146
- }
147
-
148
- return { state: lastState, model, notice: lastNotice };
149
- }
150
-
151
- export { MAX_FALLBACK_ATTEMPTS };
@@ -1,33 +0,0 @@
1
- /**
2
- * lib/chat/permission-prompt.mjs — interactive permission decisions for ask mode.
3
- *
4
- * Maps user keystrokes and readline answers to the host-agnostic decision
5
- * vocabulary (allow | allow_always | reject) shared by the permission gate
6
- * (apps/chat/engine/tools/permission.mjs). Both the Ink overlay and the linear
7
- * readline prompt use the same parser so behaviour stays identical.
8
- */
9
-
10
- export function formatPermissionQuestion({ tool = 'tool', input = null } = {}) {
11
- const detail = input && typeof input === 'object'
12
- ? Object.keys(input).slice(0, 3).join(', ')
13
- : '';
14
- const suffix = detail ? ` (${detail})` : '';
15
- return `Allow "${tool}"${suffix}? [y] once [a] always [n] reject`;
16
- }
17
-
18
- export function parsePermissionDecision(raw) {
19
- const s = String(raw ?? '').trim().toLowerCase();
20
- if (['a', 'always', 'allow_always', 'all'].includes(s)) return 'allow_always';
21
- if (['n', 'no', 'reject', 'r', 'deny', '0'].includes(s)) return 'reject';
22
- if (['y', 'yes', 'allow', 'once', '1', ''].includes(s)) return 'allow';
23
- return null;
24
- }
25
-
26
- export function parsePermissionKey(char) {
27
- if (!char) return null;
28
- const c = char.toLowerCase();
29
- if (c === 'a') return 'allow_always';
30
- if (c === 'n' || c === 'r') return 'reject';
31
- if (c === 'y') return 'allow';
32
- return null;
33
- }
@@ -1,45 +0,0 @@
1
- /**
2
- * lib/chat/picker-catalog.mjs — predefined list-picker item sets for construct chat.
3
- */
4
-
5
- import { LAYER_KEYS, PERMISSION_MODES, SANDBOX_LEVELS, INSPECTOR_MODES } from './config.mjs';
6
-
7
- export const PERMISSION_PICKER_ITEMS = Object.freeze([
8
- { id: 'allow', label: 'Allow once', detail: 'y' },
9
- { id: 'allow_always', label: 'Allow always', detail: 'a' },
10
- { id: 'reject', label: 'Reject', detail: 'n' },
11
- ]);
12
-
13
- export const BOOL_PICKER_ITEMS = Object.freeze([
14
- { id: 'on', label: 'on', detail: 'true' },
15
- { id: 'off', label: 'off', detail: 'false' },
16
- ]);
17
-
18
- export function settingKeyPickerItems() {
19
- return [
20
- { id: 'thinking', label: 'thinking', tag: 'bool' },
21
- ...LAYER_KEYS.map((k) => ({ id: k, label: k, tag: 'layer' })),
22
- { id: 'permission', label: 'permission mode', tag: 'enum' },
23
- { id: 'sandbox', label: 'sandbox', tag: 'enum' },
24
- { id: 'inspector', label: 'inspector panel', tag: 'enum' },
25
- { id: 'ascii', label: 'ascii glyphs', tag: 'bool' },
26
- { id: 'theme', label: 'color theme', tag: 'enum' },
27
- { id: 'model', label: 'model', tag: 'model' },
28
- ];
29
- }
30
-
31
- export function enumPickerItems(key) {
32
- if (key === 'permission') return PERMISSION_MODES.map((v) => ({ id: v, label: v }));
33
- if (key === 'sandbox') return SANDBOX_LEVELS.map((v) => ({ id: v, label: v }));
34
- if (key === 'inspector') return INSPECTOR_MODES.map((v) => ({ id: v, label: v }));
35
- if (key === 'theme') return ['auto', 'light', 'dark'].map((v) => ({ id: v, label: v }));
36
- return [];
37
- }
38
-
39
- export function isBoolSetting(key) {
40
- return key === 'thinking' || key === 'ascii' || LAYER_KEYS.includes(key);
41
- }
42
-
43
- export function isEnumSetting(key) {
44
- return key === 'permission' || key === 'sandbox' || key === 'inspector' || key === 'theme';
45
- }
@@ -1,34 +0,0 @@
1
- /**
2
- * lib/chat/policy-telemetry.mjs — bounded sink for execution-policy degraded-mode
3
- * events (construct-6zga.1.2 / construct-rv2x).
4
- *
5
- * When the owned loop compiles a turn's execution policy from a missing, unknown,
6
- * or degraded capability profile, the policy collapses to the conservative
7
- * envelope. The engine records that fact here instead of printing to the chat
8
- * stream so the degradation is observable (dashboard, diagnostics) without
9
- * touching the renderer event union or the live transcript. The buffer is
10
- * in-process and bounded; it is a diagnostic signal, not durable state.
11
- */
12
-
13
- const RECENT = [];
14
- const MAX_EVENTS = 50;
15
-
16
- export function recordPolicyTelemetry(event = {}) {
17
- const entry = {
18
- kind: typeof event.kind === 'string' ? event.kind : 'execution-policy-degraded',
19
- model: event.model ?? null,
20
- capabilityClass: event.capabilityClass ?? 'unknown',
21
- reasons: Array.isArray(event.reasons) ? [...event.reasons] : [],
22
- };
23
- RECENT.push(entry);
24
- if (RECENT.length > MAX_EVENTS) RECENT.shift();
25
- return entry;
26
- }
27
-
28
- export function recentPolicyTelemetry() {
29
- return RECENT.map((e) => ({ ...e, reasons: [...e.reasons] }));
30
- }
31
-
32
- export function clearPolicyTelemetry() {
33
- RECENT.length = 0;
34
- }