@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
package/lib/graph/cli.mjs CHANGED
@@ -2,11 +2,32 @@
2
2
  * lib/graph/cli.mjs — `construct matrix` command surface.
3
3
  *
4
4
  * Subcommands:
5
- * build Regenerate the dependency graph from the registry seeds
6
- * (and, once available, the static import graph) into
7
- * <projectDir>/.cx/graph/.
8
- * stat Print node/edge counts by type and relation.
9
- * query <id> Print a node's dependencies and dependents.
5
+ * build Regenerate the dependency graph from the registry,
6
+ * corpus-annotation, embed-manifest (LMCP-P6), and static
7
+ * import-graph seeds into <projectDir>/.cx/graph/.
8
+ * stat Print node/edge counts by type and relation.
9
+ * query <id> Print a node's dependencies and dependents.
10
+ * query --type <t> List every node of one type with its dependencies
11
+ * (e.g. `--type embed` enumerates the embed presets).
12
+ * query --missing-tests List capabilities/workflows with zero inbound
13
+ * validates edges (see lib/graph/gaps.mjs).
14
+ * impacted --changed <files...> Traverse from changed files to the
15
+ * workflows/tests/docs they impact (see
16
+ * lib/graph/impacted.mjs); scopes the CI drift gate.
17
+ * missing-tests [--security], missing-docs, stale, dependencies, providers,
18
+ * surfaces Read-only gap queries over the store (see
19
+ * lib/graph/gap-queries.mjs). `missing-tests --security`
20
+ * lists workflows/embed presets with no `secures` edge.
21
+ * owasp OWASP GenAI Top-10 coverage matrix generated from the
22
+ * graph's `@owasp`-tagged security tests (LMCP-N8, see
23
+ * lib/graph/security-coverage.mjs).
24
+ * explain <workflow-id> Full ownership picture, grouped by every
25
+ * EDGE_RELS relation plus manifest roleChain (LMCP-C3):
26
+ * absent-but-expected links print as MISSING, matching
27
+ * `graph validate` findings. Also carries last-execution
28
+ * runtime evidence and execution-staleness (LMCP-C9, see
29
+ * lib/graph/runtime-evidence.mjs and the execution
30
+ * dimension in lib/graph/staleness.mjs).
10
31
  *
11
32
  * Seeds are read from the Construct package root (rootDir); the graph is
12
33
  * persisted under the active project (projectDir) so it travels with .cx/.
@@ -15,7 +36,24 @@
15
36
  import { buildFromRegistry } from './build-from-registry.mjs';
16
37
  import { buildImportGraph } from './build-import-graph.mjs';
17
38
  import { buildCoChange } from './build-co-change.mjs';
18
- import { writeGraph, loadGraph, dependenciesOf, dependentsOf } from './store.mjs';
39
+ import { buildFromCorpus } from './build-from-corpus.mjs';
40
+ import { buildFromEmbed } from './build-from-embed.mjs';
41
+ import { buildFromSecurity, buildOwaspMatrix, findWorkflowsMissingSecurity, OWASP_GENAI_TOP10 } from './security-coverage.mjs';
42
+ import { buildRuntimeEvidence } from './runtime-evidence.mjs';
43
+ import { writeGraph, loadGraph, dependenciesOf, dependentsOf, nodesByType, EDGE_RELS } from './store.mjs';
44
+ import { validateGraph } from './validate.mjs';
45
+ import { findMissingTestCapabilities } from './gaps.mjs';
46
+ import { computeImpacted } from './impacted.mjs';
47
+ import { computeSourceHashes, checkExecutionStaleness } from './staleness.mjs';
48
+ import {
49
+ findMissingDocs,
50
+ findStale,
51
+ findDependencies,
52
+ findProviders,
53
+ findSurfaces,
54
+ } from './gap-queries.mjs';
55
+ import { loadAllWorkflows } from '../workflows/loader.mjs';
56
+ import { checkWorkflowLiveness } from '../workflows/liveness.mjs';
19
57
 
