@geraldmaron/construct 1.2.3 → 1.3.1

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 (505) 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/cli.mjs +12 -2
  44. package/lib/doctor/source-checkout.mjs +3 -4
  45. package/lib/doctor/watchers/consistency.mjs +148 -43
  46. package/lib/doctor/watchers/credential-parity.mjs +97 -0
  47. package/lib/doctor/watchers/mcp-protocol.mjs +1 -1
  48. package/lib/doctor/watchers/service-health.mjs +4 -32
  49. package/lib/document-export.mjs +73 -9
  50. package/lib/document-extract.mjs +10 -198
  51. package/lib/embed/artifact.mjs +2 -3
  52. package/lib/embed/cli.mjs +1 -1
  53. package/lib/embed/config.mjs +1 -2
  54. package/lib/embed/customer-profiles.mjs +1 -1
  55. package/lib/embed/daemon.mjs +5 -6
  56. package/lib/embed/demand-fetch.mjs +114 -2
  57. package/lib/embed/docs-lifecycle.mjs +9 -5
  58. package/lib/embed/intake-metrics.mjs +2 -2
  59. package/lib/embed/notifications.mjs +2 -3
  60. package/lib/embed/recommendation-store.mjs +25 -22
  61. package/lib/embed/role-framing.mjs +4 -9
  62. package/lib/embedded-contract/capability.mjs +4 -2
  63. package/lib/embedded-contract/contract-version.mjs +1 -1
  64. package/lib/env-config.mjs +2 -2
  65. package/lib/flavors/loader.mjs +21 -19
  66. package/lib/graph/build-from-registry.mjs +31 -7
  67. package/lib/graph/staleness.mjs +1 -1
  68. package/lib/headhunt.mjs +82 -35
  69. package/lib/hooks/agent-tracker.mjs +2 -2
  70. package/lib/hooks/config-protection.mjs +1 -1
  71. package/lib/hooks/registry-sync.mjs +3 -3
  72. package/lib/host-capabilities.mjs +5 -5
  73. package/lib/improvement/controller.mjs +2 -2
  74. package/lib/init-unified.mjs +19 -21
  75. package/lib/intake/classify.mjs +26 -0
  76. package/lib/intake/daemon.mjs +2 -3
  77. package/lib/intake/prepare.mjs +10 -3
  78. package/lib/intake/session-prelude.mjs +1 -1
  79. package/lib/intake/tables/creative.mjs +9 -9
  80. package/lib/intake/tables/operations.mjs +4 -4
  81. package/lib/intake/tables/research.mjs +2 -2
  82. package/lib/integrations/intake-integrations.mjs +9 -10
  83. package/lib/knowledge/research-store.mjs +2 -2
  84. package/lib/mcp/server.mjs +104 -61
  85. package/lib/mcp/tool-budget.mjs +1 -46
  86. package/lib/mcp/tool-recovery.mjs +44 -0
  87. package/lib/mcp/tools/artifact-author.mjs +36 -0
  88. package/lib/mcp/tools/find-tool.mjs +86 -0
  89. package/lib/mcp/tools/orchestration-run.mjs +107 -69
  90. package/lib/mcp/tools/project.mjs +4 -3
  91. package/lib/mcp/tools/{profile.mjs → scope.mjs} +54 -52
  92. package/lib/mcp/tools/skills.mjs +39 -22
  93. package/lib/mcp/tools/telemetry.mjs +2 -1
  94. package/lib/mcp/tools/workflow.mjs +1 -1
  95. package/lib/mcp-platform-config.mjs +7 -5
  96. package/lib/migrations/index.mjs +4 -2
  97. package/lib/migrations/v2-unified-registry.mjs +35 -0
  98. package/lib/model-router.mjs +203 -36
  99. package/lib/models/catalog.mjs +25 -9
  100. package/lib/models/execution-capability-profile.mjs +3 -3
  101. package/lib/models/execution-policy.mjs +7 -6
  102. package/lib/models/provider-poll.mjs +18 -4
  103. package/lib/opencode-config.mjs +56 -1
  104. package/lib/opencode-runtime-plugin.mjs +13 -10
  105. package/lib/oracle/artifact-gate.mjs +14 -4
  106. package/lib/oracle/cli.mjs +2 -0
  107. package/lib/oracle/dispatch.mjs +18 -1
  108. package/lib/oracle/execute.mjs +39 -7
  109. package/lib/oracle/index.mjs +1 -1
  110. package/lib/oracle/org-graph.mjs +10 -5
  111. package/lib/oracle/policy.mjs +6 -0
  112. package/lib/oracle/read-model.mjs +103 -6
  113. package/lib/oracle/reconcile.mjs +31 -4
  114. package/lib/oracle/remediation-dispatch.mjs +53 -0
  115. package/lib/oracle/routing.mjs +5 -0
  116. package/lib/oracle/synthesize.mjs +80 -3
  117. package/lib/orchestration/routing-tables.mjs +44 -10
  118. package/lib/orchestration/runtime.mjs +2 -0
  119. package/lib/orchestration/worker.mjs +1 -1
  120. package/lib/orchestration-policy.mjs +351 -62
  121. package/lib/overrides/resolver.mjs +1 -12
  122. package/lib/parity.mjs +37 -11
  123. package/lib/policy/engine.mjs +80 -15
  124. package/lib/prompt-composer.js +51 -15
  125. package/lib/prompt-metadata.mjs +3 -2
  126. package/lib/providers/connection-probe.mjs +58 -0
  127. package/lib/providers/copilot-auth.mjs +1 -1
  128. package/lib/providers/credential-bootstrap.mjs +1 -1
  129. package/lib/providers/op-run.mjs +3 -4
  130. package/lib/providers/secret-resolver.mjs +31 -0
  131. package/lib/publish-tooling.mjs +3 -11
  132. package/lib/publish.mjs +12 -29
  133. package/lib/reconcile/mcp-entry-reconcile.mjs +14 -9
  134. package/lib/reflect.mjs +2 -2
  135. package/lib/registry/agent-manifest.mjs +190 -0
  136. package/lib/registry/assemble.mjs +133 -0
  137. package/lib/registry/catalog.mjs +171 -0
  138. package/lib/registry/cli.mjs +590 -6
  139. package/lib/registry/consolidation.mjs +5 -4
  140. package/lib/registry/docs-sync.mjs +67 -0
  141. package/lib/registry/loader.mjs +147 -0
  142. package/lib/registry/org-io.mjs +76 -0
  143. package/lib/registry/retired-paths.mjs +71 -0
  144. package/lib/registry/surface-map.mjs +5 -7
  145. package/lib/registry/validator.mjs +438 -0
  146. package/lib/research-execution-policy.mjs +173 -0
  147. package/lib/roles/catalog.mjs +4 -9
  148. package/lib/roles/fence.mjs +107 -1
  149. package/lib/roles/flavor-bindings.mjs +68 -0
  150. package/lib/roles/gateway.mjs +140 -29
  151. package/lib/roles/manifest.mjs +22 -13
  152. package/lib/roles/router.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 +6 -26
  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 -8
  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 +269 -51
  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/apps/chat/engine/ai-sdk-agent.mjs +0 -183
  433. package/apps/chat/engine/loop-driver.mjs +0 -211
  434. package/apps/chat/engine/models.mjs +0 -122
  435. package/apps/chat/engine/provider-adapters.mjs +0 -171
  436. package/apps/chat/engine/tools/permission.mjs +0 -54
  437. package/apps/chat/engine/tools/primitives.mjs +0 -180
  438. package/apps/chat/engine/tools/registry.mjs +0 -122
  439. package/apps/chat/engine/turn-controls.mjs +0 -70
  440. package/lib/boundary.mjs +0 -127
  441. package/lib/certification/dashboard-api.mjs +0 -71
  442. package/lib/chat/cli.mjs +0 -333
  443. package/lib/chat/command-suggest.mjs +0 -161
  444. package/lib/chat/commands.mjs +0 -215
  445. package/lib/chat/config.mjs +0 -142
  446. package/lib/chat/context-compactor.mjs +0 -250
  447. package/lib/chat/context-continuation.mjs +0 -253
  448. package/lib/chat/continuation-source.mjs +0 -58
  449. package/lib/chat/demo-guide.mjs +0 -61
  450. package/lib/chat/design-tokens.mjs +0 -91
  451. package/lib/chat/desktop-binary.mjs +0 -81
  452. package/lib/chat/desktop-build.mjs +0 -130
  453. package/lib/chat/desktop-launcher.mjs +0 -133
  454. package/lib/chat/evidence.mjs +0 -145
  455. package/lib/chat/export.mjs +0 -74
  456. package/lib/chat/harness/driver.mjs +0 -91
  457. package/lib/chat/list-picker.mjs +0 -112
  458. package/lib/chat/model-picker.mjs +0 -356
  459. package/lib/chat/openrouter-fallback.mjs +0 -151
  460. package/lib/chat/permission-prompt.mjs +0 -33
  461. package/lib/chat/picker-catalog.mjs +0 -45
  462. package/lib/chat/policy-telemetry.mjs +0 -34
  463. package/lib/chat/present.mjs +0 -246
  464. package/lib/chat/session-context.mjs +0 -39
  465. package/lib/chat/session-persist.mjs +0 -73
  466. package/lib/chat/session-restore.mjs +0 -71
  467. package/lib/chat/session-settings.mjs +0 -53
  468. package/lib/chat/system-prompt.mjs +0 -52
  469. package/lib/chat/transparency.mjs +0 -93
  470. package/lib/chat/tui/color-scheme.mjs +0 -42
  471. package/lib/chat/tui/markdown.mjs +0 -123
  472. package/lib/chat/tui/presentation.mjs +0 -100
  473. package/lib/chat/tui/render.mjs +0 -500
  474. package/lib/chat/tui/turn-block.mjs +0 -284
  475. package/lib/chat/tui/turn-present.mjs +0 -18
  476. package/lib/chat/tui/turn-state.mjs +0 -88
  477. package/lib/chat/tui/usage.mjs +0 -122
  478. package/lib/chat/web-commands.mjs +0 -146
  479. package/lib/chat/web-launcher.mjs +0 -63
  480. package/lib/chat/web-picker-keys.mjs +0 -46
  481. package/lib/chat/web-session.mjs +0 -159
  482. package/lib/config/alias.mjs +0 -57
  483. package/lib/dashboard-demo.mjs +0 -71
  484. package/lib/dashboard-static.mjs +0 -175
  485. package/lib/install/desktop-binary-download.mjs +0 -88
  486. package/lib/profiles/loader.mjs +0 -123
  487. package/lib/profiles/rebrand.mjs +0 -46
  488. package/lib/server/auth.mjs +0 -169
  489. package/lib/server/chat-loop.mjs +0 -622
  490. package/lib/server/chat.mjs +0 -336
  491. package/lib/server/cors.mjs +0 -77
  492. package/lib/server/csrf.mjs +0 -103
  493. package/lib/server/demo-preview.mjs +0 -63
  494. package/lib/server/index.mjs +0 -2641
  495. package/lib/server/insights.mjs +0 -780
  496. package/lib/server/langfuse-login.mjs +0 -58
  497. package/lib/server/rate-limit.mjs +0 -93
  498. package/lib/server/telemetry-login.mjs +0 -100
  499. package/lib/server/webhook.mjs +0 -512
  500. package/specialists/contracts.json +0 -1032
  501. package/specialists/contracts.schema.json +0 -83
  502. package/specialists/policy-inventory.json +0 -188
  503. package/specialists/registry.json +0 -1412
  504. package/specialists/role-manifests.json +0 -217
  505. package/specialists/teams.json +0 -94
