@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,24 @@
1
+ /**
2
+ * lib/doctor/command-on-path.mjs — resolve a command's PATH location the way a
3
+ * user's terminal would, without requiring zsh.
4
+ *
5
+ * `command -v` needs a login-ish shell to see the same PATH a real terminal
6
+ * session would build (rc files, PATH managers, etc). zsh is the
7
+ * macOS/most-desktop-Linux default but is not guaranteed present (minimal
8
+ * containers, some CI images) — spawning it unconditionally turns a missing
9
+ * zsh into a false "not on PATH" result for every dependent check. Falling
10
+ * back to `sh -lc` on ENOENT keeps this a real PATH lookup instead.
11
+ */
12
+
13
+ import { spawnSync } from 'node:child_process';
14
+
15
+ export function resolveCommandOnPath(command, { spawnSyncImpl = spawnSync, platform = process.platform } = {}) {
16
+ if (platform === 'win32') {
17
+ return spawnSyncImpl('where', [command], { encoding: 'utf8', stdio: 'pipe' });
18
+ }
19
+ const viaZsh = spawnSyncImpl('zsh', ['-lc', `command -v ${command}`], { encoding: 'utf8', stdio: 'pipe' });
20
+ if (viaZsh.error?.code === 'ENOENT') {
21
+ return spawnSyncImpl('sh', ['-lc', `command -v ${command}`], { encoding: 'utf8', stdio: 'pipe' });
22
+ }
23
+ return viaZsh;
24
+ }
@@ -17,4 +17,93 @@ export const DOCTOR_STATES = {
17
17
  'entrypoint-missing': 'A binary or script the runtime expects (construct-mcp, construct) is not found on PATH.',
18
18
  'healthy': 'The check passed with no issues detected.',
19
19
  'degraded': 'The check passed with warnings; functionality may be reduced.',
20
+ 'stub': 'The capability exists but is a no-op or partial implementation; not production-safe.',
20
21
  };
