@geraldmaron/construct 1.4.2 → 1.5.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 (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +57 -45
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +273 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +104 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -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),
@@ -26,6 +26,7 @@ import { missingIgnorePatterns, isConstructPackageRepo } from './host-dispositio
26
26
  import { HOST_KEYS, displayNameToKey } from './platforms/capabilities.mjs';
27
27
  import { DOC_LANES, LANE_ORDER, DOC_PRESETS } from './init/doc-lanes.mjs';
28
28
  import { initWorkflow } from './workflow-state.mjs';
29
+ import { isMainModule } from './roots.mjs';
29
30
 
30
31
  const __dirname = fileURLToPath(new URL(".", import.meta.url));
31
32
  const ROOT_DIR = path.join(__dirname, "..");
@@ -45,6 +46,7 @@ const withPostmortemsFlag = args.includes("--with-postmortems");
45
46
  const withArchitectureFlag = args.includes("--with-architecture");
46
47
  const withReadmeFlag = args.includes("--with-readme");
47
48
  const withDevcontainerFlag = args.includes("--devcontainer");
49
+ const withSeedIndexFlag = args.includes("--seed-index");
48
50
 
49
51
  // Behavior flags
50
52
  const verbose = args.includes("--verbose") || args.includes("-v");
@@ -56,6 +58,10 @@ const skipInteractive = !interactive;
56
58
  // templates/ already exist (issue #97).
57
59
  const forceScaffold = args.includes("--force");
58
60
 
61
+ // --no-beads skips tracker initialization entirely and marks beads as disabled
62
+ // in the result. Use when the host environment cannot run bd (e.g. ephemeral CI).
63
+ const noBeads = args.includes("--no-beads");
64
+
59
65
  // `bd init` is git-native and unconditionally commits its bootstrap; ADR-0027
60
66
  // §3 requires init to leave the host repo's commit history untouched unless the
61
67
  // user opts in. --commit-bootstrap keeps that commit; the default withholds it.
@@ -986,41 +992,49 @@ async function main() {
986
992
  skipped.push('.cx/construct_guide.md');
987
993
  }
988
994
 
989
- // Auto-init beads
995
+ // Auto-init beads. Fail-closed: a tracker failure leaves the project in an
996
+ // inconsistent state (no hooks, no issue tracking) — better to stop early than
997
+ // silently proceed. Pass --no-beads to opt out for ephemeral/CI environments.
990
998
  const beadsMeta = path.join(target, ".beads", "metadata.json");