@@ -55,13 +55,17 @@
55
55
  #let construct-font-display = ("Space Grotesk",)
56
56
  #let construct-font-mono = ("JetBrains Mono",)
57
57
 
58
+ #let horizontalrule = block(width: 100%, above: 0.85em, below: 0.95em)[
59
+ #line(length: 100%, stroke: 0.6pt + hairline-strong)
60
+ ]
61
+
58
62
  // Type scale. One modular ramp shared by every document type so sizing never
59
63
  // drifts between a PRD, a research brief, and a decision record.
60
64
 
61
65
  #let fs-micro = 8pt
62
66
  #let fs-small = 8.5pt
63
67
  #let fs-meta = 9pt
64
- #let fs-body = 10pt
68
+ #let fs-body = 10.4pt
65
69
  #let fs-h4 = 8.5pt
66
70
  #let fs-h3 = 11pt
67
71
  #let fs-h2 = 13pt
@@ -76,8 +80,8 @@
76
80
  #let wt-semibold = 600
77
81
  #let wt-bold = 700
78
82
 
79
- #let construct-figure-max-width = 58%
80
- #let construct-figure-max-height = 2.35in
83
+ #let construct-figure-max-width = 92%
84
+ #let construct-figure-max-height = 3.4in
81
85
 
82
86
  // A document-type word maps to a compact badge so the masthead reads as a
