@geraldmaron/construct 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -1,44 +1,275 @@
1
1
  /**
2
- * lib/graph/staleness.mjs — dependency graph seed-hash staleness checks.
2
+ * lib/graph/staleness.mjs — per-source seed-hash staleness checks (LMCP-C6).
3
3
  *
4
- * Compares the hash of registry/contracts/workflow seed files against the
5
- * hash stored at last graph build (.cx/graph/meta.json). Shared by Oracle,
6
- * the doctor graph-staleness watcher, and PostToolUse advisories.
4
+ * `hashFiles`/GRAPH_SEED_FILES (build-from-registry.mjs) hash a flat file list
5
+ * and cannot see inside a directory the historical seed list has always
6
+ * included whole-directory entries (specialists/org, docs) which `readFileSync`
7
+ * silently treats as always-missing, so a change inside the directory never
8
+ * moved the hash. `hashSourceGroup` fixes this by walking directories
9
+ * recursively and hashing every file's relative path plus contents; a single
10
+ * flat file hashes the same way. `SOURCE_GROUPS` names each seed source
11
+ * (registry, overlays, specialists/org, plugin dirs, provider manifests,
12
+ * workflow manifests) as its own group so `checkGraphStaleness` can report
13
+ * exactly which source drifted, not just that something did. The legacy
14
+ * combined `sourceHash` (single hash over the flat GRAPH_SEED_FILES list) is
15
+ * preserved for existing consumers (lib/oracle/read-model.mjs) that compare
16
+ * against it directly.
17
+ *
18
+ * `checkExecutionStaleness` (LMCP-C9) is a second, independent staleness
19
+ * dimension: source-hash staleness above asks "did the seeds change since
20
+ * the graph was built"; execution staleness asks "has this workflow actually
21
+ * run recently". A workflow can be source-fresh and execution-stale (nothing
22
+ * changed, but no one has run it in months) or the reverse, so the two are
23
+ * reported side by side rather than merged into one boolean. A workflow with
24
+ * zero runtime-evidence ever (lib/graph/runtime-evidence.mjs) is flagged
25
+ * `neverExecuted: true` distinctly from `stale: true` — the former is "no
26
+ * evidence exists", the latter is "evidence exists but has aged past the
27
+ * threshold" — so a caller never has to infer one from the absence of data
28
+ * that could equally mean the other.
7
29
  */
8
30
 
31
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
32
+ import path from 'node:path';
33
+ import { createHash } from 'node:crypto';
34
+
9
35
  import { hashFiles } from './build-from-registry.mjs';
10
36
  import { loadGraph } from './store.mjs';
37
+ import { resolveManifestDirs } from '../extensions/loader.mjs';
38
+ import { resolveWorkflowManifestDirs } from '../workflows/loader.mjs';
39
+ import { computeLastExecutionByWorkflow } from './runtime-evidence.mjs';
40
+ import { listWorkflowDefs } from '../embedded-contract/workflow-defs.mjs';
11
41
 
12
42
  export const GRAPH_SEED_FILES = [
13
43
  'registry/capabilities.json',
14
44
  'specialists/org',
15
45
  'lib/embedded-contract/workflow-defs.mjs',
46
+ 'lib/extensions/manifest-schema.mjs',
47
+ 'lib/extensions/loader.mjs',
48
+ 'lib/extensions/validate.mjs',
49
+ 'lib/extensions/manifests',
50
+ 'docs',
51
+ 'lib/registry/assemble.mjs',
16
52
  ];
17
53
 
