@geraldmaron/construct 1.1.1 → 1.2.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 (426) hide show
  1. package/README.md +30 -19
  2. package/bin/construct +487 -87
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +258 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/artifact-workflow.mjs +252 -0
  20. package/lib/audit-skills.mjs +4 -0
  21. package/lib/audit-specialists.mjs +285 -0
  22. package/lib/audit-trail.mjs +1 -1
  23. package/lib/auto-docs.mjs +90 -19
  24. package/lib/beads/drift.mjs +27 -6
  25. package/lib/beads-client.mjs +49 -121
  26. package/lib/beads-optimistic.mjs +5 -12
  27. package/lib/brand-fonts.mjs +93 -0
  28. package/lib/brand-prose.mjs +214 -0
  29. package/lib/brand-tokens.mjs +92 -0
  30. package/lib/bridges/copilot-proxy.mjs +13 -26
  31. package/lib/capability-ledger.mjs +156 -0
  32. package/lib/certification/artifact-fixtures.mjs +132 -0
  33. package/lib/certification/artifact-gates.mjs +63 -0
  34. package/lib/certification/artifact-provenance.mjs +97 -0
  35. package/lib/certification/canonical-scenarios.mjs +78 -0
  36. package/lib/certification/cli.mjs +191 -0
  37. package/lib/certification/dashboard-api.mjs +71 -0
  38. package/lib/certification/demo-parity.mjs +116 -0
  39. package/lib/certification/document-io-fixtures.mjs +246 -0
  40. package/lib/certification/document-workflow.mjs +97 -0
  41. package/lib/certification/eval-bridge.mjs +77 -0
  42. package/lib/certification/model-routing.mjs +149 -0
  43. package/lib/certification/prompt-budget.mjs +119 -0
  44. package/lib/certification/rc-gate.mjs +206 -0
  45. package/lib/certification/real-llm-scenarios.mjs +303 -0
  46. package/lib/certification/role-cards.mjs +113 -0
  47. package/lib/certification/role-overlays.mjs +117 -0
  48. package/lib/certification/run.mjs +122 -0
  49. package/lib/certification/runner.mjs +323 -0
  50. package/lib/certification/scenarios.mjs +51 -0
  51. package/lib/certification/skill-inventory.mjs +289 -0
  52. package/lib/certification/skill-scenarios.mjs +147 -0
  53. package/lib/certification/specialist-contracts.mjs +85 -0
  54. package/lib/certification/specialist-scenarios.mjs +175 -0
  55. package/lib/certification/stale-impact.mjs +146 -0
  56. package/lib/certification/status.mjs +252 -0
  57. package/lib/certification/store.mjs +77 -0
  58. package/lib/chat/cli.mjs +333 -0
  59. package/lib/chat/command-suggest.mjs +161 -0
  60. package/lib/chat/commands.mjs +215 -0
  61. package/lib/chat/config.mjs +142 -0
  62. package/lib/chat/context-compactor.mjs +250 -0
  63. package/lib/chat/context-continuation.mjs +253 -0
  64. package/lib/chat/continuation-source.mjs +58 -0
  65. package/lib/chat/demo-guide.mjs +61 -0
  66. package/lib/chat/design-tokens.mjs +91 -0
  67. package/lib/chat/desktop-binary.mjs +79 -0
  68. package/lib/chat/desktop-build.mjs +130 -0
  69. package/lib/chat/desktop-launcher.mjs +133 -0
  70. package/lib/chat/evidence.mjs +145 -0
  71. package/lib/chat/export.mjs +74 -0
  72. package/lib/chat/harness/driver.mjs +91 -0
  73. package/lib/chat/list-picker.mjs +112 -0
  74. package/lib/chat/model-picker.mjs +356 -0
  75. package/lib/chat/openrouter-fallback.mjs +151 -0
  76. package/lib/chat/permission-prompt.mjs +33 -0
  77. package/lib/chat/picker-catalog.mjs +45 -0
  78. package/lib/chat/policy-telemetry.mjs +34 -0
  79. package/lib/chat/present.mjs +246 -0
  80. package/lib/chat/session-context.mjs +39 -0
  81. package/lib/chat/session-persist.mjs +73 -0
  82. package/lib/chat/session-restore.mjs +71 -0
  83. package/lib/chat/session-settings.mjs +53 -0
  84. package/lib/chat/system-prompt.mjs +52 -0
  85. package/lib/chat/transparency.mjs +93 -0
  86. package/lib/chat/tui/color-scheme.mjs +42 -0
  87. package/lib/chat/tui/markdown.mjs +123 -0
  88. package/lib/chat/tui/presentation.mjs +100 -0
  89. package/lib/chat/tui/render.mjs +500 -0
  90. package/lib/chat/tui/turn-block.mjs +284 -0
  91. package/lib/chat/tui/turn-present.mjs +18 -0
  92. package/lib/chat/tui/turn-state.mjs +88 -0
  93. package/lib/chat/tui/usage.mjs +122 -0
  94. package/lib/chat/web-commands.mjs +146 -0
  95. package/lib/chat/web-launcher.mjs +63 -0
  96. package/lib/chat/web-picker-keys.mjs +46 -0
  97. package/lib/chat/web-session.mjs +159 -0
  98. package/lib/cli-commands.mjs +146 -15
  99. package/lib/cli-service-inventory.mjs +42 -0
  100. package/lib/comment-lint.mjs +6 -4
  101. package/lib/config/intake-policy.mjs +209 -0
  102. package/lib/config/project-config.mjs +11 -2
  103. package/lib/config/schema.mjs +86 -6
  104. package/lib/config/source-targets.mjs +311 -0
  105. package/lib/contract-schemas/decision.json +50 -0
  106. package/lib/contract-schemas/implementation.json +51 -0
  107. package/lib/contract-schemas/review-report.json +32 -0
  108. package/lib/contract-schemas/test-report.json +43 -0
  109. package/lib/contracts/construct-handoff.mjs +60 -0
  110. package/lib/contracts/validate.mjs +32 -3
  111. package/lib/contracts/violation-log.mjs +58 -5
  112. package/lib/dashboard-demo.mjs +71 -0
  113. package/lib/dashboard-static.mjs +19 -5
  114. package/lib/decisions/registry.mjs +5 -3
  115. package/lib/deck-export-pptx.mjs +1152 -0
  116. package/lib/demo-recording.mjs +142 -0
  117. package/lib/demo-script.mjs +114 -0
  118. package/lib/demo-surface.mjs +249 -0
  119. package/lib/demo.mjs +598 -140
  120. package/lib/diagram-export.mjs +192 -0
  121. package/lib/diagram.mjs +13 -11
  122. package/lib/docs-verify.mjs +25 -7
  123. package/lib/doctor/index.mjs +3 -1
  124. package/lib/doctor/project-adapters.mjs +44 -0
  125. package/lib/doctor/source-checkout.mjs +16 -0
  126. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  127. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  128. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  129. package/lib/document-export.mjs +384 -38
  130. package/lib/document-extract.mjs +44 -4
  131. package/lib/document-ingest.mjs +29 -20
  132. package/lib/embed/auto-sources.mjs +44 -0
  133. package/lib/embed/cli.mjs +4 -3
  134. package/lib/embed/daemon.mjs +18 -46
  135. package/lib/embed/demand-fetch.mjs +50 -19
  136. package/lib/embed/inbox.mjs +6 -10
  137. package/lib/embed/worker.mjs +4 -3
  138. package/lib/embedded-contract/index.mjs +11 -0
  139. package/lib/embedded-contract/model-resolve.mjs +10 -9
  140. package/lib/env-config.mjs +26 -0
  141. package/lib/evals/dataset.mjs +137 -0
  142. package/lib/evals/gates.mjs +175 -0
  143. package/lib/export-branding.mjs +32 -0
  144. package/lib/graph/build-co-change.mjs +54 -0
  145. package/lib/graph/build-from-registry.mjs +136 -0
  146. package/lib/graph/build-import-graph.mjs +153 -0
  147. package/lib/graph/cli.mjs +110 -0
  148. package/lib/graph/impact-cli.mjs +89 -0
  149. package/lib/graph/impact.mjs +108 -0
  150. package/lib/graph/staleness.mjs +44 -0
  151. package/lib/graph/store.mjs +172 -0
  152. package/lib/health-check.mjs +90 -76
  153. package/lib/hooks/artifact-release-gate.mjs +43 -0
  154. package/lib/hooks/brand-prose-lint.mjs +38 -0
  155. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  156. package/lib/hooks/session-optimize.mjs +84 -207
  157. package/lib/hooks/session-start.mjs +18 -16
  158. package/lib/host-disposition.mjs +7 -0
  159. package/lib/improvement/cli.mjs +189 -0
  160. package/lib/improvement/controller.mjs +137 -0
  161. package/lib/improvement/proposal.mjs +120 -0
  162. package/lib/improvement/specialist-loop.mjs +192 -0
  163. package/lib/improvement/store.mjs +89 -0
  164. package/lib/improvement/surface.mjs +219 -0
  165. package/lib/ingest-tooling.mjs +97 -0
  166. package/lib/init/doc-lanes.mjs +165 -0
  167. package/lib/init-docs.mjs +31 -178
  168. package/lib/init-unified.mjs +50 -119
  169. package/lib/init-update-guide.mjs +102 -0
  170. package/lib/init-update.mjs +32 -1
  171. package/lib/init.mjs +8 -0
  172. package/lib/install/desktop-binary-download.mjs +85 -0
  173. package/lib/install/legacy-global-cleanup.mjs +189 -0
  174. package/lib/intake/constants.mjs +29 -0
  175. package/lib/intake/daemon.mjs +2 -0
  176. package/lib/intake/git-queue.mjs +9 -8
  177. package/lib/intake/intake-config.mjs +52 -105
  178. package/lib/intake/legacy-paths.mjs +5 -0
  179. package/lib/intake/queue.mjs +2 -1
  180. package/lib/intake/session-prelude.mjs +90 -1
  181. package/lib/libreoffice-export.mjs +97 -0
  182. package/lib/logging/rotate.mjs +9 -0
  183. package/lib/maintenance/docker-reclaim.mjs +206 -0
  184. package/lib/mcp/external-schema-cost.mjs +74 -0
  185. package/lib/mcp/server.mjs +103 -10
  186. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  187. package/lib/mcp/tool-budget.mjs +55 -4
  188. package/lib/mcp/tools/document.mjs +18 -4
  189. package/lib/mcp/tools/embedded-contract.mjs +14 -0
  190. package/lib/mcp/tools/skills.mjs +32 -3
  191. package/lib/mcp/tools/workflow.mjs +13 -1
  192. package/lib/model-free-selector.mjs +18 -0
  193. package/lib/model-registry.mjs +13 -229
  194. package/lib/model-router.mjs +373 -92
  195. package/lib/models/behavior-matrix.mjs +289 -0
  196. package/lib/models/catalog.mjs +209 -0
  197. package/lib/models/execution-capability-profile.mjs +196 -0
  198. package/lib/models/execution-policy.mjs +307 -0
  199. package/lib/models/provider-poll.mjs +383 -0
  200. package/lib/npm-spawn-env.mjs +17 -0
  201. package/lib/ollama/installed-models.mjs +129 -0
  202. package/lib/oracle/actions.mjs +187 -0
  203. package/lib/oracle/artifact-gate.mjs +99 -0
  204. package/lib/oracle/cli.mjs +204 -0
  205. package/lib/oracle/daemon-entry.mjs +14 -0
  206. package/lib/oracle/dispatch.mjs +81 -0
  207. package/lib/oracle/execute.mjs +143 -0
  208. package/lib/oracle/gaps.mjs +76 -0
  209. package/lib/oracle/index.mjs +84 -0
  210. package/lib/oracle/issues.mjs +164 -0
  211. package/lib/oracle/org-graph.mjs +170 -0
  212. package/lib/oracle/policy.mjs +80 -0
  213. package/lib/oracle/read-model.mjs +398 -0
  214. package/lib/oracle/reconcile.mjs +191 -0
  215. package/lib/oracle/routing.mjs +89 -0
  216. package/lib/oracle/synthesize.mjs +384 -0
  217. package/lib/oracle/verdicts.mjs +51 -0
  218. package/lib/orchestration/worker.mjs +88 -27
  219. package/lib/orchestration-policy.mjs +50 -0
  220. package/lib/parity.mjs +96 -2
  221. package/lib/playwright-demo.mjs +254 -0
  222. package/lib/project-init-shared.mjs +4 -1
  223. package/lib/prompt-composer.js +7 -3
  224. package/lib/prompt-validation-contract.mjs +24 -0
  225. package/lib/provider-capabilities.js +57 -11
  226. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  227. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  228. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  229. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  230. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  231. package/lib/providers/contract/contract-tests.mjs +57 -0
  232. package/lib/providers/contract/errors.mjs +48 -0
  233. package/lib/providers/contract/interface.mjs +50 -0
  234. package/lib/providers/contract/registry.mjs +102 -0
  235. package/lib/providers/copilot-auth.mjs +297 -0
  236. package/lib/providers/credential-bootstrap.mjs +178 -0
  237. package/lib/providers/credential-catalog.mjs +46 -0
  238. package/lib/providers/credential-sources.mjs +63 -0
  239. package/lib/providers/creds.mjs +5 -2
  240. package/lib/providers/op-run.mjs +59 -0
  241. package/lib/providers/secret-resolver.mjs +159 -0
  242. package/lib/publish-template.mjs +163 -0
  243. package/lib/publish-tooling.mjs +119 -0
  244. package/lib/publish.mjs +305 -0
  245. package/lib/registry/cli.mjs +82 -0
  246. package/lib/registry/consolidation.mjs +147 -0
  247. package/lib/registry/generate-docs.mjs +75 -0
  248. package/lib/registry/skill-verification.mjs +57 -0
  249. package/lib/registry/surface-map.mjs +76 -0
  250. package/lib/registry/validate.mjs +135 -0
  251. package/lib/resources/budget.mjs +82 -0
  252. package/lib/resources/process-budget.mjs +45 -0
  253. package/lib/rules-delivery.mjs +9 -2
  254. package/lib/rules-read.mjs +26 -0
  255. package/lib/runtime-env.mjs +23 -0
  256. package/lib/runtime-pressure.mjs +51 -7
  257. package/lib/schema-infer.mjs +13 -2
  258. package/lib/server/chat-loop.mjs +622 -0
  259. package/lib/server/demo-preview.mjs +63 -0
  260. package/lib/server/index.mjs +259 -9
  261. package/lib/server/rate-limit.mjs +5 -3
  262. package/lib/server/webhook.mjs +3 -2
  263. package/lib/service-manager.mjs +60 -8
  264. package/lib/setup.mjs +70 -7
  265. package/lib/specialists/prompt-schema.mjs +19 -10
  266. package/lib/specialists/roster.mjs +43 -0
  267. package/lib/specialists/scaffold.mjs +56 -0
  268. package/lib/storage/backend.mjs +6 -6
  269. package/lib/storage/hybrid-query.mjs +7 -4
  270. package/lib/storage/state-source.mjs +5 -5
  271. package/lib/storage/sync.mjs +2 -3
  272. package/lib/telemetry/rule-calls.mjs +52 -0
  273. package/lib/template-registry.mjs +2 -2
  274. package/lib/templates/visual-requirements.mjs +27 -51
  275. package/lib/test-corpus-inventory.mjs +313 -0
  276. package/lib/uninstall/uninstall.mjs +19 -7
  277. package/lib/update.mjs +12 -0
  278. package/lib/upgrade.mjs +14 -0
  279. package/lib/wireframe.mjs +20 -14
  280. package/lib/worker/run.mjs +17 -4
  281. package/lib/worker/trace.mjs +11 -3
  282. package/package.json +28 -14
  283. package/personas/construct.md +2 -2
  284. package/platforms/claude/settings.template.json +36 -0
  285. package/rules/common/patterns.md +1 -1
  286. package/rules/common/release-gates.md +4 -3
  287. package/scripts/sync-specialists.mjs +41 -28
  288. package/skills/devops/data-engineering.md +1 -1
  289. package/skills/docs/adr-workflow.md +1 -0
  290. package/skills/docs/backlog-proposal-workflow.md +1 -0
  291. package/skills/docs/codebase-research-workflow.md +40 -0
  292. package/skills/docs/customer-profile-workflow.md +1 -0
  293. package/skills/docs/document-ingest-workflow.md +1 -0
  294. package/skills/docs/evidence-ingest-workflow.md +1 -0
  295. package/skills/docs/init-docs.md +2 -2
  296. package/skills/docs/init-project.md +7 -2
  297. package/skills/docs/prd-workflow.md +24 -1
  298. package/skills/docs/prfaq-workflow.md +1 -0
  299. package/skills/docs/product-intelligence-workflow.md +1 -0
  300. package/skills/docs/product-signal-workflow.md +1 -0
  301. package/skills/docs/research-workflow.md +54 -37
  302. package/skills/docs/runbook-workflow.md +1 -0
  303. package/skills/docs/strategy-workflow.md +1 -0
  304. package/skills/docs/user-research-workflow.md +40 -0
  305. package/skills/operating/orchestration-reference.md +1 -1
  306. package/skills/roles/architect.md +5 -0
  307. package/skills/roles/operator.docs.md +4 -0
  308. package/skills/routing.md +4 -2
  309. package/specialists/artifact-manifest.json +489 -0
  310. package/specialists/artifact-manifest.schema.json +83 -0
  311. package/specialists/audit-enrichments.json +454 -0
  312. package/specialists/contracts.json +37 -25
  313. package/specialists/prompts/_shared/validation-contract.md +26 -0
  314. package/specialists/prompts/cx-accessibility.md +21 -2
  315. package/specialists/prompts/cx-ai-engineer.md +24 -1
  316. package/specialists/prompts/cx-architect.md +4 -1
  317. package/specialists/prompts/cx-business-strategist.md +23 -2
  318. package/specialists/prompts/cx-data-analyst.md +22 -1
  319. package/specialists/prompts/cx-data-engineer.md +23 -2
  320. package/specialists/prompts/cx-debugger.md +21 -2
  321. package/specialists/prompts/cx-designer.md +26 -3
  322. package/specialists/prompts/cx-devil-advocate.md +19 -2
  323. package/specialists/prompts/cx-docs-keeper.md +28 -1
  324. package/specialists/prompts/cx-engineer.md +22 -1
  325. package/specialists/prompts/cx-evaluator.md +18 -1
  326. package/specialists/prompts/cx-explorer.md +21 -2
  327. package/specialists/prompts/cx-legal-compliance.md +21 -2
  328. package/specialists/prompts/cx-operations.md +21 -2
  329. package/specialists/prompts/cx-oracle.md +94 -0
  330. package/specialists/prompts/cx-orchestrator.md +20 -1
  331. package/specialists/prompts/cx-platform-engineer.md +25 -2
  332. package/specialists/prompts/cx-product-manager.md +32 -1
  333. package/specialists/prompts/cx-qa.md +24 -2
  334. package/specialists/prompts/cx-rd-lead.md +23 -2
  335. package/specialists/prompts/cx-release-manager.md +23 -2
  336. package/specialists/prompts/cx-researcher.md +22 -2
  337. package/specialists/prompts/cx-reviewer.md +18 -1
  338. package/specialists/prompts/cx-security.md +22 -2
  339. package/specialists/prompts/cx-sre.md +30 -3
  340. package/specialists/prompts/cx-test-automation.md +7 -1
  341. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  342. package/specialists/prompts/cx-ux-researcher.md +21 -2
  343. package/specialists/registry.json +52 -173
  344. package/specialists/tone-profiles.json +42 -0
  345. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  346. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  347. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  348. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  349. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  350. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  351. package/templates/demos/vhs/construct-cockpit.json +24 -0
  352. package/templates/distribution/construct-brand.typ +446 -0
  353. package/templates/distribution/construct-decision.typ +38 -0
  354. package/templates/distribution/construct-deck.html +95 -0
  355. package/templates/distribution/construct-pdf.typ +38 -0
  356. package/templates/distribution/construct-prd.typ +38 -0
  357. package/templates/distribution/construct-reference.docx +0 -0
  358. package/templates/distribution/construct-research.typ +38 -0
  359. package/templates/distribution/construct-web.html +92 -0
  360. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  361. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  362. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  363. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  364. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  365. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  366. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  367. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  368. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  369. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  370. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  371. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  372. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  373. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  374. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  375. package/templates/distribution/fonts/README.md +36 -0
  376. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  377. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  378. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  379. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  380. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  381. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  382. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  383. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  384. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  385. package/templates/distribution/icons/activity.svg +15 -0
  386. package/templates/distribution/icons/alert-triangle.svg +17 -0
  387. package/templates/distribution/icons/book-open.svg +16 -0
  388. package/templates/distribution/icons/bot.svg +20 -0
  389. package/templates/distribution/icons/brain.svg +22 -0
  390. package/templates/distribution/icons/circle-check.svg +16 -0
  391. package/templates/distribution/icons/clipboard-check.svg +17 -0
  392. package/templates/distribution/icons/cpu.svg +28 -0
  393. package/templates/distribution/icons/database.svg +17 -0
  394. package/templates/distribution/icons/eye.svg +16 -0
  395. package/templates/distribution/icons/file-text.svg +19 -0
  396. package/templates/distribution/icons/gauge.svg +16 -0
  397. package/templates/distribution/icons/git-branch.svg +17 -0
  398. package/templates/distribution/icons/key.svg +17 -0
  399. package/templates/distribution/icons/layers.svg +17 -0
  400. package/templates/distribution/icons/list-checks.svg +19 -0
  401. package/templates/distribution/icons/lock.svg +16 -0
  402. package/templates/distribution/icons/message-square.svg +15 -0
  403. package/templates/distribution/icons/network.svg +19 -0
  404. package/templates/distribution/icons/route.svg +17 -0
  405. package/templates/distribution/icons/scale.svg +19 -0
  406. package/templates/distribution/icons/search.svg +16 -0
  407. package/templates/distribution/icons/send.svg +16 -0
  408. package/templates/distribution/icons/server.svg +18 -0
  409. package/templates/distribution/icons/shield-check.svg +16 -0
  410. package/templates/distribution/icons/users.svg +18 -0
  411. package/templates/distribution/icons/webhook.svg +17 -0
  412. package/templates/distribution/icons/workflow.svg +17 -0
  413. package/templates/distribution/icons/wrench.svg +15 -0
  414. package/templates/distribution/run.mjs +18 -1
  415. package/templates/docs/adr.md +7 -0
  416. package/templates/docs/construct_guide.md +8 -8
  417. package/templates/docs/customer-profile.md +4 -0
  418. package/templates/docs/one-pager.md +4 -0
  419. package/templates/docs/postmortem.md +31 -0
  420. package/templates/docs/prd-platform.md +19 -0
  421. package/templates/docs/prd.md +15 -0
  422. package/templates/docs/prfaq.md +7 -0
  423. package/templates/docs/qa-strategy.md +103 -0
  424. package/templates/docs/research-brief.md +8 -0
  425. package/templates/docs/rfc-platform.md +12 -0
  426. package/templates/docs/test-plan.md +7 -0