20
58
  function isoNow() {
21
59
  return new Date().toISOString();
@@ -23,10 +61,23 @@ function isoNow() {
23
61
 
24
62
  function runBuild({ rootDir, projectDir, json, coChange }) {
25
63
  const reg = buildFromRegistry({ rootDir });
26
- const validates = reg.edges.filter((e) => e.rel === 'validates');
64
+ const corpus = buildFromCorpus({ rootDir });
65
+
66
+ // Embed presets + security tests and their annotations ship with the package,
67
+ // so these seeders read from rootDir like buildFromRegistry/buildFromCorpus —
68
+ // not projectDir, which holds only per-project runtime state (enablement,
69
+ // evidence) and carries no tests/ tree.
70
+ const embed = buildFromEmbed({ rootDir });
71
+ const security = buildFromSecurity({ rootDir });
72
+
73
+ // Only capability-targeted validates edges (registry + corpus) feed the
74
+ // import graph's realizes derivation — it maps a capability to the files its
75
+ // tests transitively reach. Embed/security edges have no such file
76
+ // realization, so they are kept in the graph but withheld from this input.
77
+ const validates = [...reg.edges, ...corpus.edges].filter((e) => e.rel === 'validates');
27
78
  const imp = buildImportGraph({ rootDir, validates });
28
- const nodes = [...reg.nodes, ...imp.nodes];
29
- const edges = [...reg.edges, ...imp.edges];
79
+ const nodes = [...reg.nodes, ...corpus.nodes, ...embed.nodes, ...security.nodes, ...imp.nodes];
80
+ const edges = [...reg.edges, ...corpus.edges, ...embed.edges, ...security.edges, ...imp.edges];
30
81
 
31
82
  if (coChange) {
32
83
  const sourceRels = imp.nodes.filter((n) => n.type === 'file' || n.type === 'test').map((n) => n.name);
@@ -35,8 +86,17 @@ function runBuild({ rootDir, projectDir, json, coChange }) {
35
86
  edges.push(...co.edges);
36
87
  }
37
88
 
89
+ // Runtime evidence (LMCP-C9) reads persisted orchestration runs off the
90
+ // active project (projectDir), not the Construct package root (rootDir) —
91
+ // runs live under the embedding project's machine-scoped state root
92
+ // (ADR-0066), keyed by projectDir.
93
+ const evidence = buildRuntimeEvidence({ rootDir: projectDir });
94
+ nodes.push(...evidence.nodes);
95
+ edges.push(...evidence.edges);
96
+
38
97
  const sourceHash = reg.sourceHash;
39
- const result = writeGraph(projectDir, { nodes, edges, generatedAt: isoNow(), sourceHash });
98
+ const sourceHashes = computeSourceHashes(projectDir);
99
+ const result = writeGraph(projectDir, { nodes, edges, generatedAt: isoNow(), sourceHash, sourceHashes });
40
100
  const meta = loadGraph(projectDir).meta;
41
101
  if (json) {
42
102
  process.stdout.write(JSON.stringify({ ok: true, ...result, meta }, null, 2) + '\n');
@@ -87,6 +147,365 @@ function runQuery({ projectDir, id, json }) {
87
147
  return 0;
88
148
  }
89
149
 
150
+ // `query --type <t>` lists every node of one type with its outbound
151
+ // dependencies — the entry point the P6 acceptance uses to enumerate embed
152
+ // nodes (`--type embed`), but generic over any NODE_TYPES member.
153
+
154
+ function runQueryByType({ projectDir, type, json }) {
155
+ const graph = loadGraph(projectDir);
156
+ if (!graph.exists) {
157
+ process.stderr.write('No graph found. Run `construct graph build` first.\n');
158
+ return 1;
159
+ }
160
+ const matches = nodesByType(graph, type).map((node) => ({
161
+ id: node.id,
162
+ node,
163
+ dependencies: dependenciesOf(graph, node.id),
164
+ dependents: dependentsOf(graph, node.id),
165
+ }));
166
+ if (json) {
167
+ process.stdout.write(JSON.stringify({ type, count: matches.length, nodes: matches }, null, 2) + '\n');
168
+ return 0;
169
+ }
170
+ process.stdout.write(`${type} nodes (${matches.length}):\n`);
171
+ for (const m of matches) {
172
+ process.stdout.write(` ${m.id}\n`);
173
+ process.stdout.write(` → ${m.dependencies.join(', ') || '(none)'}\n`);
174
+ }
175
+ return 0;
176
+ }
177
+
178
+ // `missing-tests --security` (LMCP-N8): workflows with no inbound `secures`
179
+ // edge — the security-coverage gap list, distinct from the capability
180
+ // test-coverage gaps runMissingTests reports.
181
+
182
+ function runMissingSecurity({ projectDir, json }) {
183
+ const gaps = findWorkflowsMissingSecurity(projectDir);
184
+ if (!gaps.graphPresent) {
185
+ process.stderr.write('No graph found. Run `construct graph build` first.\n');
186
+ return 1;
187
+ }
188
+ if (json) {
189
+ process.stdout.write(JSON.stringify(gaps, null, 2) + '\n');
190
+ return 0;
191
+ }
192
+ process.stdout.write(`workflows with zero linked security tests (${gaps.workflows.length}):\n`);
193
+ for (const id of gaps.workflows) process.stdout.write(` ${id}\n`);
194
+ return 0;
195
+ }
196
+
197
+ // `graph owasp` (LMCP-N8): the OWASP GenAI Top-10 coverage matrix, generated
198
+ // from the graph's security-test nodes. Every category is listed with its test
199
+ // count so uncovered categories are visible, not silently absent.
200
+
201
+ function runOwaspMatrix({ projectDir, json }) {
202
+ const matrix = buildOwaspMatrix(projectDir);
203
+ if (!matrix.graphPresent) {
204
+ process.stderr.write('No graph found. Run `construct graph build` first.\n');
205
+ return 1;
206
+ }
207
+ if (json) {
208
+ process.stdout.write(JSON.stringify(matrix, null, 2) + '\n');
209
+ return 0;
210
+ }
211
+ process.stdout.write(`OWASP GenAI Top-10 coverage (${OWASP_GENAI_TOP10.length} categories):\n`);
212
+ for (const cat of matrix.categories) {
213
+ const mark = cat.testCount === 0 ? '✗' : '✓';
214
+ process.stdout.write(` ${mark} ${cat.id} ${cat.name}: ${cat.testCount} test(s)\n`);
215
+ }
216
+ if (matrix.uncovered.length) process.stdout.write(`\nuncovered: ${matrix.uncovered.join(', ')}\n`);
217
+ return 0;
218
+ }
219
+
220
+ function runMissingTests({ projectDir, json }) {
221
+ const gaps = findMissingTestCapabilities(projectDir);
222
+ if (!gaps.graphPresent) {
223
+ process.stderr.write('No graph found. Run `construct graph build` first.\n');
224
+ return 1;
225
+ }
226
+ if (json) {
227
+ process.stdout.write(JSON.stringify(gaps, null, 2) + '\n');
228
+ return 0;
229
+ }
230
+ process.stdout.write(`capabilities with zero validating tests (${gaps.capabilities.length}):\n`);
231
+ for (const id of gaps.capabilities) process.stdout.write(` ${id}\n`);
232
+ process.stdout.write(`workflows with zero validated embedding capability (${gaps.workflows.length}):\n`);
233
+ for (const id of gaps.workflows) process.stdout.write(` ${id}\n`);
234
+ return 0;
235
+ }
236
+
237
+ function parseChangedArg(args) {
238
+ const idx = args.indexOf('--changed');
239
+ if (idx === -1) return [];
240
+ const rest = args.slice(idx + 1);
241
+ const files = [];
242
+ for (const a of rest) {
243
+ if (a.startsWith('--')) break;
244
+ files.push(a);
245
+ }
246
+ return files;
247
+ }
248
+
249
+ function runImpacted(args, { projectDir, json }) {
250
+ const changed = parseChangedArg(args);
251
+ if (changed.length === 0) {
252
+ process.stderr.write('Usage: construct graph impacted --changed <file...> [--json]\n');
253
+ return 1;
254
+ }
255
+
256
+ const result = computeImpacted({ rootDir: projectDir, changedFiles: changed });
257
+ if (!result.graphPresent) {
258
+ process.stderr.write('No graph found. Run `construct graph build` first.\n');
259
+ return 1;
260
+ }
261
+
262
+ if (json) {
263
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
264
+ return 0;
265
+ }
266
+
267
+ process.stdout.write(`Changed: ${result.changed.length} file(s)\n`);
268
+ process.stdout.write(`Impacted workflows (${result.impactedWorkflows.length}): ${result.impactedWorkflows.join(', ') || '(none)'}\n`);
269
+ process.stdout.write(`Impacted capabilities (${result.impactedCapabilities.length}): ${result.impactedCapabilities.join(', ') || '(none)'}\n`);
270
+ process.stdout.write(`Impacted tests (${result.impactedTests.length}):\n`);
271
+ for (const t of result.impactedTests) process.stdout.write(` ${t}\n`);
272
+ process.stdout.write(`Impacted docs (${result.impactedDocs.length}):\n`);
273
+ for (const d of result.impactedDocs) process.stdout.write(` ${d}\n`);
274
+ if (result.unknown.length) {
275
+ process.stdout.write(`\n⚠ Not in graph (${result.unknown.length}): ${result.unknown.join(', ')}\n`);
276
+ }
277
+ return 0;
278
+ }
279
+
280
+ // `explain` renders every EDGE_RELS relation as its own question section
281
+ // (LMCP-C3) so a reader sees the workflow's full ownership picture — not just
282
+ // dependencies/providers/surfaces — in one call. A workflow node itself only
283
+ // carries direct `embeds` (inbound, from its capability) and `documents`
284
+ // (inbound, from docs) edges (see build-from-registry.mjs); every other
285
+ // relation a workflow can meaningfully answer (uses/governed_by/requires/
286
+ // exposes/reads) lives on the capability that embeds the workflow, so those
287
+ // sections reuse the same embeddingCapabilities aggregation gap-queries.mjs
288
+ // already computes (via findDependencies/findProviders/findSurfaces) rather
289
+ // than re-deriving it. Relations that are structurally file/module/specialist
290
+ // -level (imports, realizes, covers, contains, co_changes, owned_by,
291
+ // validates) never land on a workflow node in this schema (see
292
+ // build-from-registry.mjs's edge-shape comment) and are rendered
293
+ // `applicable: false` rather than MISSING — an absent link only signals a gap
294
+ // when the relation could exist.
295
+ // `roleChain` is manifest data, not a graph edge — EDGE_RELS has no roleChain
296
+ // member — so it is rendered as its own manifest-sourced section, resolved
297
+ // against the same checkWorkflowLiveness violations `graph validate` reports,
298
+ // so a MISSING/violation marker here always has a matching validate finding.
299
+
300
+ const WORKFLOW_LEVEL_RELS = new Set(['embeds', 'documents', 'uses', 'governed_by', 'requires', 'exposes', 'reads']);
301
+
302
+ function relLabel(rel) {
303
+ return rel === 'requires' ? 'requires (via provider)' : rel;
304
+ }
305
+
306
+ // Builds one section per EDGE_RELS member. Relations sourced directly off the
307
+ // workflow node (embeds inbound, documents inbound) read the live graph;
308
+ // relations that only ever land on the embedding capability (uses,
309
+ // governed_by, requires, exposes, reads) reuse the gap-query aggregations so
310
+ // `explain` never disagrees with `dependencies`/`providers`/`surfaces`/
311
+ // `missing-docs`. A relation with zero applicable edges is reported as
312
+ // MISSING so the human/agent reader sees an explicit gap instead of a quietly
313
+ // empty list.
314
+
315
+ function buildEdgeSections(graph, workflowId, projectDir) {
316
+ const deps = findDependencies(projectDir).workflows[workflowId] || { contracts: [], uses: [], requires: [] };
317
+ const surfaces = findSurfaces(projectDir).workflows[workflowId] || [];
318
+ const docs = findMissingDocs(projectDir);
319
+ const embeddingCaps = dependentsOf(graph, workflowId, 'embeds');
320
+ const reads = new Set();
321
+ for (const capId of embeddingCaps) {
322
+ for (const providerId of dependenciesOf(graph, capId, 'uses').filter((i) => i.startsWith('provider:'))) {
323
+ for (const specId of dependentsOf(graph, providerId, 'reads')) reads.add(specId);
324
+ }
325
+ }
326
+
327
+ const values = {
328
+ embeds: embeddingCaps,
329
+ documents: dependentsOf(graph, workflowId, 'documents'),
330
+ uses: deps.uses,
331
+ governed_by: deps.contracts,
332
+ requires: deps.requires,
333
+ exposes: surfaces,
334
+ reads: [...reads].sort(),
335
+ };
336
+
337
+ const sections = [];
338
+ for (const rel of EDGE_RELS) {
339
+ const applicable = WORKFLOW_LEVEL_RELS.has(rel);
340
+ const links = applicable ? (values[rel] ?? []) : [];
341
+ sections.push({
342
+ rel,
343
+ label: relLabel(rel),
344
+ links,
345
+ missing: applicable && links.length === 0,
346
+ applicable,
347
+ });
348
+ }
349
+ // documents doubles as the missing-docs consistency check (LMCP-C5): a
350
+ // workflow with zero inbound documents edges is already reported by
351
+ // `graph missing-docs`, so mirror that exact membership test here.
352
+ const documentsSection = sections.find((s) => s.rel === 'documents');
353
+ if (documentsSection) documentsSection.missing = docs.workflows.includes(workflowId);
354
+
355
+ return sections;
356
+ }
357
+
358
+ // roleChain is manifest-only (not an EDGE_RELS member): resolved against the
359
+ // merged workflow-manifest set and checkWorkflowLiveness so a MISSING/broken
360
+ // roleChain entry here always has a matching `graph validate` violation
361
+ // string to cross-check against (the bead's consistency-test requirement).
362
+
363
+ function buildRoleChainSection(workflowType, projectDir) {
364
+ const { workflows } = loadAllWorkflows({ rootDir: projectDir });
365
+ const manifest = workflows.find((m) => m.id === workflowType);
366
+ const { violations } = checkWorkflowLiveness(workflows, { rootDir: projectDir });
367
+ const ownViolations = violations.filter((v) => v.includes(`'${workflowType}'`) || (manifest?._filePath && v.startsWith(manifest._filePath)));
368
+ const roleChain = Array.isArray(manifest?.roleChain) ? manifest.roleChain : [];
369
+ return {
370
+ rel: 'roleChain',
371
+ label: 'roleChain',
372
+ links: roleChain,
373
+ missing: roleChain.length === 0,
374
+ applicable: true,
375
+ violations: ownViolations,
376
+ provenance: manifest
377
+ ? {
378
+ source: manifest._source || 'unknown',
379
+ filePath: manifest._filePath || null,
380
+ shadows: manifest._shadowedBy || [],
381
+ }
382
+ : null,
383
+ };
384
+ }
385
+
386
+ function runExplain(args, { projectDir, json }) {
387
+ const id = args.slice(1).find((a) => !a.startsWith('--'));
388
+ if (!id) {
389
+ process.stderr.write('Usage: construct graph explain <workflow-id> [--json]\n');
390
+ return 1;
391
+ }
392
+ const workflowId = id.startsWith('workflow:') ? id : `workflow:${id}`;
393
+ const workflowType = workflowId.slice('workflow:'.length);
394
+
395
+ const graph = loadGraph(projectDir);
396
+ if (!graph.exists) {
397
+ process.stderr.write('No graph found. Run `construct graph build` first.\n');
398
+ return 1;
399
+ }
400
+ const node = graph.nodes.get(workflowId);
401
+ if (!node) {
402
+ process.stderr.write(`workflow not found in graph: ${workflowId}\n`);
403
+ return 1;
404
+ }
405
+
406
+ const deps = findDependencies(projectDir).workflows[workflowId] || { contracts: [], uses: [], requires: [] };
407
+ const providers = findProviders(projectDir).workflows[workflowId] || [];
408
+ const surfaces = findSurfaces(projectDir).workflows[workflowId] || [];
409
+ const sections = buildEdgeSections(graph, workflowId, projectDir);
410
+ const roleChainSection = buildRoleChainSection(workflowType, projectDir);
411
+ const allSections = [...sections, roleChainSection];
412
+
413
+ const execution = checkExecutionStaleness(projectDir);
414
+ const executionState = execution.workflows[workflowType] || {
415
+ lastExecution: null, neverExecuted: true, stale: false, ageDays: null, thresholdDays: null,
416
+ };
417
+
418
+ const result = {
419
+ id: workflowId,
420
+ node,
421
+ dependencies: deps,
422
+ providers,
423
+ surfaces,
424
+ sections: allSections,
425
+ missing: allSections.filter((s) => s.missing).map((s) => s.rel),
426
+ execution: executionState,
427
+ };
428
+
429
+ if (json) {
430
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
431
+ return 0;
432
+ }
433
+
434
+ process.stdout.write(`${workflowId}\n`);
435
+ for (const section of allSections) {
436
+ if (!section.applicable) continue;
437
+ if (section.missing) {
438
+ process.stdout.write(` ${section.label}: MISSING\n`);
439
+ } else {
440
+ process.stdout.write(` ${section.label} (${section.links.length}): ${section.links.join(', ') || '(none)'}\n`);
441
+ }
442
+ if (section.violations?.length) {
443
+ for (const v of section.violations) process.stdout.write(` ! ${v}\n`);
444
+ }
445
+ if (section.provenance) {
446
+ process.stdout.write(` source: ${section.provenance.source} (${section.provenance.filePath || 'n/a'})\n`);
447
+ for (const shadow of section.provenance.shadows) {
448
+ process.stdout.write(` overrides: ${shadow.source} (${shadow.filePath || 'n/a'})\n`);
449
+ }
450
+ }
451
+ }
452
+ process.stdout.write(` contracts (${deps.contracts.length}): ${deps.contracts.join(', ') || '(none)'}\n`);
453
+ process.stdout.write(` uses (${deps.uses.length}): ${deps.uses.join(', ') || '(none)'}\n`);
454
+ process.stdout.write(` requires (${deps.requires.length}): ${deps.requires.join(', ') || '(none)'}\n`);
455
+ process.stdout.write(` providers (${providers.length}): ${providers.join(', ') || '(none)'}\n`);
456
+ process.stdout.write(` surfaces (${surfaces.length}): ${surfaces.join(', ') || '(none)'}\n`);
457
+ if (executionState.neverExecuted) {
458
+ process.stdout.write(' execution: NEVER EXECUTED — no runtime evidence found\n');
459
+ } else {
460
+ const { lastExecution, stale, ageDays: age, thresholdDays } = executionState;
461
+ process.stdout.write(` execution: last run ${lastExecution.timestamp} (outcome: ${lastExecution.outcome}, run ${lastExecution.runId})\n`);
462
+ process.stdout.write(` age: ${age?.toFixed(1)}d / threshold: ${thresholdDays}d — ${stale ? 'STALE' : 'fresh'}\n`);
463
+ }
464
+ return 0;
465
+ }
466
+
467
+ // Uniform runner for the six read-only gap-query subcommands (C5): each
468
+ // query function takes projectDir and returns { graphPresent, ...payload };
469
+ // --json dumps the payload verbatim, non-JSON falls back to the same shape
470
+ // so a human running the command still sees structured field names.
471
+
472
+ function runGapQuery(queryFn, { projectDir, json }) {
473
+ const result = queryFn(projectDir);
474
+ if (!result.graphPresent) {
475
+ process.stderr.write('No graph found. Run `construct graph build` first.\n');
476
+ return 1;
477
+ }
478
+ if (json) {
479
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
480
+ return 0;
481
+ }
482
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
483
+ return 0;
484
+ }
485
+
486
+ function runValidate(args, { rootDir, projectDir }) {
487
+ const strict = args.includes('--strict');
488
+ const json = args.includes('--json');
489
+ const result = validateGraph(projectDir || rootDir, { strict });
490
+
491
+ if (json) {
492
+ console.log(JSON.stringify(result, null, 2));
493
+ } else {
494
+ for (const err of result.errors) console.error(` error: ${err}`);
495
+ for (const warn of result.warnings) console.error(` warn: ${warn}`);
496
+ for (const info of result.infos) console.log(` info: ${info}`);
497
+ if (result.errors.length > 0) {
498
+ console.error(`\n ✖ ${result.errors.length} error(s), ${result.warnings.length} warning(s)`);
499
+ } else if (result.warnings.length > 0) {
500
+ console.log(`\n ✓ ${result.warnings.length} warning(s)`);
501
+ } else {
502
+ console.log('\n ✓ graph is valid');
503
+ }
504
+ }
505
+
506
+ process.exit(result.errors.length > 0 ? 1 : 0);
507
+ }
508
+
90
509
  /**
91
510
  * @param {string[]} args
92
511
  * @param {{ rootDir: string, projectDir: string }} ctx
@@ -98,13 +517,38 @@ export function runGraphCli(args, { rootDir, projectDir }) {
98
517
  if (sub === 'build') return runBuild({ rootDir, projectDir, json, coChange: !args.includes('--no-co-change') });
99
518
  if (sub === 'stat') return runStat({ projectDir, json });
100
519
  if (sub === 'query') {
520
+ if (args.includes('--missing-tests')) return runMissingTests({ projectDir, json });
521
+ const typeIdx = args.indexOf('--type');
522
+ if (typeIdx !== -1) {
523
+ const type = args[typeIdx + 1];
524
+ if (!type || type.startsWith('--')) {
525
+ process.stderr.write('Usage: construct graph query --type <node-type> [--json]\n');
526
+ return 1;
527
+ }
528
+ return runQueryByType({ projectDir, type, json });
529
+ }
101
530
  const id = args.slice(1).find((a) => !a.startsWith('--'));
102
531
  if (!id) {
103
- process.stderr.write('Usage: construct matrix query <node-id> [--json]\n');
532
+ process.stderr.write('Usage: construct graph query <node-id> | --type <node-type> [--json]\n');
104
533
  return 1;
105
534
  }
106
535
  return runQuery({ projectDir, id, json });
107
536
  }
108
- process.stderr.write(`Unknown graph subcommand: ${sub}. Available: build, stat, query\n`);
537
+ if (sub === 'validate') {
538
+ return runValidate(args, { rootDir, projectDir });
539
+ }
540
+ if (sub === 'impacted') return runImpacted(args, { projectDir, json });
541
+ if (sub === 'owasp') return runOwaspMatrix({ projectDir, json });
542
+ if (sub === 'missing-tests') {
543
+ if (args.includes('--security')) return runMissingSecurity({ projectDir, json });
544
+ return runMissingTests({ projectDir, json });
545
+ }
546
+ if (sub === 'missing-docs') return runGapQuery(findMissingDocs, { projectDir, json });
547
+ if (sub === 'stale') return runGapQuery(findStale, { projectDir, json });
548
+ if (sub === 'dependencies') return runGapQuery(findDependencies, { projectDir, json });
549
+ if (sub === 'providers') return runGapQuery(findProviders, { projectDir, json });
550
+ if (sub === 'surfaces') return runGapQuery(findSurfaces, { projectDir, json });
551
+ if (sub === 'explain') return runExplain(args, { projectDir, json });
552
+ process.stderr.write(`Unknown graph subcommand: ${sub}. Available: build, stat, query, validate, impacted, missing-tests, missing-docs, stale, dependencies, providers, surfaces, explain\n`);
109
553
  return 1;
110
554
  }
@@ -0,0 +1,156 @@
1
+ /**
2
+ * lib/graph/gap-queries.mjs — read-only gap queries over the living graph (LMCP-C5).
3
+ *
4
+ * Six queries answer "what does this workflow/capability still need":
5
+ * missing-docs (workflow/provider with zero inbound documents edges), stale
6
+ * (per-source seed drift via lib/graph/staleness.mjs — LMCP-C6), dependencies
7
+ * (per-workflow requires-edges: capability → provider/tool), providers
8
+ * (per-workflow provider requirements via capability uses-edges to provider
9
+ * nodes), and surfaces (per-workflow surface requirements via capability
10
+ * exposes-edges). missing-tests lives in lib/graph/gaps.mjs and is re-exported
11
+ * here for a single import surface; validateGraph (lib/graph/validate.mjs)
12
+ * sources its own missing-tests warnings from the same findMissingTestCapabilities
13
+ * function so `graph missing-tests` and `graph validate` never disagree.
14
+ * Every query is read-only: it inspects the persisted store and never
15
+ * mutates it.
16
+ */
17
+
18
+ import { loadGraph, nodesByType, dependenciesOf, dependentsOf } from './store.mjs';
19
+ import { checkGraphStaleness } from './staleness.mjs';
20
+ import { findMissingTestCapabilities } from './gaps.mjs';
21
+
22
+ export { findMissingTestCapabilities as findMissingTests };
23
+
24
+ /**
25
+ * Workflows and providers with zero inbound `documents` edges.
26
+ *
27
+ * @param {string} rootDir
28
+ * @returns {{ graphPresent: boolean, workflows: string[], providers: string[] }}
29
+ */
30
+ export function findMissingDocs(rootDir) {
31
+ const graph = loadGraph(rootDir);
32
+ if (!graph.exists) return { graphPresent: false, workflows: [], providers: [] };
33
+
34
+ const workflows = nodesByType(graph, 'workflow')
35
+ .filter((wf) => dependentsOf(graph, wf.id, 'documents').length === 0)
36
+ .map((wf) => wf.id)
37
+ .sort();
38
+
39
+ const providers = nodesByType(graph, 'provider')
40
+ .filter((p) => dependentsOf(graph, p.id, 'documents').length === 0)
41
+ .map((p) => p.id)
42
+ .sort();
43
+
44
+ return { graphPresent: true, workflows, providers };
45
+ }
46
+
47
+ /**
48
+ * Per-source seed-hash staleness (LMCP-C6): which named seed source(s), if
49
+ * any, changed since the last `construct graph build`.
50
+ *
51
+ * @param {string} rootDir
52
+ * @returns {{ graphPresent: boolean, stale: boolean, staleSources: string[], staleReason: string|null }}
53
+ */
54
+ export function findStale(rootDir) {
55
+ const state = checkGraphStaleness(rootDir);
56
+ return {
57
+ graphPresent: state.present,
58
+ stale: state.stale,
59
+ staleSources: state.staleSources || [],
60
+ staleReason: state.staleReason,
61
+ };
62
+ }
63
+
64
+ // A workflow's requirements are gathered via its embedding capabilities: a
65
+ // capability --embeds--> workflow edge is stored in the forward direction
66
+ // (capability -> workflow), so the workflow's capabilities are its
67
+ // dependents along 'embeds', not its dependencies.
68
+
69
+ function embeddingCapabilities(graph, workflowId) {
70
+ return dependentsOf(graph, workflowId, 'embeds');
71
+ }
72
+
73
+ /**
74
+ * Per-workflow declared dependencies: the union, over every capability that
75
+ * embeds the workflow, of contracts (governed_by), skills/rules (uses), and
76
+ * provider tool requirements (requires).
77
+ *
78
+ * @param {string} rootDir
79
+ * @returns {{ graphPresent: boolean, workflows: Record<string, { contracts: string[], uses: string[], requires: string[] }> }}
80
+ */
81
+ export function findDependencies(rootDir) {
82
+ const graph = loadGraph(rootDir);
83
+ if (!graph.exists) return { graphPresent: false, workflows: {} };
84
+
85
+ const workflows = {};
86
+ for (const wf of nodesByType(graph, 'workflow')) {
87
+ const caps = embeddingCapabilities(graph, wf.id);
88
+ const contracts = new Set();
89
+ const uses = new Set();
90
+ const requires = new Set();
91
+ for (const capId of caps) {
92
+ for (const id of dependenciesOf(graph, capId, 'governed_by')) contracts.add(id);
93
+ for (const id of dependenciesOf(graph, capId, 'uses')) uses.add(id);
94
+ for (const providerId of dependenciesOf(graph, capId, 'uses').filter((id) => id.startsWith('provider:'))) {
95
+ for (const toolId of dependenciesOf(graph, providerId, 'requires')) requires.add(toolId);
96
+ }
97
+ }
98
+ workflows[wf.id] = {
99
+ contracts: [...contracts].sort(),
100
+ uses: [...uses].sort(),
101
+ requires: [...requires].sort(),
102
+ };
103
+ }
104
+
105
+ return { graphPresent: true, workflows };
106
+ }
107
+
108
+ /**
109
+ * Per-workflow provider requirements: providers named by any embedding
110
+ * capability's `uses` edges.
111
+ *
112
+ * @param {string} rootDir
113
+ * @returns {{ graphPresent: boolean, workflows: Record<string, string[]> }}
114
+ */
115
+ export function findProviders(rootDir) {
116
+ const graph = loadGraph(rootDir);
117
+ if (!graph.exists) return { graphPresent: false, workflows: {} };
118
+
119
+ const workflows = {};
120
+ for (const wf of nodesByType(graph, 'workflow')) {
121
+ const caps = embeddingCapabilities(graph, wf.id);
122
+ const providers = new Set();
123
+ for (const capId of caps) {
124
+ for (const id of dependenciesOf(graph, capId, 'uses')) {
125
+ if (id.startsWith('provider:')) providers.add(id);
126
+ }
127
+ }
128
+ workflows[wf.id] = [...providers].sort();
129
+ }
130
+
131
+ return { graphPresent: true, workflows };
132
+ }
133
+
134
+ /**
135
+ * Per-workflow surface requirements: surfaces exposed by any embedding
136
+ * capability.
137
+ *
138
+ * @param {string} rootDir
139
+ * @returns {{ graphPresent: boolean, workflows: Record<string, string[]> }}
140
+ */
141
+ export function findSurfaces(rootDir) {
142
+ const graph = loadGraph(rootDir);
143
+ if (!graph.exists) return { graphPresent: false, workflows: {} };
144
+
145
+ const workflows = {};
146
+ for (const wf of nodesByType(graph, 'workflow')) {
147
+ const caps = embeddingCapabilities(graph, wf.id);
148
+ const surfaces = new Set();
149
+ for (const capId of caps) {
150
+ for (const id of dependenciesOf(graph, capId, 'exposes')) surfaces.add(id);
151
+ }
152
+ workflows[wf.id] = [...surfaces].sort();
153
+ }
154
+
155
+ return { graphPresent: true, workflows };
156
+ }