54
+ function walkFiles(absPath) {
55
+ let st;
56
+ try { st = statSync(absPath); } catch { return null; }
57
+ if (st.isFile()) return [absPath];
58
+ if (!st.isDirectory()) return null;
59
+ const out = [];
60
+ const stack = [absPath];
61
+ while (stack.length) {
62
+ const dir = stack.pop();
63
+ let entries;
64
+ try { entries = readdirSync(dir, { withFileTypes: true }); } catch { continue; }
65
+ for (const entry of entries) {
66
+ const full = path.join(dir, entry.name);
67
+ if (entry.isDirectory()) stack.push(full);
68
+ else if (entry.isFile()) out.push(full);
69
+ }
70
+ }
71
+ return out.sort();
72
+ }
73
+
74
+ /**
75
+ * Hash a named group of seed paths (files and/or directories), recursing into
76
+ * directories so an edit anywhere inside moves the hash. Missing paths hash
77
+ * as a stable sentinel rather than throwing, so an optional seed (e.g. a
78
+ * project override file that may not exist) does not abort hashing.
79
+ *
80
+ * @param {string} rootDir
81
+ * @param {string[]} rels — repo-relative file or directory paths.
82
+ * @returns {string} 16-hex-char digest.
83
+ */
84
+ export function hashSourceGroup(rootDir, rels) {
85
+ const h = createHash('sha256');
86
+ for (const rel of rels) {
87
+ const abs = path.join(rootDir, rel);
88
+ const files = walkFiles(abs);
89
+ if (!files) { h.update(`${rel}\0missing`); continue; }
90
+ for (const filePath of files) {
91
+ const relToRoot = path.relative(rootDir, filePath);
92
+ h.update(relToRoot);
93
+ try { h.update(readFileSync(filePath)); } catch { h.update('\0unreadable'); }
94
+ }
95
+ }
96
+ return h.digest('hex').slice(0, 16);
97
+ }
98
+
99
+ /**
100
+ * Named seed-source groups making up the full graph seed set: registry
101
+ * catalog + overlays, specialists/org, plugin (pack) directories, provider
102
+ * manifests (builtin + user + project), and workflow manifests (builtin +
103
+ * project; pack tiers are resolved per-pack root so are not enumerable here).
104
+ *
105
+ * @param {string} rootDir
106
+ * @returns {Record<string,string[]>}
107
+ */
108
+ export function sourceGroups(rootDir) {
109
+ const manifestDirs = resolveManifestDirs({ rootDir });
110
+ const workflowDirs = resolveWorkflowManifestDirs({ rootDir });
111
+
112
+ return {
113
+ registry: ['registry/capabilities.json', 'lib/registry/assemble.mjs', 'lib/registry/loader.mjs'],
114
+ overlays: [
115
+ path.join('.cx', 'org'),
116
+ path.join('.cx', 'unified-registry.json'),
117
+ ],
118
+ specialistsOrg: ['specialists/org'],
119
+ plugins: [path.join('.cx', 'packs'), 'lib/packs/manifests'],
120
+ providerManifests: [
121
+ manifestDirs.builtin,
122
+ manifestDirs.user,
123
+ manifestDirs.project,
124
+ path.join('.cx', 'providers.json'),
125
+ ].map((p) => path.isAbsolute(p) ? path.relative(rootDir, p) : p),
126
+ workflowManifests: [
127
+ workflowDirs.builtin,
128
+ workflowDirs.project,
129
+ 'lib/embedded-contract/workflow-defs.mjs',
130
+ ].map((p) => path.isAbsolute(p) ? path.relative(rootDir, p) : p),
131
+ };
132
+ }
133
+
134
+ /**
135
+ * @param {string} rootDir
136
+ * @returns {Record<string,string>} hash per named source group.
137
+ */
138
+ export function computeSourceHashes(rootDir) {
139
+ const groups = sourceGroups(rootDir);
140
+ const out = {};
141
+ for (const [name, rels] of Object.entries(groups)) {
142
+ out[name] = hashSourceGroup(rootDir, rels);
143
+ }
144
+ return out;
145
+ }
146
+
18
147
  /**
19
148
  * @param {string} rootDir — project root holding .cx/graph/.
20
- * @returns {{ present: boolean, stale: boolean, staleReason: string|null, currentHash?: string, storedHash?: string|null }}
149
+ * @returns {{
150
+ * present: boolean,
151
+ * stale: boolean,
152
+ * staleReason: string|null,
153
+ * staleSources: string[],
154
+ * currentHash?: string,
155
+ * storedHash?: string|null,
156
+ * }}
21
157
  */