83
87
  // labelled artifact rather than a bare title.
@@ -290,20 +294,18 @@
290
294
 
291
295
  #let construct-theme(body) = {
292
296
  set text(font: construct-font-sans, size: fs-body, fill: ink-body, lang: "en", tracking: 0.002em)
293
- set par(justify: false, leading: 0.82em, spacing: 1.15em)
297
+ set par(justify: false, leading: 0.9em, spacing: 1.24em)
294
298
  set heading(numbering: none, outlined: true)
295
299
 
296
300
  show strong: set text(font: construct-font-sans, weight: wt-semibold, fill: ink)
297
301
  show emph: set text(style: "italic")
298
302
 
299
- // Lists inherit the open body leading; a per-item block gap keeps a wrapped
300
- // bullet from reading as two bullets, with the gap clearly wider than the
301
- // line leading within an item.
303
+ // Lists need looser vertical rhythm than body prose, but ordered lists must
304
+ // preserve Typst's native counter context or every item will render as `1.`.
305
+ // Spacing belongs on the list container, not on a wrapped enum.item block.
302
306
 
303
- show list.item: it => block(below: 0.6em, it)
304
- show enum.item: it => block(below: 0.6em, it)
305
- set list(marker: (text(fill: ink)[•], text(fill: ink-muted)[‣], text(fill: ink-faint)[–]), indent: 2pt, body-indent: 8pt, spacing: 0.9em)
306
- set enum(indent: 2pt, body-indent: 8pt, spacing: 0.9em)
307
+ set list(marker: (text(fill: ink)[•], text(fill: ink-muted)[‣], text(fill: ink-faint)[–]), indent: 2pt, body-indent: 9pt, spacing: 1.02em)
308
+ set enum(numbering: "1.", indent: 2pt, body-indent: 9pt, spacing: 1.02em)
307
309
 
