@geraldmaron/construct 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +55 -38
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +261 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +103 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -0,0 +1,303 @@
1
+ /**
2
+ * lib/embed/capability-loader.mjs — embed-capability manifest loader (ADR-0061, LMCP-P2).
3
+ *
4
+ * An embed capability is a workflow manifest (lib/workflows/loader.mjs, D1)
5
+ * with `type: "embed"` and an `embed` block. This module discovers those
6
+ * manifests across the three D1 tiers (builtin, pack, project), validates
7
+ * the `embed` block against the ADR-0061 shape — including provider
8
+ * bindings (E4) and the ADR-0060 filter block (reused read-only from
9
+ * lib/providers/filter-schema.mjs / lib/providers/contract.mjs) — and
10
+ * exposes the per-project enable/disable lifecycle described in the ADR:
11
+ * pack tiers ship *available* defaults, `.cx/embed/<id>.manifest.json`
12
+ * (project tier) makes a capability *active*.
13
+ *
14
+ * Every validation failure returns a JSON-schema-style field path
15
+ * (`embed.providerBindings[1]`, `embed.filter.scope.repos`, …) so a failed
16
+ * `enable` names exactly what is wrong — this is a fail-closed surface, not
17
+ * best-effort: an invalid manifest is never silently accepted or partially
18
+ * applied.
19
+ */
20
+
21
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
22
+ import { join } from 'node:path';
23
+
24
+ import { loadWorkflowManifestsFromDir, mergeWorkflowManifests, resolveWorkflowManifestDirs } from '../workflows/loader.mjs';
25
+ import { validateWorkflowManifest } from '../workflows/validate.mjs';
26
+ import { loadAllPacks } from '../packs/loader.mjs';
27
+ import { validateFilterConfig } from '../providers/contract.mjs';
28
+ import { TOP_LEVEL_KEYS as FILTER_TOP_LEVEL_KEYS, SCOPE_KEYS as FILTER_SCOPE_KEYS, PREDICATE_KEYS as FILTER_PREDICATE_KEYS } from '../providers/filter-schema.mjs';
29
+
30
+ export const EMBED_MANIFEST_TYPE = 'embed';
31
+
32
+ export const RUNTIME_VALUES = Object.freeze(['in-process', 'external', 'auto', 'none']);
33
+
34
+ export const PROPOSAL_AUTHORITY_VALUES = Object.freeze(['propose-only', 'governed-write']);
35
+
36
+ const REQUIRED_EMBED_FIELDS = Object.freeze([
37
+ 'specialist', 'providerBindings', 'framework', 'outputContract', 'proposalAuthority', 'runtime',
38
+ ]);
39
+
40
+ /**
41
+ * Directory holding per-project embed enable/override manifests
42
+ * (project tier of the D1 loader, mirrored under `.cx/embed/`).
43
+ */
44
+ export function embedProjectDir(rootDir = process.cwd()) {
45
+ return join(rootDir, '.cx', 'embed');
46
+ }
47
+
48
+ function fieldError(filePath, fieldPath, message) {
49
+ const prefix = filePath ? `${filePath}: ` : '';
50
+ return `${prefix}${fieldPath}: ${message}`;
51
+ }
52
+
53
+ /**
54
+ * Validate the `embed` block of a `type: "embed"` workflow manifest against
55
+ * the ADR-0061 shape. Returns `{ valid: true }` or `{ valid: false, errors }`
56
+ * where every error string is prefixed with its JSON-schema-style field path
57
+ * (e.g. `embed.providerBindings[0]`) so a caller can point at exactly what
58
+ * broke. Never throws.
59
+ *
60
+ * `knownSpecialists` — merged E1 pack registry specialist ids; when supplied,
61
+ * an unresolvable `embed.specialist` is a hard error (fail closed rather than
62
+ * silently accepting a typo'd persona reference).
63
+ */
64
+ export function validateEmbedBlock(manifest, { filePath, knownSpecialists } = {}) {
65
+ const errors = [];
66
+
67
+ if (manifest.type !== EMBED_MANIFEST_TYPE) {
68
+ return { valid: false, errors: [fieldError(filePath, 'type', `must be "${EMBED_MANIFEST_TYPE}" (got '${manifest.type}')`)] };
69
+ }
70
+
71
+ const embed = manifest.embed;
72
+ if (embed === undefined || embed === null || typeof embed !== 'object' || Array.isArray(embed)) {
73
+ return { valid: false, errors: [fieldError(filePath, 'embed', 'must be an object present on every type:"embed" manifest')] };
74
+ }
75
+
76
+ for (const field of REQUIRED_EMBED_FIELDS) {
77
+ if (!(field in embed) || embed[field] === undefined || embed[field] === null) {
78
+ errors.push(fieldError(filePath, `embed.${field}`, 'missing required field'));
79
+ }
80
+ }
81
+
82
+ if ('specialist' in embed && embed.specialist !== undefined && embed.specialist !== null) {
83
+ if (typeof embed.specialist !== 'string' || embed.specialist.length === 0) {
84
+ errors.push(fieldError(filePath, 'embed.specialist', 'must be a non-empty string'));
85
+ } else if (Array.isArray(knownSpecialists) && knownSpecialists.length > 0 && !knownSpecialists.includes(embed.specialist)) {
86
+ errors.push(fieldError(filePath, 'embed.specialist', `unresolvable specialist id '${embed.specialist}' (not present in the merged pack registry)`));
87
+ }
88
+ }
89
+
90
+ if ('providerBindings' in embed && embed.providerBindings !== undefined && embed.providerBindings !== null) {
91
+ if (!Array.isArray(embed.providerBindings)) {
92
+ errors.push(fieldError(filePath, 'embed.providerBindings', 'must be an array of provider ids'));
93
+ } else if (embed.providerBindings.length === 0) {
94
+ errors.push(fieldError(filePath, 'embed.providerBindings', 'must declare at least one provider id'));
95
+ } else {
96
+ embed.providerBindings.forEach((binding, idx) => {
97
+ if (typeof binding !== 'string' || binding.length === 0) {
98
+ errors.push(fieldError(filePath, `embed.providerBindings[${idx}]`, 'must be a non-empty string'));
99
+ }
100
+ });
101
+ }
102
+ }
103
+
104
+ if ('framework' in embed && embed.framework !== undefined && embed.framework !== null) {
105
+ if (typeof embed.framework !== 'string' || embed.framework.length === 0) {
106
+ errors.push(fieldError(filePath, 'embed.framework', 'must be a non-empty string'));
107
+ }
108
+ }
109
+
110
+ if ('outputContract' in embed && embed.outputContract !== undefined && embed.outputContract !== null) {
111
+ if (typeof embed.outputContract !== 'string' || embed.outputContract.length === 0) {
112
+ errors.push(fieldError(filePath, 'embed.outputContract', 'must be a non-empty string'));
113
+ }
114
+ }
115
+
116
+ if ('proposalAuthority' in embed && embed.proposalAuthority !== undefined && embed.proposalAuthority !== null) {
117
+ if (!PROPOSAL_AUTHORITY_VALUES.includes(embed.proposalAuthority)) {
118
+ errors.push(fieldError(filePath, 'embed.proposalAuthority', `must be one of: ${PROPOSAL_AUTHORITY_VALUES.join(', ')} (got '${embed.proposalAuthority}')`));
119
+ }
120
+ }
121
+
122
+ if ('runtime' in embed && embed.runtime !== undefined && embed.runtime !== null) {
123
+ if (!RUNTIME_VALUES.includes(embed.runtime)) {
124
+ errors.push(fieldError(filePath, 'embed.runtime', `must be one of: ${RUNTIME_VALUES.join(', ')} (got '${embed.runtime}')`));
125
+ }
126
+ }
127
+
128
+ if ('cadence' in embed && embed.cadence !== undefined && embed.cadence !== null) {
129
+ if (typeof embed.cadence !== 'object' || Array.isArray(embed.cadence)) {
130
+ errors.push(fieldError(filePath, 'embed.cadence', 'must be an object (e.g. { "every": "PT15M" })'));
131
+ } else if ('every' in embed.cadence && typeof embed.cadence.every !== 'string') {
132
+ errors.push(fieldError(filePath, 'embed.cadence.every', 'must be a string (ISO-8601 duration)'));
133
+ }
134
+ }
135
+
136
+ if ('filter' in embed && embed.filter !== undefined && embed.filter !== null) {
137
+ errors.push(...validateEmbedFilterBlock(embed.filter, embed.providerBindings, filePath));
138
+ }
139
+
140
+ if (errors.length > 0) return { valid: false, errors };
141
+ return { valid: true };
142
+ }
143
+
144
+ /**
145
+ * Validate the ADR-0060 filter block carried at `embed.filter`, field-path
146
+ * prefixed for the embed-manifest context. Structural checks mirror
147
+ * lib/providers/filter-schema.mjs; per-provider-kind scope legality is
148
+ * re-checked with the read-only lib/providers/contract.mjs
149
+ * `validateFilterConfig` against every bound provider id so a filter that
150
+ * is legal for one binding but not another is caught here rather than at
151
+ * daemon runtime.
152
+ */
153
+ function validateEmbedFilterBlock(filter, providerBindings, filePath) {
154
+ const errors = [];
155
+
156
+ if (typeof filter !== 'object' || filter === null || Array.isArray(filter)) {
157
+ return [fieldError(filePath, 'embed.filter', 'must be an object')];
158
+ }
159
+
160
+ for (const key of Object.keys(filter)) {
161
+ if (!FILTER_TOP_LEVEL_KEYS.includes(key)) {
162
+ errors.push(fieldError(filePath, `embed.filter.${key}`, `unknown filter key; allowed: ${FILTER_TOP_LEVEL_KEYS.join(', ')}`));
163
+ }
164
+ }
165
+
166
+ if (filter.scope !== undefined) {
167
+ if (typeof filter.scope !== 'object' || filter.scope === null || Array.isArray(filter.scope)) {
168
+ errors.push(fieldError(filePath, 'embed.filter.scope', 'must be an object'));
169
+ } else {
170
+ for (const key of Object.keys(filter.scope)) {
171
+ if (!FILTER_SCOPE_KEYS.includes(key)) {
172
+ errors.push(fieldError(filePath, `embed.filter.scope.${key}`, `unknown scope key; allowed: ${FILTER_SCOPE_KEYS.join(', ')}`));
173
+ } else if (!Array.isArray(filter.scope[key])) {
174
+ errors.push(fieldError(filePath, `embed.filter.scope.${key}`, 'must be an array'));
175
+ }
176
+ }
177
+ }
178
+ }
179
+
180
+ if (filter.predicates !== undefined) {
181
+ if (typeof filter.predicates !== 'object' || filter.predicates === null || Array.isArray(filter.predicates)) {
182
+ errors.push(fieldError(filePath, 'embed.filter.predicates', 'must be an object'));
183
+ } else {
184
+ for (const key of Object.keys(filter.predicates)) {
185
+ if (!FILTER_PREDICATE_KEYS.includes(key)) {
186
+ errors.push(fieldError(filePath, `embed.filter.predicates.${key}`, `unknown predicate key; allowed: ${FILTER_PREDICATE_KEYS.join(', ')}`));
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ // Structural errors already found: skip the per-provider re-check, its
193
+ // messages would only restate the same problem under a different path.
194
+ if (errors.length > 0) return errors;
195
+
196
+ // Fail closed per bound provider: a filter valid for "github" but not
197
+ // "jira" must not silently apply to jira's snapshot with github's rules.
198
+ for (const providerId of Array.isArray(providerBindings) ? providerBindings : []) {
199
+ try {
200
+ validateFilterConfig(providerId, filter);
201
+ } catch (err) {
202
+ errors.push(fieldError(filePath, 'embed.filter', `invalid for bound provider '${providerId}': ${err.message}`));
203
+ }
204
+ }
205
+
206
+ return errors;
207
+ }
208
+
209
+ /**
210
+ * Discover embed-capability manifests across the D1 three-tier loader
211
+ * (builtin, every pack root's workflows/ dir, project .cx/embed/), validate
212
+ * the base workflow-manifest shape via lib/workflows/validate.mjs and the
213
+ * ADR-0061 `embed` block via validateEmbedBlock, and merge by id with
214
+ * project overriding pack overriding builtin. Non-`type:"embed"` workflow
215
+ * manifests present in the same directories are silently excluded — this
216
+ * loader is embed-capability-scoped, not a general workflow loader.
217
+ *
218
+ * @param {{ rootDir?: string, packRoots?: string[], knownSpecialists?: string[] }} [opts]
219
+ * @returns {{ capabilities: object[], errors: string[] }}
220
+ */
221
+ export function loadEmbedCapabilities(opts = {}) {
222
+ const { rootDir = process.cwd() } = opts;
223
+ const errors = [];
224
+
225
+ let packRoots = opts.packRoots;
226
+ let knownSpecialists = opts.knownSpecialists;
227
+ if (!packRoots || !knownSpecialists) {
228
+ const { packs, errors: packErrors } = loadAllPacks({ rootDir });
229
+ errors.push(...packErrors);
230
+ if (!packRoots) packRoots = packs.map((p) => p._packDir).filter(Boolean);
231
+ if (!knownSpecialists) knownSpecialists = [...new Set(packs.flatMap((p) => p.specialists ?? []))];
232
+ }
233
+
234
+ const dirs = resolveWorkflowManifestDirs({ rootDir, packRoots });
235
+
236
+ // Project-tier embed manifests live under .cx/embed/ (not .cx/workflows/,
237
+ // which is D1's general workflow project tier) — see embedProjectDir().
238
+ const projectDir = embedProjectDir(rootDir);
239
+
240
+ const builtin = loadWorkflowManifestsFromDir(dirs.builtin);
241
+ errors.push(...builtin.errors);
242
+
243
+ const packManifests = [];
244
+ for (const packDir of dirs.pack) {
245
+ const result = loadWorkflowManifestsFromDir(packDir);
246
+ packManifests.push(...result.manifests);
247
+ errors.push(...result.errors);
248
+ }
249
+
250
+ const project = loadWorkflowManifestsFromDir(projectDir);
251
+ errors.push(...project.errors);
252
+
253
+ const merged = mergeWorkflowManifests(builtin.manifests, packManifests, project.manifests);
254
+
255
+ const capabilities = [];
256
+ for (const manifest of merged) {
257
+ if (manifest.type !== EMBED_MANIFEST_TYPE) continue;
258
+
259
+ const result = validateEmbedBlock(manifest, { filePath: manifest._filePath, knownSpecialists });
260
+ if (!result.valid) {
261
+ errors.push(...result.errors);
262
+ continue;
263
+ }
264
+ capabilities.push(manifest);
265
+ }
266
+
267
+ return { capabilities, errors };
268
+ }
269
+
270
+ /**
271
+ * Validate a single candidate manifest object end to end (base workflow
272
+ * shape + ADR-0061 embed block) without touching disk, so an `enable` call
273
+ * can reject a bad manifest before any project-tier file is written.
274
+ */
275
+ export function validateEmbedManifest(manifest, { filePath, knownSpecialists } = {}) {
276
+ const base = validateWorkflowManifest(manifest, { filePath });
277
+ if (!base.valid) return base;
278
+ return validateEmbedBlock(manifest, { filePath, knownSpecialists });
279
+ }
280
+
281
+ /**
282
+ * Read the project-tier enable/override manifest for `id`, if present.
283
+ * Returns null when the capability has never been enabled in this project.
284
+ */
285
+ export function readProjectEmbedManifest(id, rootDir = process.cwd()) {
286
+ const filePath = join(embedProjectDir(rootDir), `${id}.manifest.json`);
287
+ if (!existsSync(filePath)) return null;
288
+ return { manifest: JSON.parse(readFileSync(filePath, 'utf8')), filePath };
289
+ }
290
+
291
+ /**
292
+ * Write the project-tier enable/override manifest for `id`, creating
293
+ * `.cx/embed/` on demand. Callers must validate before calling — the
294
+ * function performs no validation, matching the D1 loader's separation
295
+ * between building a manifest object and validating one.
296
+ */
297
+ export function writeProjectEmbedManifest(id, manifest, rootDir = process.cwd()) {
298
+ const dir = embedProjectDir(rootDir);
299
+ mkdirSync(dir, { recursive: true });
300
+ const filePath = join(dir, `${id}.manifest.json`);
301
+ writeFileSync(filePath, `${JSON.stringify(manifest, null, 2)}\n`);
302
+ return filePath;
303
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * lib/embed/capability-runtime.mjs — ADR-0061 §3 runtime selector resolution.
3
+ *
4
+ * Resolves an embed capability's `runtime` field (`in-process` | `external`
5
+ * | `auto` | `none`) to the runtime that will actually execute reasoning,
6
+ * without performing that reasoning. `auto` resolves to `in-process` when a
7
+ * model provider is configured (the existing model-resolution path,
8
+ * lib/model-router.mjs `readCurrentModels`), else `external` when an
9
+ * external host is configured (`CONSTRUCT_EXTERNAL_AGENT_HOST`), else
10
+ * `none`. `none` is an honest, visible skip — never a fabricated result —
11
+ * per the workflow-invoke.mjs no-fabrication invariant this ADR preserves.
12
+ */
13
+
14
+ export const SKIP_REASON_NO_RUNTIME = 'no-runtime';
15
+
16
+ /**
17
+ * True when the existing model-resolution path has a model configured for
18
+ * any tier. Reuses lib/model-router.mjs so this module adds no new
19
+ * dependency — it is a read-only probe of state Construct already resolves.
20
+ */
21
+ async function hasConfiguredModelProvider(env) {
22
+ try {
23
+ const { readCurrentModels } = await import('../model-router.mjs');
24
+ const models = readCurrentModels(null, {}, env);
25
+ return Boolean(models?.fast || models?.standard || models?.reasoning);
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ function hasExternalRuntimeConfigured(env) {
32
+ return Boolean((env.CONSTRUCT_EXTERNAL_AGENT_HOST ?? '').trim());
33
+ }
34
+
35
+ /**
36
+ * Resolve the declared `runtime` selector to the runtime that will actually
37
+ * execute reasoning.
38
+ *
39
+ * @param {'in-process'|'external'|'auto'|'none'} runtime
40
+ * @param {object} [env] - env object (default: process.env)
41
+ * @returns {Promise<{ resolved: 'in-process'|'external'|'none', reason?: string }>}
42
+ */
43
+ export async function resolveRuntime(runtime, env = process.env) {
44
+ if (runtime === 'in-process') return { resolved: 'in-process' };
45
+ if (runtime === 'external') {
46
+ if (!hasExternalRuntimeConfigured(env)) {
47
+ return { resolved: 'none', reason: SKIP_REASON_NO_RUNTIME };
48
+ }
49
+ return { resolved: 'external' };
50
+ }
51
+ if (runtime === 'none') return { resolved: 'none', reason: SKIP_REASON_NO_RUNTIME };
52
+
53
+ // auto: in-process if a model provider is configured, else external if a
54
+ // host is configured, else none.
55
+ if (await hasConfiguredModelProvider(env)) return { resolved: 'in-process' };
56
+ if (hasExternalRuntimeConfigured(env)) return { resolved: 'external' };
57
+ return { resolved: 'none', reason: SKIP_REASON_NO_RUNTIME };
58
+ }
package/lib/embed/cli.mjs CHANGED
@@ -7,14 +7,25 @@
7
7
  * status Print daemon status + last snapshot summary
8
8
  * snapshot [--config <path>] Run a one-shot snapshot and print to stdout
9
9
  *
10
+ * Embed-capability lifecycle (ADR-0061, LMCP-P2) — per-specialist capability
11
+ * manifests, distinct from the daemon process above:
12
+ * list Available capabilities + per-project enabled state
13
+ * enable <id> Write .cx/embed/<id>.manifest.json, validate, activate
14
+ * disable <id> Mark a capability disabled (scheduled job stops)
15
+ * status <id> [--json] Bindings/filter/runtime/last-tick for one capability
16
+ * dry-run <id> [--json] Resolve the binding chain; no side effects
17
+ *
10
18
  * Exports:
11
19
  * resolveEmbedStatus(env, homeDir) — returns { level, label, detail }
12
20
  * level: 'running' | 'stopped' | 'none'
13
21
  * Consumed by session-start hook and `construct embed status`.
14
22
  *
15
- * autoStartEmbedIfNeeded(env, rootDir, homeDir) — spawns daemon when
16
- * provider credentials are present but daemon is stopped. No-op if already
17
- * running or no credentials. Returns { started: bool, pid?, reason? }.
23
+ * autoStartEmbedIfNeeded(env, rootDir, homeDir, cwd) — spawns daemon when
24
+ * provider credentials are present but daemon is stopped, gated by
25
+ * CX_AUTO_EMBED (env, wins) or construct.config.json `autoEmbed` (config
26
+ * fallback, same env > config > default precedence as deployment mode).
27
+ * No-op if already running or no credentials. Returns
28
+ * { started: bool, pid?, reason? }.
18
29
  */
19
30
 
20
31
  import fs from 'node:fs';
@@ -23,6 +34,7 @@ import path from 'node:path';
23
34
  import { spawn } from 'node:child_process';
24
35
  import { configDir } from '../config/xdg.mjs';
25
36
  import { doctorRoot } from '../config/xdg.mjs';
37
+ import { loadProjectConfig } from '../config/project-config.mjs';
26
38
 
27
39
  const STATE_FILE = 'embed-daemon.json';
28
40
  const LOG_FILE = 'embed-daemon.log';
@@ -202,10 +214,17 @@ export function resolveEmbedStatus(env = process.env, homeDir = os.homedir()) {
202
214
  * @param {object} [env] - Env object (default: process.env)
203
215
  * @param {string} [rootDir] - Construct root dir (where lib/embed/worker.mjs lives)
204
216
  * @param {string} [homeDir] - Home dir override
217
+ * @param {string} [cwd] - Project dir for the construct.config.json `autoEmbed` fallback
205
218
  * @returns {{ started: boolean, pid?: number, reason?: string }}
206
219
  */
207
- export async function autoStartEmbedIfNeeded(env = process.env, rootDir, homeDir = os.homedir()) {
208
- if (!env.CX_AUTO_EMBED && env.CX_AUTO_EMBED !== '1') {
220
+ export async function autoStartEmbedIfNeeded(env = process.env, rootDir, homeDir = os.homedir(), cwd = process.cwd()) {
221
+ let configEnabled = false;
222
+ try {
223
+ const { config } = loadProjectConfig(cwd, env);
224
+ configEnabled = config?.autoEmbed === true;
225
+ } catch { /* loader is best-effort — falls through to env-only gating */ }
226
+
227
+ if (env.CX_AUTO_EMBED !== '1' && !configEnabled) {
209
228
  return { started: false, reason: 'CX_AUTO_EMBED not set' };
210
229
  }
211
230
 
@@ -420,6 +439,152 @@ async function cmdEmbedSnapshot(args, { homeDir = os.homedir() } = {}) {
420
439
  process.stderr.write(`\n${snapshot.summary.totalItems} items, ${snapshot.summary.errorCount} errors\n`);
421
440
  }
422
441
 
442
+ // ---------------------------------------------------------------------------
443
+ // Embed-capability lifecycle subcommands (ADR-0061, LMCP-P2)
444
+ // ---------------------------------------------------------------------------
445
+
446
+ function jsonFlag(args) {
447
+ return args.includes('--json');
448
+ }
449
+
450
+ function positionalId(args) {
451
+ return args.find((a) => !a.startsWith('--'));
452
+ }
453
+
454
+ async function cmdCapabilityList(args, { rootDir = process.cwd() } = {}) {
455
+ const { listCapabilities } = await import('./capability-lifecycle.mjs');
456
+ const { capabilities, errors } = listCapabilities({ rootDir });
457
+
458
+ if (jsonFlag(args)) {
459
+ process.stdout.write(`${JSON.stringify({ capabilities: capabilities.map((c) => ({
460
+ id: c.id, available: c.available, enabled: c.enabled, source: c.source,
461
+ runtime: c.manifest.embed.runtime, specialist: c.manifest.embed.specialist,
462
+ })), errors }, null, 2)}\n`);
463
+ if (errors.length) process.exitCode = 1;
464
+ return;
465
+ }
466
+
467
+ if (!capabilities.length) {
468
+ process.stdout.write('No embed capabilities discovered (pack defaults or .cx/embed/).\n');
469
+ }
470
+ for (const c of capabilities) {
471
+ process.stdout.write(`${c.id}${c.enabled ? ' [enabled]' : ' [available]'} — specialist=${c.manifest.embed.specialist} runtime=${c.manifest.embed.runtime}\n`);
472
+ }
473
+ for (const err of errors) {
474
+ process.stderr.write(`error: ${err}\n`);
475
+ }
476
+ if (errors.length) process.exitCode = 1;
477
+ }
478
+
479
+ async function cmdCapabilityEnable(args, { rootDir = process.cwd() } = {}) {
480
+ const id = positionalId(args);
481
+ if (!id) {
482
+ process.stderr.write('Usage: construct embed enable <id>\n');
483
+ process.exitCode = 1;
484
+ return;
485
+ }
486
+
487
+ // --dry-run resolves the binding chain and writes nothing: enable preview
488
+ // must never touch .cx/embed/, matching the ADR "execute nothing" rule.
489
+ if (args.includes('--dry-run')) {
490
+ return cmdCapabilityDryRun(args, { rootDir });
491
+ }
492
+
493
+ const { enableCapability } = await import('./capability-lifecycle.mjs');
494
+ const result = enableCapability(id, { rootDir });
495
+
496
+ if (!result.ok) {
497
+ process.stderr.write(`embed enable ${id}: invalid manifest\n`);
498
+ for (const err of result.errors) process.stderr.write(` ${err}\n`);
499
+ process.exitCode = 1;
500
+ return;
501
+ }
502
+
503
+ process.stdout.write(`embed capability '${id}' enabled → ${result.filePath}\n`);
504
+ }
505
+
506
+ async function cmdCapabilityDisable(args, { rootDir = process.cwd() } = {}) {
507
+ const id = positionalId(args);
508
+ if (!id) {
509
+ process.stderr.write('Usage: construct embed disable <id>\n');
510
+ process.exitCode = 1;
511
+ return;
512
+ }
513
+
514
+ const { disableCapability } = await import('./capability-lifecycle.mjs');
515
+ const result = disableCapability(id, { rootDir });
516
+ process.stdout.write(result.wasEnabled
517
+ ? `embed capability '${id}' disabled\n`
518
+ : `embed capability '${id}' was not enabled (no-op)\n`);
519
+ }
520
+
521
+ async function cmdCapabilityStatus(args, { rootDir = process.cwd() } = {}) {
522
+ const id = positionalId(args);
523
+ const { capabilityStatus } = await import('./capability-lifecycle.mjs');
524
+ const result = await capabilityStatus(id, { rootDir });
525
+
526
+ if (!result.ok) {
527
+ if (jsonFlag(args)) {
528
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
529
+ } else {
530
+ for (const err of result.errors) process.stderr.write(`error: ${err}\n`);
531
+ }
532
+ process.exitCode = 1;
533
+ return;
534
+ }
535
+
536
+ if (jsonFlag(args)) {
537
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
538
+ return;
539
+ }
540
+
541
+ process.stdout.write(`embed capability '${id}': ${result.enabled ? 'enabled' : 'available (not enabled)'}\n`);
542
+ process.stdout.write(` specialist: ${result.chain.specialist}\n`);
543
+ process.stdout.write(` providers: ${result.chain.providerBindings.join(', ')}\n`);
544
+ process.stdout.write(` filter: ${result.chain.filter ? JSON.stringify(result.chain.filter) : '(none)'}\n`);
545
+ process.stdout.write(` framework: ${result.chain.framework}\n`);
546
+ process.stdout.write(` authority: ${result.chain.proposalAuthority}\n`);
547
+ process.stdout.write(` runtime: declared=${result.chain.runtime.declared} resolved=${result.chain.runtime.resolved}${result.chain.runtime.reason ? ` (${result.chain.runtime.reason})` : ''}\n`);
548
+ process.stdout.write(` last tick: ${result.lastTick ? `${result.lastTick.status}${result.lastTick.reason ? ` (${result.lastTick.reason})` : ''} at ${result.lastTick.tickedAt}` : '(never ticked)'}\n`);
549
+ }
550
+
551
+ async function cmdCapabilityDryRun(args, { rootDir = process.cwd() } = {}) {
552
+ const id = positionalId(args);
553
+ if (!id) {
554
+ process.stderr.write('Usage: construct embed dry-run <id>\n');
555
+ process.exitCode = 1;
556
+ return;
557
+ }
558
+
559
+ const { resolveCapabilityChain } = await import('./capability-lifecycle.mjs');
560
+ const result = await resolveCapabilityChain(id, { rootDir });
561
+
562
+ if (!result.ok) {
563
+ if (jsonFlag(args)) {
564
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
565
+ } else {
566
+ for (const err of result.errors) process.stderr.write(`error: ${err}\n`);
567
+ }
568
+ process.exitCode = 1;
569
+ return;
570
+ }
571
+
572
+ if (jsonFlag(args)) {
573
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
574
+ return;
575
+ }
576
+
577
+ process.stdout.write(`dry-run '${id}' — resolved chain (no side effects):\n`);
578
+ process.stdout.write(` specialist → ${result.chain.specialist}\n`);
579
+ process.stdout.write(` providerBindings → ${result.chain.providerBindings.join(', ')}\n`);
580
+ process.stdout.write(` filter → ${result.chain.filter ? JSON.stringify(result.chain.filter) : '(none)'}\n`);
581
+ process.stdout.write(` framework → ${result.chain.framework}\n`);
582
+ process.stdout.write(` outputContract → ${result.chain.outputContract}\n`);
583
+ process.stdout.write(` proposalAuthority → ${result.chain.proposalAuthority}\n`);
584
+ process.stdout.write(` cadence → ${result.chain.cadence ? JSON.stringify(result.chain.cadence) : '(event-driven only)'}\n`);
585
+ process.stdout.write(` runtime → declared=${result.chain.runtime.declared} resolved=${result.chain.runtime.resolved}${result.chain.runtime.reason ? ` (${result.chain.runtime.reason})` : ''}\n`);
586
+ }
587
+
423
588
  // ---------------------------------------------------------------------------
424
589
  // Main export
425
590
  // ---------------------------------------------------------------------------
@@ -435,12 +600,23 @@ export async function runEmbedCli(args, opts = {}) {
435
600
  const homeDir = opts.homeDir ?? os.homedir();
436
601
  const rootDir = opts.rootDir ?? new URL('../..', import.meta.url).pathname;
437
602
 
603
+ // Capability-lifecycle subcommands (ADR-0061) resolve manifests against the
604
+ // project the operator is standing in, not the Construct package root the
605
+ // daemon-process subcommands above use for locating worker.mjs.
606
+ const capabilityRootDir = opts.capabilityRootDir ?? process.cwd();
607
+
438
608
  switch (sub) {
439
609
  case 'start': return cmdEmbedStart(subArgs, { homeDir, rootDir, _workerPath: opts._workerPath, _livenessCheckMs: opts._livenessCheckMs });
440
610
  case 'stop': return cmdEmbedStop(subArgs, { homeDir });
441
- case 'status': return cmdEmbedStatus(subArgs, { homeDir });
611
+ case 'status':
612
+ if (positionalId(subArgs)) return cmdCapabilityStatus(subArgs, { rootDir: capabilityRootDir });
613
+ return cmdEmbedStatus(subArgs, { homeDir });
442
614
  case 'snapshot': return cmdEmbedSnapshot(subArgs, { homeDir });
443
615
  case 'migrate-model': return cmdEmbedMigrateModel(subArgs);
616
+ case 'list': return cmdCapabilityList(subArgs, { rootDir: capabilityRootDir });
617
+ case 'enable': return cmdCapabilityEnable(subArgs, { rootDir: capabilityRootDir });
618
+ case 'disable': return cmdCapabilityDisable(subArgs, { rootDir: capabilityRootDir });
619
+ case 'dry-run': return cmdCapabilityDryRun(subArgs, { rootDir: capabilityRootDir });
444
620
  case 'supervise': {
445
621
  const { installSupervision } = await import('./supervision.mjs');
446
622
  const result = await installSupervision();
@@ -460,7 +636,8 @@ export async function runEmbedCli(args, opts = {}) {
460
636
  }
461
637
  default:
462
638
  process.stderr.write(
463
- `Usage: construct embed <start|stop|status|snapshot|migrate-model|supervise|unsupervise> [--config <path>]\n`,
639
+ `Usage: construct embed <start|stop|status|snapshot|migrate-model|supervise|unsupervise> [--config <path>]\n` +
640
+ ` construct embed <list|enable|disable|status|dry-run> <id> [--json]\n`,
464
641
  );
465
642
  if (sub && sub !== '--help' && sub !== '-h') {
466
643
  process.stderr.write(`Unknown subcommand: ${sub}\n`);
@@ -495,7 +672,7 @@ async function cmdEmbedMigrateModel(args) {
495
672
 
496
673
  const client = createSqlClient(process.env);
497
674
  if (!client) {
498
- process.stderr.write('No DATABASE_URL configured nothing to migrate.\n');
675
+ process.stderr.write('Postgres was removed; nothing to migrate.\n');
499
676
  process.exit(1);
500
677
  }
501
678
 
@@ -31,6 +31,10 @@
31
31
  * projects: [PROJ, INFRA] # OR list
32
32
  * refs: [issues, sprints]
33
33
  * jql: "project = PROJ AND sprint in openSprints()" # override JQL
34
+ * filter: # ADR-0060 filter block (LMCP-B11)
35
+ * scope: { projects: [PROJ] }
36
+ * predicates: { statusCategory: [in-progress], updatedSince: P14D }
37
+ * nativeQuery: "sprint in openSprints()" # optional passthrough
34
38
  *
35
39
  * outputs:
36
40
  * - type: markdown # write snapshot to a file