22
158
  export function checkGraphStaleness(rootDir) {
23
159
  const graph = loadGraph(rootDir);
24
160
  if (!graph.exists) {
25
- return { present: false, stale: false, staleReason: null };
161
+ return { present: false, stale: false, staleReason: null, staleSources: [] };
26
162
  }
27
163
 
28
164
  try {
29
165
  const current = hashFiles(rootDir, GRAPH_SEED_FILES);
30
166
  const stored = graph.meta?.sourceHash ?? null;
31
- if (stored && current !== stored) {
167
+
168
+ const currentSources = computeSourceHashes(rootDir);
169
+ const storedSources = graph.meta?.sourceHashes ?? null;
170
+ const staleSources = [];
171
+ if (storedSources) {
172
+ for (const name of Object.keys(currentSources)) {
173
+ if (currentSources[name] !== storedSources[name]) staleSources.push(name);
174
+ }
175
+ }
176
+
177
+ const stale = staleSources.length > 0 || (stored != null && current !== stored && !storedSources);
178
+ if (stale) {
179
+ const reason = staleSources.length > 0
180
+ ? `source(s) changed since last build: ${staleSources.join(', ')}`
181
+ : 'registry/contracts/workflow seeds changed since last build';
32
182
  return {
33
183
  present: true,
34
184
  stale: true,
35
- staleReason: 'registry/contracts/workflow seeds changed since last build',
185
+ staleReason: reason,
186
+ staleSources,
36
187
  currentHash: current,
37
188
  storedHash: stored,
38
189
  };
39
190
  }
40
- return { present: true, stale: false, staleReason: null, currentHash: current, storedHash: stored };
191
+ return { present: true, stale: false, staleReason: null, staleSources: [], currentHash: current, storedHash: stored };
41
192
  } catch {
42
- return { present: true, stale: false, staleReason: null };
193
+ return { present: true, stale: false, staleReason: null, staleSources: [] };
43
194
  }
44
195
  }
196
+
197
+ /**
198
+ * Default execution-staleness threshold: a workflow with runtime evidence
199
+ * older than this many days is flagged stale. Overridable per-workflow via
200
+ * the `thresholds` option (keyed by workflow type), so a high-churn workflow
201
+ * can demand fresher evidence than a rarely-invoked one.
202
+ */
203
+ export const EXECUTION_STALENESS_DEFAULT_THRESHOLD_DAYS = 30;
204
+
205
+ function ageDays(timestamp, now) {
206
+ const then = Date.parse(timestamp);
207
+ if (!Number.isFinite(then)) return null;
208
+ return (now - then) / (1000 * 60 * 60 * 24);
209
+ }
210
+
211
+ /**
212
+ * Per-workflow execution-staleness (LMCP-C9): how long since a workflow's
213
+ * last runtime-evidence edge (lib/graph/runtime-evidence.mjs), against a
214
+ * per-workflow threshold. A workflow with no evidence at all is reported
215
+ * `neverExecuted: true` and `stale: false` — distinct from a workflow whose
216
+ * evidence exists but has aged past the threshold (`stale: true`,
217
+ * `neverExecuted: false`) — so the two conditions are never conflated.
218
+ *
219
+ * @param {string} rootDir — project root; runs are read from its machine-scoped
220
+ * state root (ADR-0066), not a project-relative path.
221
+ * @param {{ thresholds?: Record<string,number>, now?: number }} [opts]
222
+ * @returns {{
223
+ * workflows: Record<string, {
224
+ * lastExecution: { timestamp: string, outcome: string, runId: string }|null,
225
+ * neverExecuted: boolean,
226
+ * stale: boolean,
227
+ * ageDays: number|null,
228
+ * thresholdDays: number,
229
+ * }>,
230
+ * neverExecuted: string[],
231
+ * staleWorkflows: string[],
232
+ * }}
233
+ */
234
+ export function checkExecutionStaleness(rootDir, { thresholds = {}, now = Date.now() } = {}) {
235
+ const lastExecutionByWorkflow = computeLastExecutionByWorkflow(rootDir);
236
+
237
+ const workflows = {};
238
+ const neverExecuted = [];
239
+ const staleWorkflows = [];
240
+
241
+ for (const wf of listWorkflowDefs()) {
242
+ const lastExecution = lastExecutionByWorkflow[wf.type] ?? null;
243
+ const thresholdDays = thresholds[wf.type] ?? EXECUTION_STALENESS_DEFAULT_THRESHOLD_DAYS;
244
+
245
+ if (!lastExecution) {
246
+ neverExecuted.push(wf.type);
247
+ workflows[wf.type] = {
248
+ lastExecution: null,
249
+ neverExecuted: true,
250
+ stale: false,
251
+ ageDays: null,
252
+ thresholdDays,
253
+ };
254
+ continue;
255
+ }
256
+
257
+ const age = ageDays(lastExecution.timestamp, now);
258
+ const stale = age !== null && age > thresholdDays;
259
+ if (stale) staleWorkflows.push(wf.type);
260
+
261
+ workflows[wf.type] = {
262
+ lastExecution,
263
+ neverExecuted: false,
264
+ stale,
265
+ ageDays: age,
266
+ thresholdDays,
267
+ };
268
+ }
269
+
270
+ return {
271
+ workflows,
272
+ neverExecuted: neverExecuted.sort(),
273
+ staleWorkflows: staleWorkflows.sort(),
274
+ };
275
+ }
@@ -3,15 +3,22 @@
3
3
  *
4
4
  * The substrate for Construct's living dependency matrix. Nodes are typed
5
5
  * entities (file, module, workflow, capability, test, contract, surface,
6
- * skill, rule) addressed by canonical ids of the form `type:key`. Edges are
7
- * directed and carry a relation kind (imports, realizes, validates, covers,
8
- * exposes, governed_by, uses, embeds, co_changes) and a provenance source
9
- * (registry, import-graph, co-change, override).
6
+ * skill, rule, provider, tool, pack, doc, specialist, runtime-evidence) addressed
7
+ * by canonical ids of the form `type:key`. Edges are directed and carry a
8
+ * relation kind (imports, realizes, validates, covers, exposes, governed_by,
9
+ * uses, embeds, co_changes, contains, requires, documents, evidenced_by,
10
+ * owned_by) and a provenance source
11
+ * (registry, import-graph, co-change, override, corpus-annotation, runtime-evidence).
10
12
  *