22
+
23
+ /**
24
+ * STUB_ICON — the distinct display character rendered next to stub-tier diagnoses.
25
+ * Renderers that format doctor output should use this rather than hard-coding '~'.
26
+ */
27
+ export const STUB_ICON = '~';
28
+
29
+ /**
30
+ * classifyCapability(capabilityId, mode)
31
+ *
32
+ * Reads the capability registry for `mode` and maps the capability's status to one
33
+ * of three classification strings:
34
+ * 'implemented' — fully wired; production-safe.
35
+ * 'stub' — code path exists but is a no-op / partial (status: 'stub').
36
+ * 'not-implemented' — no code path exists yet (status: 'not-implemented').
37
+ *
38
+ * Returns 'not-implemented' when the capability is not found in the registry for
39
+ * the given mode (unknown capability IDs are treated as absent).
40
+ *
41
+ * Dynamic import of mode-capabilities happens on first call — in hot paths,
42
+ * prefer `classifyCapabilitySync` with a pre-fetched capabilities array.
43
+ */
44
+ export async function classifyCapability(capabilityId, mode) {
45
+ let getCapabilities;
46
+ try {
47
+ ({ getCapabilities } = await import('../mode-capabilities.mjs'));
48
+ } catch {
49
+ getCapabilities = () => [];
50
+ }
51
+ return classifyCapabilitySync(capabilityId, getCapabilities(mode));
52
+ }
53
+
54
+ /**
55
+ * classifyCapabilitySync(capabilityId, caps)
56
+ *
57
+ * Synchronous variant that accepts a pre-fetched capabilities array. Avoids
58
+ * dynamic import overhead in hot paths (e.g., batch diagnosis loops).
59
+ */
60
+ export function classifyCapabilitySync(capabilityId, caps) {
61
+ const cap = (caps ?? []).find((c) => c.id === capabilityId);
62
+ if (!cap) return 'not-implemented';
63
+ if (cap.status === 'implemented') return 'implemented';
64
+ if (cap.status === 'stub') return 'stub';
65
+ return 'not-implemented';
66
+ }
67
+
68
+ /**
69
+ * diagnosisLevelForCapability(classification)
70
+ *
71
+ * Maps a classifyCapabilitySync result to the doctor diagnosis level:
72
+ * 'implemented' → 'healthy' (green)
73
+ * 'stub' → 'stub' (yellow ~ icon)
74
+ * 'not-implemented' → 'stub' (yellow ~ icon, "Not implemented" message)
75
+ */
76
+ export function diagnosisLevelForCapability(classification) {
77
+ if (classification === 'implemented') return 'healthy';
78
+ return 'stub';
79
+ }
80
+
81
+ /**
82
+ * diagnosisMessageForCapability(classification)
83
+ *
84
+ * Returns the human-readable message for a capability diagnosis entry.
85
+ */
86
+ export function diagnosisMessageForCapability(classification) {
87
+ if (classification === 'implemented') return 'Fully implemented';
88
+ if (classification === 'stub') return 'Partial implementation';
89
+ return 'Not implemented';
90
+ }
91
+
92
+ /**
93
+ * renderDiagnosisLevel(level)
94
+ *
95
+ * Returns the display icon for a given diagnosis level. Renderers that format
96
+ * doctor output should use this rather than embedding icon logic inline.
97
+ *
98
+ * Level → icon mapping:
99
+ * 'healthy' → '✓'
100
+ * 'degraded' → '⚠'
101
+ * 'stub' → '~'
102
+ * anything else → '✗'
103
+ */
104
+ export function renderDiagnosisLevel(level) {
105
+ if (level === 'healthy') return '✓';
106
+ if (level === 'degraded') return '⚠';
107
+ if (level === 'stub') return STUB_ICON;
108
+ return '✗';
109
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * lib/doctor/embedding-health.mjs — doctor visibility for the active embedding model.
3
+ *
4
+ * `local` (lib/storage/embeddings-local.mjs, @huggingface/transformers) is the
5
+ * embedding engine's DEFAULT_MODEL (lib/storage/embeddings-engine.mjs),
6
+ * backing knowledge-search and evidence-ingest by default rather than a
7
+ * rarely-used opt-in path. The adapter's runtime already degrades silently to
8
+ * the 256d hashing adapter when the model fails to load (embeddings-local.mjs
9
+ * catch block); doctor visibility surfaces that degradation before a
10
+ * retrieval-quality drop goes unexplained (LMCP-K4), implementing the
11
+ * healthCheck declared for @huggingface/transformers in deps/intent.json:
12
+ * "require this package — absent means local embedding unavailable; hashing
13
+ * fallback activates automatically." A module-resolution import is the probe
14
+ * rather than invoking the ONNX pipeline, since presence/absence — not model
15
+ * inference — is the doctor-relevant signal.
16
+ */
17
+ import { getEmbeddingModelInfo } from '../storage/embeddings-engine.mjs';
18
+
19
+ /**
20
+ * checkEmbeddingModelForDoctor(opts) — the active embedding model's doctor-shaped finding.
21
+ *
22
+ * Non-`local` models (openai, ollama, hashing) report their configuration as-is;
23
+ * only `local` depends on the optional @huggingface/transformers package, so
24
+ * only that path probes for it.
25
+ *
26
+ * @param {object} [opts]
27
+ * @param {NodeJS.ProcessEnv} [opts.env] injectable env (CONSTRUCT_EMBEDDING_MODEL) for tests
28
+ * @param {Function} [opts.importTransformers] injectable seam over the dynamic import, for tests
29
+ * @returns {Promise<{ ok: boolean, degraded: boolean, label: string, optional: boolean }>}
30
+ */
31
+ export async function checkEmbeddingModelForDoctor(opts = {}) {
32
+ const { env = process.env, importTransformers = () => import('@huggingface/transformers') } = opts;
33
+ const info = await getEmbeddingModelInfo({ env });
34
+
35
+ if (info.id !== 'local') {
36
+ return { ok: true, degraded: false, label: `Embedding model: ${info.id} (${info.model}, ${info.dimensions}d)`, optional: true };
37
+ }
38
+
39
+ try {
40
+ await importTransformers();
41
+ return { ok: true, degraded: false, label: `Embedding model: local (${info.model}, ${info.dimensions}d) — @huggingface/transformers resolves`, optional: true };
42
+ } catch (err) {
43
+ return {
44
+ ok: true,
45
+ degraded: true,
46
+ label: `Embedding model: local requested but @huggingface/transformers is not installed (${err.message}) — degrades to hashing-bow-v1 (256d, no semantic understanding). Install with \`npm install @huggingface/transformers\`, or set CONSTRUCT_EMBEDDING_MODEL=hashing to opt into the degraded model explicitly.`,
47
+ optional: true,
48
+ };
49
+ }
50
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * lib/doctor/engine-health.mjs — doctor checks for the document-export toolchain.
3
+ *
4
+ * lib/document-export.mjs's detect() already probes pandoc/typst/libreoffice/
5
+ * pptxgenjs per export format and returns binary presence, version, and an
6
+ * install hint (ADR-0024). This module turns those probes into one doctor-shaped
7
+ * finding per engine — binary presence, version, and a degradation message naming
8
+ * the missing binary and the export formats it owns — so `construct doctor`
9
+ * predicts whether a pdf/doc/pptx export will work before a workflow relies on
10
+ * it (LMCP-K3), mirroring lib/doctor/sidecar-providers.mjs's pattern of a pure,
11
+ * directly-testable function rather than inline probe logic in the CLI
12
+ * entrypoint or the consistency watcher.
13
+ *
14
+ * branding: 'plain' isolates the pandoc/typst/libreoffice probes from bundled
15
+ * distribution-template presence (construct-pdf.typ, construct-web.html), which
16
+ * is a separate, already-surfaced doctor concern. pptxgenjs is probed with the
17
+ * default 'construct' branding because detect() only selects the pptxgenjs
18
+ * engine (vs. pandoc's native pptx writer) outside of 'plain' branding.
19
+ */
20
+ import { detect, installHint } from '../document-export.mjs';
21
+
22
+ export const EXPORT_ENGINES = [
23
+ {
24
+ id: 'pandoc',
25
+ format: 'html',
26
+ branding: 'plain',
27
+ owningWorkflow: 'document-export (pdf, docx, doc, deck, html, rtf, odt, epub, tex, txt formats)',
28
+ },
29
+ {
30
+ id: 'typst',
31
+ format: 'pdf',
32
+ branding: 'plain',
33
+ owningWorkflow: 'document-export (pdf format, pandoc --pdf-engine)',
34
+ },
35
+ {
36
+ id: 'libreoffice',
37
+ format: 'doc',
38
+ branding: 'plain',
39
+ owningWorkflow: 'document-export (doc format, docx-to-doc down-conversion)',
40
+ },
41
+ {
42
+ id: 'pptxgenjs',
43
+ format: 'pptx',
44
+ branding: 'construct',
45
+ owningWorkflow: 'document-export (pptx format)',
46
+ },
47
+ ];
48
+
49
+ /**
50
+ * checkEngineHealthForDoctor(engineId, opts) — one export engine's doctor-shaped finding.
51
+ *
52
+ * Absence is reported as an optional (non-fatal) finding carrying the same
53
+ * install hint `detect()` returns to export preflight, so the message a user
54
+ * sees in `construct doctor` and the message export preflight refuses with
55
+ * are the same string.
56
+ *
57
+ * @param {string} engineId 'pandoc' | 'typst' | 'libreoffice' | 'pptxgenjs'
58
+ * @param {object} [opts]
59
+ * @param {NodeJS.ProcessEnv} [opts.env] injectable PATH/env for tests
60
+ * @param {Function} [opts.detectFn] injectable seam for lib/document-export.mjs detect()
61
+ * @returns {{ ok: boolean, installed: boolean, label: string, optional: boolean }}
62
+ */
63
+ export function checkEngineHealthForDoctor(engineId, opts = {}) {
64
+ const probe = EXPORT_ENGINES.find((p) => p.id === engineId);
65
+ if (!probe) throw new Error(`Unknown export engine: ${engineId}`);
66
+
67
+ const { env = process.env, detectFn = detect } = opts;
68
+ const result = detectFn(probe.format, env, { branding: probe.branding });
69
+ const installed = !result.missing.includes(engineId);
70
+
71
+ if (installed) {
72
+ const status = result.binaries?.find((b) => b.name === engineId);
73
+ const version = status?.version ? ` ${status.version}` : '';
74
+ return { ok: true, installed: true, label: `Export engine '${engineId}'${version} — ${probe.owningWorkflow}`, optional: true };
75
+ }
76
+
77
+ return {
78
+ ok: true,
79
+ installed: false,
80
+ label: `Export engine '${engineId}' not installed — ${probe.owningWorkflow} degrades. ${installHint(engineId)}`,
81
+ optional: true,
82
+ };
83
+ }
84
+
85
+ /**
86
+ * checkEngineHealthAllForDoctor(opts) — pandoc + typst + libreoffice + pptxgenjs findings.
87
+ *
88
+ * @param {object} [opts] forwarded per-engine to checkEngineHealthForDoctor
89
+ * @returns {Array<{ ok: boolean, installed: boolean, label: string, optional: boolean }>}
90
+ */
91
+ export function checkEngineHealthAllForDoctor(opts = {}) {
92
+ return EXPORT_ENGINES.map((p) => checkEngineHealthForDoctor(p.id, opts));
93
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * lib/doctor/graph-validate.mjs — doctor check that runs `graph validate` (LMCP-C7).
3
+ *
4
+ * Wraps lib/graph/validate.mjs's validateGraph() into the doctor check shape
5
+ * ({ ok, label, warning, errors, warnings }) used across lib/doctor/*.mjs
6
+ * (see project-adapters.mjs for the same contract). Read-only: this module
7
+ * never writes to the graph store or the runtime-evidence store — it only
8
+ * classifies what validateGraph already reports, so `construct doctor` and
9
+ * `construct graph validate` can never disagree about whether the graph is
10
+ * clean.
11
+ *
12
+ * A missing graph (no `construct graph build` run yet) is reported as a
13
+ * warning, not a hard failure — the graph is an optional living-truth layer
14
+ * that a fresh project has not built yet, not a broken install.
15
+ */
16
+
17
+ import { validateGraph } from '../graph/validate.mjs';
18
+
19
+ export function checkGraphValidateForDoctor({ rootDir, strict = false, deploymentMode } = {}) {
20
+ const result = validateGraph(rootDir, { strict, deploymentMode });
21
+ const noGraph = result.errors.some((e) => e.startsWith('no graph found'));
22
+
23
+ if (noGraph) {
24
+ return {
25
+ ok: true,
26
+ warning: true,
27
+ label: 'Living graph: not built yet (run `construct graph build`)',
28
+ errors: [],
29
+ warnings: [],
30
+ result,
31
+ };
32
+ }
33
+
34
+ const ok = result.valid;
35
+ const label = ok
36
+ ? `Living graph valid (${result.warnings.length} warning${result.warnings.length === 1 ? '' : 's'})`
37
+ : `Living graph invalid: ${result.errors.length} error${result.errors.length === 1 ? '' : 's'} (run \`construct graph validate\`)`;
38
+
39
+ return {
40
+ ok,
41
+ warning: false,
42
+ label,
43
+ errors: result.errors,
44
+ warnings: result.warnings,
45
+ result,
46
+ };
47
+ }
@@ -26,15 +26,28 @@ import * as consistency from './watchers/consistency.mjs';
26
26
  import * as mcpProtocol from './watchers/mcp-protocol.mjs';
27
27
  import * as cxBudget from './watchers/cx-budget.mjs';
28
28
  import * as graphStaleness from './watchers/graph-staleness.mjs';
29
+ import * as providerBreaker from './watchers/provider-breaker.mjs';
30
+ import * as oracleLiveness from './watchers/oracle-liveness.mjs';
31
+ import * as orchestrationRuns from './watchers/orchestration-runs.mjs';
29
32
  import { stateDir } from '../config/xdg.mjs';
33
+ import { isMainModule } from '../roots.mjs';
30
34
 
31
35
  const STATE_PATH = join(stateDir(), 'doctor.json');
32
- const WATCHERS = [disk, cost, processPressure, serviceHealth, bdWatch, handoffs, consistency, mcpProtocol, cxBudget, graphStaleness];
36
+ const WATCHERS = [disk, cost, processPressure, serviceHealth, bdWatch, handoffs, consistency, mcpProtocol, cxBudget, graphStaleness, providerBreaker, oracleLiveness, orchestrationRuns];
33
37
 
34
38
  let running = false;
35
39
  let timers = [];
36
40
  let lastTickAt = {};
37
41
 
42
+ // Most watchers read only global/XDG state and never needed the project
43
+ // directory. oracle-liveness is the first that does (listPending(projectDir))
44
+ // — threaded here rather than each watcher reaching for process.cwd() itself,
45
+ // so a test can drive tick() against a fixture project without touching the
46
+ // process-wide cwd. start() defaults to process.cwd() because the daemon is
47
+ // always spawned with the project root as its own cwd (lib/service-manager.mjs).
48
+
49
+ let currentProjectDir = process.cwd();
50
+
38
51
  function writeState(state) {
39
52
  const dir = stateDir();
40
53
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
@@ -68,7 +81,7 @@ async function runWatcher(watcher) {
68
81
  if (!running) return;
69
82
  const startedAt = Date.now();
70
83
  try {
71
- const result = await watcher.tick();
84
+ const result = await watcher.tick({ projectDir: currentProjectDir });
72
85
  lastTickAt[watcher.name] = startedAt;
73
86
  const tookMs = Date.now() - startedAt;
74
87
  if (process.env.CONSTRUCT_DOCTOR_VERBOSE === '1') {
@@ -87,10 +100,11 @@ async function runWatcher(watcher) {
87
100
  }
88
101
  }
89
102
 
90
- export async function start() {
103
+ export async function start({ projectDir = process.cwd() } = {}) {
91
104
  if (running) return;
92
105
  running = true;
93
106
  lastTickAt = {};
107
+ currentProjectDir = projectDir;
94
108
 
95
109
  writeState({ pid: process.pid, startedAt: Date.now(), watchers: WATCHERS.map((w) => w.name) });
96
110
  record({
@@ -138,7 +152,7 @@ export function statusSnapshot() {
138
152
  }
139
153
 
140
154
  const isMain = (() => {
141
- try { return import.meta.url === `file://${process.argv[1]}`; } catch { return false; }
155
+ try { return isMainModule(import.meta.url); } catch { return false; }
142
156
  })();
143
157
  if (isMain) {
144
158
  // A long-lived daemon in its own process: wire the audit sink at the entry so any
@@ -0,0 +1,56 @@
1
+ /**
2
+ * lib/doctor/sidecar-providers.mjs — doctor checks for governed ingestion sidecars.
3
+ *
4
+ * docling and whisper are declared as `ingestion-provider` manifests
5
+ * (lib/extensions/manifests/docling.manifest.json, whisper.manifest.json) with
6
+ * an install probe, a health check, and a degradation chain. This module turns
7
+ * those probes into doctor-shaped findings so `construct doctor` reports
8
+ * install+health accurately whether or not the sidecar is present — mirroring
9
+ * lib/doctor/project-adapters.mjs's pattern of a pure, directly-testable
10
+ * function rather than inline logic in the CLI entrypoint.
11
+ */
12
+ import { probeHealth } from '../ingest/sidecar-providers.mjs';
13
+
14
+ /**
15
+ * checkSidecarProviderForDoctor(id, opts) — one provider's doctor-shaped finding.
16
+ *
17
+ * Absence is reported as an optional (non-fatal) finding with an actionable
18
+ * label; presence with a failing health check is reported as a non-optional
19
+ * finding since an installed-but-broken sidecar is a real defect, not an
20
+ * expected absence.
21
+ *
22
+ * @param {string} id 'docling' | 'whisper'
23
+ * @param {object} [opts] forwarded to lib/ingest/sidecar-providers.mjs probeHealth (injectable seams for fake probes)
24
+ * @returns {{ ok: boolean, label: string, optional: boolean }}
25
+ */
26
+ export function checkSidecarProviderForDoctor(id, opts = {}) {
27
+ const health = probeHealth(id, opts);
28
+
29
+ if (!health.ok) {
30
+ return { ok: false, label: `Ingestion provider '${id}': check failed (${health.detail})`, optional: false };
31
+ }
32
+ if (!health.installProbe.installed) {
33
+ return {
34
+ ok: true,
35
+ label: `Ingestion provider '${id}': not installed (${health.detail}) — ingest degrades per its declared fallback chain`,
36
+ optional: true,
37
+ };
38
+ }
39
+ if (!health.healthy) {
40
+ return { ok: false, label: `Ingestion provider '${id}': installed but unhealthy (${health.detail})`, optional: false };
41
+ }
42
+ return { ok: true, label: `Ingestion provider '${id}': ${health.detail}`, optional: true };
43
+ }
44
+
45
+ /**
46
+ * checkSidecarProvidersForDoctor(opts) — docling + whisper doctor findings.
47
+ *
48
+ * @param {object} [opts] forwarded per-provider to checkSidecarProviderForDoctor
49
+ * @returns {Array<{ ok: boolean, label: string, optional: boolean }>}
50
+ */
51
+ export function checkSidecarProvidersForDoctor(opts = {}) {
52
+ return [
53
+ checkSidecarProviderForDoctor('docling', opts),
54
+ checkSidecarProviderForDoctor('whisper', opts),
55
+ ];
56
+ }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * lib/doctor/watchers/consistency.mjs — cross-surface drift watcher.
3
3
  *
4
- * Five drift checks bundled at one cadence:
4
+ * Drift checks bundled at one cadence, including:
5
5
  * 1. Hook manifest ↔ files: every entry in platforms/claude/settings.template.json
6
6
  * points to a real .mjs file in lib/hooks/ that parses.
7
7
  * 2. MCP registration ↔ impl: every `name === '<tool>'` branch in the MCP
@@ -13,6 +13,16 @@
13
13
  * registry.agents[].promptFile exists on disk.
14
14
  * 5. Contracts ↔ schemas: specialists/org passes validateContractsFile
15
15
  * (cross-file refs, producer/consumer names, schema paths).
16
+ * 6. Postcondition coverage (construct-rf26.12): reports the executable-
17
+ * vs-advisory split across specialists/org/contracts/*.json postconditions
18
+ * (lib/contracts/coverage.mjs); a nonzero unclassified count is drift.
19
+ * 7. Host surfaces: adapter config present for installed AI hosts.
20
+ * 8. Export engine health: pandoc/typst/libreoffice/pptxgenjs presence via
21
+ * lib/doctor/engine-health.mjs (LMCP-K3).
22
+ * 9. Embedding model health: active embedding model, and whether the default
23
+ * local model's @huggingface/transformers dependency resolves, via
24
+ * lib/doctor/embedding-health.mjs (LMCP-K4).
25
+ * 10. Credential parity: see lib/doctor/watchers/credential-parity.mjs.
16
26
  *
17
27
  * Each violation produces an audit record. Blocking severity escalates to a
18
28
  * role intake; advisory severity records only. Tick is cheap — reads JSON +
@@ -27,6 +37,8 @@ import { fileURLToPath } from 'node:url';
27
37
 
28
38
  import { record } from '../audit.mjs';
29
39
  import { escalate } from '../escalate.mjs';
40
+ import { checkEngineHealthAllForDoctor } from '../engine-health.mjs';
41
+ import { checkEmbeddingModelForDoctor } from '../embedding-health.mjs';
30
42
 
31
43
  export const name = 'consistency';
32
44
  export const intervalMs = 15 * 60 * 1000;
@@ -87,9 +99,18 @@ export async function runAllChecks({ repoRoot = REPO_ROOT } = {}) {
87
99
  const contracts = await checkContractsDrift({ repoRoot });
88
100
  collect(contracts, findings, passed, 'contracts-drift');
89
101
 
102
+ const postconditionCoverage = await checkPostconditionCoverage({ repoRoot });
103
+ collect(postconditionCoverage, findings, passed, 'postcondition-coverage');
104
+
90
105
  const hosts = checkHostSurfaces({ repoRoot });
91
106
  collect(hosts, findings, passed, 'host-surfaces');
92
107
 
108
+ const engines = checkEngineHealthDrift();
109
+ collect(engines, findings, passed, 'export-engine-health');
110
+
111
+ const embeddingModel = await checkEmbeddingModelDrift();
112
+ collect(embeddingModel, findings, passed, 'embedding-model-health');
113
+
93
114
  const { runCredentialParityChecks } = await import('./credential-parity.mjs');
94
115
  const credentials = await runCredentialParityChecks({ rootDir: repoRoot });
95
116
  collect(credentials, findings, passed, 'credential-parity');
@@ -422,6 +443,32 @@ async function checkContractsDrift({ repoRoot }) {
422
443
  return { summary: `contracts: ${violations.length} drift`, violations };
423
444
  }
424
445
 
446
+ // Reports executable-vs-advisory postcondition coverage (construct-rf26.12).
447
+ // Informational only — never fails on a low percentage, since advisory
448
+ // postconditions are a legitimate, deliberate classification, not drift. A
449
+ // nonzero `unclassified` count IS drift: a legacy bare-string postcondition
450
+ // that slipped past the rf26.12 migration.
451
+
452
+ async function checkPostconditionCoverage({ repoRoot }) {
453
+ try {
454
+ const { computePostconditionCoverage, formatCoverageSummary } = await import('../../contracts/coverage.mjs');
455
+ const coverage = computePostconditionCoverage({ repoRoot });
456
+ const violations = coverage.unclassified > 0
457
+ ? [{
458
+ category: 'postcondition-coverage',
459
+ severity: 'warning',
460
+ summary: `${coverage.unclassified} postcondition(s) across contracts have no postconditionType (executable|advisory) classification`,
461
+ }]
462
+ : [];
463
+ return { summary: formatCoverageSummary(coverage), violations };
464
+ } catch (err) {
465
+ return {
466
+ summary: 'postcondition coverage unavailable',
467
+ violations: [{ category: 'postcondition-coverage', severity: 'warning', summary: `postcondition coverage check failed: ${err.message}` }],
468
+ };
469
+ }
470
+ }
471
+
425
472
  // ── Check 6: host surfaces ─────────────────────────────────────────────────
426
473
 
427
474
  function getVSCodeSettingsDir(homeDir) {
@@ -463,3 +510,48 @@ function checkHostSurfaces({ repoRoot: _repoRoot }) {
463
510
 
464
511
  return { summary: `host-surfaces: ${surfaces.length - absent.length}/${surfaces.length} present`, violations };
465
512
  }
513
+
514
+ // ── Check 7: export engine health ──────────────────────────────────────────
515
+ //
516
+ // pandoc/typst/libreoffice/pptxgenjs have no preflight today; failures surface
517
+ // mid-export. lib/doctor/engine-health.mjs probes the same detect() export
518
+ // preflight uses, so absence here and an export refusal carry the same
519
+ // install hint (LMCP-K3).
520
+
521
+ function checkEngineHealthDrift() {
522
+ const violations = [];
523
+ const findingsList = checkEngineHealthAllForDoctor();
524
+ const absent = findingsList.filter((f) => !f.installed);
525
+
526
+ for (const f of absent) {
527
+ violations.push({
528
+ category: 'export-engine-health',
529
+ severity: 'warning',
530
+ tier: 'actionable',
531
+ summary: f.label,
532
+ });
533
+ }
534
+
535
+ return {
536
+ summary: `export-engine-health: ${findingsList.length - absent.length}/${findingsList.length} engines present`,
537
+ violations,
538
+ };
539
+ }
540
+
541
+ // ── Check 8: embedding model health ────────────────────────────────────────
542
+ //
543
+ // `local` (@huggingface/transformers) is the embedding engine's default and
544
+ // degrades silently to hashing on failure; lib/doctor/embedding-health.mjs
545
+ // surfaces that degradation so it is visible before retrieval quality drops
546
+ // unexplained (LMCP-K4).
547
+
548
+ async function checkEmbeddingModelDrift() {
549
+ const finding = await checkEmbeddingModelForDoctor();
550
+ if (finding.degraded) {
551
+ return {
552
+ summary: finding.label,
553
+ violations: [{ category: 'embedding-model-health', severity: 'warning', tier: 'actionable', summary: finding.label }],
554
+ };
555
+ }
556
+ return { summary: finding.label, violations: [] };
557
+ }
@@ -65,7 +65,7 @@ export async function tick() {
65
65
  kind: 'action',
66
66
  watcher: name,
67
67
  action: 'emergency-reclaim',
68
- target: '.cx/traces|.cx/runtime/worker',
68
+ target: 'state-root:traces|state-root:runtime/worker',
69
69
  summary: `emergency reclaim removed ${emergencyResult.removed.length} file(s), freed ${emergencyResult.bytesFreed} bytes`,
70
70
  context: emergencyResult,
71
71
  });
@@ -2,7 +2,7 @@
2
2
  * lib/doctor/watchers/graph-staleness.mjs — dependency graph freshness watcher.
3
3
  *
4
4
  * Ticks every 30 minutes. When registry/contracts/workflow seeds changed since
5
- * the last `construct graph build`, records an audit entry so doctor surfaces
5
+ * the last `construct matrix build`, records an audit entry so doctor surfaces
6
6
  * stale-graph drift without waiting for the next Oracle tick.
7
7
  */
8
8
 
@@ -35,10 +35,27 @@ function readJson(file) {
35
35
 
36
36
  export function collectMcpServers({ home = homedir() } = {}) {
37
37
  const servers = [];
38
+ const seenClaudeIds = new Set();
39
+
40
+ // Claude Code's user-scope MCP servers live in ~/.claude.json's top-level
41
+ // `mcpServers`, not settings.json (settings.json carries hooks/permissions
42
+ // only — construct-ranh). ~/.claude/settings.json is still checked so a
43
+ // legacy pre-migration install's (inert, but still worth flagging) entries
44
+ // are not silently dropped from the probe sweep.
45
+
46
+ const claudeUserConfigPath = join(home, '.claude.json');
47
+ if (existsSync(claudeUserConfigPath)) {
48
+ const cfg = readJson(claudeUserConfigPath);
49
+ for (const [id, entry] of Object.entries(cfg?.mcpServers ?? {})) {
50
+ servers.push({ host: 'claude', id, entry });
51
+ seenClaudeIds.add(id);
52
+ }
53
+ }
38
54
  const claudePath = join(home, '.claude', 'settings.json');
39
55
  if (existsSync(claudePath)) {
40
56
  const cfg = readJson(claudePath);
41
57
  for (const [id, entry] of Object.entries(cfg?.mcpServers ?? {})) {
58
+ if (seenClaudeIds.has(id)) continue;
42
59
  servers.push({ host: 'claude', id, entry });
43
60
  }
44
61
  }