@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
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import { existsSync, mkdirSync, readdirSync, statSync, writeFileSync } from 'node:fs';
11
11
  import { basename, dirname, extname, isAbsolute, join, parse, relative, resolve, sep } from 'node:path';
12
- import { extractDocumentText, extractDocumentTextAsync, extractDocumentTextNodeNative, extractDocumentMetadata, isExtractableDocumentPath } from './document-extract.mjs';
12
+ import { extractDocumentTextAsync, extractDocumentTextNodeNative, extractDocumentMetadata, isExtractableDocumentPath } from './document-extract.mjs';
13
13
  import { syncFileStateToSql } from './storage/sync.mjs';
14
14
  import { stampFrontmatter } from './doc-stamp.mjs';
15
15
  import { KNOWLEDGE_ROOT, KNOWLEDGE_SUBDIRS } from './knowledge/layout.mjs';
@@ -157,9 +157,9 @@ function resolveOutputPath(sourcePath, { cwd, outputPath, outputDir, target }) {
157
157
  // can stall (a large PDF, a slow model download) or fail. Without a bound the
158
158
  // caller hangs: the CLI never returns, and the ingest_document MCP tool blocks
159
159
  // until the client times out and surfaces an opaque error. Bound the whole
160
- // docling attempt and fall back to the legacy extractor so ingest always returns
161
- // a usable result, with the fallback recorded in droppedInfo. Override the bound
162
- // with CONSTRUCT_DOCLING_TIMEOUT_MS (0 disables the timeout).
160
+ // docling attempt and fall back to the node-native extractor so ingest always
161
+ // returns a usable result, with the fallback recorded in droppedInfo. Override
162
+ // the bound with CONSTRUCT_DOCLING_TIMEOUT_MS (0 disables the timeout).
163
163
 
164
164
  const DOCLING_TIMEOUT_MS = (() => {
165
165
  const raw = Number(process.env.CONSTRUCT_DOCLING_TIMEOUT_MS);
@@ -176,15 +176,15 @@ export function withTimeout(promise, ms, onTimeoutMessage) {
176
176
 
177
177
  /**
178
178
  * Run the high-fidelity (docling) extractor with a bound, falling back to the
179
- * legacy extractor on timeout or any failure so a caller never hangs. The
179
+ * node-native extractor on timeout or any failure so a caller never hangs. The
180
180
  * extractor functions are injectable for testing; production uses the real
181
- * docling-async and legacy-sync extractors.
181
+ * docling-async and node-native extractors.
182
182
  */
183
183
  export async function extractWithDoclingFallback(sourcePath, {
184
184
  maxChars = null,
185
185
  timeoutMs = DOCLING_TIMEOUT_MS,
186
186
  asyncExtract = extractDocumentTextAsync,
187
- syncExtract = extractDocumentText,
187
+ nodeNativeExtract = extractDocumentTextNodeNative,
188
188
  } = {}) {
189
189
  try {
190
190
  return await withTimeout(
@@ -193,24 +193,23 @@ export async function extractWithDoclingFallback(sourcePath, {
193
193
  `docling extraction exceeded ${Math.round(timeoutMs / 1000)}s`,
194
194
  );
195
195
  } catch (err) {
196
- const legacy = syncExtract(sourcePath, { maxChars });
197
- legacy.droppedInfo = [
198
- ...(legacy.droppedInfo ?? []),
196
+ const fallback = await nodeNativeExtract(sourcePath, { maxChars });
197
+ fallback.droppedInfo = [
198
+ ...(fallback.droppedInfo ?? []),
199
199
  {
200
200
  kind: 'docling-fallback',
201
201
  count: 1,
202
- reason: `High-fidelity (docling) extraction failed or timed out (${err.message}); used the legacy extractor. Re-run after \`construct install --with-docling\`, or pass --legacy-extractor to skip docling.`,
202
+ reason: `High-fidelity (docling) extraction failed or timed out (${err.message}); used the node-native extractor. Re-run after \`construct install --with-docling\`, or pass --fidelity=fast to skip docling.`,
203
203
  recoverable: true,
204
204
  },
205
205
  ];
206
- return legacy;
206
+ return fallback;
207
207
  }
208
208
  }
209
209
 
210
210
  // The default adapter path prefers the Node-native extractor (unpdf/mammoth) so
211
- // plain PDF/DOCX ingest needs no Python venv; it self-degrades to the sync
212
- // system-binary extractor when the optional dep is absent. High-fidelity stays
213
- // on docling, reserved for scanned/layout-critical and audio/video.
211
+ // plain PDF/DOCX ingest needs no Python venv; high-fidelity stays on docling for
212
+ // scanned/layout-critical content and formats without a Node backend.
214
213
 
215
214
  function extractViaAdapter(sourcePath, { highFidelity, maxChars }) {
216
215
  return highFidelity
@@ -354,6 +353,7 @@ export async function ingestDocuments(inputPaths, {
354
353
  droppedInfo: results.flatMap((r) => r.droppedInfo.map((d) => ({ ...d, sourcePath: r.sourcePath }))),
355
354
  droppedCount: totalDrops,
356
355
  highFidelity,
356
+ fidelity: highFidelity ? 'high' : 'fast',
357
357
  ingestion: {
358
358
  strategy: resolvedStrategy.strategy,
359
359
  fallback: resolvedStrategy.fallback,
@@ -373,7 +373,7 @@ export async function runIngestCli(argv = process.argv.slice(2), { cwd = process
373
373
  let target = 'knowledge/internal';
374
374
  let sync = false;
375
375
  let strict = false;
376
- let highFidelity = true;
376
+ let fidelity = 'high';
377
377
  let strategy = null;
378
378
  let orchestration = null;
379
379
 
@@ -384,21 +384,30 @@ export async function runIngestCli(argv = process.argv.slice(2), { cwd = process
384
384
  else if (arg.startsWith('--strategy=')) strategy = arg.split('=').slice(1).join('=');
385
385
  else if (arg === '--strategy') strategy = '';
386
386
  else if (arg.startsWith('--orchestration=')) orchestration = arg.split('=').slice(1).join('=');
387
- else if (arg === '--sync') sync = true;
387
+ else if (arg.startsWith('--fidelity=')) {
388
+ const value = arg.split('=').slice(1).join('=');
389
+ if (value !== 'fast' && value !== 'high') {
390
+ throw new Error(`Unsupported fidelity: ${value}. Valid values: fast, high`);
391
+ }
392
+ fidelity = value;
393
+ } else if (arg === '--sync') sync = true;
388
394
  else if (arg === '--strict') strict = true;
389
- else if (arg === '--legacy-extractor') highFidelity = false;
395
+ else if (arg === '--legacy-extractor') fidelity = 'fast';
390
396
  else if (strategy === '') strategy = arg;
391
397
  else inputs.push(arg);
392
398
  }
393
399
 
400
+ const highFidelity = fidelity === 'high';
401
+
394
402
  if (inputs.length === 0) {
395
403
  throw new Error(
396
404
  `Usage: construct ingest <file-or-dir> [more paths] [--out=FILE] [--out-dir=DIR] ` +
397
- `[--target=sibling|knowledge/<subdir>] [--strategy=adapter|provider] [--orchestration=prompt-only|orchestrated] [--sync] [--strict] [--legacy-extractor]\n` +
405
+ `[--target=sibling|knowledge/<subdir>] [--strategy=adapter|provider] [--orchestration=prompt-only|orchestrated] [--sync] [--strict] [--fidelity=fast|high]\n` +
398
406
  ` --strategy: extraction strategy override (adapter = local extractors; provider = configured provider/model)\n` +
399
407
  ` --orchestration: prompt-only (deterministic extraction) or orchestrated (engage the specialist chain)\n` +
400
408
  ` --strict: exit non-zero if any extraction drops occur\n` +
401
- ` --legacy-extractor: use the pre-docling regex extractor (lower fidelity)\n` +
409
+ ` --fidelity=fast|high: fast = node-native extractors; high = docling (default)\n` +
410
+ ` --legacy-extractor: deprecated alias for --fidelity=fast\n` +
402
411
  ` knowledge subdirs: ${KNOWLEDGE_SUBDIRS.join(', ')}`,
403
412
  );
404
413
  }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * lib/embed/auto-sources.mjs — resolve embed auto-discovery sources from project config.
3
+ *
4
+ * When embed.yaml is absent, builds provider source records from typed
5
+ * construct.config.json targets merged with legacy env lists. Falls back
6
+ * to broad provider defaults only when no targets are configured.
7
+ */
8
+
9
+ import { loadProjectConfig } from '../config/project-config.mjs';
10
+ import {
11
+ resolveEffectiveSourceTargetsFromConfig,
12
+ targetsToEmbedSources,
13
+ legacyEnvSourceTargets,
14
+ } from '../config/source-targets.mjs';
15
+
16
+ export function resolveAutoEmbedSources({ cwd = process.cwd(), env = process.env, registry }) {
17
+ const { config } = loadProjectConfig(cwd, env);
18
+ const targets = resolveEffectiveSourceTargetsFromConfig(config, env);
19
+
20
+ if (targets.length === 0) {
21
+ return registry.autoSources(env);
22
+ }
23
+
24
+ const configuredProviders = new Set(targets.map((t) => t.provider));
25
+ const hasLegacyForUnconfigured = (provider) =>
26
+ legacyEnvSourceTargets(env).some((t) => t.provider === provider);
27
+
28
+ const sources = targetsToEmbedSources(targets);
29
+
30
+ for (const name of registry.names()) {
31
+ if (configuredProviders.has(name)) continue;
32
+ if (hasLegacyForUnconfigured(name)) continue;
33
+ const instance = registry.get(name);
34
+ if (typeof instance?.defaultSources !== 'function') continue;
35
+ }
36
+
37
+ const seen = new Set();
38
+ return sources.filter((s) => {
39
+ const key = `${s.provider}:${(s.refs ?? []).join(',')}:${JSON.stringify(s.repos ?? s.project ?? s.team ?? s.channels ?? '')}`;
40
+ if (seen.has(key)) return false;
41
+ seen.add(key);
42
+ return true;
43
+ });
44
+ }
package/lib/embed/cli.mjs CHANGED
@@ -392,16 +392,17 @@ async function cmdEmbedSnapshot(args, { homeDir = os.homedir() } = {}) {
392
392
  const { loadEmbedConfig, EMPTY_CONFIG } = await import('./config.mjs');
393
393
  const { SnapshotEngine, renderMarkdown } = await import('./snapshot.mjs');
394
394
  const { ProviderRegistry } = await import('./providers/registry.mjs');
395
- const { loadConstructEnv } = await import('../env-config.mjs');
395
+ const { prepareConstructEnv } = await import('../runtime-env.mjs');
396
396
 
397
- const env = loadConstructEnv();
397
+ const env = prepareConstructEnv();
398
398
  const registry = await ProviderRegistry.fromEnv(env);
399
399
 
400
400
  let cfg;
401
401
  if (fs.existsSync(configPath)) {
402
402
  cfg = loadEmbedConfig(configPath);
403
403
  } else {
404
- cfg = { ...EMPTY_CONFIG, sources: registry.autoSources(env) };
404
+ const { resolveAutoEmbedSources } = await import('./auto-sources.mjs');
405
+ cfg = { ...EMPTY_CONFIG, sources: resolveAutoEmbedSources({ cwd: process.cwd(), env, registry }) };
405
406
  }
406
407
 
407
408
  if (!cfg.sources.length) {
@@ -58,54 +58,11 @@ function logCatch(source, err) {
58
58
  }
59
59
 
60
60
  /**
61
- * Resolve an LLM API key from multiple sources: env config.env ~/.env shell rc.
62
- * Supports 1Password op:// refs in .zshrc or other shell config files for the
63
- * telemetry-generation probe.
61
+ * Resolve an LLM API key via the shared secret resolver (cached op read per process).
64
62
  */
65
63
  async function resolveLLMKey(varName, env) {
66
- if (env[varName] && typeof env[varName] === 'string' && env[varName].length > 0) return env[varName];
67
- const homeDir = homedir();
68
- const files = [join(homeDir, '.construct', 'config.env'), join(homeDir, '.env')];
69
- for (const file of files) {
70
- try {
71
- if (!existsSync(file)) continue;
72
- const content = readFileSync(file, 'utf8');
73
- const m = content.match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
74
- if (m && m[1]) return m[1].trim();
75
- } catch { /* skip */ }
76
- }
77
- // Check shell rc files for exports and command substitutions (including op:// refs)
78
- const rcs = [join(homeDir, '.zshrc'), join(homeDir, '.bashrc'), join(homeDir, '.bash_profile'), join(homeDir, '.profile')];
79
- const { spawnSync } = await import('child_process');
80
- for (const rc of rcs) {
81
- if (!existsSync(rc)) continue;
82
- try {
83
- const content = readFileSync(rc, 'utf8');
84
-
85
- // op:// ref via 1Password CLI
86
- const opRe = new RegExp(`export\\s+${varName}=["']?\\$\\(op read '([^']+)'\\)["']?`, 'm');
87
- const opMatch = content.match(opRe);
88
- if (opMatch) {
89
- try {
90
- const r = spawnSync('op', ['read', opMatch[1]], { encoding: 'utf8', timeout: 5000 });
91
- if (r.status === 0 && r.stdout?.trim()) return r.stdout.trim();
92
- } catch { /* op read failed */ }
93
- }
94
-
95
- // Source the rc file and echo — handles any $(...) command substitution
96
- const anyExportRe = new RegExp(`^\\s*export\\s+${varName}=`, 'm');
97
- if (anyExportRe.test(content)) {
98
- try {
99
- const r = spawnSync('zsh', ['-c', `source "${rc}" 2>/dev/null; echo "\${${varName}}"`], { encoding: 'utf8', timeout: 5000 });
100
- if (r.status === 0 && r.stdout?.trim()) {
101
- const val = r.stdout.trim().split('\n').pop()?.trim();
102
- if (val && !val.includes('ERROR') && !val.includes('op read') && !val.startsWith('$(')) return val;
103
- }
104
- } catch { /* shell eval failed */ }
105
- }
106
- } catch { /* skip */ }
107
- }
108
- return null;
64
+ const { resolveSecret } = await import('../providers/secret-resolver.mjs');
65
+ return resolveSecret(varName, { env, cwd: process.cwd() });
109
66
  }
110
67
 
111
68
  // ─── Root dir resolution ─────────────────────────────────────────────────────
@@ -614,6 +571,21 @@ export class EmbedDaemon {
614
571
  { runImmediately: true },
615
572
  );
616
573
 
574
+ // ── Job 7: rss-cap ────────────────────────────────────────────────────────
575
+ const { memoryCapMbFor, rssOverCap, currentRssMb } = await import('../resources/process-budget.mjs');
576
+ const rssCapMb = memoryCapMbFor('embed-daemon', this.#rootDir, this.#env);
577
+ this.#scheduler.register(
578
+ 'rss-cap',
579
+ 60_000,
580
+ async () => {
581
+ if (!rssOverCap(rssCapMb)) return;
582
+ process.stderr.write(
583
+ `[embed] RSS ${Math.round(currentRssMb())}MB exceeds cap ${rssCapMb}MB — stopping daemon\n`,
584
+ );
585
+ this.stop();
586
+ },
587
+ );
588
+
617
589
  // ── Job 0: telemetry-setup ───────────────────────────────────────────────────
618
590
  // On startup — ensure annotation queues and eval configs exist (idempotent)
619
591
  this.#scheduler.register(
@@ -19,6 +19,11 @@ import { homedir } from 'node:os';
19
19
  import { join } from 'node:path';
20
20
  import { loadConstructEnv } from '../env-config.mjs';
21
21
  import { addObservation } from '../observation-store.mjs';
22
+ import { loadProjectConfig } from '../config/project-config.mjs';
23
+ import {
24
+ resolveEffectiveSourceTargetsFromConfig,
25
+ resolveKnownSourcesFromTargets,
26
+ } from '../config/source-targets.mjs';
22
27
 
23
28
  // ─── Self-query detection ────────────────────────────────────────────────────
24
29
 
@@ -62,7 +67,13 @@ function isSelfQuery(query) {
62
67
  * @param {object} env
63
68
  * @returns {{ id: string, provider: string, ref: string, display: string }[]}
64
69
  */
65
- export function resolveKnownSources(env = process.env) {
70
+ export function resolveKnownSources(env = process.env, cwd = process.cwd()) {
71
+ const { config } = loadProjectConfig(cwd, env);
72
+ const targets = resolveEffectiveSourceTargetsFromConfig(config, env);
73
+ if (targets.length > 0) {
74
+ return resolveKnownSourcesFromTargets(targets);
75
+ }
76
+
66
77
  const sources = [];
67
78
 
68
79
  // GitHub repos
@@ -112,8 +123,8 @@ export function resolveKnownSources(env = process.env) {
112
123
  * @param {object} env
113
124
  * @returns {{ provider: string, ref: string, display: string } | null}
114
125
  */
115
- export function matchSourceFromQuery(query, env = process.env) {
116
- const sources = resolveKnownSources(env);
126
+ export function matchSourceFromQuery(query, env = process.env, cwd = process.cwd()) {
127
+ const sources = resolveKnownSources(env, cwd);
117
128
  const q = query.toLowerCase();
118
129
 
119
130
  // Exact or substring match — longest id wins
@@ -138,13 +149,12 @@ export function matchSourceFromQuery(query, env = process.env) {
138
149
  * @param {object} [opts.env] - Env override
139
150
  * @returns {Promise<DemandFetchResult>}
140
151
  */
141
- export async function demandFetch({ query, rootDir, env } = {}) {
152
+ export async function demandFetch({ query, rootDir, env, cwd } = {}) {
142
153
  const resolvedEnv = env ?? loadConstructEnv();
143
154
  const root = rootDir ?? (resolvedEnv.CX_DATA_DIR?.trim() || homedir());
155
+ const projectRoot = cwd ?? root;
144
156
 
145
- // Provider match takes priority: if the query names a configured repo/project,
146
- // always fetch from that provider — even if isSelfQuery() also fires.
147
- const match = matchSourceFromQuery(query, resolvedEnv);
157
+ const match = matchSourceFromQuery(query, resolvedEnv, projectRoot);
148
158
 
149
159
  // Self-referential queries about Construct itself → route to knowledge_search,
150
160
  // but only when no external provider source was matched.
@@ -168,8 +178,7 @@ export async function demandFetch({ query, rootDir, env } = {}) {
168
178
  }
169
179
 
170
180
  if (!match) {
171
- // No specific source match and not a self-query — fetch from all configured providers
172
- return demandFetchAll({ query, rootDir: root, env: resolvedEnv });
181
+ return demandFetchAll({ query, rootDir: root, env: resolvedEnv, cwd: projectRoot });
173
182
  }
174
183
 
175
184
  // Dynamically import provider registry to avoid loading all providers at module init
@@ -188,7 +197,7 @@ export async function demandFetch({ query, rootDir, env } = {}) {
188
197
  }
189
198
 
190
199
  // Build read calls for the matched source and execute them
191
- const readCalls = buildReadCalls(match, resolvedEnv);
200
+ const readCalls = buildReadCalls(match, resolvedEnv, projectRoot);
192
201
  if (!readCalls.length) {
193
202
  return {
194
203
  ok: false,
@@ -281,23 +290,32 @@ export async function demandFetch({ query, rootDir, env } = {}) {
281
290
  * @param {object} [opts.env] - Resolved env
282
291
  * @returns {Promise<DemandFetchResult>}
283
292
  */
284
- async function demandFetchAll({ query, rootDir, env } = {}) {
293
+ async function demandFetchAll({ query, rootDir, env, cwd } = {}) {
285
294
  const { ProviderRegistry } = await import('./providers/registry.mjs');
286
295
  const registry = await ProviderRegistry.fromEnv(env);
287
- const providerNames = registry.names();
296
+ const projectRoot = cwd ?? rootDir ?? process.cwd();
297
+ const { config } = loadProjectConfig(projectRoot, env);
298
+ const targets = resolveEffectiveSourceTargetsFromConfig(config, env);
299
+ const scoped = targets.length > 0;
300
+
301
+ const providerNames = scoped
302
+ ? [...new Set(targets.map((t) => t.provider))].filter((name) => registry.get(name))
303
+ : registry.names();
288
304
 
289
305
  if (!providerNames.length) {
290
306
  return {
291
307
  ok: false,
292
308
  reason: 'no_providers',
293
- message: 'No provider credentials configured in config.env',
309
+ message: scoped
310
+ ? 'No provider credentials configured for the declared source targets'
311
+ : 'No provider credentials configured in config.env',
294
312
  items: [],
295
313
  };
296
314
  }
297
315
 
298
316
  const allItems = [];
299
317
  const errors = [];
300
- const sources = resolveKnownSources(env);
318
+ const sources = resolveKnownSources(env, projectRoot);
301
319
 
302
320
  // Group known sources by provider so we can batch-fetch per-provider
303
321
  const byProvider = new Map();
@@ -306,11 +324,16 @@ async function demandFetchAll({ query, rootDir, env } = {}) {
306
324
  byProvider.get(src.provider).push(src);
307
325
  }
308
326
 
309
- // Also include providers that have no explicit sources (Jira, Linear default fetch)
310
327
  for (const name of providerNames) {
311
328
  if (!byProvider.has(name)) byProvider.set(name, []);
312
329
  }
313
330
 
331
+ if (scoped) {
332
+ for (const key of [...byProvider.keys()]) {
333
+ if (!providerNames.includes(key)) byProvider.delete(key);
334
+ }
335
+ }
336
+
314
337
  for (const [providerName, provSources] of byProvider) {
315
338
  const provider = registry.get(providerName);
316
339
  if (!provider) continue;
@@ -318,10 +341,12 @@ async function demandFetchAll({ query, rootDir, env } = {}) {
318
341
  // Build read calls — one set per known source, or a default if none listed
319
342
  const matchList = provSources.length > 0
320
343
  ? provSources
321
- : [{ provider: providerName, ref: null, display: providerName }];
344
+ : (scoped ? [] : [{ provider: providerName, ref: null, display: providerName }]);
345
+
346
+ if (!matchList.length) continue;
322
347
 
323
348
  for (const src of matchList) {
324
- const readCalls = buildReadCalls(src, env);
349
+ const readCalls = buildReadCalls(src, env, projectRoot);
325
350
  if (!readCalls.length) continue;
326
351
  for (const { ref, opts, display } of readCalls) {
327
352
  try {
@@ -384,7 +409,7 @@ async function demandFetchAll({ query, rootDir, env } = {}) {
384
409
  * @param {object} env
385
410
  * @returns {{ ref: string, opts: object, display: string }[]}
386
411
  */
387
- function buildReadCalls(match, env) {
412
+ function buildReadCalls(match, env, cwd = process.cwd()) {
388
413
  if (match.provider === 'github') {
389
414
  const opts = { repos: [match.ref], limit: 25 };
390
415
  return [
@@ -397,7 +422,13 @@ function buildReadCalls(match, env) {
397
422
  ];
398
423
  }
399
424
  if (match.provider === 'jira') {
400
- const projects = (env.JIRA_PROJECTS ?? '').split(',').map(p => p.trim()).filter(Boolean);
425
+ const { config } = loadProjectConfig(cwd, env);
426
+ const targets = resolveEffectiveSourceTargetsFromConfig(config, env)
427
+ .filter((t) => t.provider === 'jira')
428
+ .map((t) => t.selector.project);
429
+ const projects = targets.length
430
+ ? targets
431
+ : (env.JIRA_PROJECTS ?? '').split(',').map((p) => p.trim()).filter(Boolean);
401
432
  const recencyDays = parseInt(env.JIRA_FETCH_RECENCY_DAYS, 10) || 30;
402
433
 
403
434
  let jql;
@@ -148,6 +148,12 @@ export function resolveInboxDirs(rootDir, env = process.env) {
148
148
  const config = loadIntakeConfig(rootDir, env);
149
149
  const dirs = [];
150
150
 
151
+ if (config.includeRootInbox ?? config.includeArchetypeInbox) {
152
+ const rootInbox = join(rootDir, ARCHETYPE_INBOX_SUBDIR);
153
+ mkdirSync(rootInbox, { recursive: true });
154
+ dirs.push(rootInbox);
155
+ }
156
+
151
157
  if (config.includeProjectInbox) {
152
158
  const projectInbox = join(rootDir, DEFAULT_INBOX_SUBDIR);
153
159
  mkdirSync(projectInbox, { recursive: true });
@@ -159,16 +165,6 @@ export function resolveInboxDirs(rootDir, env = process.env) {
159
165
  if (existsSync(docsIntake)) dirs.push(docsIntake);
160
166
  }
161
167
 
162
- // Archetype inbox is a profile-driven drop zone for raw source files
163
- // (capabilities.intake.inbox). Off by default; init flips it on when the
164
- // active profile declares the capability so non-archetype projects keep
165
- // their pre-Piece-C behavior.
166
-
167
- if (config.includeArchetypeInbox) {
168
- const archetypeInbox = join(rootDir, ARCHETYPE_INBOX_SUBDIR);
169
- if (existsSync(archetypeInbox)) dirs.push(archetypeInbox);
170
- }
171
-
172
168
  for (const candidate of config.parentDirs) {
173
169
  if (!candidate) continue;
174
170
  if (!existsSync(candidate)) continue;
@@ -8,7 +8,7 @@
8
8
 
9
9
  import os from 'node:os';
10
10
  import path from 'node:path';
11
- import { loadConstructEnv } from '../env-config.mjs';
11
+ import { prepareConstructEnv } from '../runtime-env.mjs';
12
12
  import { EmbedDaemon } from './daemon.mjs';
13
13
 
14
14
  function parseArgv(argv) {
@@ -19,7 +19,7 @@ function parseArgv(argv) {
19
19
  }
20
20
 
21
21
  const configPath = parseArgv(process.argv.slice(2));
22
- const env = loadConstructEnv();
22
+ const env = prepareConstructEnv();
23
23
 
24
24
  // If no config file, build an auto-discovered config inline and inject it
25
25
  let daemonOpts = { env };
@@ -28,8 +28,9 @@ if (configPath) {
28
28
  } else {
29
29
  const { ProviderRegistry } = await import('./providers/registry.mjs');
30
30
  const { EMPTY_CONFIG } = await import('./config.mjs');
31
+ const { resolveAutoEmbedSources } = await import('./auto-sources.mjs');
31
32
  const registry = await ProviderRegistry.fromEnv(env);
32
- const sources = registry.autoSources(env);
33
+ const sources = resolveAutoEmbedSources({ cwd: process.cwd(), env, registry });
33
34
  daemonOpts.registry = registry;
34
35
  daemonOpts.config = {
35
36
  ...EMPTY_CONFIG,
@@ -13,6 +13,7 @@ import { recommendPlan as recommendPlanCore } from './triage.mjs';
13
13
  import { invokeWorkflow as invokeWorkflowCore } from './workflow-invoke.mjs';
14
14
  import { buildCapabilityContract } from './capability.mjs';
15
15
  import { resolveExecution as resolveExecutionCore } from './execution.mjs';
16
+ import { planArtifactWorkflow as planArtifactWorkflowCore, runArtifactWorkflow as runArtifactWorkflowCore } from '../artifact-workflow.mjs';
16
17
 
17
18
  export { CONTRACT_VERSION, MIN_CLIENT_CONTRACT_VERSION, isClientCompatible } from './contract-version.mjs';
18
19
  export { APPROVAL_MODES } from './audit.mjs';
@@ -85,3 +86,13 @@ export function describeCapabilities({ env, cwd, rootDir } = {}) {
85
86
  export function resolveExecution(request = {}, { env, cwd, registryPath } = {}) {
86
87
  return wrapContractResult(resolveExecutionCore(request, { env, cwd, registryPath }), { surface: 'sdk', env, cwd });
87
88
  }
89
+
90
+ /** Plan a manifest-backed artifact workflow without executing specialists. */
91
+ export function planArtifactWorkflow(request = {}, { env, cwd, rootDir } = {}) {
92
+ return wrapContractResult(planArtifactWorkflowCore(request, { cwd, rootDir }), { surface: 'sdk', env, cwd });
93
+ }
94
+
95
+ /** Run only locally observable artifact steps and return truthful provenance. */
96
+ export function runArtifactWorkflow(request = {}, { env, cwd, rootDir } = {}) {
97
+ return wrapContractResult(runArtifactWorkflowCore(request, { cwd, rootDir }), { surface: 'sdk', env, cwd });
98
+ }
@@ -61,15 +61,15 @@ function matchCapabilities(modelId, requested, warnings) {
61
61
  return matched;
62
62
  }
63
63
 
64
- function familyByProvider(hostProvider, env) {
64
+ function familyByProvider(hostProvider, env, allowAmbient) {
65
65
  if (!hostProvider || typeof hostProvider !== 'string') return null;
66
- const families = listModelFamilies({ env });
66
+ const families = listModelFamilies({ env, allowAmbient });
67
67
  return families.find((f) => f.id === hostProvider)
68
68
  || families.find((f) => f.id === `openrouter-${hostProvider}`)
69
69
  || null;
70
70
  }
71
71
 
72
- function success({ selectedModel, family, tier, resolutionSource, fallbackReason = null, tierSource = null, capabilities, warnings, env }) {
72
+ function success({ selectedModel, family, tier, resolutionSource, fallbackReason = null, tierSource = null, capabilities, warnings, env, allowAmbient }) {
73
73
  return {
74
74
  selectedModel,
75
75
  selectedProvider: family ? family.id : null,
@@ -123,28 +123,28 @@ function configError({ tier, reason, remediation, warnings }) {
123
123
  * @param {string} [opts.registryPath]
124
124
  * @returns {object}
125
125
  */
126
- export function resolveEmbeddedModel(request = {}, { env = process.env, registryPath = null } = {}) {
126
+ export function resolveEmbeddedModel(request = {}, { env = process.env, registryPath = null, allowAmbient = env === process.env } = {}) {
127
127
  const { workflowType, requestedTier, hostModel, hostProvider, capabilities, allowCrossProviderFallback = false } = request;
128
128
  const tier = resolveTier({ requestedTier, workflowType });
129
129
  const warnings = [];
130
130
 
131
- const hostFamily = hostModel ? describeModelFamily(hostModel, { env }) : null;
131
+ const hostFamily = hostModel ? describeModelFamily(hostModel, { env, allowAmbient }) : null;
132
132
 
133
133
  if (hostModel && hostFamily) {
134
- return success({ selectedModel: hostModel, family: hostFamily, tier, resolutionSource: 'host-model', capabilities, warnings, env });
134
+ return success({ selectedModel: hostModel, family: hostFamily, tier, resolutionSource: 'host-model', capabilities, warnings, env, allowAmbient });
135
135
  }
136
136
  if (hostModel && !hostFamily) {
137
137
  warnings.push(`Host model "${hostModel}" is not a recognized provider family; attempting same-family or tier fallback.`);
138
138
  }
139
139
 
140
- const family = hostFamily || familyByProvider(hostProvider, env);
140
+ const family = hostFamily || familyByProvider(hostProvider, env, allowAmbient);
141
141
  if (family) {
142
142
  const model = family.tiers[tier];
143
143
  if (model) {
144
144
  const fallbackReason = hostModel
145
145
  ? `Exact host model unavailable; resolved the ${tier} model in the same provider family (${family.id}).`
146
146
  : `Resolved the ${tier} model within the host provider family (${family.id}).`;
147
- return success({ selectedModel: model, family, tier, resolutionSource: 'same-family-fallback', fallbackReason, capabilities, warnings, env });
147
+ return success({ selectedModel: model, family, tier, resolutionSource: 'same-family-fallback', fallbackReason, capabilities, warnings, env, allowAmbient });
148
148
  }
149
149
  }
150
150
 
@@ -166,7 +166,7 @@ export function resolveEmbeddedModel(request = {}, { env = process.env, registry
166
166
  : 'No host context supplied; used the Construct tier default.';
167
167
  return success({
168
168
  selectedModel: defaultModel,
169
- family: describeModelFamily(defaultModel, { env }),
169
+ family: describeModelFamily(defaultModel, { env, allowAmbient }),
170
170
  tier,
171
171
  resolutionSource: 'tier-default',
172
172
  fallbackReason,
@@ -174,6 +174,7 @@ export function resolveEmbeddedModel(request = {}, { env = process.env, registry
174
174
  capabilities,
175
175
  warnings,
176
176
  env,
177
+ allowAmbient,
177
178
  });
178
179
  }
179
180
 
@@ -8,6 +8,8 @@
8
8
  import fs from 'node:fs';
9
9
  import os from 'node:os';
10
10
  import path from 'node:path';
11
+ import { API_KEY_CREDENTIALS } from './providers/credential-catalog.mjs';
12
+ import { extractOpRef } from './providers/secret-resolver.mjs';
11
13
 
12
14
  export function parseEnvFile(filePath) {
13
15
  if (!filePath || !fs.existsSync(filePath)) return {};
@@ -88,6 +90,22 @@ const ENV_SHADOW_WARN = new Set([
88
90
  'CONSTRUCT_DEPLOYMENT_MODE',
89
91
  ]);
90
92
 
93
+ const CREDENTIAL_ENV_KEYS = new Set(
94
+ API_KEY_CREDENTIALS.flatMap((entry) => entry.envVars),
95
+ );
96
+
97
+ function isOpReference(value) {
98
+ return Boolean(extractOpRef(value));
99
+ }
100
+
101
+ function preferInjectedCredential(envValue, fileValue) {
102
+ return Boolean(
103
+ envValue
104
+ && !isOpReference(envValue)
105
+ && isOpReference(fileValue),
106
+ );
107
+ }
108
+
91
109
  export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = process.env, warn = true } = {}) {
92
110
  const rootEnv = rootDir ? parseEnvFile(path.join(rootDir, '.env')) : {};
93
111
  const userEnv = parseEnvFile(getUserEnvPath(homeDir));
@@ -103,6 +121,7 @@ export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = proces
103
121
  // silently and no user action is needed.
104
122
  if (!env[key]) continue;
105
123
  if (key in fileEnv && fileEnv[key] !== env[key]) {
124
+ if (preferInjectedCredential(env[key], fileEnv[key])) continue;
106
125
  process.stderr.write(
107
126
  `[construct] WARNING: process.env.${key} (${env[key].slice(0, 6)}…) ` +
108
127
  `differs from config.env value (${fileEnv[key].slice(0, 6)}…). ` +
@@ -115,7 +134,14 @@ export function loadConstructEnv({ rootDir, homeDir = os.homedir(), env = proces
115
134
 
116
135
  // process.env is the base; config.env (file) values override shell values
117
136
  // so that credentials set in the dashboard always win over stale shell exports.
137
+ // Materialized credentials from `op run` (or an explicit export) win over stored
138
+ // op:// references so one 1Password auth per invocation is enough.
118
139
  const merged = { ...env, ...fileEnv };
140
+ for (const key of CREDENTIAL_ENV_KEYS) {
141
+ if (preferInjectedCredential(env[key], fileEnv[key])) {
142
+ merged[key] = env[key];
143
+ }
144
+ }
119
145
  const databaseUrl = resolveDatabaseUrl(merged);
120
146
  if (databaseUrl && !cleanEnvValue(merged.DATABASE_URL)) {
121
147
  merged.DATABASE_URL = databaseUrl;