11
13
  * Persisted under `.cx/graph/` as deterministic JSONL so a graph round-trips
12
14
  * losslessly and diffs cleanly: nodes.jsonl (sorted by id), edges.jsonl
13
15
  * (sorted by from,rel,to), meta.json (generatedAt, sourceHash, counts).
14
16
  * Dependency-free and pure-JS per ADR-0001.
17
+ *
18
+ * `reads` (LMCP-E4): specialist --reads--> provider, seeded from pack
19
+ * `embedBindings` grants (build-from-registry.mjs). Makes the embed
20
+ * authority-guard's per-specialist read/search grant visible in
21
+ * `construct graph`/`construct matrix` rather than living only in pack JSON.
15
22
  */
16
23
 
17
24
  import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from 'node:fs';
@@ -19,13 +26,15 @@ import path from 'node:path';
19
26
 
20
27
  export const NODE_TYPES = new Set([
21
28
  'file', 'module', 'workflow', 'capability', 'test', 'contract', 'surface', 'skill', 'rule',
29
+ 'provider', 'tool', 'pack', 'doc', 'specialist', 'runtime-evidence', 'embed',
22
30
  ]);
23
31
 
24
32
  export const EDGE_RELS = new Set([
25
33
  'imports', 'realizes', 'validates', 'covers', 'exposes', 'governed_by', 'uses', 'embeds', 'co_changes', 'contains',
34
+ 'requires', 'documents', 'evidenced_by', 'owned_by', 'reads', 'secures',
26
35
  ]);
27
36
 
28
- export const EDGE_SOURCES = new Set(['registry', 'import-graph', 'co-change', 'override']);
37
+ export const EDGE_SOURCES = new Set(['registry', 'import-graph', 'co-change', 'override', 'corpus-annotation', 'runtime-evidence', 'embed-manifest']);
29
38
 
30
39
  const STORE_SUBDIR = path.join('.cx', 'graph');
31
40
 
