@geraldmaron/construct 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +55 -38
  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 +261 -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 +103 -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
package/lib/status.mjs CHANGED
@@ -26,6 +26,21 @@ import { resolveTraceBackend, telemetryProviderLabel } from './telemetry/client.
26
26
  import { doctorRoot } from './config/xdg.mjs';
27
27
  import { resolveUiColors } from './ui/theme.mjs';
28
28
  import { applyLinks, terminalLinksEnabled } from './ui/links.mjs';
29
+ import { getModeCapabilityStatus, getUnsupportedCapabilities, getCapabilities, categorizeEnterpriseCapability } from './mode-capabilities.mjs';
30
+ import { ProviderRegistry } from './embed/providers/registry.mjs';
31
+ import { resolveProviders } from './providers/registry.mjs';
32
+ import { getState as getBreakerState } from './providers/circuit-breaker.mjs';
33
+ import { ConsumptionBudgetStore } from './policy/consumption-budget.mjs';
34
+ import { loadGraph, nodesByType, nodeId } from './graph/store.mjs';
35
+ import { validateGraph } from './graph/validate.mjs';
36
+ import { computeLastExecutionByWorkflow } from './graph/runtime-evidence.mjs';
37
+ import { summarizeTeamHealth } from './team/health.mjs';
38
+ import { loadProjectConfig } from './config/project-config.mjs';
39
+ import { projectKey } from './orchestration/store.mjs';
40
+ import { resolveTraceStore } from './orchestration/trace-store.mjs';
41
+ import { resolveSharedMemoryStore } from './storage/shared-memory.mjs';
42
+ import { runtimeDir as orchestrationRuntimeDir } from './orchestration/run-store.mjs';
43
+ import { resolveStateDir } from './state-root.mjs';
29
44
  const TOTAL_BYTES_WARNING_THRESHOLD = 750_000;
30
45
 