@@ -13,7 +13,7 @@ import { resolve, dirname } from 'node:path';
13
13
  import { fileURLToPath } from 'node:url';
14
14
  import { realpathSync } from 'node:fs';
15
15
  import { loadToolkitEnv } from '../toolkit-env.mjs';
16
- import { loadConstructEnv } from '../env-config.mjs';
16
+ import { loadConstructEnv, prepareConstructEnv } from '../runtime-env.mjs';
17
17
  import { getInstalledVersion } from '../version.mjs';
18
18
  import { getDeploymentMode } from '../deployment-mode.mjs';
19
19
  import { withGenAiSpan, GenAiAttrs, extractTraceContext, injectTraceContext } from '../telemetry/otel-tracer.mjs';
@@ -21,7 +21,7 @@ import { withGenAiSpan, GenAiAttrs, extractTraceContext, injectTraceContext } fr
21
21
  // Apply config.env values to process.env, letting config.env win over shell env
22
22
  // so telemetry/OpenRouter credentials are always correct regardless of host env.
23
23
  {
24
- const confEnv = loadConstructEnv({ warn: false });
24
+ const confEnv = prepareConstructEnv({ warn: false });
25
25
  for (const [k, v] of Object.entries(confEnv)) {
26
26
  process.env[k] = v;
27
27
  }
@@ -58,7 +58,7 @@ import {
58
58
  profileCreate, profileArchive, sandboxList, learningStatus,
59
59
  knowledgeGraphAsk,
60
60
  } from './tools/profile.mjs';
61
- import { modelResolve, triageRecommend, workflowInvoke, capabilityDescribe, executionResolve } from './tools/embedded-contract.mjs';
61
+ import { modelResolve, triageRecommend, workflowInvoke, capabilityDescribe, executionResolve, artifactWorkflow } from './tools/embedded-contract.mjs';
62
62
 
63
63
  const DEFAULT_ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
64
64
  const ROOT_DIR = resolve(process.env.CX_TOOLKIT_DIR || DEFAULT_ROOT_DIR);
@@ -170,7 +170,7 @@ const ALL_TOOL_DEFS = [
170
170
  },
171
171
  {
172
172
  name: 'extract_document_text',
173
- description: 'Extracts readable text from a local document path. High-fidelity backend (docling Python sidecar via uv, whisper.cpp for audio/video) is wired into `construct ingest`; this MCP path returns the legacy regex extraction for backwards compatibility. Supports PDF, DOCX, XLSX, PPTX, HTML, plain text, email, and transcripts.',
173
+ description: 'Extracts readable text from a local document path. Uses node-native extractors (unpdf/mammoth) first; escalates to the docling Python sidecar or whisper.cpp when needed (same pipeline as `construct ingest`). Supports PDF, DOCX, XLSX, PPTX, HTML, plain text, email, and transcripts.',
174
174
  inputSchema: {
175
175
  type: 'object',
176
176
  properties: {
@@ -375,7 +375,7 @@ const ALL_TOOL_DEFS = [
375
375
  },
376
376
  {
377
377
  name: 'get_skill',
378
- description: 'Reads a specific skill playbook from the Construct knowledge base (e.g. "security/security-arch", "web/design-quality").',
378
+ description: 'Reads a specific skill playbook from the Construct knowledge base (e.g. "docs/adr-workflow", "roles/engineer", "architecture/security-arch").',
379
379
  inputSchema: {
380
380
  type: 'object',
381
381
  properties: {
@@ -932,15 +932,50 @@ const ALL_TOOL_DEFS = [
932
932
  },
933
933
  {
934
934
  name: 'document_export',
935
- description: 'Convert a markdown file into a distributable PDF, DOCX, or HTML document via Pandoc (DOCX/HTML) and Typst (PDF engine), per ADR-0024. Both engines are optional system binaries discovered at runtime — the tool returns a structured "install <binary>" error when tooling is absent, never crashes. Use `detect_only=true` to check availability without running an export.',
935
+ description: 'Convert a markdown file into a distributable document — PDF, DOCX, legacy DOC (LibreOffice), deck HTML, PPTX, HTML, RTF, ODT, EPUB, LaTeX, plain text, or Markdown — via Pandoc and Typst (PDF engine) plus optional pptxgenjs/LibreOffice, per ADR-0024. Engines are optional dependencies discovered at runtime — the tool returns a structured "install <binary>" error when tooling is absent, never crashes. Use `detect_only=true` to check availability without running an export.',
936
936
  inputSchema: {
937
937
  type: 'object',
938
938
  required: ['format'],
939
939
  properties: {
940
940
  input_path: { type: 'string', description: 'Absolute path to the markdown source file (required unless detect_only=true).' },
941
941
  output_path: { type: 'string', description: 'Optional absolute output path; defaults to <input>.<format> next to the source.' },
942
- format: { type: 'string', enum: ['pdf', 'docx', 'html'], description: 'Target format.' },
942
+ format: { type: 'string', enum: ['pdf', 'docx', 'doc', 'deck', 'pptx', 'html', 'rtf', 'odt', 'epub', 'tex', 'txt', 'md', 'mdx'], description: 'Target format.' },
943
943
  detect_only: { type: 'boolean', description: 'When true, report binary availability without exporting. Default false.' },
944
+ figures: { type: 'boolean', description: 'When true, render d2/mermaid via pandoc-ext/diagram filter. Default false.' },
945
+ },
946
+ },
947
+ },
948
+ {
949
+ name: 'publish_detect',
950
+ description: 'Detect availability of publish pipeline tooling: Pandoc/Typst export, pandoc-ext/diagram figures, VHS terminal demos, and Playwright dashboard demos.',
951
+ inputSchema: {
952
+ type: 'object',
953
+ properties: {
954
+ format: { type: 'string', enum: ['pdf', 'docx', 'doc', 'deck', 'pptx', 'html', 'rtf', 'odt', 'epub', 'tex', 'txt', 'md', 'mdx'], description: 'Export format to probe (default pdf).' },
955
+ figures: { type: 'boolean', description: 'Include figure binaries (default true).' },
956
+ demo: { type: 'string', description: 'When set, require VHS/asciinema for terminal demo.' },
957
+ dashboard_demo: { type: 'string', description: 'When set, require Playwright workspace for dashboard demo.' },
958
+ },
959
+ },
960
+ },
961
+ {
962
+ name: 'publish_run',
963
+ description: 'Run the publish pipeline: export markdown with optional figure filter and optional demo recordings. Use dry_run=true to probe tooling only.',
964
+ inputSchema: {
965
+ type: 'object',
966
+ required: ['input_path'],
967
+ properties: {
968
+ input_path: { type: 'string', description: 'Absolute path to markdown research brief.' },
969
+ output_path: { type: 'string', description: 'Optional output path for export.' },
970
+ format: { type: 'string', enum: ['pdf', 'docx', 'doc', 'deck', 'pptx', 'html', 'rtf', 'odt', 'epub', 'tex', 'txt', 'md', 'mdx'], description: 'Target format (default pdf).' },
971
+ demo: { type: 'string', description: 'Terminal tape name to record.' },
972
+ dashboard_demo: { type: 'string', description: 'Dashboard Playwright demo spec basename.' },
973
+ figures: { type: 'boolean', description: 'Render diagrams (default true).' },
974
+ strict: { type: 'boolean', description: 'Fail when toolchain or release gate fails (default true).' },
975
+ source_only: { type: 'boolean', description: 'Write sources only (default false).' },
976
+ no_gate: { type: 'boolean', description: 'Skip artifact release gate (default false).' },
977
+ artifact_type: { type: 'string', description: 'Manifest doc type for release gate when path inference is ambiguous.' },
978
+ dry_run: { type: 'boolean', description: 'Detect tooling only (default false).' },
944
979
  },
945
980
  },
946
981
  },
@@ -1099,6 +1134,23 @@ const ALL_TOOL_DEFS = [
1099
1134
  },
1100
1135
  },
1101
1136
  },
1137
+ {
1138
+ name: 'artifact_workflow',
1139
+ description: 'Plan a manifest-backed document artifact workflow and return a truthful provenance report. It separates planned steps from locally executed validation/export and never claims a host-planned specialist review or rewrite was completed. Durable local export requires approval_mode=allow-durable-write.',
1140
+ inputSchema: {
1141
+ type: 'object',
1142
+ properties: {
1143
+ input: { type: 'string', description: 'Natural-language artifact request.' },
1144
+ artifact_type: { type: 'string', description: 'Registered manifest document class.' },
1145
+ file_path: { type: 'string', description: 'Optional existing source document for local validation/export.' },
1146
+ format: { type: 'string', description: 'Distribution format such as pdf, docx, html, deck, or pptx.' },
1147
+ output_path: { type: 'string', description: 'Optional output destination when locally exporting.' },
1148
+ branding: { type: 'string', enum: ['construct', 'plain'], description: 'Construct is default; plain is an explicit opt-out.' },
1149
+ overrides: { type: 'object', description: 'Per-invocation manifest workflow overrides.' },
1150
+ approval_mode: { type: 'string', enum: ['proposal-only', 'requires-human-approval', 'allow-durable-write'], description: 'Only allow-durable-write performs local validation/export.' },
1151
+ },
1152
+ },
1153
+ },
1102
1154
  {
1103
1155
  name: 'model_resolve',
1104
1156
  description: 'Resolve which model an embedded Construct workflow should use given the host/IDE provider context. Precedence: host model → same-provider-family fallback → Construct tier default → structured config error. Never reads or returns credential values (requiresCredential is a boolean) and never claims unverified provider health. Read-only; performs no writes.',
@@ -1270,7 +1322,7 @@ export async function dispatchToolByName(name, args = {}) {
1270
1322
  if (name === 'agent_health') result = agentHealth(args);
1271
1323
  else if (name === 'summarize_diff') result = summarizeDiff(args);
1272
1324
  else if (name === 'scan_file') result = scanFile(args);
1273
- else if (name === 'extract_document_text') result = extractDocumentText(args);
1325
+ else if (name === 'extract_document_text') result = await extractDocumentText(args);
1274
1326
  else if (name === 'ingest_document') result = await ingestDocument(args);
1275
1327
  else if (name === 'infer_document_schema') result = await inferDocumentSchemaTool(args);
1276
1328
  else if (name === 'list_schema_artifacts') result = listSchemaArtifactsTool(args);
@@ -1324,9 +1376,49 @@ export async function dispatchToolByName(name, args = {}) {
1324
1376
  else if (name === 'document_export') {
1325
1377
  const { detect, exportMarkdown } = await import('../document-export.mjs');
1326
1378
  if (args.detect_only) {
1327
- result = detect(args.format);
1379
+ result = detect(args.format, process.env, { figures: Boolean(args.figures) });
1380
+ } else {
1381
+ result = exportMarkdown({
1382
+ inputPath: args.input_path,
1383
+ outputPath: args.output_path,
1384
+ format: args.format,
1385
+ figures: Boolean(args.figures),
1386
+ });
1387
+ }
1388
+ }
1389
+ else if (name === 'publish_detect') {
1390
+ const { detectPublishPipeline } = await import('../publish-tooling.mjs');
1391
+ result = detectPublishPipeline({
1392
+ format: args.format || 'pdf',
1393
+ includeFigures: args.figures !== false,
1394
+ includeTerminalDemo: Boolean(args.demo),
1395
+ includeDashboardDemo: Boolean(args.dashboard_demo),
1396
+ });
1397
+ }
1398
+ else if (name === 'publish_run') {
1399
+ const { runPublish } = await import('../publish.mjs');
1400
+ if (args.dry_run) {
1401
+ const { detectPublishPipeline } = await import('../publish-tooling.mjs');
1402
+ result = detectPublishPipeline({
1403
+ format: args.format || 'pdf',
1404
+ includeFigures: args.figures !== false,
1405
+ includeTerminalDemo: Boolean(args.demo),
1406
+ includeDashboardDemo: Boolean(args.dashboard_demo),
1407
+ });
1328
1408
  } else {
1329
- result = exportMarkdown({ inputPath: args.input_path, outputPath: args.output_path, format: args.format });
1409
+ result = runPublish({
1410
+ inputPath: args.input_path,
1411
+ format: args.format || 'pdf',
1412
+ outputPath: args.output_path,
1413
+ demos: args.demo ? [args.demo] : [],
1414
+ dashboardDemos: args.dashboard_demo ? [args.dashboard_demo] : [],
1415
+ figures: args.figures !== false,
1416
+ strict: args.strict !== false,
1417
+ gate: args.no_gate !== true,
1418
+ artifactType: args.artifact_type || args.type || null,
1419
+ sourceOnly: Boolean(args.source_only),
1420
+ repoRoot: process.cwd(),
1421
+ });
1330
1422
  }
1331
1423
  }
1332
1424
  else if (name === 'profile_show') result = profileShow(args);
@@ -1346,6 +1438,7 @@ export async function dispatchToolByName(name, args = {}) {
1346
1438
  else if (name === 'workflow_invoke') result = await workflowInvoke(args);
1347
1439
  else if (name === 'capability_describe') result = capabilityDescribe(args);
1348
1440
  else if (name === 'construct_execution_resolve') result = executionResolve(args);
1441
+ else if (name === 'artifact_workflow') result = artifactWorkflow(args);
1349
1442
  else if (name === 'orchestration_run') { const m = await import('./tools/orchestration-run.mjs'); result = await m.orchestrationRun(args); }
1350
1443
  else if (name === 'orchestration_status') { const m = await import('./tools/orchestration-run.mjs'); result = await m.orchestrationStatus(args); }
1351
1444
  else if (name === 'construct_call') {
@@ -0,0 +1,188 @@
1
+ /**
2
+ * lib/mcp/stdio-mcp-probe.mjs — newline-delimited JSON-RPC probe for stdio MCP servers.
3
+ *
4
+ * Spawns a child MCP process, completes initialize + notifications/initialized, then
5
+ * issues tools/list to capture serialized tool schemas for token-budget fixtures.
6
+ * Stdout is the only protocol channel; stderr is ignored.
7
+ */
8
+
9
+ import { spawn } from 'node:child_process';
10
+
11
+ const DEFAULT_TIMEOUT_MS = 25_000;
12
+
13
+ function parseFrames(chunk, state) {
14
+ state.buffer += chunk;
15
+ let idx;
16
+ while ((idx = state.buffer.indexOf('\n')) >= 0) {
17
+ const raw = state.buffer.slice(0, idx).trim();
18
+ state.buffer = state.buffer.slice(idx + 1);
19
+ if (!raw) continue;
20
+ try {
21
+ state.frames.push(JSON.parse(raw));
22
+ } catch {
23
+ /* non-JSON noise */
24
+ }
25
+ }
26
+ }
27
+
28
+ function send(child, frame) {
29
+ child.stdin.write(`${JSON.stringify(frame)}\n`);
30
+ }
31
+
32
+ function waitForFrame(frames, predicate, timeoutMs) {
33
+ const deadline = Date.now() + timeoutMs;
34
+ return new Promise((resolve, reject) => {
35
+ const tick = () => {
36
+ const hit = frames.find(predicate);
37
+ if (hit) {
38
+ resolve(hit);
39
+ return;
40
+ }
41
+ if (Date.now() >= deadline) {
42
+ reject(new Error(`Timed out after ${timeoutMs}ms; frames=${frames.length}`));
43
+ return;
44
+ }
45
+ setTimeout(tick, 50);
46
+ };
47
+ tick();
48
+ });
49
+ }
50
+
51
+ // Probe a stdio MCP server and return the tools/list result array.
52
+
53
+ export async function probeStdioMcpTools(command, args, {
54
+ env = {},
55
+ timeoutMs = DEFAULT_TIMEOUT_MS,
56
+ } = {}) {
57
+ const child = spawn(command, args, {
58
+ env: { ...process.env, ...env },
59
+ stdio: ['pipe', 'pipe', 'pipe'],
60
+ });
61
+ const state = { buffer: '', frames: [] };
62
+ child.stdout.setEncoding('utf8');
63
+ child.stdout.on('data', (chunk) => parseFrames(chunk, state));
64
+
65
+ const cleanup = () => {
66
+ try {
67
+ child.kill('SIGTERM');
68
+ } catch {
69
+ /* already exited */
70
+ }
71
+ };
72
+
73
+ try {
74
+ send(child, {
75
+ jsonrpc: '2.0',
76
+ id: 1,
77
+ method: 'initialize',
78
+ params: {
79
+ protocolVersion: '2024-11-05',
80
+ capabilities: {},
81
+ clientInfo: { name: 'construct-mcp-probe', version: '1' },
82
+ },
83
+ });
84
+ await waitForFrame(state.frames, (f) => f.id === 1, timeoutMs);
85
+ send(child, { jsonrpc: '2.0', method: 'notifications/initialized' });
86
+ send(child, { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} });
87
+ const listFrame = await waitForFrame(state.frames, (f) => f.id === 2, timeoutMs);
88
+ if (listFrame.error) {
89
+ throw new Error(listFrame.error.message || JSON.stringify(listFrame.error));
90
+ }
91
+ return listFrame.result?.tools || [];
92
+ } finally {
93
+ cleanup();
94
+ }
95
+ }
96
+
97
+ // Parse JSON or MCP-over-SSE (event: message / data: {...}) bodies from HTTP MCP hosts.
98
+
99
+ function parseMcpHttpFrame(text) {
100
+ try {
101
+ return JSON.parse(text);
102
+ } catch {
103
+ for (const line of text.split('\n')) {
104
+ const trimmed = line.trim();
105
+ if (!trimmed.startsWith('data:')) continue;
106
+ const payload = trimmed.slice(5).trim();
107
+ if (!payload) continue;
108
+ try {
109
+ return JSON.parse(payload);
110
+ } catch {
111
+ /* try next data line */
112
+ }
113
+ }
114
+ throw new Error('response was not JSON or SSE data');
115
+ }
116
+ }
117
+
118
+ // Probe an HTTP MCP endpoint (GitHub Copilot MCP) with bearer auth.
119
+
120
+ export async function probeHttpMcpTools(url, headers = {}, { timeoutMs = DEFAULT_TIMEOUT_MS } = {}) {
121
+ const initBody = {
122
+ jsonrpc: '2.0',
123
+ id: 1,
124
+ method: 'initialize',
125
+ params: {
126
+ protocolVersion: '2024-11-05',
127
+ capabilities: {},
128
+ clientInfo: { name: 'construct-mcp-probe', version: '1' },
129
+ },
130
+ };
131
+ const listBody = { jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} };
132
+ const commonHeaders = {
133
+ 'Content-Type': 'application/json',
134
+ Accept: 'application/json, text/event-stream',
135
+ ...headers,
136
+ };
137
+ const controller = new AbortController();
138
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
139
+
140
+ async function postRpc(body, extraHeaders = {}) {
141
+ const res = await fetch(url, {
142
+ method: 'POST',
143
+ headers: { ...commonHeaders, ...extraHeaders },
144
+ body: JSON.stringify(body),
145
+ signal: controller.signal,
146
+ });
147
+ const text = await res.text();
148
+ return { res, text };
149
+ }
150
+
151
+ try {
152
+ const { res: initRes, text: initText } = await postRpc(initBody);
153
+ if (!initRes.ok) {
154
+ throw new Error(`initialize HTTP ${initRes.status}: ${initText.slice(0, 160)}`);
155
+ }
156
+ parseMcpHttpFrame(initText);
157
+
158
+ const sessionId = initRes.headers.get('mcp-session-id')
159
+ || initRes.headers.get('Mcp-Session-Id')
160
+ || null;
161
+ const sessionHeaders = sessionId ? { 'mcp-session-id': sessionId } : {};
162
+
163
+ const { res: listRes, text: listText } = await postRpc(listBody, sessionHeaders);
164
+ if (!listRes.ok) {
165
+ throw new Error(`tools/list HTTP ${listRes.status}: ${listText.slice(0, 160)}`);
166
+ }
167
+ const frame = parseMcpHttpFrame(listText);
168
+ if (frame.error) {
169
+ throw new Error(frame.error.message || JSON.stringify(frame.error));
170
+ }
171
+ return frame.result?.tools || [];
172
+ } finally {
173
+ clearTimeout(timer);
174
+ }
175
+ }
176
+
177
+ // Probe the Construct memory stdio bridge, which forwards tools/list to cm.
178
+
179
+ export async function probeMemoryBridgeTools({
180
+ bridgePath,
181
+ backendUrl = 'http://127.0.0.1:8765/',
182
+ timeoutMs = DEFAULT_TIMEOUT_MS,
183
+ } = {}) {
184
+ return probeStdioMcpTools(process.execPath, [bridgePath], {
185
+ env: { CONSTRUCT_MEMORY_BRIDGE_URL: backendUrl },
186
+ timeoutMs,
187
+ });
188
+ }
@@ -8,15 +8,20 @@
8
8
  * 2. which external MCP servers are `enabled` in opencode.json — sync disables
9
9
  * the heavy ones for local-capable setups (they cannot be trimmed at runtime).
10
10
  * Holds the shared token-sizing helper and the external-server id list both
11
- * levers reason about.
11
+ * levers reason about, plus the owned-loop tool-budget filter that trims the
12
+ * Vercel AI SDK tool set to a compiled execution policy's allowed groups and
13
+ * schema cap (lib/models/execution-policy.mjs).
12
14
  */
13
15
 
16
+ import { getMeasuredHeavyExternalMcpTokenCost } from './external-schema-cost.mjs';
17
+
14
18
  const TOKENS_PER_CHAR = 0.25;
15
19
 
16
20
  // Heavy external MCP servers Construct registers globally. On a local-capable
17
- // setup these alone serialize ~12k tokens into every agent's window (including
18
- // the built-in Build/Plan agents) and cannot be filtered per request, so sync
19
- // disables them in opencode.json. construct-mcp's own knowledge_search /
21
+ // setup these serialize a measured ~37k tokens of tool schema into every agent's
22
+ // window (github alone ~30k as of 2026-06-22; see tests/fixtures/mcp-tool-schemas/
23
+ // and scripts/measure-external-mcp-schemas.mjs) and cannot be filtered per request,
24
+ // so sync disables them in opencode.json. construct-mcp's own knowledge_search /
20
25
  // memory_search cover the search/memory cases.
21
26
 
22
27
  export const HEAVY_EXTERNAL_MCP_IDS = ['context7', 'github', 'memory', 'sequential-thinking', 'playwright'];
@@ -29,6 +34,7 @@ export const LOCAL_SURFACE_MODES = ['auto', 'on', 'off'];
29
34
 
30
35
  export function isLocalModel(model) {
31
36
  const m = (model || "").toLowerCase();
37
+ if (m.startsWith("local/")) return true;
32
38
  return m.includes("ollama") || m.includes("localhost") || m.includes("127.0.0.1");
33
39
  }
34
40
 
@@ -51,3 +57,48 @@ export function estimateToolTokens(tools) {
51
57
  const arr = Array.isArray(tools) ? tools : Object.values(tools);
52
58
  return Math.round(JSON.stringify(arr).length * TOKENS_PER_CHAR);
53
59
  }
60
+
61
+ export { getMeasuredHeavyExternalMcpTokenCost };
62
+
63
+ // Maps the owned loop's agent tool names (apps/chat/engine/tools/registry.mjs)
64
+ // onto the execution-policy tool groups (lib/models/execution-policy.mjs) so a
65
+ // compiled policy can trim the serialized tool surface for a capability-
66
+ // constrained model. construct_tool is the single MCP gateway, so it stands in
67
+ // for the whole 'construct' group. An unrecognized name falls back to
68
+ // 'construct' — a group present in every tier — so an unclassified tool is never
69
+ // silently dropped from a constrained surface.
70
+
71
+ const AGENT_TOOL_GROUPS = Object.freeze({
72
+ read: 'read',
73
+ glob: 'search',
74
+ grep: 'search',
75
+ write: 'edit',
76
+ edit: 'edit',
77
+ shell: 'shell',
78
+ construct_tool: 'construct',
79
+ });
80
+
81
+ export function toolGroupForName(name) {
82
+ return AGENT_TOOL_GROUPS[name] || 'construct';
83
+ }
84
+
85
+ // Trim a tool set to the compiled policy's envelope: drop any tool whose group is
86
+ // outside allowedToolGroups, then cap the survivors at maxToolSchemas in stable
87
+ // insertion order. A null allowedToolGroups means "no group filter" and an
88
+ // unbounded cap means "no count limit", so the rich/hosted-direct envelope (all
89
+ // groups, cap 32) returns the input set unchanged.
90
+
91
+ export function applyToolBudget(tools, { allowedToolGroups = null, maxToolSchemas = Infinity } = {}) {
92
+ if (!tools || typeof tools !== 'object') return tools;
93
+ const allow = Array.isArray(allowedToolGroups) ? new Set(allowedToolGroups) : null;
94
+ const cap = Number.isFinite(maxToolSchemas) ? Math.max(0, Math.floor(maxToolSchemas)) : Infinity;
95
+ const out = {};
96
+ let kept = 0;
97
+ for (const [name, def] of Object.entries(tools)) {
98
+ if (allow && !allow.has(toolGroupForName(name))) continue;
99
+ if (kept >= cap) break;
100
+ out[name] = def;
101
+ kept += 1;
102
+ }
103
+ return out;
104
+ }
@@ -1,17 +1,31 @@
1
1
  /**
2
2
  * lib/mcp/tools/document.mjs — Document-handling MCP tools: extract text, ingest, infer schema, list schema artifacts.
3
3
  *
4
- * All functions are async-safe; extractDocumentText and listSchemaArtifactsTool are synchronous.
4
+ * All functions are async-safe; listSchemaArtifactsTool is synchronous.
5
5
  * Depends on lib/document-extract.mjs, lib/document-ingest.mjs, lib/schema-infer.mjs, lib/schema-artifact.mjs.
6
6
  */
7
7
  import { existsSync } from 'node:fs';
8
8
  import { resolve } from 'node:path';
9
- import { extractDocumentText as extractLocalDocumentText } from '../../document-extract.mjs';
9
+ import {
10
+ extractDocumentTextAsync,
11
+ extractDocumentTextNodeNative,
12
+ } from '../../document-extract.mjs';
10
13
  import { ingestDocuments } from '../../document-ingest.mjs';
11
14
  import { inferDocumentSchema, inferUnifiedSchema } from '../../schema-infer.mjs';
12
15
  import { writeSchemaArtifact, listSchemaArtifacts, SCHEMA_DIR } from '../../schema-artifact.mjs';
13
16
 
14
- export function extractDocumentText(args) {
17
+ async function extractViaIngestPipeline(filePath, { maxChars }) {
18
+ try {
19
+ return await extractDocumentTextNodeNative(filePath, { maxChars });
20
+ } catch (err) {
21
+ if (err.code === 'OFFICE_REQUIRES_DOCLING' || err.code === 'PDF_REQUIRES_DOCLING') {
22
+ return extractDocumentTextAsync(filePath, { maxChars });
23
+ }
24
+ throw err;
25
+ }
26
+ }
27
+
28
+ export async function extractDocumentText(args) {
15
29
  const filePath = resolve(String(args.file_path || ''));
16
30
  const maxChars = Number.isFinite(Number(args.max_chars)) && Number(args.max_chars) > 0
17
31
  ? Math.min(Number(args.max_chars), 200_000)
@@ -22,7 +36,7 @@ export function extractDocumentText(args) {
22
36
  }
23
37
 
24
38
  try {
25
- const extracted = extractLocalDocumentText(filePath, { maxChars });
39
+ const extracted = await extractViaIngestPipeline(filePath, { maxChars });
26
40
  return {
27
41
  file_path: extracted.filePath,
28
42
  extension: extracted.extension,
@@ -14,6 +14,7 @@ import { invokeWorkflow as invokeWorkflowCore } from '../../embedded-contract/wo
14
14
  import { buildCapabilityContract } from '../../embedded-contract/capability.mjs';
15
15
  import { resolveExecution as resolveExecutionCore } from '../../embedded-contract/execution.mjs';
16
16
  import { resolveInput } from '../../embedded-contract/ingest.mjs';
17
+ import { runArtifactWorkflow as runArtifactWorkflowCore } from '../../artifact-workflow.mjs';
17
18
 
18
19
  async function ingestArgs(args) {
19
20
  if (args.input || !args.file_path) return { input: args.input, ingestion: undefined };
@@ -91,3 +92,16 @@ export function executionResolve(args = {}) {
91
92
  };
92
93
  return wrapContractResult(resolveExecutionCore(request), { surface: 'mcp' });
93
94
  }
95
+
96
+ export function artifactWorkflow(args = {}) {
97
+ return wrapContractResult(runArtifactWorkflowCore({
98
+ input: args.input || args.request,
99
+ artifactType: args.artifact_type,
100
+ filePath: args.file_path,
101
+ format: args.format,
102
+ outputPath: args.output_path,
103
+ branding: args.branding,
104
+ overrides: args.overrides,
105
+ approvalMode: args.approval_mode,
106
+ }), { surface: 'mcp' });
107
+ }
@@ -8,9 +8,17 @@
8
8
  import { readFileSync, readdirSync, existsSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
10
  import { execFileSync } from 'node:child_process';
11
- import { routeRequest, routeRequestVerified, requiresExecutiveApproval, TERMINAL_STATES } from '../../orchestration-policy.mjs';
11
+ import {
12
+ buildConstructToOrchestratorPacket,
13
+ routeRequest,
14
+ routeRequestVerified,
15
+ requiresExecutiveApproval,
16
+ TERMINAL_STATES,
17
+ } from '../../orchestration-policy.mjs';
12
18
  import { buildTaskPacketFromIntent } from '../../workflow-state.mjs';
13
19
  import { logSkillCall } from '../../telemetry/skill-calls.mjs';
20
+ import { templateMetadata } from '../../artifact-manifest.mjs';
21
+ import { buildSpecialistCatalog } from '../../specialists/roster.mjs';
14
22
 
15
23
  export function listSkills({ ROOT_DIR }) {
16
24
  const skillsDir = join(ROOT_DIR, 'skills');
@@ -120,14 +128,21 @@ export function getTemplate(args, { ROOT_DIR }) {
120
128
  ];
121
129
  for (const { source, path: p } of candidates) {
122
130
  if (existsSync(p)) {
123
- return { source, path: p, content: readFileSync(p, 'utf8') };
131
+ const meta = templateMetadata(safe, { cwd, rootDir: ROOT_DIR });
132
+ return { source, path: p, content: readFileSync(p, 'utf8'), metadata: meta };
124
133
  }
125
134
  }
126
135
  return { error: `Template not found: ${safe}`, available: listTemplatesRaw({ ROOT_DIR }) };
127
136
  }
128
137
 
129
138
  export function listTemplates({ ROOT_DIR }) {
130
- return listTemplatesRaw({ ROOT_DIR });
139
+ const raw = listTemplatesRaw({ ROOT_DIR });
140
+ const cwd = process.cwd();
141
+ const metadata = {};
142
+ for (const name of [...new Set([...raw.shipped, ...raw.overridden])]) {
143
+ metadata[name] = templateMetadata(name, { cwd, rootDir: ROOT_DIR });
144
+ }
145
+ return { ...raw, metadata };
131
146
  }
132
147
 
133
148
  export async function agentContract(args = {}) {
@@ -171,6 +186,18 @@ export async function orchestrationPolicy(args) {
171
186
  const draftTask = route.track !== 'immediate'
172
187
  ? buildTaskPacketFromIntent(args?.request || '', { fileCount: args?.fileCount, moduleCount: args?.moduleCount })
173
188
  : null;
189
+ const handoffPacket = route.track !== 'immediate'
190
+ ? buildConstructToOrchestratorPacket({
191
+ request: args?.request || '',
192
+ route,
193
+ goal: args?.goal,
194
+ acceptanceCriteria: args?.acceptanceCriteria,
195
+ fileCount: args?.fileCount,
196
+ moduleCount: args?.moduleCount,
197
+ introducesContract: args?.introducesContract,
198
+ explicitDrive: args?.explicitDrive,
199
+ })
200
+ : null;
174
201
 
175
202
  // Per-specialist context packets: when the caller supplied a candidate
176
203
  // artifact list, build the role-filtered bundle each specialist should
@@ -249,9 +276,11 @@ export async function orchestrationPolicy(args) {
249
276
 
250
277
  return {
251
278
  ...route,
279
+ specialistCatalog: buildSpecialistCatalog(),
252
280
  approvalRequired,
253
281
  terminalStates: TERMINAL_STATES,
254
282
  draftTask,
283
+ handoffPacket,
255
284
  ...(contextPackets ? { contextPackets } : {}),
256
285
  ...(taskGraph ? { taskGraph } : {}),
257
286
  ...(intakeError ? { intakeError } : {}),
@@ -115,11 +115,23 @@ export function workflowValidate(args) {
115
115
  */
116
116
  export async function workflowContractValidate(args) {
117
117
  const { validateHandoff } = await import('../../contracts/validate.mjs');
118
+ const { enrichConstructOrchestratorHandoff } = await import('../../contracts/construct-handoff.mjs');
119
+ const contractId = args.id
120
+ ?? (args.producer === 'construct' && args.consumer === 'cx-orchestrator' ? 'construct-to-orchestrator' : null);
121
+ let artifact = args.artifact;
122
+ if (contractId === 'construct-to-orchestrator' && artifact && typeof artifact === 'object') {
123
+ artifact = enrichConstructOrchestratorHandoff(artifact, {
124
+ request: args.request,
125
+ fileCount: args.fileCount,
126
+ moduleCount: args.moduleCount,
127
+ introducesContract: args.introducesContract,
128
+ });
129
+ }
118
130
  return validateHandoff({
119
131
  producer: args.producer,
120
132
  consumer: args.consumer,
121
133
  id: args.id,
122
- artifact: args.artifact,
134
+ artifact,
123
135
  packet: args.packet,
124
136
  enforcement: args.enforcement || 'block',
125
137
  });
@@ -62,6 +62,7 @@ export async function pollFreeModels(apiKey) {
62
62
  (m) =>
63
63
  m?.pricing?.prompt === "0" && m?.pricing?.completion === "0"
64
64
  )
65
+ .filter(isTextOutputModel)
65
66
  .map((m) => ({
66
67
  id: `openrouter/${m.id}`,
67
68
  name: m.name ?? m.id,
@@ -72,6 +73,23 @@ export async function pollFreeModels(apiKey) {
72
73
  return models.sort((a, b) => score(b, "standard") - score(a, "standard"));
73
74
  }
74
75
 
76
+ // A coding/chat tier must emit text; image-generation models (output modality
77
+ // "image") are never valid tier defaults. OpenRouter reports modalities under
78
+ // `architecture`; absent metadata is treated as text so unknown models are not
79
+ // dropped. Vision models (image INPUT, text OUTPUT) remain eligible.
80
+
81
+ export function isTextOutputModel(model = {}) {
82
+ const arch = model.architecture || {};
83
+ let output = arch.output_modalities;
84
+ if (!Array.isArray(output) && typeof arch.modality === "string" && arch.modality.includes("->")) {
85
+ output = arch.modality.split("->")[1].split("+").map((s) => s.trim());
86
+ }
87
+ if (Array.isArray(output) && output.length) {
88
+ return output.includes("text") && !output.includes("image");
89
+ }
90
+ return true;
91
+ }
92
+
75
93
  /**
76
94
  * Score a model for a given tier (higher is better).
77
95
  * @param {object} model – Model object as returned by `pollFreeModels`.