308
310
  // Links carry ink with a hairline underline so citations stay legible without
309
311
  // introducing color.
@@ -322,7 +324,7 @@
322
324
  text(font: construct-font-display, size: fs-h1, weight: wt-semibold, fill: ink, tracking: -0.015em)[#it.body]
323
325
  v(0.22em)
324
326
  line(length: 100%, stroke: 0.5pt + hairline)
325
- v(0.55em, weak: true)
327
+ v(0.7em, weak: true)
326
328
  })
327
329
  }
328
330
  show heading.where(level: 2): it => {
@@ -331,14 +333,14 @@
331
333
  text(font: construct-font-display, size: fs-h2, weight: wt-semibold, fill: ink, tracking: -0.01em)[#it.body]
332
334
  v(0.4em)
333
335
  line(length: 26pt, stroke: 1.5pt + ink)
334
- v(0.62em, weak: true)
336
+ v(0.78em, weak: true)
335
337
  })
336
338
  }
337
339
  show heading.where(level: 3): it => {
338
340
  v(1.15em, weak: true)
339
341
  block(sticky: true, {
340
342
  text(font: construct-font-sans, size: fs-h3, weight: wt-semibold, fill: ink-strong)[#it.body]
341
- v(0.38em, weak: true)
343
+ v(0.48em, weak: true)
342
344
  })
343
345
  }
344
346
  show heading.where(level: 4): it => {
@@ -414,10 +416,13 @@
414
416
  radius: 4pt,
415
417
  )[
416
418
  #align(center)[
417
- #box(width: construct-figure-max-width)[
418
- #show image: img => image(img, width: 100%, height: construct-figure-max-height, fit: "contain")
419
- #it.body
420
- ]
419
+ #show image: img => image(
420
+ img,
421
+ width: construct-figure-max-width,
422
+ height: construct-figure-max-height,
423
+ fit: "contain",
424
+ )
425
+ #it.body
421
426
  ]
422
427
  ]