31
46
  function readJSON(path) {
@@ -190,6 +205,252 @@ export function readSessionUsage(homeDir) {
190
205
  }
191
206
 
192
207
 
208
+ /**
209
+ * readDegradationRecords(cwd)
210
+ *
211
+ * Reads `.cx/degradation.jsonl` (written by `requireTeamCapabilityOrDegrade` in
212
+ * deployment-mode.mjs) and returns an array of structured degradation detail objects.
213
+ *
214
+ * Each record in the JSONL file has the shape written by deployment-mode.mjs:
215
+ * { ts, mode, subsystem, degradedOk: true }
216
+ *
217
+ * Each record is surfaced as:
218
+ * { subsystem, declared, actual, reason }
219
+ *
220
+ * `declared` and `actual` are inferred from the subsystem name when not
221
+ * explicitly present in the record (the deployment-mode writer does not yet emit
222
+ * them). `reason` reflects the CONSTRUCT_DEGRADED_OK value.
223
+ *
224
+ * Returns an empty array when the file is absent or unreadable.
225
+ */
226
+ function readDegradationRecords(cwd) {
227
+ const filePath = join(cwd, '.cx', 'degradation.jsonl');
228
+ if (!existsSync(filePath)) return [];
229
+ let lines;
230
+ try {
231
+ lines = readFileSync(filePath, 'utf8').split('\n').filter(Boolean);
232
+ } catch {
233
+ return [];
234
+ }
235
+ const records = [];
236
+ for (const line of lines) {
237
+ try {
238
+ const entry = JSON.parse(line);
239
+ if (!entry || typeof entry.subsystem !== 'string') continue;
240
+ // Infer declared/actual from the subsystem id when not explicit.
241
+ // Convention: subsystem ids use the format "<declared>-queue", "<declared>-memory", etc.
242
+ const declared = entry.declared ?? entry.subsystem;
243
+ const actual = entry.actual ?? 'fallback';
244
+ const reason = entry.reason
245
+ ?? (entry.degradedOk ? `CONSTRUCT_DEGRADED_OK=${entry.subsystem} set` : 'degraded');
246
+ records.push({
247
+ subsystem: entry.subsystem,
248
+ declared,
249
+ actual,
250
+ reason,
251
+ ts: entry.ts ?? null,
252
+ mode: entry.mode ?? null,
253
+ });
254
+ } catch {
255
+ // malformed line — skip
256
+ }
257
+ }
258
+ return records;
259
+ }
260
+
261
+ /**
262
+ * countPersonaDegradedRuns(cwd)
263
+ *
264
+ * Reads orchestration run records from the machine-scoped state root
265
+ * (`<stateRoot>/runtime/orchestration/runs/`, ADR-0066 — same directory
266
+ * lib/orchestration/run-store.mjs's runtimeDir() writes to) and counts runs
267
+ * carrying at least one task with `personaAvailable === false` (LMCP-E2:
268
+ * solo-mode persona fallback). Reads the filesystem store directly — the
269
+ * same best-effort, no-throw pattern as readDegradationRecords — since
270
+ * status is a read-only surface and must not fail when a run file is
271
+ * corrupt or the sqlite/postgres store is in use instead of the default.
272
+ *
273
+ * Returns { total, runs } where `runs` is the degraded run ids (capped at 20
274
+ * for the summary), and `total` counts every matching run.
275
+ */
276
+ function countPersonaDegradedRuns(cwd) {
277
+ const dir = join(orchestrationRuntimeDir(cwd), 'runs');
278
+ if (!existsSync(dir)) return { total: 0, runs: [] };
279
+ let entries;
280
+ try {
281
+ entries = readdirSync(dir);
282
+ } catch {
283
+ return { total: 0, runs: [] };
284
+ }
285
+ const runs = [];
286
+ for (const name of entries) {
287
+ if (!name.endsWith('.json')) continue;
288
+ try {
289
+ const run = JSON.parse(readFileSync(join(dir, name), 'utf8'));
290
+ const hasPersonaFallback = (run.tasks || []).some((t) => t.personaAvailable === false);
291
+ if (hasPersonaFallback) runs.push(run.runId || name.replace(/\.json$/, ''));
292
+ } catch {
293
+ // a corrupt run file is skipped, not fatal to the count
294
+ }
295
+ }
296
+ return { total: runs.length, runs: runs.slice(0, 20) };
297
+ }
298
+
299
+ /**
300
+ * summarizeRecentRunExecutionStates(cwd, opts)
301
+ *
302
+ * Reads orchestration run records from the machine-scoped state root (same
303
+ * `<stateRoot>/runtime/orchestration/runs/` directory as
304
+ * countPersonaDegradedRuns above, ADR-0066) and buckets the most recent runs
305
+ * by their LMCP-F4 run-level `executionState`
306
+ * (prepared|executed|degraded-executed|failed|unknown), so `construct status`
307
+ * can distinguish a run that only prepared specialist work from one that
308
+ * actually executed it — the honesty gap construct-1yhp.2 and incident
309
+ * run-02158a157d53 (construct-neq9.7) both trace back to.
310
+ *
311
+ * A run with no tasks (prompt-only/host-direct) or a pre-F4 legacy record
312
+ * carrying no `executionState` field is bucketed `unknown` rather than guessed
313
+ * into `prepared` or `executed`.
314
+ *
315
+ * Returns { total, byState: {prepared,executed,degraded-executed,failed,unknown},
316
+ * recent: [{runId, executionState, status, createdAt}] } ordered newest-first
317
+ * and capped at `limit` (default 10) for the `recent` list; `byState` counts
318
+ * every readable run regardless of the cap.
319
+ */
320
+ function summarizeRecentRunExecutionStates(cwd, { limit = 10 } = {}) {
321
+ const dir = join(orchestrationRuntimeDir(cwd), 'runs');
322
+ const empty = { total: 0, byState: { prepared: 0, executed: 0, 'degraded-executed': 0, failed: 0, unknown: 0 }, recent: [] };
323
+ if (!existsSync(dir)) return empty;
324
+ let entries;
325
+ try {
326
+ entries = readdirSync(dir);
327
+ } catch {
328
+ return empty;
329
+ }
330
+ const runs = [];
331
+ for (const name of entries) {
332
+ if (!name.endsWith('.json')) continue;
333
+ try {
334
+ const run = JSON.parse(readFileSync(join(dir, name), 'utf8'));
335
+ runs.push({
336
+ runId: run.runId || name.replace(/\.json$/, ''),
337
+ executionState: run.executionState ?? 'unknown',
338
+ status: run.status ?? 'unknown',
339
+ createdAt: run.createdAt ?? null,
340
+ });
341
+ } catch {
342
+ // a corrupt run file is skipped, not fatal to the summary
343
+ }
344
+ }
345
+ runs.sort((a, b) => String(b.createdAt || '').localeCompare(String(a.createdAt || '')));
346
+
347
+ const byState = { prepared: 0, executed: 0, 'degraded-executed': 0, failed: 0, unknown: 0 };
348
+ for (const r of runs) {
349
+ const key = Object.hasOwn(byState, r.executionState) ? r.executionState : 'unknown';
350
+ byState[key] += 1;
351
+ }
352
+
353
+ return { total: runs.length, byState, recent: runs.slice(0, limit) };
354
+ }
355
+
356
+ // LMCP-G10: cross-machine read side of team mode's trace/memory boundary.
357
+ // lib/orchestration/runtime.mjs's persistTeamTrace writes every completed
358
+ // run's lifecycle trace into the same postgres-backed trace store this reads
359
+ // back from, keyed on the same projectKey(config, cwd) — a trace worker A's
360
+ // machine wrote is the exact row machine B's `construct status` lists here.
361
+ // Shared memory (lib/storage/shared-memory.mjs) is listed the same way; only
362
+ // records that opted in with visibility 'shared-project' + provenance ever
363
+ // appear. Solo mode (both stores resolve to kind:'none') reports
364
+ // unavailable rather than fabricating an empty list as "no team activity".
365
+
366
+ async function summarizeSharedState(cwd, env, config) {
367
+ const project = projectKey(config, cwd);
368
+ const traceStore = resolveTraceStore({ env, cwd, config });
369
+ const memoryStore = resolveSharedMemoryStore({ env, cwd, config });
370
+ if (traceStore.kind !== 'postgres' && memoryStore.kind !== 'postgres') {
371
+ return { status: 'unavailable', reason: 'no-team-store-configured', recentTraces: [], sharedMemory: [] };
372
+ }
373
+ const [recentTraces, sharedMemory] = await Promise.all([
374
+ traceStore.kind === 'postgres' ? traceStore.listTeamTraces({ project, limit: 5 }).catch(() => []) : Promise.resolve([]),
375
+ memoryStore.kind === 'postgres' ? memoryStore.listSharedMemory({ project, limit: 5 }).catch(() => []) : Promise.resolve([]),
376
+ ]);
377
+ return { status: 'healthy', project, recentTraces, sharedMemory };
378
+ }
379
+
380
+ /**
381
+ * summarizeGraphWorkflows(rootDir)
382
+ *
383
+ * Classifies every workflow node in the living graph (LMCP-C1/C2) as
384
+ * available / degraded / missing, sourced from the same validateGraph() the
385
+ * `graph validate` CLI and the doctor check (lib/doctor/graph-validate.mjs)
386
+ * both call — so status, doctor, and `construct graph validate` can never
387
+ * report three different truths about the same workflow.
388
+ *
389
+ * - missing: no `workflow:<id>` node exists in the graph (or the graph has
390
+ * not been built yet).
391
+ * - degraded: the node exists but validateGraph raised an error or warning
392
+ * that names this workflow, or the most recent runtime-evidence outcome
393
+ * (LMCP-C9, runtime-evidence.mjs) for it is 'failed'.
394
+ * - available: the node exists and neither of the above applies.
395
+ *
396
+ * Read-only against .cx/graph (project-local) and the machine-scoped
397
+ * orchestration run store (ADR-0066) — this function never writes to the
398
+ * graph store or the runtime-evidence store.
399
+ */
400
+ function summarizeGraphWorkflows(rootDir) {
401
+ const graph = loadGraph(rootDir);
402
+ if (!graph.exists) {
403
+ return { available: [], degraded: [], missing: [], graphBuilt: false, errors: [], warnings: [] };
404
+ }
405
+
406
+ const validation = validateGraph(rootDir);
407
+ const graphWorkflowIds = new Set(nodesByType(graph, 'workflow').map((n) => n.id));
408
+ const lastExecutionByWorkflow = computeLastExecutionByWorkflow(rootDir);
409
+
410
+ const findingsFor = (id, name) => {
411
+ const needles = [id, name].filter(Boolean);
412
+ const matches = (msg) => needles.some((needle) => msg.includes(`'${needle}'`));
413
+ return {
414
+ errors: validation.errors.filter(matches),
415
+ warnings: validation.warnings.filter(matches),
416
+ };
417
+ };
418
+
419
+ const available = [];
420
+ const degraded = [];
421
+ const missing = [];
422
+
423
+ for (const type of Object.keys(lastExecutionByWorkflow)) {
424
+ const id = nodeId('workflow', type);
425
+ const evidence = lastExecutionByWorkflow[type];
426
+ if (!graphWorkflowIds.has(id)) {
427
+ missing.push({ type, reason: 'no workflow node in living graph' });
428
+ continue;
429
+ }
430
+ const findings = findingsFor(id, type);
431
+ const evidenceFailed = evidence?.outcome === 'failed';
432
+ if (findings.errors.length > 0 || findings.warnings.length > 0 || evidenceFailed) {
433
+ degraded.push({
434
+ type,
435
+ errors: findings.errors,
436
+ warnings: findings.warnings,
437
+ lastExecution: evidence,
438
+ });
439
+ } else {
440
+ available.push({ type, lastExecution: evidence });
441
+ }
442
+ }
443
+
444
+ return {
445
+ available,
446
+ degraded,
447
+ missing,
448
+ graphBuilt: true,
449
+ errors: validation.errors,
450
+ warnings: validation.warnings,
451
+ };
452
+ }
453
+
193
454
  function newestMtime(dir) {
194
455
  if (!existsSync(dir)) return null;
195
456
  let newest = 0;
@@ -438,6 +699,79 @@ function summarizeIntegrations(features) {
438
699
  return { counts, summary: summary || 'No integrations detected' };
439
700
  }
440
701
 
702
+ // Embed daemon provider set is the unified extension registry (lib/extensions/*)
703
+ // filtered by capability + credential presence (LMCP-B3) — this reads the same
704
+ // ProviderRegistry.fromEnv() the daemon builds, so status can never drift from
705
+ // what the daemon actually resolves.
706
+
707
+ async function summarizeEmbedProviders(env) {
708
+ const registry = await ProviderRegistry.fromEnv(env);
709
+ const available = registry.names().filter((name, idx, all) => all.indexOf(name) === idx);
710
+ const unavailable = registry.unavailable();
711
+
712
+ const summary = [
713
+ available.length ? `${available.length} available` : null,
714
+ unavailable.length ? `${unavailable.length} unavailable` : null,
715
+ ].filter(Boolean).join(' · ') || 'no embed providers configured';
716
+
717
+ return {
718
+ summary,
719
+ available,
720
+ unavailable: unavailable.map((u) => ({ id: u.id, reason: u.reason })),
721
+ };
722
+ }
723
+
724
+ // Circuit-breaker state (lib/providers/circuit-breaker.mjs) is per resolved
725
+ // data-source provider (lib/providers/registry.mjs wraps read/search/watch/
726
+ // write/webhook with a breaker keyed 'provider:<id>', failureThreshold=5,
727
+ // cooldownMs=30_000 — LMCP-B9). Reading it here through resolveProviders()
728
+ // means status can never show a provider getBreaker never wrapped.
729
+
730
+ async function summarizeProviderBreakers({ rootDir, env }) {
731
+ const { providers, errors } = await resolveProviders({ rootDir, env });
732
+ const entries = Object.keys(providers).sort().map((id) => {
733
+ const breaker = getBreakerState(`provider:${id}`);
734
+ const state = breaker ? breaker.state : 'CLOSED';
735
+ return {
736
+ id,
737
+ state,
738
+ failures: breaker ? breaker.failures : 0,
739
+ lastFailure: breaker?.lastFailure ? breaker.lastFailure.toISOString() : null,
740
+ open: state === 'OPEN',
741
+ };
742
+ });
743
+ const openCount = entries.filter((e) => e.open).length;
744
+ const summary = entries.length === 0
745
+ ? 'No providers configured'
746
+ : openCount > 0
747
+ ? `${openCount} of ${entries.length} provider circuit(s) OPEN`
748
+ : `${entries.length} provider(s), all circuits closed`;
749
+ return { entries, openCount, summary, loadErrors: errors };
750
+ }
751
+
752
+ // Per-actor/run durable consumption (lib/policy/consumption-budget.mjs,
753
+ // LMCP-N5) — accrued by lib/mcp/broker.mjs's Broker.invoke whenever a caller
754
+ // passes a runId. Rows only exist for runs that have actually consumed
755
+ // something, so an empty list here means no run has recorded consumption
756
+ // yet, not that budgets are unconfigured.
757
+
758
+ function summarizeConsumptionBudgets({ rootDir, env }) {
759
+ const store = new ConsumptionBudgetStore({ rootDir, env });
760
+ const entries = store.allEntries().map((entry) => {
761
+ const overBudget = entry.budget
762
+ ? Object.entries(entry.budget).some(([kind, cap]) => Number.isFinite(cap) && entry.consumption[kind] > cap)
763
+ : false;
764
+ return { ...entry, overBudget };
765
+ });
766
+ const overCount = entries.filter((e) => e.overBudget).length;
767
+ const summary = entries.length === 0
768
+ ? 'No run consumption recorded yet'
769
+ : overCount > 0
770
+ ? `${overCount} of ${entries.length} run(s) over budget`
771
+ : `${entries.length} run(s) tracked, all within budget`;
772
+ return { entries, overCount, summary };
773
+ }
774
+
441
775
  function summarizePlugins(pluginRegistry) {
442
776
  const plugins = pluginRegistry?.plugins ?? [];
443
777
  const external = plugins.filter((plugin) => !plugin.builtIn);
@@ -497,14 +831,14 @@ function traceBackendDefinition(env) {
497
831
  return {
498
832
  id: 'telemetry',
499
833
  name: 'Telemetry',
500
- url: 'local://.cx/traces',
834
+ url: 'local://<state-root>/traces',
501
835
  runtime: 'local',
502
836
  note: backend === 'none' ? 'Remote disabled; local JSONL preserved' : 'Local JSONL traces',
503
837
  healthyMessage: 'Local trace capture enabled',
504
838
  impactsOverall: false,
505
839
  selfCheck: {
506
840
  status: backend === 'none' ? 'disabled' : 'healthy',
507
- message: backend === 'none' ? 'Remote telemetry disabled' : 'Writing .cx/traces/*.jsonl',
841
+ message: backend === 'none' ? 'Remote telemetry disabled' : 'Writing <state-root>/traces/*.jsonl',
508
842
  },
509
843
  };
510
844
  }
@@ -572,6 +906,111 @@ function serviceDefinitions(env) {
572
906
  ];
573
907
  }
574
908
 
909
+ // Storage health: real filesystem state plus a live LanceDB open probe (tableNames() call).
910
+ // A corrupted or locked store reports unhealthy. A timeout (default 2000ms) prevents a
911
+ // locked DB from stalling `construct status`. Results are cached with a short TTL.
912
+
913
+ const STORAGE_PROBE_CACHE = new Map();
914
+ const STORAGE_PROBE_CACHE_TTL_MS = 5000;
915
+
916
+ // Exported for testing: allows callers to inject a custom lancedb opener and clock.
917
+ export async function probeStorageHealth(
918
+ cwd,
919
+ {
920
+ env = process.env,
921
+ fsExistsSync = existsSync,
922
+ lancedbOpener = null,
923
+ probeTimeoutMs = 2000,
924
+ now = () => Date.now(),
925
+ } = {},
926
+ ) {
927
+ const cxExists = fsExistsSync(join(cwd, '.cx'));
928
+
929
+ // CONSTRUCT_LANCEDB_PATH wins when set (a managed install shares one
930
+ // machine-wide store); otherwise the index lives at the machine-scoped
931
+ // state root (ADR-0066), not a project-relative `.cx/lancedb` — mirrors
932
+ // lib/storage/vector-client.mjs's own resolution so status never reports a
933
+ // location the real store doesn't use.
934
+
935
+ const lancedbPath = env.CONSTRUCT_LANCEDB_PATH || resolveStateDir(cwd, 'lancedb', { ensureDir: false });
936
+ const lancedbExists = fsExistsSync(lancedbPath);
937
+
938
+ const sqlStore = { mode: 'lancedb', label: 'LanceDB + Git-Backed', dbUrl: null, vectorEnabled: true };
939
+
940
+ if (!cxExists) {
941
+ return {
942
+ sqlStore,
943
+ sqlHealth: { status: 'unavailable', message: 'Local store not initialized (.cx/ absent)' },
944
+ vectorStore: { enabled: false, backend: 'lancedb', label: 'Embedded LanceDB' },
945
+ };
946
+ }
947
+
948
+ if (!lancedbExists) {
949
+ return {
950
+ sqlStore,
951
+ sqlHealth: { status: 'degraded', message: 'Local embedded — no vector index yet (no lancedb store found)' },
952
+ vectorStore: { enabled: false, backend: 'lancedb', label: 'Embedded LanceDB' },
953
+ };
954
+ }
955
+
956
+ // Check cache before probing.
957
+ const cacheKey = lancedbPath;
958
+ const cached = STORAGE_PROBE_CACHE.get(cacheKey);
959
+ if (cached && now() - cached.at < STORAGE_PROBE_CACHE_TTL_MS) {
960
+ return cached.result;
961
+ }
962
+
963
+ // Probe the LanceDB store: open it and call tableNames() with a timeout.
964
+ let sqlHealth;
965
+ try {
966
+ const opener = lancedbOpener ?? (async (p) => {
967
+ const ldb = await import('@lancedb/lancedb');
968
+ return ldb.connect(p);
969
+ });
970
+
971
+ const probeResult = await Promise.race([
972
+ (async () => {
973
+ const db = await opener(lancedbPath);
974
+ await db.tableNames();
975
+ return { status: 'healthy', message: 'Local embedded' };
976
+ })(),
977
+ new Promise((resolve) =>
978
+ setTimeout(
979
+ () => resolve({ status: 'unhealthy', reason: 'LanceDB open timed out' }),
980
+ probeTimeoutMs,
981
+ ),
982
+ ),
983
+ ]);
984
+
985
+ if (probeResult.status === 'unhealthy') {
986
+ sqlHealth = { status: 'unhealthy', message: probeResult.reason };
987
+ } else {
988
+ sqlHealth = probeResult;
989
+ }
990
+ } catch (err) {
991
+ sqlHealth = { status: 'unhealthy', message: `LanceDB open failed: ${err?.message ?? String(err)}` };
992
+ }
993
+
994
+ const result = {
995
+ sqlStore,
996
+ sqlHealth,
997
+ vectorStore: {
998
+ enabled: sqlHealth.status === 'healthy',
999
+ backend: 'lancedb',
1000
+ label: 'Embedded LanceDB',
1001
+ },
1002
+ };
1003
+
1004
+ STORAGE_PROBE_CACHE.set(cacheKey, { at: now(), result });
1005
+ return result;
1006
+ }
1007
+
1008
+ // categorizeEnterpriseCapability (ADR-0057's active/fail-closed/later/absent
1009
+ // partition) lives in mode-capabilities.mjs — the single authoritative registry
1010
+ // module — and is re-exported here so existing status.mjs consumers/tests don't
1011
+ // need a second import path.
1012
+ export { categorizeEnterpriseCapability } from './mode-capabilities.mjs';
1013
+
575
1014
  export async function buildStatus({
576
1015
  rootDir,
577
1016
  cwd = process.cwd(),
@@ -591,6 +1030,9 @@ export async function buildStatus({
591
1030
  registry = { teams: {}, specialists: {}, contracts: {}, policies: {} };
592
1031
  }
593
1032
  const settings = readJSON(join(homeDir, '.claude', 'settings.json')) ?? {};
1033
+ // MCP server definitions live in ~/.claude.json's top-level `mcpServers`, not
1034
+ // settings.json (settings.json carries hooks/permissions only — construct-ranh).
1035
+ const claudeUserConfig = readJSON(join(homeDir, '.claude.json')) ?? {};
594
1036
  const features = await checkAllFeatures({ homeDir, cwd, env: mergedEnv });
595
1037
  const pluginRegistry = loadPluginRegistry({ cwd, homeDir, rootDir, env: mergedEnv });
596
1038
  const services = [];
@@ -622,6 +1064,9 @@ export async function buildStatus({
622
1064
 
623
1065
  const runtime = summarizeRuntime(services);
624
1066
  const integrations = summarizeIntegrations(features);
1067
+ const embedProviders = await summarizeEmbedProviders(mergedEnv);
1068
+ const providerBreakers = await summarizeProviderBreakers({ rootDir: cwd, env: mergedEnv });
1069
+ const consumptionBudgets = summarizeConsumptionBudgets({ rootDir: cwd, env: mergedEnv });
625
1070
  const plugins = summarizePlugins(pluginRegistry);
626
1071
  const contextInspection = inspectContextState(cwd);
627
1072
  const sessionEfficiency = readSessionEfficiency(homeDir);
@@ -634,10 +1079,12 @@ export async function buildStatus({
634
1079
  triggerAutoBackfillIfSparse(telemetryRichness, { ...mergedEnv });
635
1080
  const efficiencyDigest = summarizeEfficiencyData(readEfficiencyLog(homeDir));
636
1081
  const activeOverlays = getActiveOverlays(cwd);
637
- const sqlStore = { mode: 'lancedb', label: 'LanceDB + Git-Backed', dbUrl: null, vectorEnabled: true };
638
- const vectorStore = { enabled: true, backend: 'lancedb', label: 'Embedded LanceDB' };
639
- const sqlHealth = { status: 'healthy', message: 'Local embedded' };
1082
+ const { sqlStore, vectorStore, sqlHealth } = await probeStorageHealth(cwd, { env: mergedEnv });
640
1083
  const promotionRequests = getPromotionRequests(cwd);
1084
+ const degradationDetails = readDegradationRecords(cwd);
1085
+ const personaDegradedRuns = countPersonaDegradedRuns(cwd);
1086
+ const recentRunExecutionStates = summarizeRecentRunExecutionStates(cwd);
1087
+ const workflows = summarizeGraphWorkflows(cwd);
641
1088
  const executionContractModel = resolveExecutionContractModelMetadata({
642
1089
  envValues: mergedEnv,
643
1090
  registryModels: registry.models ?? {},
@@ -681,6 +1128,37 @@ export async function buildStatus({
681
1128
 
682
1129
  const deploymentMode = getDeploymentMode(mergedEnv);
683
1130
  const resourceMode = resolveResourceMode(deploymentMode);
1131
+ const capStatus = getModeCapabilityStatus(deploymentMode);
1132
+ const unsupportedCaps = getUnsupportedCapabilities(deploymentMode);
1133
+ const teamHealth = deploymentMode === 'team' || deploymentMode === 'enterprise'
1134
+ ? await summarizeTeamHealth({ rootDir: cwd, env: mergedEnv })
1135
+ : null;
1136
+ const sharedState = deploymentMode === 'team' || deploymentMode === 'enterprise'
1137
+ ? await summarizeSharedState(cwd, mergedEnv, loadProjectConfig(cwd, mergedEnv).config)
1138
+ : null;
1139
+
1140
+ // Enterprise mode: build a per-capability truth table so status/doctor can show
1141
+ // the real implementation state rather than a single aggregate label.
1142
+ // The overall enterprise verdict is 'unsupported' until all three ADR-0057
1143
+ // IMPLEMENT-NOW capabilities (tenant-isolation, rbac, mandatory-audit) are
1144
+ // marked 'implemented' in mode-capabilities.mjs.
1145
+ const ENTERPRISE_IMPLEMENT_NOW_IDS = new Set(['tenant-isolation', 'rbac', 'mandatory-audit']);
1146
+ let enterpriseCapabilityTable = null;
1147
+ let enterpriseVerdict = null;
1148
+ if (deploymentMode === 'enterprise') {
1149
+ const allCaps = getCapabilities('enterprise');
1150
+ enterpriseCapabilityTable = allCaps.map((cap) => ({
1151
+ id: cap.id,
1152
+ label: cap.label,
1153
+ status: cap.status,
1154
+ category: categorizeEnterpriseCapability(cap),
1155
+ implementNow: ENTERPRISE_IMPLEMENT_NOW_IDS.has(cap.id),
1156
+ }));
1157
+ const implementNowDone = allCaps
1158
+ .filter((cap) => ENTERPRISE_IMPLEMENT_NOW_IDS.has(cap.id))
1159
+ .every((cap) => cap.status === 'implemented');
1160
+ enterpriseVerdict = implementNowDone ? 'supported' : 'unsupported';
1161
+ }
684
1162
 
685
1163
  return {
686
1164
  version: pkg.version ?? '1.0.0',
@@ -689,6 +1167,12 @@ export async function buildStatus({
689
1167
  mode: deploymentMode,
690
1168
  resourceMode,
691
1169
  description: describeDeploymentMode(deploymentMode),
1170
+ capabilityStatus: capStatus,
1171
+ unsupportedCapabilities: capStatus !== 'fully-implemented' ? unsupportedCaps : [],
1172
+ ...(deploymentMode === 'enterprise' && {
1173
+ enterpriseCapabilityTable,
1174
+ enterpriseVerdict,
1175
+ }),
692
1176
  },
693
1177
  system: {
694
1178
  overall: runtime,
@@ -697,6 +1181,9 @@ export async function buildStatus({
697
1181
  plugins,
698
1182
  },
699
1183
  features,
1184
+ embedProviders,
1185
+ providerBreakers,
1186
+ consumptionBudgets,
700
1187
  plugins: {
701
1188
  status: plugins.status,
702
1189
  summary: plugins.summary,
@@ -724,7 +1211,7 @@ export async function buildStatus({
724
1211
  skills: listSkills(rootDir),
725
1212
  commands: listCommands(rootDir),
726
1213
  cliCommands: CLI_COMMANDS,
727
- mcpServers: Object.keys(settings.mcpServers ?? {}),
1214
+ mcpServers: Object.keys(claudeUserConfig.mcpServers ?? settings.mcpServers ?? {}),
728
1215
  publicHealth,
729
1216
  storage: {
730
1217
  sql: sqlStore,
@@ -736,6 +1223,8 @@ export async function buildStatus({
736
1223
  : { status: 'unavailable', message: 'No vector index configured; using file-state only' },
737
1224
  },
738
1225
  },
1226
+ teamHealth,
1227
+ sharedState,
739
1228
  executionContractModel,
740
1229
  sessionEfficiency,
741
1230
  efficiencyDigest,
@@ -743,6 +1232,10 @@ export async function buildStatus({
743
1232
  telemetryRichness,
744
1233
  overlays: activeOverlays,
745
1234
  promotionRequests,
1235
+ degradationDetails,
1236
+ personaDegradedRuns,
1237
+ recentRunExecutionStates,
1238
+ workflows,
746
1239
  };
747
1240
  }
748
1241
 
@@ -776,6 +1269,27 @@ export function formatStatusReport(status) {
776
1269
  if (status.deployment?.mode) {
777
1270
  const r = status.deployment.resourceMode || {};
778
1271
  lines.push(`Deployment: ${status.deployment.mode} · queue:${r.queue} · workers:${r.workers} · telemetry:${r.telemetry}`);
1272
+ if (status.deployment.capabilityStatus && status.deployment.capabilityStatus !== 'fully-implemented') {
1273
+ const caps = status.deployment.unsupportedCapabilities ?? [];
1274
+ lines.push(`Mode capabilities: ${status.deployment.capabilityStatus} · ${caps.length} not fully implemented: ${caps.map(c => c.label).join(', ')}`);
1275
+ }
1276
+ if (status.deployment.mode === 'enterprise') {
1277
+ lines.push(`Enterprise verdict: ${status.deployment.enterpriseVerdict ?? 'unsupported'}`);
1278
+ lines.push('WARNING: Enterprise mode: most capabilities not yet implemented. See construct status for details.');
1279
+ // ADR-0057 status contract: render only active/fail-closed/later — never a
1280
+ // bare 'not-implemented' that could read as active, and never collapse
1281
+ // fail-closed (hard-errors at runtime) into later (no runtime effect) or
1282
+ // vice versa. A capability categorized 'absent' is not advertised at all.
1283
+ const table = (status.deployment.enterpriseCapabilityTable ?? []).filter((cap) => cap.category !== 'absent');
1284
+ if (table.length > 0) {
1285
+ lines.push('Enterprise capability table:');
1286
+ for (const cap of table) {
1287
+ const icon = cap.category === 'active' ? '✓' : cap.category === 'fail-closed' ? '✗' : '·';
1288
+ const tag = cap.implementNow ? ' [implement-now]' : '';
1289
+ lines.push(` ${icon} ${cap.label} (${cap.category})${tag}`);
1290
+ }
1291
+ }
1292
+ }
779
1293
  }
780
1294
  lines.push('Coordination: external tracker + plan.md · single-writer per file · cass-memory for recall');
781
1295
  if (status.executionContractModel?.selectedTier && status.executionContractModel?.selectedModel) {
@@ -785,6 +1299,16 @@ export function formatStatusReport(status) {
785
1299
  lines.push(`Storage: ${status.storage.sql?.mode ?? 'unknown'} SQL · ${status.storage.vector?.mode ?? 'unknown'} vector`);
786
1300
  lines.push(`Storage health: SQL ${status.storage.health?.sql?.status ?? 'unknown'} · vector ${status.storage.health?.vector?.status ?? 'unknown'}`);
787
1301
  }
1302
+ if (status.teamHealth) {
1303
+ lines.push(`Team health: ${statusLabel(status.teamHealth.status)} · ${status.teamHealth.summary}`);
1304
+ }
1305
+ if (status.sharedState) {
1306
+ if (status.sharedState.status === 'unavailable') {
1307
+ lines.push('Shared state: unavailable · no team trace/memory store configured');
1308
+ } else {
1309
+ lines.push(`Shared state: ${status.sharedState.recentTraces.length} recent team trace(s) · ${status.sharedState.sharedMemory.length} shared memory record(s)`);
1310
+ }
1311
+ }
788
1312
  if (status.sessionEfficiency) {
789
1313
  lines.push(`Efficiency: ${statusLabel(status.sessionEfficiency.status)} · score ${status.sessionEfficiency.score.toFixed(2)} · ${status.sessionEfficiency.summary}`);
790
1314
  for (const warning of status.sessionEfficiency.warnings ?? []) {
@@ -826,6 +1350,43 @@ export function formatStatusReport(status) {
826
1350
  lines.push(` - ${request.domain} · ${request.status}${challenge}`);
827
1351
  }
828
1352
  }
1353
+ if ((status.degradationDetails ?? []).length > 0) {
1354
+ lines.push('Degradation details:');
1355
+ for (const d of status.degradationDetails) {
1356
+ lines.push(` ${d.subsystem}: declared=${d.declared} actual=${d.actual} (${d.reason})`);
1357
+ }
1358
+ }
1359
+ if ((status.personaDegradedRuns?.total ?? 0) > 0) {
1360
+ lines.push(`Persona-degraded runs: ${status.personaDegradedRuns.total} (solo-mode persona-fallback)`);
1361
+ }
1362
+ if ((status.recentRunExecutionStates?.total ?? 0) > 0) {
1363
+ const b = status.recentRunExecutionStates.byState;
1364
+ lines.push(
1365
+ `Recent runs: ${status.recentRunExecutionStates.total} total`
1366
+ + ` · prepared ${b.prepared}` + ` · executed ${b.executed}`
1367
+ + ` · degraded-executed ${b['degraded-executed']}` + ` · failed ${b.failed}`
1368
+ + (b.unknown ? ` · unknown ${b.unknown}` : ''),
1369
+ );
1370
+ for (const r of status.recentRunExecutionStates.recent.slice(0, 5)) {
1371
+ lines.push(` ${r.runId}: executionState=${r.executionState} (status=${r.status})`);
1372
+ }
1373
+ }
1374
+ if (status.workflows) {
1375
+ lines.push('');
1376
+ if (!status.workflows.graphBuilt) {
1377
+ lines.push('Workflows: living graph not built yet — run `construct graph build`');
1378
+ } else {
1379
+ const w = status.workflows;
1380
+ lines.push(`Workflows: ${w.available.length} available · ${w.degraded.length} degraded · ${w.missing.length} missing (source: graph validate)`);
1381
+ for (const entry of w.degraded) {
1382
+ const reason = entry.errors[0] || entry.warnings[0] || (entry.lastExecution?.outcome === 'failed' ? 'last run failed' : 'degraded');
1383
+ lines.push(` ⚠ ${entry.type} — ${reason}`);
1384
+ }
1385
+ for (const entry of w.missing) {
1386
+ lines.push(` ✗ ${entry.type} — ${entry.reason}`);
1387
+ }
1388
+ }
1389
+ }
829
1390
  lines.push('');
830
1391
  lines.push('Runtime');
831
1392
  for (const service of status.system.services) {
@@ -838,6 +1399,36 @@ export function formatStatusReport(status) {
838
1399
  for (const feature of status.features) {
839
1400
  lines.push(` ${serviceIcon(feature.status)} ${feature.name.padEnd(22)} ${statusLabel(feature.status)} — ${feature.message}`);
840
1401
  }
1402
+ if (status.embedProviders) {
1403
+ lines.push('');
1404
+ lines.push(`Embed providers: ${status.embedProviders.summary}`);
1405
+ for (const name of status.embedProviders.available) {
1406
+ lines.push(` ${serviceIcon('healthy')} ${name.padEnd(22)} configured`);
1407
+ }
1408
+ for (const entry of status.embedProviders.unavailable) {
1409
+ lines.push(` ${serviceIcon('unavailable')} ${entry.id.padEnd(22)} configured but unavailable — ${entry.reason}`);
1410
+ }
1411
+ }
1412
+ if (status.providerBreakers && status.providerBreakers.entries.length > 0) {
1413
+ lines.push('');
1414
+ lines.push(`Provider circuits: ${status.providerBreakers.summary}`);
1415
+ for (const entry of status.providerBreakers.entries) {
1416
+ const icon = entry.open ? serviceIcon('unavailable') : serviceIcon('healthy');
1417
+ const detail = entry.open ? ` — OPEN since ${entry.lastFailure} (${entry.failures} failures)` : '';
1418
+ lines.push(` ${icon} ${entry.id.padEnd(22)} ${entry.state}${detail}`);
1419
+ }
1420
+ }
1421
+ if (status.consumptionBudgets && status.consumptionBudgets.entries.length > 0) {
1422
+ lines.push('');
1423
+ lines.push(`Run consumption: ${status.consumptionBudgets.summary}`);
1424
+ for (const entry of status.consumptionBudgets.entries) {
1425
+ const icon = entry.overBudget ? serviceIcon('unavailable') : serviceIcon('healthy');
1426
+ const budgetNote = entry.budget
1427
+ ? `tokens ${entry.consumption.tokens}/${entry.budget.tokens} · toolCalls ${entry.consumption.toolCalls}/${entry.budget.toolCalls}`
1428
+ : `tokens ${entry.consumption.tokens} · toolCalls ${entry.consumption.toolCalls} (unbounded)`;
1429
+ lines.push(` ${icon} ${entry.actor}/${entry.runId} — ${budgetNote}`);
1430
+ }
1431
+ }
841
1432
  if (status.plugins) {
842
1433
  lines.push('');
843
1434
  lines.push(`Plugins: ${statusLabel(status.plugins.status)} · ${status.plugins.summary}`);