@geraldmaron/construct 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -1,20 +1,32 @@
1
1
  /**
2
2
  * lib/host-disposition.mjs — single source of truth for the disposition of
3
- * every artifact Construct creates in a host project (ADR-0027 §1).
3
+ * every artifact Construct creates in a host project (ADR-0027 §1, extended
4
+ * by ADR-0066 §"Decision" for the machine-scoped heavy-state split).
4
5
  *
5
6
  * Scope model (see docs/guides/concepts/project-scopes.md):
6
- * templates/, docs/ — package/repo durable sources (committed)
7
- * .construct/ — per-project launcher (ignored)
8
- * .cx/ — per-project runtime: intake, oracle, observations, demos (ignored)
9
- * ~/.construct/ user credentials and doctor state
10
- * ~/.cx/ — user-scope telemetry fallback
7
+ * templates/, docs/ — package/repo durable sources (committed)
8
+ * .construct/ — per-project launcher (ignored)
9
+ * .cx/ — per-project config-layer + small runtime
10
+ * markers: context.md/json, workflow.json,
11
+ * intake, oracle, observations, knowledge,
12
+ * graph, demos (ignored)
13
+ * ~/.construct/projects/<key>/ — per-project machine-scoped heavy state:
14
+ * traces, orchestration runs, worker logs,
15
+ * the LanceDB vector index, docling/whisper
16
+ * bootstrap venvs (ADR-0066)
17
+ * ~/.construct/ — user credentials and doctor state
18
+ * ~/.cx/ — user-scope telemetry fallback
11
19
  *
12
20
  * `IGNORED_PATTERNS` enumerates machine-specific, sync-regenerated artifacts
13
21
  * that must never be committed; the init `.gitignore` writer and the
14
22
  * `gitignore-coverage` reconciliation both read from here, and uninstall /
15
23
  * adapter-prune consume `ADAPTER_DIRS`. Keep this 1:1 with the host-relevant
16
24
  * subset of Construct's own repo .gitignore so forward-fix and backward-repair
17
- * never diverge.
25
+ * never diverge. `.cx/` covers both the config-layer files and the small
26
+ * runtime markers that remain project-local post-ADR-0066 — the heavy
27
+ * categories that moved to `~/.construct/projects/<key>/` are resolved
28
+ * through `lib/state-root.mjs`, not listed here, since they were never
29
+ * project-relative paths to gitignore in the first place.
18
30
  */
19
31
 
20
32
  import fs from 'node:fs';
@@ -41,13 +53,22 @@ export const IGNORED_PATTERNS = [
41
53
  '.construct/',
42
54
  '.mcp.json',
43
55
  '.github/prompts/',
44
- '.github/copilot-instructions.md',
56
+ '.github/agents/',
45
57
  'config.env',
46
58
  'embed.yaml',
47
59
  'plan.md',
48
60
  'inbox/',
49
61
  ];
50
62
 
63
+ // User-owned files Construct mutates only via replaceManagedBlock (ADR-0027 §2).
64
+ // These are durable repo source, not ignored cache — they must NOT appear in
65
+ // IGNORED_PATTERNS, but they still need an explicit disposition so consumers
66
+ // know not to gitignore or delete them on uninstall.
67
+
68
+ export const MANAGED_PATTERNS = [
69
+ '.github/copilot-instructions.md',
70
+ ];
71
+
51
72
  // A pattern counts as already-ignored when an existing line matches it exactly,
52
73
  // matches its bare/slashed form, or is a broader `*` / `**` catch-all.
53
74
 