@@ -98,11 +107,17 @@ function countBy(items, key) {
98
107
  * Persist a full graph (regenerate semantics). Nodes and edges are normalized
99
108
  * and sorted; meta records counts and the caller-supplied source hash.
100
109
  *
110
+ * `sourceHashes` (LMCP-C6) is an optional `{ [sourceName]: hash }` map recording
111
+ * a hash per seed source (registry, overlays, specialists/org, plugins,
112
+ * provider manifests, workflow manifests) alongside the single combined
113
+ * `sourceHash`, so staleness detection can name which source drifted instead
114
+ * of only reporting "something changed".
115
+ *
101
116
  * @param {string} rootDir
102
- * @param {{ nodes: object[], edges: object[], generatedAt?: string, sourceHash?: string }} graph
117
+ * @param {{ nodes: object[], edges: object[], generatedAt?: string, sourceHash?: string, sourceHashes?: Record<string,string> }} graph
103
118
  * @returns {{ nodeCount: number, edgeCount: number, dir: string }}
104
119
  */
105
- export function writeGraph(rootDir, { nodes = [], edges = [], generatedAt = null, sourceHash = null } = {}) {
120
+ export function writeGraph(rootDir, { nodes = [], edges = [], generatedAt = null, sourceHash = null, sourceHashes = null } = {}) {
106
121
  const dir = graphDir(rootDir);
107
122
  mkdirSync(dir, { recursive: true });
108
123
  const normNodes = normalizeNodes(nodes);
@@ -110,8 +125,10 @@ export function writeGraph(rootDir, { nodes = [], edges = [], generatedAt = null
110
125
  writeAtomic(nodesPath(rootDir), normNodes.map((n) => JSON.stringify(n)).join('\n') + (normNodes.length ? '\n' : ''));
111
126
  writeAtomic(edgesPath(rootDir), normEdges.map((e) => JSON.stringify(e)).join('\n') + (normEdges.length ? '\n' : ''));
112
127
  const meta = {
128
+ schemaVersion: 1,
113
129
  generatedAt,
114
130
  sourceHash,
131
+ sourceHashes: sourceHashes || null,
115
132
  nodeCount: normNodes.length,
116
133
  edgeCount: normEdges.length,
117
134
  nodesByType: countBy(normNodes, 'type'),
@@ -0,0 +1,161 @@
1
+ /**
2
+ * lib/graph/validate.mjs — living-graph structural validation.
3
+ *
4
+ * validateGraph() checks workflow→provider→tool requires-edges, provider-manifest
5
+ * disk presence, doc-file existence, capability test coverage (LMCP-C5,
6
+ * sourced from lib/graph/gaps.mjs so `graph validate` and
7
+ * `graph missing-tests` never disagree), workflow-manifest liveness
8
+ * (roleChain resolution, skill existence, acyclic handoffs, surface/mode
9
+ * reachability — LMCP-C11, lib/workflows/liveness.mjs), and workflow
10
+ * surface parity (declared `surfaces` vs actual CLI/MCP/SDK registration —
11
+ * LMCP-D4, lib/workflows/surface-parity.mjs), classifying each gap
12
+ * as error vs warning per deployment mode (solo lenient, team/enterprise
13
+ * strict). Backs `construct graph validate`.
14
+ */
15
+
16
+ import { loadGraph, nodesByType, dependenciesOf, dependentsOf } from './store.mjs';
17
+ import { getDeploymentMode } from '../deployment-mode.mjs';
18
+ import { loadAllWorkflows } from '../workflows/loader.mjs';
19
+ import { checkWorkflowLiveness } from '../workflows/liveness.mjs';
20
+ import { checkSurfaceParity } from '../workflows/surface-parity.mjs';
21
+ import { findMissingTestCapabilities } from './gaps.mjs';
22
+ import { existsSync } from 'node:fs';
23
+ import path from 'node:path';
24
+
25
+ const MANIFESTS_REL_DIR = path.join('lib', 'extensions', 'manifests');
26
+
27
+ export function nodeParts(id) {
28
+ const colon = id.indexOf(':');
29
+ return colon === -1 ? { type: null, key: id } : { type: id.slice(0, colon), key: id.slice(colon + 1) };
30
+ }
31
+
32
+ export function validateGraph(rootDir, { strict = false, deploymentMode } = {}) {
33
+ const graph = loadGraph(rootDir);
34
+ if (!graph.exists) {
35
+ return { valid: false, errors: ['no graph found — run construct graph build first'], warnings: [], infos: [] };
36
+ }
37
+
38
+ const mode = deploymentMode || getDeploymentMode(process.env, { cwd: rootDir });
39
+ const isStrict = mode === 'team' || mode === 'enterprise' || strict;
40
+
41
+ const errors = [];
42
+ const warnings = [];
43
+ const infos = [];
44
+
45
+ const workflowNodes = nodesByType(graph, 'workflow');
46
+ for (const wf of workflowNodes) {
47
+ const embeddingCaps = dependentsOf(graph, wf.id, 'embeds');
48
+ for (const capId of embeddingCaps) {
49
+ const usedToolIds = dependenciesOf(graph, capId, 'uses')
50
+ .filter(id => id.startsWith('provider:'));
51
+ for (const providerId of usedToolIds) {
52
+ const providerToolIds = dependenciesOf(graph, providerId, 'requires');
53
+ const hasRequiredTools = providerToolIds.some(toolId =>
54
+ dependenciesOf(graph, capId, 'requires').includes(toolId)
55
+ );
56
+ if (!hasRequiredTools) {
57
+ const providerName = nodeParts(providerId).key;
58
+ const msg = `workflow '${wf.name || wf.id}' embeds capability '${capId}' missing 'requires' edge to provider tools for '${providerName}'`;
59
+ if (isStrict) errors.push(msg); else warnings.push(msg);
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ const providerNodes = nodesByType(graph, 'provider');
66
+ for (const provider of providerNodes) {
67
+ const providerName = provider.attrs?.id || nodeParts(provider.id).key;
68
+ const manifestPath = path.join(rootDir, MANIFESTS_REL_DIR, `${providerName}.manifest.json`);
69
+ if (!existsSync(manifestPath)) {
70
+ const msg = `provider '${provider.id}' has no manifest on disk at ${path.relative(rootDir, manifestPath)}`;
71
+ if (isStrict) errors.push(msg); else warnings.push(msg);
72
+ }
73
+ }
74
+
75
+ const docNodes = nodesByType(graph, 'doc');
76
+ for (const doc of docNodes) {
77
+ const docRel = doc.attrs?.path || nodeParts(doc.id).key;
78
+ if (docRel) {
79
+ const resolved = path.isAbsolute(docRel) ? docRel : path.join(rootDir, docRel);
80
+ if (!existsSync(resolved)) {
81
+ const msg = `doc '${doc.id}' references file '${resolved}' that does not exist`;
82
+ if (isStrict) errors.push(msg); else warnings.push(msg);
83
+ }
84
+ }
85
+ }
86
+
87
+ // Capability test coverage (LMCP-C5): sourced from the same
88
+ // findMissingTestCapabilities the `missing-tests` gap query uses, so the
89
+ // two never drift out of consistency with each other.
90
+ const gaps = findMissingTestCapabilities(rootDir);
91
+ for (const capId of gaps.capabilities) {
92
+ const msg = `capability '${capId}' has zero validating tests`;
93
+ if (isStrict) errors.push(msg); else warnings.push(msg);
94
+ }
95
+ for (const wfId of gaps.workflows) {
96
+ const msg = `workflow '${wfId}' has zero validated embedding capability`;
97
+ if (isStrict) errors.push(msg); else warnings.push(msg);
98
+ }
99
+
100
+ // Embed-node integrity (LMCP-P6): every binding an embed manifest declares
101
+ // must resolve to a real node. A `uses`/`owned_by`/`governed_by` edge whose
102
+ // target node is absent is a broken binding — the manifest names a provider,
103
+ // specialist, or contract that does not exist in the graph — and is drift the
104
+ // C8 gate must catch. An embed with zero validating tests is treated like an
105
+ // untested capability so presets cannot silently lose coverage.
106
+ const embedTargetRels = [
107
+ ['uses', 'provider'],
108
+ ['owned_by', 'specialist'],
109
+ ['governed_by', 'contract'],
110
+ ];
111
+ for (const embed of nodesByType(graph, 'embed')) {
112
+ for (const [rel, kind] of embedTargetRels) {
113
+ for (const targetId of dependenciesOf(graph, embed.id, rel)) {
114
+ if (!graph.nodes.has(targetId)) {
115
+ const msg = `embed '${embed.name || embed.id}' binds ${kind} '${targetId}' which has no node in the graph`;
116
+ if (isStrict) errors.push(msg); else warnings.push(msg);
117
+ }
118
+ }
119
+ }
120
+ if (dependentsOf(graph, embed.id, 'validates').length === 0) {
121
+ const msg = `embed '${embed.name || embed.id}' has zero validating tests`;
122
+ if (isStrict) errors.push(msg); else warnings.push(msg);
123
+ }
124
+ }
125
+
126
+ // Security-coverage edge integrity (LMCP-N8): a `@secures <id>` tag that
127
+ // names a workflow/embed with no node is a typo'd or stale link that would
128
+ // silently contribute zero coverage — flag the dangling target.
129
+ for (const edge of graph.edges) {
130
+ if (edge.rel !== 'secures') continue;
131
+ if (!graph.nodes.has(edge.to)) {
132
+ const msg = `security test '${edge.from}' @secures '${edge.to}' which has no node in the graph`;
133
+ if (isStrict) errors.push(msg); else warnings.push(msg);
134
+ }
135
+ }
136
+
137
+ // Workflow liveness (LMCP-C11): schema validation only checks shape; these
138
+ // checks catch a roleChain naming a nonexistent specialist, a circular
139
+ // handoff, or a workflow no surface/mode can reach — failures that
140
+ // otherwise surface only at workflow-run time.
141
+ const { workflows } = loadAllWorkflows({ rootDir });
142
+ const { violations } = checkWorkflowLiveness(workflows, { rootDir });
143
+ for (const msg of violations) {
144
+ if (isStrict) errors.push(msg); else warnings.push(msg);
145
+ }
146
+
147
+ // Workflow surface parity (LMCP-D4): a manifest's declared `surfaces` must
148
+ // match its actual CLI/MCP registration (both wrap the same invokeWorkflow
149
+ // core, so any undeclared divergence is a real authoring drift, not a
150
+ // runtime possibility) — an undeclared mismatch is always an error,
151
+ // regardless of deployment mode, since it is never a matter of team policy.
152
+ const { errors: surfaceErrors, infos: surfaceInfos } = checkSurfaceParity(workflows);
153
+ errors.push(...surfaceErrors);
154
+ infos.push(...surfaceInfos);
155
+
156
+ infos.push('degraded_by check: not yet implemented (no degraded_by edges in C1 scope)');
157
+ infos.push('evidenced_by edges: sourced from persisted orchestration runs (LMCP-C9, see lib/graph/runtime-evidence.mjs and `graph explain`)');
158
+
159
+ const valid = errors.length === 0;
160
+ return { valid, errors, warnings, infos };
161
+ }
package/lib/headhunt.mjs CHANGED
@@ -16,6 +16,7 @@ import { selectOption } from './tty-prompts.mjs';
16
16
  import { resolveActiveScope } from './scopes/loader.mjs';
17
17
  import { classifyObjectiveForScope } from './scopes/teams.mjs';
18
18
  import { loadRegistry } from './registry/loader.mjs';
19
+ import { isMainModule } from './roots.mjs';
19
20
 
20
21
  const OVERLAY_DIRNAME = 'domain-overlays';
21
22
  const PROMOTION_DIRNAME = 'promotion-requests';
@@ -102,7 +103,7 @@ export function classifyOverlay(objective = '', explicitTeam = '', { cwd = proce
102
103
  if (/runbook|docs|documentation|guide|onboard/.test(text)) {
103
104
  return {
104
105
  focus: 'documentation',
105
- attachTo: ['cx-docs-keeper', 'cx-researcher'],
106
+ attachTo: ['cx-operations', 'cx-researcher'],
106
107
  teamFocus: 'operations-group',
107
108
  recommendedTeam: 'operations-group',
108
109
  };
@@ -292,10 +293,10 @@ export function promoteHeadhunt(id, { cwd = process.cwd(), owner = null } = {})
292
293
  attachTo: overlayEntry.record.attachTo,
293
294
  focus: overlayEntry.record.focus,
294
295
  sourceOverlay: overlayEntry.filePath,
295
- reviewFlow: ['cx-architect', 'cx-devil-advocate', 'cx-docs-keeper'],
296
+ reviewFlow: ['cx-architect', 'cx-reviewer', 'cx-operations'],
296
297
  challenge: {
297
298
  required: true,
298
- owner: 'cx-devil-advocate',
299
+ owner: 'cx-reviewer',
299
300
  status: 'pending',
300
301
  reason: 'Persistent capabilities must be challenged before promotion.',
301
302
  },
@@ -313,7 +314,7 @@ export function updatePromotionChallenge(id, { cwd = process.cwd(), status, note
313
314
  const requestPath = path.join(getPromotionDir(root), `${id}.json`);
314
315
  const request = readJsonIfExists(requestPath);
315
316
  if (!request) throw new Error(`Unknown promotion request: ${id}`);
316
- request.challenge = request.challenge || { required: true, owner: 'cx-devil-advocate', status: 'pending' };
317
+ request.challenge = request.challenge || { required: true, owner: 'cx-reviewer', status: 'pending' };
317
318
  if (status) request.challenge.status = status;
318
319
  if (note) request.challenge.note = note;
319
320
  request.updatedAt = new Date().toISOString();
@@ -539,7 +540,7 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
539
540
  promotionGates: buildPromotionGates(),
540
541
  challenge: permanence === 'temporary' ? null : {
541
542
  required: true,
542
- owner: 'cx-devil-advocate',
543
+ owner: 'cx-reviewer',
543
544
  status: 'pending',
544
545
  reason: 'Promotion requests must be challenged before any persistent capability is added.',
545
546
  },
@@ -587,10 +588,10 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
587
588
  attachTo: classification.attachTo,
588
589
  focus: classification.focus,
589
590
  sourceOverlay: overlayJsonPath,
590
- reviewFlow: ['cx-architect', 'cx-devil-advocate', 'cx-docs-keeper'],
591
+ reviewFlow: ['cx-architect', 'cx-reviewer', 'cx-operations'],
591
592
  challenge: {
592
593
  required: true,
593
- owner: 'cx-devil-advocate',
594
+ owner: 'cx-reviewer',
594
595
  status: 'pending',
595
596
  reason: 'Promotion requests must be challenged before any persistent capability is added.',
596
597
  },
@@ -617,7 +618,7 @@ export async function runHeadhunt({ args = [], cwd = process.cwd(), homeDir = os
617
618
  }
618
619
  }
619
620
 
620
- if (import.meta.url === `file://${process.argv[1]}`) {
621
+ if (isMainModule(import.meta.url)) {
621
622
  runHeadhunt({ args: process.argv.slice(2) }).catch((error) => {
622
623
  process.stderr.write(`Error: ${error.message}\n`);
623
624
  process.exit(1);
@@ -12,6 +12,7 @@ import path from 'node:path';
12
12
  import { existsSync, readFileSync, chmodSync } from 'node:fs';
13
13
  import { extractOpRef, hasAnySecret, hasSecret } from './providers/secret-resolver.mjs';
14
14
  import { configDir } from './config/xdg.mjs';
15
+ import { getDeploymentMode } from './deployment-mode.mjs';
15
16
 
16
17
  const isWindows = os.platform() === 'win32';
17
18
 
@@ -61,16 +62,22 @@ export async function checkPrerequisites(options = {}) {
61
62
  const missing = [];
62
63
  const warnings = [];
63
64
 
64
- // Check Docker
65
+ // Docker is required only when the deployment mode uses Docker worker pools (team/enterprise).
66
+ // Solo mode uses embedded LanceDB + Git-backed state; Postgres was removed, so Docker is optional.
67
+ const deploymentMode = getDeploymentMode(process.env);
68
+ const dockerRequired = deploymentMode === 'team' || deploymentMode === 'enterprise';
69
+
65
70
  const dockerVersion = getVersion('docker');
66
71
  const hasDocker = !!dockerVersion;
67
72
  checks.push({
68
73
  name: 'Docker',
69
- required: true,
74
+ required: dockerRequired,
70
75
  installed: hasDocker,
71
76
  version: dockerVersion,
72
- why: 'Required for managed Postgres; trace capture works locally without Docker',
73
- install: isWindows
77
+ why: dockerRequired
78
+ ? 'Required for Docker worker pool in team/enterprise mode'
79
+ : 'Optional in solo mode (required for team/enterprise)',
80
+ install: isWindows
74
81
  ? 'https://docs.docker.com/desktop/install/windows-install/'
75
82
  : 'https://docs.docker.com/get-docker/',
76
83
  });
@@ -403,9 +410,10 @@ export async function quickHealthCheck(options = {}) {
403
410
  const { homeDir, showCredentials = false } = options;
404
411
 
405
412
  const critical = [];
406
-
407
- // Silent Docker check
408
- if (!commandExists('docker')) {
413
+
414
+ // Docker is only critical in team/enterprise mode where Docker worker pools are required.
415
+ const deployMode = getDeploymentMode(process.env);
416
+ if ((deployMode === 'team' || deployMode === 'enterprise') && !commandExists('docker')) {
409
417
  critical.push('Docker');
410
418
  }
411
419
 
@@ -436,7 +436,7 @@ export function formatHealthStatus(health) {
436
436
  output += `\nCRITICAL ISSUES:\n`;
437
437
  output += `The following hooks are critical and not functioning:\n`;
438
438
  output += health.criticalFailed.join(', ') + '\n';
439
- output += `\nRecommendation: Run \`construct doctor hooks --fix\` to attempt repair\n`;
439
+ output += `\nRecommendation: Run \`construct hooks:health --fix\` to attempt repair\n`;
440
440
  }
441
441
 
442
442
  return output;