423
428
  if it.caption != none {
@@ -8,7 +8,7 @@
8
8
 
9
9
  #set page(
10
10
  paper: "a4",
11
- margin: (x: 2cm, top: 1.8cm, bottom: 2.2cm),
11
+ margin: (x: 2.15cm, top: 1.95cm, bottom: 2.35cm),
12
12
  numbering: "1",
13
13
  header: construct-running-header(
14
14
  "$if(title)$$title$$endif$",
@@ -8,7 +8,7 @@
8
8
 
9
9
  #set page(
10
10
  paper: "a4",
11
- margin: (x: 2cm, top: 1.8cm, bottom: 2.2cm),
11
+ margin: (x: 2.15cm, top: 1.95cm, bottom: 2.35cm),
12
12
  numbering: "1",
13
13
  header: construct-running-header(
14
14
  "$if(title)$$title$$endif$",
@@ -8,7 +8,7 @@
8
8
 
9
9
  #set page(
10
10
  paper: "a4",
11
- margin: (x: 2cm, top: 1.8cm, bottom: 2.2cm),
11
+ margin: (x: 2.15cm, top: 1.95cm, bottom: 2.35cm),
12
12
  numbering: "1",
13
13
  header: construct-running-header(
14
14
  "$if(title)$$title$$endif$",
@@ -8,7 +8,7 @@
8
8
 
9
9
  #set page(
10
10
  paper: "a4",
11
- margin: (x: 1.85cm, top: 1.75cm, bottom: 2.1cm),
11
+ margin: (x: 2cm, top: 1.9cm, bottom: 2.25cm),
12
12
  numbering: "1",
13
13
  header: construct-running-header(
14
14
  "$if(title)$$title$$endif$",
@@ -0,0 +1,8 @@
1
+ {
2
+ "args": [
3
+ "--no-sandbox",
4
+ "--disable-setuid-sandbox",
5
+ "--disable-dev-shm-usage",
6
+ "--disable-gpu"
7
+ ]
8
+ }
@@ -1,6 +1,6 @@
1
1
  # <Role display name>
2
2
 
3
- > Anchor doc for the <role-id> persona. Methodology: `docs/guides/concepts/persona-research.md`. This file is generated by `construct profile create` and is part of the discover + frame phases. The persona is not promotable until every section below is filled from evidence, not opinion.
3
+ > Anchor doc for the <role-id> persona. Methodology: `docs/guides/concepts/persona-research.md`. This file is generated by `construct scope create` and is part of the discover + frame phases. The persona is not promotable until every section below is filled from evidence, not opinion.
4
4
 
5
5
  ## Goals
6
6
 
@@ -20,6 +20,8 @@ not the whole document. Keep em dashes rare; prefer commas, periods, or parenthe
20
20
  -->
21
21
 
22
22
  ## Summary
23
+ This PRD defines {title} for {primary user or customer segment}. It explains the current friction, the measurable outcome this work should unlock, and the smallest shippable path to prove the change works. Replace this paragraph with evidence-backed product context before review.
24
+
23
25
  <!--
24
26
  One paragraph (3-5 sentences) that a busy reader can use as the whole PRD.
25
27
  What the change is, who it's for, why now, and what changes when it ships.
@@ -27,6 +29,8 @@ No solutions in this section that aren't decided. No ticket IDs. No team names.
27
29
  -->
28
30
 
29
31
  ## Background
32
+ Today, {current workflow or system} creates avoidable friction for {affected users}. The evidence that matters for this PRD should include observed behavior, quantitative signals, or qualitative research, and the links below should let reviewers verify the claim without relying on memory.
33
+
30
34
  <!--
31
35
  Context the reader needs to evaluate this PRD without asking around.
32
36
  Include relevant prior work (linked PRDs, ADRs, RFCs), observed signals, and
@@ -38,6 +42,8 @@ Avoid roadmap-speak ("this is a Q3 priority"); state what is happening to users.
38
42
  -->
39
43
 
40
44
  ## Problem
45
+ {Affected users} cannot reliably achieve {desired outcome} because {constraint or failure mode}. The problem is important now because the current workaround is costly, error-prone, or blocks adoption for a clearly named segment.
46
+
41
47
  <!--
42
48
  The specific user or business outcome that is currently blocked. One or two
43
49
  paragraphs. State the pain, not the solution.
@@ -1,183 +0,0 @@
1
- /**
2
- * apps/chat/engine/ai-sdk-agent.mjs — the real owned-loop engine (Vercel AI SDK).
3
- *
4
- * Lazy-loaded by the launcher only when `construct chat` runs the rich/owned path,
5
- * so the optional dependencies (`ai`, `@ai-sdk/*`, `zod`) never load in the zero-dep
6
- * core or in tests of the mapping layer. It resolves a Construct model id (the
7
- * router's `provider/model` form) to an AI SDK language model through the adapter
8
- * registry (provider-adapters.mjs), builds the agent tool set from the tool
9
- * registry, and runs streamText under the turn's compiled execution policy
10
- * (turn-controls.mjs): the step cap, tool-group / schema budget, output cap, and
11
- * caching eligibility all derive from the resolved capability profile, so the loop
12
- * adapts to the model while staying behavior-preserving for hosted-direct. It
13
- * yields the SDK fullStream parts unchanged; loop-driver.mjs owns the
14
- * normalization into the event union.
15
- */
16
-
17
- import { listChatModels } from './models.mjs';
18
- import { resolveLanguageModel } from './provider-adapters.mjs';
19
- import { resolveTurnControls } from './turn-controls.mjs';
20
- import { buildSystemPrompt } from '../../../lib/chat/system-prompt.mjs';
21
- import { applyToolBudget } from '../../../lib/mcp/tool-budget.mjs';
22
- import { recordPolicyTelemetry } from '../../../lib/chat/policy-telemetry.mjs';
23
- import { maybeCompact, estimateTextTokens, messageText } from '../../../lib/chat/context-compactor.mjs';
24
-
25
- // Real summarization of the compactible layers (tool results, prior assistant
26
- // reasoning) the contract elides, run on the same language model as the turn.
27
- // Bounded output and zero retries keep the once-per-compaction cost small; a throw
28
- // is caught upstream and falls back to the deterministic extractive summary, so a
29
- // summarizer failure degrades fidelity without breaking the turn.
30
-
31
- function makeSummarizer({ generateText, languageModel, signal }) {
32
- return async (text, meta = {}) => {
33
- const { text: summary } = await generateText({
34
- model: languageModel,
35
- system: 'Compress earlier agent work into a faithful, terse recap for context continuation. Preserve decisions, findings, file paths, identifiers, and unresolved threads. Never invent facts. No preamble.',
36
- prompt: `Summarize these ${meta.segmentCount || 'earlier'} turn(s) of an agent working in a repository. Use compact bullets, keep concrete identifiers, stay under 200 words:\n\n${text}`,
37
- maxOutputTokens: 512,
38
- maxRetries: 0,
39
- abortSignal: signal,
40
- });
41
- return summary;
42
- };
43
- }
44
-
45
- export async function createAiSdkAgent({ env = process.env, cwd = process.cwd(), model = null, handlers = {}, systemPrompt = '', tools = null, onTelemetry = recordPolicyTelemetry } = {}) {
46
- const { streamText, stepCountIs, generateText } = await import('ai');
47
-
48
- const { buildAgentTools } = await import('./tools/registry.mjs');
49
- const sdkTools = await buildAgentTools({ env, cwd, handlers, only: tools });
50
-
51
- const messages = [];
52
- const languageModels = new Map();
53
- let activeModelId = model;
54
- let contextTokens = 0;
55
-
56
- async function languageModelFor(modelId) {
57
- const id = modelId || activeModelId;
58
- if (!id) {
59
- const err = new Error('No model selected and no configured provider found. Run `construct models` or set CX_MODEL_STANDARD.');
60
- err.code = 'PROVIDER_MODEL_UNRESOLVED';
61
- throw err;
62
- }
63
- if (!languageModels.has(id)) {
64
- languageModels.set(id, await resolveLanguageModel(id, env));
65
- }
66
- return languageModels.get(id);
67
- }
68
-
69
- return {
70
- sessionId: `construct-${Date.now()}`,
71
- model,
72
- listModels: () => listChatModels({ env }),
73
- async *streamTurn(text, { signal, model: turnModel = null, turnOverlay = null } = {}) {
74
- if (turnModel) activeModelId = turnModel;
75
- const languageModel = await languageModelFor(activeModelId);
76
-
77
- const controls = resolveTurnControls({ model: activeModelId, turnOverlay, env });
78
- if (controls.degraded) {
79
- onTelemetry({
80
- kind: 'execution-policy-degraded',
81
- model: activeModelId,
82
- capabilityClass: controls.policy?.source?.capabilityClass || 'unknown',
83
- reasons: controls.policy?.telemetry?.reasons || [],
84
- });
85
- }
86
- const turnTools = applyToolBudget(sdkTools, {
87
- allowedToolGroups: controls.allowedToolGroups,
88
- maxToolSchemas: controls.maxToolSchemas,
89
- });
90
-
91
- // Per-turn routing policy belongs in the SYSTEM role (via buildSystemPrompt),
92
- // never prepended to the user message: a user-role policy gets echoed in model
93
- // reasoning and compounds in persisted history.
94
- messages.push({ role: 'user', content: String(text) });
95
- const systemText = buildSystemPrompt({ base: systemPrompt || undefined, overlay: turnOverlay });
96
-
97
- const request = {
98
- model: languageModel,
99
- tools: turnTools,
100
- stopWhen: stepCountIs(controls.iterations),
101
- abortSignal: signal,
102
- maxRetries: 0,
103
- };
104
- if (controls.outputCap) request.maxOutputTokens = controls.outputCap;
105
-
106
- // A cache-eligible provider marks the stable system prefix as a cache
107
- // breakpoint: the AI SDK maps a single leading system message onto the
108
- // provider's system field, so the request stays output-identical and only the
109
- // cache_control annotation is added. Every other provider keeps the plain
110
- // system string — today's exact request shape.
111
- if (controls.cacheEligible) {
112
- const providerNs = String(activeModelId || '').split('/')[0] || 'anthropic';
113
- request.messages = [
114
- { role: 'system', content: systemText, providerOptions: { [providerNs]: { cacheControl: { type: 'ephemeral' } } } },
115
- ...messages,
116
- ];
117
- } else {
118
- request.system = systemText;
119
- request.messages = messages;
120
- }
121
-
122
- const result = streamText(request);
123
-
124
- // The compaction trigger needs the current context SIZE, which is the last
125
- // model call's per-step input (system + full history), not the turn's
126
- // aggregate input — totalUsage sums input across tool steps and would
127
- // over-count a multi-step turn, tripping compaction below the real budget.
128
- // Prefer the last finish-step's per-call usage; fall back to the aggregate
129
- // only when the host reports no per-step usage. Host numbers only, no split.
130
- let lastStepUsage = null;
131
- let aggregateUsage = null;
132
- for await (const part of result.fullStream) {
133
- if (part.type === 'finish-step' && part.usage) lastStepUsage = part.usage;
134
- if (part.type === 'finish') aggregateUsage = part.totalUsage || part.usage || aggregateUsage;
135
- yield part;
136
- }
137
- const turnUsage = lastStepUsage || aggregateUsage;
138
-
139
- // Persist the assistant turn (incl. tool exchanges) so the next prompt has history.
140
- try {
141
- const response = await result.response;
142
- if (Array.isArray(response?.messages)) messages.push(...response.messages);
143
- // Surface the model that actually answered — the OpenRouter free router
144
- // resolves to an underlying model id, not the "openrouter/free" alias.
145
- const resolved = response?.modelId;
146
- if (resolved && resolved !== activeModelId && !String(activeModelId).endsWith(resolved)) {
147
- yield { type: 'model-resolved', model: resolved };
148
- }
149
- } catch { /* history append is best-effort */ }
150
-
151
- // The next turn's context ≈ this turn's input (system + full history) plus the
152
- // output just appended. When that crosses the policy's continuation trigger,
153
- // compact without silent loss. Behavior-preserving: hosted only crosses near
154
- // its ~150k budget, so short conversations are never touched. Compaction is
155
- // best-effort — any failure leaves the live history intact.
156
- const input = Number(turnUsage?.inputTokens) || 0;
157
- const output = Number(turnUsage?.outputTokens) || 0;
158
- if (input || output) contextTokens = input + output;
159
-
160
- const trigger = controls.continuation?.triggerTokens || null;
161
- if (trigger && contextTokens >= trigger) {
162
- try {
163
- const outcome = await maybeCompact({
164
- messages,
165
- systemText,
166
- triggerTokens: trigger,
167
- contextTokens,
168
- summarize: makeSummarizer({ generateText, languageModel, signal }),
169
- });
170
- if (outcome.packet) yield { type: 'context-continuation', packet: outcome.packet, compacted: outcome.compacted };
171
- if (outcome.notice) {
172
- yield { type: 'context-notice', level: outcome.blocker ? 'warn' : 'info', code: outcome.blocker ? 'context-blocker' : 'context-compacted', message: outcome.notice };
173
- }
174
- if (outcome.compacted && Array.isArray(outcome.messages)) {
175
- messages.length = 0;
176
- messages.push(...outcome.messages);
177
- contextTokens = estimateTextTokens(systemText) + messages.reduce((n, m) => n + estimateTextTokens(messageText(m)), 0);
178
- }
179
- } catch { /* compaction must never break a turn */ }
180
- }
181
- },
182
- };
183
- }
@@ -1,211 +0,0 @@
1
- /**
2
- * apps/chat/engine/loop-driver.mjs — Construct's owned agent loop, exposed as a
3
- * driver implementing the normalized event union (lib/chat/harness/driver.mjs).
4
- *
5
- * ADR-0041 reverses the delegate-the-loop posture: Construct now runs the loop
6
- * (prompt -> model -> tool calls -> tool results -> repeat) on a provider-agnostic
7
- * engine (Vercel AI SDK), so every token, tool result, and routing choice is
8
- * first-party data. This module is the seam that keeps the rest of the surface
9
- * host-agnostic: it owns the lifecycle (start/prompt/cancel/stop), maps the
10
- * engine's fullStream parts onto the driver event union, and accumulates per-turn
11
- * usage from the host's own numbers (no fabricated splits).
12
- *
13
- * The engine itself is injected as `createAgent` so this mapping layer is tested
14
- * against a scripted mock with no network or API key — the same dependency-
15
- * injection discipline the retired host adapters used (fetchImpl / spawnFn). The
16
- * real engine lives in ai-sdk-agent.mjs and is lazy-imported only when chat runs.
17
- */
18
-
19
- import { AsyncEventQueue } from '../../../lib/chat/harness/driver.mjs';
20
- import { listChatModels, recommendChatModel } from './models.mjs';
21
-
22
- // Vercel AI SDK fullStream parts use a few delta field names across versions;
23
- // read text from any of them so the mapping survives a minor SDK bump.
24
-
25
- function partText(part) {
26
- if (typeof part.text === 'string') return part.text;
27
- if (typeof part.textDelta === 'string') return part.textDelta;
28
- if (typeof part.delta === 'string') return part.delta;
29
- return '';
30
- }
31
-
32
- // Usage arrives on finish parts as inputTokens/outputTokens/etc.; normalize to
33
- // the union's token shape, emitting only fields the engine actually reported.
34
-
35
- function normalizeUsage(part) {
36
- const u = part.totalUsage || part.usage || null;
37
- if (!u) return null;
38
- const tokens = {};
39
- if (Number.isFinite(u.inputTokens)) tokens.input = u.inputTokens;
40
- if (Number.isFinite(u.outputTokens)) tokens.output = u.outputTokens;
41
- if (Number.isFinite(u.reasoningTokens)) tokens.reasoning = u.reasoningTokens;
42
- if (Number.isFinite(u.cachedInputTokens)) tokens.cacheRead = u.cachedInputTokens;
43
- if (Number.isFinite(u.totalTokens)) tokens.total = u.totalTokens;
44
- else if (Number.isFinite(tokens.input) || Number.isFinite(tokens.output)) {
45
- tokens.total = (tokens.input || 0) + (tokens.output || 0) + (tokens.reasoning || 0);
46
- }
47
- if (!Object.keys(tokens).length) return null;
48
- const event = { type: 'usage', tokens };
49
- if (part.providerMetadata?.cost && Number.isFinite(part.providerMetadata.cost.amount)) {
50
- event.cost = { amount: part.providerMetadata.cost.amount, currency: part.providerMetadata.cost.currency || 'USD' };
51
- }
52
- return event;
53
- }
54
-
55
- // One fullStream part -> zero or more normalized driver events on the turn queue.
56
- // Tool ids are tracked so a tool_call is emitted once and tool_update carries the
57
- // terminal state, matching the union the renderers already consume.
58
-
59
- function mapPart(part, queue, state) {
60
- switch (part.type) {
61
- case 'text-delta':
62
- case 'text': {
63
- const t = partText(part);
64
- if (t) queue.push({ type: 'text', text: t, messageId: part.id || null });
65
- break;
66
- }
67
- case 'reasoning-delta':
68
- case 'reasoning': {
69
- const t = partText(part);
70
- if (t) queue.push({ type: 'thinking', text: t, messageId: part.id || null });
71
- break;
72
- }
73
- case 'model-resolved': {
74
- if (part.model) queue.push({ type: 'model_resolved', model: part.model });
75
- break;
76
- }
77
- case 'context-notice': {
78
- queue.push({ type: 'notice', level: part.level || 'info', code: part.code || 'context', message: part.message || '' });
79
- break;
80
- }
81
- case 'context-continuation': {
82
- queue.push({ type: 'context_continuation', packet: part.packet || null, compacted: part.compacted === true });
83
- break;
84
- }
85
- case 'tool-call': {
86
- const id = part.toolCallId || part.id;
87
- state.tools.add(id);
88
- queue.push({ type: 'tool_call', id, title: part.toolName || 'tool', kind: part.toolName || 'other', status: 'pending', input: part.input ?? part.args ?? null });
89
- break;
90
- }
91
- case 'tool-result': {
92
- const id = part.toolCallId || part.id;
93
- queue.push({ type: 'tool_update', id, status: 'completed', content: part.output ?? part.result ?? null });
94
- break;
95
- }
96
- case 'tool-error': {
97
- const id = part.toolCallId || part.id;
98
- queue.push({ type: 'tool_update', id, status: 'failed', content: part.error ? String(part.error.message || part.error) : null });
99
- break;
100
- }
101
- case 'finish-step':
102
- case 'finish': {
103
- const usage = normalizeUsage(part);
104
- if (usage) queue.push(usage);
105
- break;
106
- }
107
- case 'error':
108
- state.errored = true;
109
- queue.push({ type: 'error', message: part.error ? String(part.error.message || part.error) : 'engine error' });
110
- break;
111
- case 'abort':
112
- state.aborted = true;
113
- break;
114
- default:
115
- break;
116
- }
117
- }
118
-
119
- export function createOwnedLoopDriver({
120
- env = process.env,
121
- cwd = process.cwd(),
122
- model = null,
123
- handlers = {},
124
- systemPrompt = '',
125
- tools = null,
126
- createAgent,
127
- } = {}) {
128
- if (typeof createAgent !== 'function') {
129
- throw new Error('createOwnedLoopDriver requires a `createAgent` factory (real engine or test mock)');
130
- }
131
-
132
- let agent = null;
133
- let activeTurn = null;
134
- let currentModel = model || null;
135
-
136
- async function start() {
137
- agent = await createAgent({ env, cwd, model: currentModel, handlers, systemPrompt, tools });
138
- if (agent?.model && !currentModel) currentModel = agent.model;
139
- return { sessionId: agent?.sessionId || `owned-${Date.now()}`, capabilities: { host: 'construct', ownedLoop: true } };
140
- }
141
-
142
- function prompt(text, opts = {}) {
143
- if (!agent) throw new Error('prompt() called before start()');
144
- const queue = new AsyncEventQueue();
145
- const controller = new AbortController();
146
- const turn = { queue, controller, tools: new Set(), aborted: false, errored: false };
147
- activeTurn = turn;
148
- if (opts.model) currentModel = opts.model;
149
-
150
- (async () => {
151
- try {
152
- const stream = await agent.streamTurn(text, {
153
- signal: controller.signal,
154
- model: currentModel,
155
- turnOverlay: opts.turnOverlay ?? null,
156
- });
157
- for await (const part of stream) {
158
- if (queue.closed) break;
159
- mapPart(part, queue, turn);
160
- }
161
- queue.push({ type: 'done', stopReason: turn.aborted ? 'cancelled' : turn.errored ? 'error' : 'end_turn' });
162
- } catch (err) {
163
- if (err?.name === 'AbortError' || turn.aborted) {
164
- queue.push({ type: 'done', stopReason: 'cancelled' });
165
- } else {
166
- queue.push({ type: 'error', message: err?.message || String(err) });
167
- queue.push({ type: 'done', stopReason: 'error' });
168
- }
169
- } finally {
170
- queue.close();
171
- if (activeTurn === turn) activeTurn = null;
172
- }
173
- })();
174
-
175
- return queue;
176
- }
177
-
178
- function cancel() {
179
- if (activeTurn) {
180
- activeTurn.aborted = true;
181
- try { activeTurn.controller.abort(); } catch { /* already aborted */ }
182
- }
183
- }
184
-
185
- function stop() {
186
- try { agent?.dispose?.(); } catch { /* nothing to dispose */ }
187
- }
188
-
189
- async function listModels() {
190
- if (agent?.listModels) {
191
- try { return await agent.listModels(); } catch { /* fall through to catalog */ }
192
- }
193
- return listChatModels({ env });
194
- }
195
-
196
- async function recommendModel() {
197
- if (currentModel) return null;
198
- return recommendChatModel({ env });
199
- }
200
-
201
- return {
202
- start,
203
- prompt,
204
- cancel,
205
- stop,
206
- listModels,
207
- recommendModel,
208
- get model() { return currentModel; },
209
- get sessionId() { return agent?.sessionId || null; },
210
- };
211
- }