@@ -0,0 +1,92 @@
1
+ /**
2
+ * lib/identity.mjs — actor and service identity for the control plane.
3
+ *
4
+ * Resolves human vs service identity from request headers (X-Construct-Actor-Id,
5
+ * -Tenant-Id, -Session-Id) or env fallback, and validates it per deployment mode
6
+ * (team/enterprise reject implicit-solo identity). Feeds policy, approval, and audit.
7
+ */
8
+
9
+ import { getDeploymentMode } from './deployment-mode.mjs';
10
+ import os from 'node:os';
11
+
12
+ export class IdentityError extends Error {
13
+ constructor(message) { super(message); this.name = 'IdentityError'; }
14
+ }
15
+
16
+ export function serviceIdentity({ serviceId, deploymentMode, role, source } = {}) {
17
+ return {
18
+ type: 'service',
19
+ serviceId: serviceId || 'construct-mcp',
20
+ deploymentMode: deploymentMode || 'solo',
21
+ pid: process.pid,
22
+ role: role || null,
23
+ source: source || 'implicit-solo',
24
+ };
25
+ }
26
+
27
+ export function humanIdentity({ userId, tenantId, sessionId, role, source } = {}) {
28
+ return {
29
+ type: 'human',
30
+ userId: userId || null,
31
+ tenantId: tenantId || null,
32
+ sessionId: sessionId || null,
33
+ role: role || null,
34
+ source: source || 'headers',
35
+ };
36
+ }
37
+
38
+ export function resolveIdentity(params = {}, { env = process.env, cwd } = {}) {
39
+ const mode = getDeploymentMode(env, { cwd });
40
+ const meta = params?._meta || {};
41
+
42
+ const actorId = meta['X-Construct-Actor-Id'] || '';
43
+ const tenantId = meta['X-Construct-Tenant-Id'] || '';
44
+ const sessionId = meta['X-Construct-Session-Id'] || '';
45
+ const hasHeaders = Boolean(actorId);
46
+
47
+ if ((mode === 'team' || mode === 'enterprise') && !hasHeaders) {
48
+ const role = env.CONSTRUCT_ROLE;
49
+ if (role) {
50
+ return serviceIdentity({ serviceId: `role:${role}`, deploymentMode: mode, role, source: 'env-fallback' });
51
+ }
52
+ throw new IdentityError(`Missing actor identity in ${mode} mode. Set X-Construct-Actor-Id header or CONSTRUCT_ROLE env var.`);
53
+ }
54
+
55
+ if (hasHeaders) {
56
+ const isHuman = actorId.includes('@');
57
+ if (isHuman) {
58
+ return humanIdentity({ userId: actorId, tenantId: tenantId || null, sessionId: sessionId || null, role: env.CONSTRUCT_ROLE || null, source: 'headers' });
59
+ }
60
+ return serviceIdentity({ serviceId: actorId, deploymentMode: mode, role: env.CONSTRUCT_ROLE || null, source: 'headers' });
61
+ }
62
+
63
+ return serviceIdentity({ serviceId: `local-${os.hostname()}`, deploymentMode: mode, role: env.CONSTRUCT_ROLE || null, source: 'implicit-solo' });
64
+ }
65
+
66
+ export function identityRole(identity) {
67
+ return identity?.role || null;
68
+ }
69
+
70
+ export function validateIdentity(identity, mode) {
71
+ if (!identity) return 'Identity is required';
72
+ if (!identity.type) return 'Identity type is required';
73
+ if (identity.type === 'human' && !identity.userId) return 'Human identity requires userId';
74
+ if (identity.type === 'service' && !identity.serviceId) return 'Service identity requires serviceId';
75
+ if ((mode === 'team' || mode === 'enterprise') && identity.source === 'implicit-solo') {
76
+ return `Implicit identity not allowed in ${mode} mode`;
77
+ }
78
+ return null;
79
+ }
80
+
81
+ export function identityToRecord(identity) {
82
+ if (!identity) return {};
83
+ return {
84
+ type: identity.type,
85
+ userId: identity.userId || null,
86
+ serviceId: identity.serviceId || null,
87
+ tenantId: identity.tenantId || null,
88
+ sessionId: identity.sessionId || null,
89
+ role: identity.role || null,
90
+ deploymentMode: identity.deploymentMode || null,
91
+ };
92
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * lib/ingest/degraded-extract.mjs — manifest-governed extraction with a declared fallback chain.
3
+ *
4
+ * lib/document-ingest.mjs already bounds a docling attempt with a timeout and
5
+ * falls back to the node-native extractor on any failure; extractWithChain adds
6
+ * the governance piece LMCP-K2 requires — the fallback path comes from the
7
+ * docling ingestion-provider manifest's `degradation.chain` (docling ->
8
+ * node-native -> refuse) rather than being implicit in the calling code, and
9
+ * every result is stamped `degraded` so a caller can tell "docling ran" from
10
+ * "docling was unavailable and a lower-fidelity extractor stood in." A file
11
+ * type outside the node-native backend's coverage (xlsx/pptx/odt/ods) walks the
12
+ * chain straight to `refuse` and throws, per the manifest's terminal step.
13
+ */
14
+ import { degradationChain, probeInstall } from './sidecar-providers.mjs';
15
+
16
+ const NODE_NATIVE_EXTS = new Set(['.pdf', '.docx']);
17
+
18
+ function degradedResult(extracted, { chainStep, reason }) {
19
+ return {
20
+ ...extracted,
21
+ degraded: true,
22
+ degradationStep: chainStep,
23
+ droppedInfo: [
24
+ ...(extracted.droppedInfo ?? []),
25
+ {
26
+ kind: 'ingestion-provider-degraded',
27
+ count: 1,
28
+ reason,
29
+ recoverable: true,
30
+ },
31
+ ],
32
+ };
33
+ }
34
+
35
+ /**
36
+ * extractWithChain(filePath, opts) — run docling extraction governed by the
37
+ * docling manifest's declared degradation chain.
38
+ *
39
+ * @param {object} opts
40
+ * @param {string} opts.extension lowercased file extension (drives node-native eligibility)
41
+ * @param {Function} opts.doclingExtract async (filePath) => extraction result; throws on failure
42
+ * @param {Function} [opts.nodeNativeExtract] async (filePath) => extraction result; throws on failure
43
+ * @param {Function} [opts.installProbeImpl] injectable probeInstall (fake probes for tests)
44
+ * @param {boolean} [opts.skipInstallProbe] when true, always attempts doclingExtract
45
+ * regardless of the install probe (used when the caller already knows
46
+ * docling ran, e.g. contract-test doubles)
47
+ * @returns {Promise<object>} extraction result; `degraded: true` when the chain fell through
48
+ */
49
+ export async function extractWithChain(filePath, {
50
+ extension,
51
+ doclingExtract,
52
+ nodeNativeExtract = null,
53
+ installProbeImpl = probeInstall,
54
+ skipInstallProbe = false,
55
+ } = {}) {
56
+ const chain = degradationChain('docling');
57
+ if (!chain) {
58
+ throw new Error('docling ingestion-provider manifest is missing or has no degradation chain declared');
59
+ }
60
+
61
+ const installed = skipInstallProbe || installProbeImpl('docling').installed;
62
+ if (installed) {
63
+ try {
64
+ const extracted = await doclingExtract(filePath);
65
+ return { ...extracted, degraded: false, degradationStep: 'docling' };
66
+ } catch (err) {
67
+ return fallToNodeNative(filePath, { extension, nodeNativeExtract, reason: `docling extraction failed: ${err.message}` });
68
+ }
69
+ }
70
+
71
+ return fallToNodeNative(filePath, { extension, nodeNativeExtract, reason: 'docling is not installed' });
72
+ }
73
+
74
+ async function fallToNodeNative(filePath, { extension, nodeNativeExtract, reason }) {
75
+ const nodeNativeEligible = NODE_NATIVE_EXTS.has(extension) && typeof nodeNativeExtract === 'function';
76
+ if (!nodeNativeEligible) {
77
+ const err = new Error(
78
+ `${reason}; no node-native fallback covers '${extension}' — refusing per the docling degradation chain. ` +
79
+ 'Run `construct provider test docling` for diagnosis, or `construct install --with-docling`.',
80
+ );
81
+ err.code = 'INGESTION_PROVIDER_REFUSED';
82
+ err.chainStep = 'refuse';
83
+ throw err;
84
+ }
85
+
86
+ try {
87
+ const extracted = await nodeNativeExtract(filePath);
88
+ return degradedResult(extracted, { chainStep: 'node-native', reason: `${reason}; used the node-native extractor (${extracted.extractionMethod}).` });
89
+ } catch (err) {
90
+ const refuseErr = new Error(`${reason}; node-native fallback also failed: ${err.message} — refusing.`);
91
+ refuseErr.code = 'INGESTION_PROVIDER_REFUSED';
92
+ refuseErr.chainStep = 'refuse';
93
+ refuseErr.cause = err;
94
+ throw refuseErr;
95
+ }
96
+ }
@@ -14,6 +14,7 @@
14
14
  */
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
17
+ import { resolveNonNegativeSetting } from '../env-config.mjs';
17
18
 