991
- if (!fs.existsSync(beadsMeta) && !isToolRepo) {
999
+ let beadsReady = false;
1000
+ if (noBeads) {
1001
+ console.log(' ⚠️ Skipping task tracker initialization (--no-beads).');
1002
+ skipped.push('.beads/ (--no-beads)');
1003
+ } else if (!fs.existsSync(beadsMeta) && !isToolRepo) {
992
1004
  console.log('');
993
1005
  console.log('Initializing the task tracker (beads — local-first, git-synced, works offline)…');
994
1006
  fs.mkdirSync(path.join(target, ".beads"), { recursive: true });
995
- try {
996
- const beads = initializeBeadsTracker(target, { commitBootstrap });
997
- created.push(".beads/ (initialized)");
998
- const prefixNote = beads?.prefix ? ` issues prefixed \`${beads.prefix}-\`` : "";
999
- console.log(` ✓ Task tracker ready${prefixNote}. Run \`bd quickstart\` to start.`);
1000
- if (beads?.withheldCommit) {
1001
- console.log(" Bootstrap files left uncommitted (commit them yourself, or re-run with --commit-bootstrap).");
1002
- }
1003
- } catch (e) {
1004
- console.warn("⚠️ Beads init failed:", e.message);
1007
+ const beads = initializeBeadsTracker(target, { commitBootstrap });
1008
+ created.push(".beads/ (initialized)");
1009
+ const prefixNote = beads?.prefix ? ` — issues prefixed \`${beads.prefix}-\`` : "";
1010
+ console.log(` ✓ Task tracker ready${prefixNote}. Run \`bd quickstart\` to start.`);
1011
+ if (beads?.withheldCommit) {
1012
+ console.log(" Bootstrap files left uncommitted (commit them yourself, or re-run with --commit-bootstrap).");
1005
1013
  }
1006
1014
  console.log('');
1015
+ beadsReady = true;
1016
+ } else if (fs.existsSync(beadsMeta)) {
1017
+ beadsReady = true;
1007
1018
  }
1008
1019
 
1009
1020
  // Wire the project's git hooks to .beads/hooks so Construct's pre-commit
1010
1021
  // secret-scan and policy gates activate. Owned by init, not install (ADR-0027
1011
1022
  // §3): install must never mutate the cwd repo. Idempotent — no-op when
1012
1023
  // .beads/hooks/pre-commit is absent or a non-default custom path is set.
1024
+ // Skipped when beads is disabled or was not successfully initialized.
1013
1025
 
1014
- try {
1015
- const { ensureGitHooksPath } = await import('./git-hooks-path.mjs');
1016
- const hooks = ensureGitHooksPath({ cwd: target });
1017
- if (hooks.status === 'set') {
1018
- created.push('git core.hooksPath .beads/hooks');
1019
- if (!quiet) console.log(` ✓ ${hooks.message}`);
1020
- } else if (hooks.status === 'warning' && !quiet) {
1021
- console.log(` ⚠️ ${hooks.message}`);
1022
- }
1023
- } catch { /* git-hooks wiring is advisory; init proceeds */ }
1026
+ if (beadsReady) {
1027
+ try {
1028
+ const { ensureGitHooksPath } = await import('./git-hooks-path.mjs');
1029
+ const hooks = ensureGitHooksPath({ cwd: target });
1030
+ if (hooks.status === 'set') {
1031
+ created.push('git core.hooksPath → .beads/hooks');
1032
+ if (!quiet) console.log(` ✓ ${hooks.message}`);
1033
+ } else if (hooks.status === 'warning' && !quiet) {
1034
+ console.log(` ⚠️ ${hooks.message}`);
1035
+ }
1036
+ } catch { /* git-hooks wiring is advisory; init proceeds */ }
1037
+ }
1024
1038
 
1025
1039
  // Inject Construct's integration guidance into AGENTS.md and CLAUDE.md as a
1026
1040
  // versioned, hash-stamped marker block (ADR-0027 §2). Runs after bd init so
@@ -1154,22 +1168,25 @@ async function main() {
1154
1168
  }
1155
1169
  }
1156
1170
 
1157
- // Seed the vector index from the project's existing docs so the agent
1158
- // can compare new intake against established PRDs/RFCs/ADRs from day
1159
- // one. Best-effort: skipped silently when Postgres + embedding model
1160
- // aren't ready yet (DATABASE_URL unset, no ONNX cache). User can re-run
1161
- // `construct ingest` or `construct init` later to seed manually.
1171
+ // Vector-index seeding (embedding the project's existing docs so the agent
1172
+ // can compare new intake against established PRDs/RFCs/ADRs) is opt-in via
1173
+ // --seed-index (construct-rf26.17): the default init path performs zero
1174
+ // embedding work and never touches LanceDB, so a project that never uses
1175
+ // semantic search never pays for the model cache or the index directory.
1176
+ // `construct ingest` provisions both lazily on first real use.
1177
+
1178
+ if (withSeedIndexFlag) {
1179
+ try {
1180
+ const { syncFileStateToSql } = await import('./storage/sync.mjs');
1181
+ const projectName = path.basename(target);
1182
+ const seed = await syncFileStateToSql(target, { env: process.env, project: projectName });
1183
+ if (seed?.status === 'ok' && (seed.documentsSynced || 0) > 0) {
1184
+ console.log(`\n🔍 Indexed existing project material: ${seed.documentsSynced} doc(s), ${seed.embeddingsSynced || 0} embeddings.`);
1185
+ console.log(` The agent can now compare new intake (inbox/) against your existing PRDs, ADRs, and notes.`);
1186
+ }
1187
+ } catch { /* silent — corpus seeding is best-effort, not a setup blocker */ }
1188
+ }
1162
1189
 
1163
- try {
1164
- const { syncFileStateToSql } = await import('./storage/sync.mjs');
1165
- const projectName = path.basename(target);
1166
- const seed = await syncFileStateToSql(target, { env: process.env, project: projectName });
1167
- if (seed?.status === 'ok' && (seed.documentsSynced || 0) > 0) {
1168
- console.log(`\n🔍 Indexed existing project material: ${seed.documentsSynced} doc(s), ${seed.embeddingsSynced || 0} embeddings.`);
1169
- console.log(` The agent can now compare new intake (inbox/) against your existing PRDs, ADRs, and notes.`);
1170
- }
1171
- } catch { /* silent — corpus seeding is best-effort, not a setup blocker */ }
1172
-
1173
1190
  // ── Devcontainer recipe ────────────────────────────────────────────────
1174
1191
  if (withDevcontainerFlag) {
1175
1192
  const dcDir = path.join(target, '.devcontainer');
@@ -1217,15 +1234,10 @@ async function main() {
1217
1234
  try {
1218
1235
  const { startServices } = await import('./service-manager.mjs');
1219
1236
  const { homedir } = await import('node:os');
1220
- const { detectDockerCompose } = await import('./setup.mjs');
1221
-
1222
- const homeDir = homedir();
1223
- const composeRunner = detectDockerCompose();
1224
-
1237
+
1225
1238
  const { results } = await startServices({
1226
1239
  rootDir: target,
1227
- homeDir,
1228
- detectDockerComposeFn: () => composeRunner,
1240
+ homeDir: homedir(),
1229
1241
  });
1230
1242
 
1231
1243
  if (!quiet) {
@@ -1252,7 +1264,7 @@ async function main() {
1252
1264
  }
1253
1265
  }
1254
1266
 
1255
- if (import.meta.url === `file://${process.argv[1]}`) {
1267
+ if (isMainModule(import.meta.url)) {
1256
1268
  main().catch((error) => {
1257
1269
  console.error(`Error: ${error.message}`);
1258
1270
  process.exit(1);
@@ -21,6 +21,7 @@ import {
21
21
  needsConstructGuideUpdate,
22
22
  writeConstructGuideProposal,
23
23
  } from "./init-update-guide.mjs";
24
+ import { isMainModule } from './roots.mjs';
24
25
 
25
26
  const args = process.argv.slice(2);
26
27
  const dryRun = args.includes("--dry-run");
@@ -226,7 +227,7 @@ async function main() {
226
227
  console.log("To replace .cx/construct_guide.md with the shipped template: construct init:update --apply-guide");
227
228
  }
228
229
 
229
- if (import.meta.url === `file://${process.argv[1]}`) {
230
+ if (isMainModule(import.meta.url)) {
230
231
  main().catch((error) => {
231
232
  console.error(`Error: ${error.message}`);
232
233
  process.exit(1);
@@ -62,6 +62,30 @@ function stripClaudeSettings(homeDir, opts) {
62
62
  if (changed) writeJson(filePath, settings, { ...opts, label: '~/.claude/settings.json Construct entries' });
63
63
  }
64
64
 
65
+ // construct-ranh moved Claude Code's MCP write path off ~/.claude/settings.json
66
+ // (never read by the CLI for server definitions) to ~/.claude.json's top-level
67
+ // `mcpServers`. A post-fix install registers MCP servers there, so this must be
68
+ // stripped alongside settings.json for the memory/cass/construct-mcp bridges to
69
+ // be fully removed.
70
+
71
+ function stripClaudeUserConfig(homeDir, opts) {
72
+ const filePath = path.join(homeDir, '.claude.json');
73
+ const config = readJson(filePath);
74
+ if (!config || typeof config !== 'object') return;
75
+ if (!config.mcpServers || typeof config.mcpServers !== 'object') return;
76
+
77
+ let changed = false;
78
+ for (const key of CONSTRUCT_MCP_KEYS) {
79
+ if (key in config.mcpServers) {
80
+ delete config.mcpServers[key];
81
+ changed = true;
82
+ }
83
+ }
84
+ if (Object.keys(config.mcpServers).length === 0) delete config.mcpServers;
85
+
86
+ if (changed) writeJson(filePath, config, { ...opts, label: '~/.claude.json Construct MCP entries' });
87
+ }
88
+
65
89
  function stripOpenCode(homeDir, opts) {
66
90
  const filePath = path.join(homeDir, '.config', 'opencode', 'opencode.json');
67
91
  const config = readJson(filePath);
@@ -173,6 +197,7 @@ export function cleanupLegacyGlobalConstruct({ homeDir, dryRun = false } = {}) {
173
197
  });
174
198
 
175
199
  stripClaudeSettings(homeDir, opts);
200
+ stripClaudeUserConfig(homeDir, opts);
176
201
  stripOpenCode(homeDir, opts);
177
202
  stripVsCode(homeDir, opts);
178
203
  stripCodex(homeDir, opts);