18
19
  const DEFAULT_TIMEOUT_MS = 600_000;
19
20
 
@@ -38,7 +39,7 @@ export async function extractViaDoclingRemote({ filePath, maxChars = null, env =
38
39
  throw err;
39
40
  }
40
41
 
41
- const budget = timeoutMs ?? (Number(env.CONSTRUCT_DOCLING_TIMEOUT_MS) || DEFAULT_TIMEOUT_MS);
42
+ const budget = timeoutMs ?? resolveNonNegativeSetting(env, 'CONSTRUCT_DOCLING_TIMEOUT_MS', DEFAULT_TIMEOUT_MS);
42
43
  const controller = new AbortController();
43
44
  const timer = setTimeout(() => controller.abort(), budget);
44
45
  try {
@@ -21,10 +21,8 @@
21
21
  * end against a mock provider without a live key.
22
22
  */
23
23
 
24
- import { readFileSync, existsSync } from 'node:fs';
25
- import { extname, join } from 'node:path';
26
- import { homedir } from 'node:os';
27
- import { configDir } from '../config/xdg.mjs';
24
+ import { readFileSync } from 'node:fs';
25
+ import { extname } from 'node:path';
28
26
 
29
27
  import {
30
28
  AUDIO_VIDEO_EXTS,
@@ -32,6 +30,7 @@ import {
32
30
  TRANSCRIPT_EXTS,
33
31
  CALENDAR_EXTS,
34
32
  } from '../document-extract.mjs';
33
+ import { resolveSecret } from '../providers/secret-resolver.mjs';
35
34
 
36
35
  export const PROVIDER_IMAGE_EXTS = new Set(['.png', '.jpg', '.jpeg', '.gif', '.webp']);
37
36
 
@@ -67,23 +66,12 @@ function isAnthropic(provider, model) {
67
66
  return /anthropic|claude/i.test(provider || '') || /claude/i.test(model || '');
68
67
  }
69
68
 
70
- // Key resolution mirrors the daemon's cheap sources (env, then the two dotenv
71
- // files) without the shell-rc/1Password walk, which belongs to long-running
72
- // processes rather than a per-file extraction call. Ambient dotenv discovery
73
- // only augments the real process env; a caller that injects an explicit env
74
- // object (embedded callers, tests) is authoritative and hermetic.
75
-
76
69
  function resolveKey(varName, env, allowAmbient) {
77
- if (env[varName] && typeof env[varName] === 'string' && env[varName].length > 0) return env[varName];
78
- if (!allowAmbient) return null;
79
- for (const file of [join(configDir(), 'config.env'), join(homedir(), '.env')]) {
80
- try {
81
- if (!existsSync(file)) continue;
82
- const m = readFileSync(file, 'utf8').match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
83
- if (m && m[1]) return m[1].trim();
84
- } catch { /* unreadable dotenv is not authoritative */ }
70
+ try {
71
+ return resolveSecret(varName, { env, allowAmbient });
72
+ } catch {
73
+ return null;
85
74
  }
86
- return null;
87
75
  }
88
76
 
89
77
  async function bodyExcerpt(res) {
@@ -0,0 +1,196 @@
1
+ /**
2
+ * lib/ingest/sidecar-providers.mjs — governed install/health probes for ingestion sidecars.
3
+ *
4
+ * docling (document extraction, provisioned via uv venv) and whisper (audio
5
+ * transcription, provisioned as a system/cached binary) are ingestion providers
6
+ * declared as `ingestion-provider` manifests under lib/extensions/manifests/.
7
+ * The single place that turns a manifest's `installProbe` and `healthCheck`
8
+ * declarations into an actual probe result, and exposes the manifest's
9
+ * `degradation.chain` for the ingest pipeline to consume.
10
+ *
11
+ * Every probe accepts an injectable `exec`/`fs` seam so doctor and CLI tests can
12
+ * exercise the present AND absent code paths without depending on the real uv
13
+ * venv or whisper-cli binary being installed on the test machine (FAKE probes).
14
+ * Nothing here throws on absence — a missing sidecar is a reportable state
15
+ * (`installed: false`), never a crash.
16
+ */
17
+ import { existsSync, readFileSync } from 'node:fs';
18
+ import { spawnSync } from 'node:child_process';
19
+ import { join } from 'node:path';
20
+
21
+ import { loadManifestsFromDir, resolveManifestDirs } from '../extensions/loader.mjs';
22
+
23
+ export const SIDECAR_PROVIDER_IDS = ['docling', 'whisper'];
24
+
25
+ function loadManifest(id) {
26
+ const { builtin } = resolveManifestDirs();
27
+ const { manifests, errors } = loadManifestsFromDir(builtin);
28
+ const manifest = manifests.find((m) => m.id === id && m.kind === 'ingestion-provider');
29
+ if (!manifest) {
30
+ const relevantError = errors.find((e) => e.includes(`${id}.manifest.json`));
31
+ return { manifest: null, error: relevantError || `no ingestion-provider manifest found for '${id}'` };
32
+ }
33
+ return { manifest, error: null };
34
+ }
35
+
36
+ function which(bin, { execImpl = spawnSync } = {}) {
37
+ const checker = process.platform === 'win32' ? 'where' : 'which';
38
+ const result = execImpl(checker, [bin], { encoding: 'utf8' });
39
+ if (result.status !== 0) return null;
40
+ return result.stdout.trim().split('\n')[0] || null;
41
+ }
42
+
43
+ // docling's install probe reads the venv marker the same way
44
+ // lib/runtime/uv-bootstrap.mjs does, but through an injectable fs seam so tests
45
+ // can simulate "marker present, venv missing" (stale install) and "nothing
46
+ // present" without touching the real filesystem.
47
+
48
+ function probeDoclingInstall({ runtimeDir, existsImpl = existsSync, readFileImpl = readFileSync } = {}) {
49
+ const pythonBin = join(runtimeDir, '.venv', 'bin', 'python');
50
+ const pythonBin3 = join(runtimeDir, '.venv', 'bin', 'python3');
51
+ const venvPython = existsImpl(pythonBin) ? pythonBin : (existsImpl(pythonBin3) ? pythonBin3 : null);
52
+ const markerPath = join(runtimeDir, '.install-marker.json');
53
+
54
+ if (!existsImpl(markerPath)) {
55
+ return { installed: false, version: null, reason: 'no install marker at ' + markerPath };
56
+ }
57
+ let marker;
58
+ try {
59
+ marker = JSON.parse(readFileImpl(markerPath, 'utf8'));
60
+ } catch (err) {
61
+ return { installed: false, version: null, reason: `install marker unreadable: ${err.message}` };
62
+ }
63
+ if (!venvPython) {
64
+ return { installed: false, version: marker.doclingVersion || null, reason: 'install marker present but venv python binary missing (stale install)' };
65
+ }
66
+ return { installed: true, version: marker.doclingVersion || null, reason: null, pythonBin: venvPython, runtimeDir };
67
+ }
68
+
69
+ // whisper's install probe mirrors lib/runtime/whisper-bootstrap.mjs's PATH +
70
+ // cached-binary lookup, again through an injectable exec/fs seam.
71
+
72
+ function probeWhisperInstall({ runtimeDir, candidates, cachedBinaryRel, execImpl = spawnSync, existsImpl = existsSync } = {}) {
73
+ for (const candidate of candidates) {
74
+ const found = which(candidate, { execImpl });
75
+ if (found) return { installed: true, version: null, reason: null, binary: found, source: 'system' };
76
+ }
77
+ const cached = join(runtimeDir, cachedBinaryRel);
78
+ if (existsImpl(cached)) {
79
+ return { installed: true, version: null, reason: null, binary: cached, source: 'cached' };
80
+ }
81
+ return { installed: false, version: null, reason: `not found on PATH (${candidates.join(', ')}) or cached at ${cached}` };
82
+ }
83
+
84
+ /**
85
+ * probeInstall(id, opts) — install probe for a governed ingestion provider.
86
+ *
87
+ * @param {string} id 'docling' | 'whisper'
88
+ * @param {object} [opts]
89
+ * @param {string} [opts.runtimeDir] override runtime dir (defaults to manifest's declared default under cwd)
90
+ * @param {string} [opts.cwd]
91
+ * @param {Function} [opts.existsImpl] injectable fs.existsSync (fake probes)
92
+ * @param {Function} [opts.readFileImpl] injectable fs.readFileSync (fake probes)
93
+ * @param {Function} [opts.execImpl] injectable spawnSync (fake probes)
94
+ * @returns {{ ok: boolean, installed: boolean, version: (string|null), reason: (string|null), manifest: object }}
95
+ */
96
+ export function probeInstall(id, { runtimeDir = null, cwd = process.cwd(), existsImpl = existsSync, readFileImpl = readFileSync, execImpl = spawnSync } = {}) {
97
+ const { manifest, error } = loadManifest(id);
98
+ if (!manifest) return { ok: false, installed: false, version: null, reason: error, manifest: null };
99
+
100
+ const probeSpec = manifest.installProbe;
101
+ const resolvedRuntimeDir = runtimeDir || join(cwd, probeSpec.defaultRuntimeDir);
102
+
103
+ if (probeSpec.kind === 'venv-marker') {
104
+ const result = probeDoclingInstall({ runtimeDir: resolvedRuntimeDir, existsImpl, readFileImpl });
105
+ return { ok: true, ...result, manifest };
106
+ }
107
+ if (probeSpec.kind === 'binary-on-path') {
108
+ const result = probeWhisperInstall({
109
+ runtimeDir: resolvedRuntimeDir,
110
+ candidates: probeSpec.candidates,
111
+ cachedBinaryRel: probeSpec.cachedBinary,
112
+ execImpl,
113
+ existsImpl,
114
+ });
115
+ return { ok: true, ...result, manifest };
116
+ }
117
+ return { ok: false, installed: false, version: null, reason: `unknown installProbe.kind '${probeSpec.kind}'`, manifest };
118
+ }
119
+
120
+ /**
121
+ * probeHealth(id, opts) — health check for a governed ingestion provider.
122
+ *
123
+ * Runs only when the install probe reports `installed: true`; a not-installed
124
+ * provider is unhealthy by definition and this short-circuits without
125
+ * spawning a process (so a missing binary never masquerades as an
126
+ * exec failure).
127
+ *
128
+ * @param {string} id 'docling' | 'whisper'
129
+ * @param {object} [opts] same injectable seams as probeInstall, plus:
130
+ * @param {Function} [opts.execImpl] injectable spawnSync for the health subprocess (fake probes)
131
+ * @returns {{ ok: boolean, healthy: boolean, detail: string, installProbe: object }}
132
+ */
133
+ export function probeHealth(id, { runtimeDir = null, cwd = process.cwd(), existsImpl = existsSync, readFileImpl = readFileSync, execImpl = spawnSync } = {}) {
134
+ const installProbe = probeInstall(id, { runtimeDir, cwd, existsImpl, readFileImpl, execImpl });
135
+ if (!installProbe.ok) {
136
+ return { ok: false, healthy: false, detail: installProbe.reason, installProbe };
137
+ }
138
+ if (!installProbe.installed) {
139
+ return { ok: true, healthy: false, detail: `${id} not installed: ${installProbe.reason}`, installProbe };
140
+ }
141
+
142
+ const check = installProbe.manifest.healthCheck;
143
+ if (check.kind !== 'subprocess-version') {
144
+ return { ok: false, healthy: false, detail: `unknown healthCheck.kind '${check.kind}'`, installProbe };
145
+ }
146
+
147
+ const command = check.command === '<resolvedBinary>'
148
+ ? installProbe.binary
149
+ : check.command.replace('<runtimeDir>', installProbe.runtimeDir);
150
+ const args = check.args.map((a) => (a === '<runtimeDir>' ? installProbe.runtimeDir : a));
151
+ const result = execImpl(command, args, { encoding: 'utf8', timeout: check.timeoutMs });
152
+
153
+ if (result.error) {
154
+ return { ok: true, healthy: false, detail: `${id} health check failed to spawn: ${result.error.message}`, installProbe };
155
+ }
156
+ if (result.status !== 0) {
157
+ return { ok: true, healthy: false, detail: `${id} health check exited ${result.status}: ${(result.stderr || result.stdout || '').trim().slice(0, 200)}`, installProbe };
158
+ }
159
+ const output = (result.stdout || result.stderr || '').trim();
160
+ return { ok: true, healthy: true, detail: output ? `${id} ${output.split('\n')[0]}` : `${id} healthy`, installProbe };
161
+ }
162
+
163
+ /**
164
+ * degradationChain(id) — the manifest-declared fallback chain for a provider.
165
+ *
166
+ * @param {string} id
167
+ * @returns {{ chain: Array<{id:string, mode:string, description:string}>, terminal: string, resultMarking: string }|null}
168
+ */
169
+ export function degradationChain(id) {
170
+ const { manifest } = loadManifest(id);
171
+ return manifest?.degradation || null;
172
+ }
173
+
174
+ /**
175
+ * testProvider(id, opts) — combined install+health probe shaped for CLI/doctor
176
+ * consumption (`construct provider test docling`). Never throws: an absent
177
+ * provider degrades loudly (`ok: true, healthy: false`) rather than crashing.
178
+ *
179
+ * @param {string} id
180
+ * @param {object} [opts] same injectable seams as probeHealth
181
+ * @returns {{ id: string, installed: boolean, healthy: boolean, version: (string|null), detail: string, degradation: object|null }}
182
+ */
183
+ export function testProvider(id, opts = {}) {
184
+ if (!SIDECAR_PROVIDER_IDS.includes(id)) {
185
+ return { id, installed: false, healthy: false, version: null, detail: `unknown ingestion provider '${id}'; known: ${SIDECAR_PROVIDER_IDS.join(', ')}`, degradation: null };
186
+ }
187
+ const health = probeHealth(id, opts);
188
+ return {
189
+ id,
190
+ installed: !!health.installProbe?.installed,
191
+ healthy: health.healthy,
192
+ version: health.installProbe?.version || null,
193
+ detail: health.detail,
194
+ degradation: degradationChain(id),
195
+ };
196
+ }
@@ -9,6 +9,7 @@ import fs from 'node:fs';
9
9
  import path from 'node:path';
10
10
  import { spawnSync } from 'node:child_process';
11
11
  import { resolveDoclingServeUrl } from './ingest/docling-remote.mjs';
12
+ import { defaultRuntimeDir } from './runtime/uv-bootstrap.mjs';
12
13
 
13
14
  function whichBin(name) {
14
15
  const cmd = process.platform === 'win32' ? 'where' : 'which';
@@ -17,12 +18,17 @@ function whichBin(name) {
17
18
  return (result.stdout || '').trim().split('\n')[0] || null;
18
19
  }
19
20
 
20
- export function doclingVenvPath(cwd = process.cwd()) {
21
- return path.join(cwd, '.cx', 'runtime', 'docling', '.venv');
21
+ // One shared, machine-scoped venv (construct-rf26.16), not project-keyed —
22
+ // no cwd/project argument, so every caller on this machine resolves the
23
+ // identical path. `defaultRuntimeDir()` (lib/runtime/uv-bootstrap.mjs) is
24
+ // the single source of truth for where the venv lives.
25
+
26
+ export function doclingVenvPath() {
27
+ return path.join(defaultRuntimeDir(), '.venv');
22
28
  }
23
29
 
24
- export function detectDoclingSidecar({ cwd = process.cwd() } = {}) {
25
- const venv = doclingVenvPath(cwd);
30
+ export function detectDoclingSidecar() {
31
+ const venv = doclingVenvPath();
26
32
  const python = process.platform === 'win32'
27
33
  ? path.join(venv, 'Scripts', 'python.exe')
28
34
  : path.join(venv, 'bin', 'python');
@@ -79,7 +85,7 @@ export function detectDoclingRemote(env = process.env) {
79
85
 
80
86
  export function detectIngestPipeline({ cwd = process.cwd(), env = process.env, repoRoot = cwd } = {}) {
81
87
  const steps = {
82
- docling: detectDoclingSidecar({ cwd }),
88
+ docling: detectDoclingSidecar(),
83
89
  nodeNative: detectNodeNativeDeps({ repoRoot }),
84
90
  whisper: detectWhisper(env),
85
91
  doclingRemote: detectDoclingRemote(env),