@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,365 @@
1
+ /**
2
+ * lib/embed/capability-jobs.mjs — daemon-side registration of embed-capability
3
+ * scheduled jobs (ADR-0061 §4, LMCP-P2/F5).
4
+ *
5
+ * `registerEmbedCapabilityJobs` reads the enabled set and registers exactly
6
+ * one Scheduler job per enabled capability — no more, no fewer, and never
7
+ * for a capability that is merely available-but-not-enabled. Each job body
8
+ * (`runCapabilityTick`) is the real F5 glue: it slices the specialist's
9
+ * bound provider snapshot (E4 providerBindings + B11 filter enforcement)
10
+ * out of the daemon's last snapshot, asks `workflow-invoke.mjs` for the
11
+ * orchestration plan, and — only when a reasoning executor is actually
12
+ * wired in (see below) — runs it, validates the resulting output packet
13
+ * against the role's output contract (F2's `validateOutputPacket`), checks
14
+ * every proposed external write against the specialist's E4 grant
15
+ * (`AuthorityGuard`), and enqueues surviving proposals as durable
16
+ * writeIntents in the approval queue (I2). The daemon never calls a
17
+ * provider write adapter itself — enqueue is the only path out of this
18
+ * module; the J2 envelope executor is the sole drain.
19
+ *
20
+ * Reasoning-executor placement (ADR-0061 §3): `resolveRuntime` reports
21
+ * whether an `in-process` or `external` runtime is *configured*, but no
22
+ * actual reasoning engine ships in this repo yet — invoking one is a
23
+ * separate, later capability. `runCapabilityTick` accepts an optional
24
+ * `reasoningExecutor(plan, sliceCtx) -> { outputPacket, writeProposals }`
25
+ * so a real engine can be wired in without changing this module again;
26
+ * absent that injection, a resolved runtime with no executor records a
27
+ * visible `skipped-with-reason(reasoning-executor-not-available)` tick —
28
+ * never a fabricated completion. A `runtime: 'none'` resolution still
29
+ * short-circuits to `skipped-with-reason(no-runtime)` before any of this
30
+ * even runs, exactly as the pre-F5 stub did.
31
+ */
32
+
33
+ import { loadEmbedCapabilities } from './capability-loader.mjs';
34
+ import { enabledCapabilityIds, writeCapabilityTick } from './capability-lifecycle.mjs';
35
+ import { resolveRuntime } from './capability-runtime.mjs';
36
+ import { matchesFilter, validateFilterConfig } from '../providers/contract.mjs';
37
+ import { validateOutputPacket } from '../orchestration/worker.mjs';
38
+ import { invokeWorkflow } from '../embedded-contract/workflow-invoke.mjs';
39
+ import { loadAllPacks } from '../packs/loader.mjs';
40
+ import { AuthorityGuard } from './authority-guard.mjs';
41
+
42
+ /** Default cadence (ms) for a capability with no declared `embed.cadence.every`. */
43
+ const DEFAULT_CADENCE_MS = 15 * 60_000;
44
+
45
+ /** Reserved reason recorded when a runtime resolves but no reasoning executor is wired in. */
46
+ export const SKIP_REASON_NO_EXECUTOR = 'reasoning-executor-not-available';
47
+
48
+ /** Generic linear workflow used to obtain a role-scoped plan for a capability's bound specialist. */
49
+ const CAPABILITY_WORKFLOW_TYPE = 'structure-notes';
50
+
51
+ const PACK_TIER_RANK = { project: 0, user: 1, builtin: 2, unknown: 3 };
52
+
53
+ /**
54
+ * Parse the ISO-8601 duration subset accepted by `embed.cadence.every`
55
+ * (P and T with D/H/M components — the same grammar ADR-0060's
56
+ * `updatedSince` predicate accepts). An unparsable value returns null,
57
+ * falling back to DEFAULT_CADENCE_MS rather than throwing at daemon
58
+ * startup over one malformed capability.
59
+ */
60
+ export function parseCadenceMs(every) {
61
+ if (typeof every !== 'string' || !every) return null;
62
+ const match = /^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?)?$/.exec(every);
63
+ if (!match) return null;
64
+ const days = Number(match[1] ?? 0);
65
+ const hours = Number(match[2] ?? 0);
66
+ const minutes = Number(match[3] ?? 0);
67
+ const ms = ((days * 24 + hours) * 60 + minutes) * 60_000;
68
+ return ms > 0 ? ms : null;
69
+ }
70
+
71
+ /**
72
+ * Strip a `cx-` prefix from a specialist id, mirroring the bare role ids
73
+ * `workflow-invoke.mjs` (role-facts.mjs `roleMap`) and `AuthorityGuard`'s
74
+ * embedBindings map key expect.
75
+ */
76
+ function bareRoleId(specialistId) {
77
+ return String(specialistId || '').replace(/^cx-/, '');
78
+ }
79
+
80
+ /**
81
+ * Merge every loaded pack's `embedBindings` block into a single
82
+ * specialistId → { providers[], proposals[] } map, project tier winning
83
+ * over user winning over builtin — the same precedence
84
+ * `lib/embedded-contract/workflow-invoke.mjs` applies to framework
85
+ * resolution. Reuses `loadAllPacks` read-only; performs no validation of
86
+ * its own (E4 validation already ran when the pack was loaded).
87
+ */
88
+ export function mergedEmbedBindings({ rootDir, env = process.env } = {}) {
89
+ const { packs } = loadAllPacks({ rootDir, env });
90
+ const ordered = [...packs].sort((a, b) => (PACK_TIER_RANK[a._tier] ?? 3) - (PACK_TIER_RANK[b._tier] ?? 3));
91
+
92
+ const merged = {};
93
+ // Lowest precedence first so a higher-precedence tier's binding for the
94
+ // same specialist overwrites rather than merges field-by-field — a
95
+ // project override fully replaces a builtin grant for that specialist.
96
+ for (const pack of [...ordered].reverse()) {
97
+ for (const [specialistId, binding] of Object.entries(pack.embedBindings || {})) {
98
+ merged[specialistId] = binding;
99
+ }
100
+ }
101
+ return merged;
102
+ }
103
+
104
+ /**
105
+ * Slice the capability's bound providers out of the daemon's last-generated
106
+ * snapshot and re-apply the capability's own `embed.filter` (ADR-0060/B11)
107
+ * on top of whatever the poll-time source filter already admitted — a
108
+ * capability's filter can be narrower than the source's, never wider,
109
+ * since it runs after the source filter already ran. Returns `null`
110
+ * sections are skipped entirely when the bound provider has no section in
111
+ * the snapshot (not configured as a source, or the provider errored).
112
+ *
113
+ * @param {object|null} snapshot the daemon's last SnapshotEngine.generate() result
114
+ * @param {string[]} providerBindings
115
+ * @param {object|null} filter embed.filter (ADR-0060 block) or null
116
+ * @returns {{ sections: Array<{provider:string, items:object[]}>, errors: string[] }}
117
+ */
118
+ export function sliceBoundSnapshot(snapshot, providerBindings, filter) {
119
+ const bound = new Set(providerBindings || []);
120
+ const sections = [];
121
+ const errors = [];
122
+
123
+ for (const section of snapshot?.sections ?? []) {
124
+ if (!bound.has(section.provider)) continue;
125
+
126
+ let items = section.items ?? [];
127
+ if (filter != null) {
128
+ try {
129
+ validateFilterConfig(section.provider, filter);
130
+ items = items.filter((item) => matchesFilter(item, filter));
131
+ } catch (err) {
132
+ errors.push(`embed.filter invalid for bound provider '${section.provider}': ${err.message}`);
133
+ items = [];
134
+ }
135
+ }
136
+ sections.push({ provider: section.provider, items });
137
+ }
138
+
139
+ return { sections, errors };
140
+ }
141
+
142
+ /**
143
+ * Any-key-autonomous authority profile: `AuthorityGuard.check()` runs its
144
+ * E4 binding gate before consulting authority level at all, but its
145
+ * approval-queued branch calls `approvalQueue.approvalMode()`, a method
146
+ * the real `lib/embed/approval-queue.mjs` ApprovalQueue does not implement
147
+ * (only test doubles do). This module owns writeIntent enqueueing itself
148
+ * (the actual durable record), so the guard is only ever consulted here
149
+ * for the binding decision — every key resolves `autonomous` so a granted
150
+ * proposal short-circuits to `allowed:true` without reaching that branch.
151
+ */
152
+ const AUTONOMOUS_AUTHORITY = new Proxy({}, { get: () => 'autonomous' });
153
+
154
+ /**
155
+ * Check a single proposed write against the specialist's E4 grant.
156
+ * Returns `{ allowed: true }` or `{ allowed: false, reason }` — never
157
+ * throws, so one denied proposal never aborts the rest of the batch.
158
+ */
159
+ async function checkProposalAuthority(embedBindings, { specialistId, providerId, writeKind }) {
160
+ const guard = new AuthorityGuard({ authority: AUTONOMOUS_AUTHORITY }, null, embedBindings);
161
+ const result = await guard.check(`${providerId}.${writeKind}`, {
162
+ proposal: { specialistId, providerId, writeKind },
163
+ });
164
+ return result.allowed
165
+ ? { allowed: true }
166
+ : { allowed: false, reason: result.reason || `proposal ${providerId}.${writeKind} denied` };
167
+ }
168
+
169
+ /**
170
+ * Real tick body (LMCP-F5): resolves the runtime selector, and when a
171
+ * runtime is available AND a reasoning executor is wired in, composes the
172
+ * specialist's bound snapshot slice, asks workflow-invoke.mjs for the
173
+ * orchestration plan, runs the executor, validates its output packet
174
+ * against the role's output contract, checks every proposed write against
175
+ * the E4 grant, and enqueues surviving proposals as writeIntents. Every
176
+ * other path — no runtime, no executor, contract failure — records an
177
+ * honest status and never a fabricated completion.
178
+ *
179
+ * @param {object} manifest embed-capability manifest (capability-loader.mjs shape)
180
+ * @param {object} [opts]
181
+ * @param {string} opts.rootDir
182
+ * @param {object} [opts.env]
183
+ * @param {() => object|null} [opts.getSnapshot] returns the daemon's last snapshot, or null
184
+ * @param {import('./approval-queue.mjs').ApprovalQueue} [opts.approvalQueue]
185
+ * @param {object} [opts.embedBindings] specialistId → {providers[],proposals[]}; built fresh from mergedEmbedBindings when omitted
186
+ * @param {(plan: object, ctx: object) => Promise<{outputPacket?: object, writeProposals?: object[]}>} [opts.reasoningExecutor]
187
+ * @returns {Promise<object>} the recorded tick
188
+ */
189
+ export async function runCapabilityTick(manifest, {
190
+ rootDir,
191
+ env = process.env,
192
+ getSnapshot = () => null,
193
+ approvalQueue = null,
194
+ embedBindings = null,
195
+ reasoningExecutor = null,
196
+ } = {}) {
197
+ const embed = manifest.embed;
198
+ const runtime = await resolveRuntime(embed.runtime, env);
199
+ const tickedAt = new Date().toISOString();
200
+
201
+ if (runtime.resolved === 'none') {
202
+ const tick = { status: 'skipped-with-reason', reason: runtime.reason, runtime: runtime.resolved, tickedAt };
203
+ writeCapabilityTick(manifest.id, tick, rootDir);
204
+ return tick;
205
+ }
206
+
207
+ if (typeof reasoningExecutor !== 'function') {
208
+ const tick = { status: 'skipped-with-reason', reason: SKIP_REASON_NO_EXECUTOR, runtime: runtime.resolved, tickedAt };
209
+ writeCapabilityTick(manifest.id, tick, rootDir);
210
+ return tick;
211
+ }
212
+
213
+ const specialistId = bareRoleId(embed.specialist);
214
+ const snapshot = getSnapshot();
215
+ const { sections, errors: sliceErrors } = sliceBoundSnapshot(snapshot, embed.providerBindings, embed.filter ?? null);
216
+
217
+ const plan = await invokeWorkflow(
218
+ {
219
+ workflowType: CAPABILITY_WORKFLOW_TYPE,
220
+ roleStrategy: 'explicit',
221
+ requestedRoles: [specialistId],
222
+ approvalMode: 'proposal-only',
223
+ context: { snapshotSections: sections },
224
+ },
225
+ { env, cwd: rootDir },
226
+ );
227
+
228
+ if (plan.status === 'error') {
229
+ const tick = {
230
+ status: 'skipped-with-reason',
231
+ reason: `plan-error: ${plan.errors?.[0]?.code || 'unknown'}`,
232
+ runtime: runtime.resolved,
233
+ tickedAt,
234
+ };
235
+ writeCapabilityTick(manifest.id, tick, rootDir);
236
+ return tick;
237
+ }
238
+
239
+ const bindings = embedBindings ?? mergedEmbedBindings({ rootDir, env });
240
+
241
+ const executorResult = await reasoningExecutor(plan, { manifest, sections, sliceErrors, specialistId });
242
+ const outputPacket = executorResult?.outputPacket ?? null;
243
+ const writeProposals = Array.isArray(executorResult?.writeProposals) ? executorResult.writeProposals : [];
244
+
245
+ const outputCheck = validateOutputPacket(
246
+ { role: `cx-${specialistId}`, outputContractId: embed.outputContract, outputPacket },
247
+ { cwd: rootDir },
248
+ );
249
+
250
+ if (outputCheck.checked && outputCheck.contractStatus === 'contract-failed') {
251
+ const tick = {
252
+ status: 'blocked',
253
+ reason: 'output-contract-violation',
254
+ contractId: outputCheck.contractId,
255
+ violations: outputCheck.violations,
256
+ runtime: runtime.resolved,
257
+ tickedAt,
258
+ };
259
+ writeCapabilityTick(manifest.id, tick, rootDir);
260
+ return tick;
261
+ }
262
+
263
+ const enqueued = [];
264
+ const denied = [];
265
+
266
+ for (const proposal of writeProposals) {
267
+ const { providerId, writeKind, payload } = proposal || {};
268
+ const authResult = await checkProposalAuthority(bindings, { specialistId, providerId, writeKind });
269
+
270
+ if (!authResult.allowed) {
271
+ denied.push({ providerId, writeKind, reason: authResult.reason });
272
+ continue;
273
+ }
274
+
275
+ const record = approvalQueue?.enqueue({
276
+ tool: `${providerId}.${writeKind}`,
277
+ args: payload ?? {},
278
+ surface: 'embed-capability',
279
+ requestedBy: { serviceId: manifest.id, role: `cx-${specialistId}` },
280
+ });
281
+ enqueued.push({ providerId, writeKind, approvalId: record?.approvalId ?? null });
282
+ }
283
+
284
+ const tick = {
285
+ status: 'ran',
286
+ runtime: runtime.resolved,
287
+ contractStatus: outputCheck.contractStatus,
288
+ proposalsEnqueued: enqueued,
289
+ proposalsDenied: denied,
290
+ tickedAt,
291
+ };
292
+ writeCapabilityTick(manifest.id, tick, rootDir);
293
+ return tick;
294
+ }
295
+
296
+ /**
297
+ * Register exactly one Scheduler job per enabled embed capability.
298
+ * Capabilities that are available but not enabled are never registered.
299
+ * An invalid or missing manifest for a nominally-enabled id is skipped with
300
+ * a stderr line rather than crashing daemon startup — the loader already
301
+ * fails closed at `enable` time, so this path only defends against a
302
+ * project manifest edited or corrupted after enable.
303
+ *
304
+ * @param {import('./scheduler.mjs').Scheduler} scheduler
305
+ * @param {object} opts
306
+ * @param {string} opts.rootDir
307
+ * @param {object} [opts.env]
308
+ * @param {string[]} [opts.packRoots]
309
+ * @param {string[]} [opts.knownSpecialists]
310
+ * @param {() => object|null} [opts.getSnapshot]
311
+ * @param {import('./approval-queue.mjs').ApprovalQueue} [opts.approvalQueue]
312
+ * @param {object} [opts.embedBindings]
313
+ * @param {(plan: object, ctx: object) => Promise<object>} [opts.reasoningExecutor]
314
+ * @returns {string[]} ids of capabilities actually registered
315
+ */
316
+ export function registerEmbedCapabilityJobs(scheduler, {
317
+ rootDir,
318
+ env = process.env,
319
+ packRoots,
320
+ knownSpecialists,
321
+ getSnapshot,
322
+ approvalQueue,
323
+ embedBindings,
324
+ reasoningExecutor,
325
+ } = {}) {
326
+ const enabledIds = new Set(enabledCapabilityIds({ rootDir, packRoots, knownSpecialists }));
327
+ if (enabledIds.size === 0) return [];
328
+
329
+ const { capabilities, errors } = loadEmbedCapabilities({ rootDir, packRoots, knownSpecialists });
330
+ for (const err of errors) {
331
+ process.stderr.write(`[embed] capability load error: ${err}\n`);
332
+ }
333
+
334
+ const registered = [];
335
+ for (const manifest of capabilities) {
336
+ if (!enabledIds.has(manifest.id)) continue;
337
+
338
+ const cadenceMs = parseCadenceMs(manifest.embed.cadence?.every) ?? DEFAULT_CADENCE_MS;
339
+ scheduler.register(
340
+ `embed-capability:${manifest.id}`,
341
+ cadenceMs,
342
+ async () => {
343
+ const tick = await runCapabilityTick(manifest, {
344
+ rootDir,
345
+ env,
346
+ getSnapshot,
347
+ approvalQueue,
348
+ embedBindings,
349
+ reasoningExecutor,
350
+ });
351
+ const detail = tick.reason ? ` (${tick.reason})` : '';
352
+ process.stderr.write(`[embed] capability '${manifest.id}': ${tick.status}${detail}\n`);
353
+ },
354
+ { runImmediately: true },
355
+ );
356
+ registered.push(manifest.id);
357
+ }
358
+
359
+ const missingIds = [...enabledIds].filter((id) => !registered.includes(id));
360
+ for (const id of missingIds) {
361
+ process.stderr.write(`[embed] capability '${id}' is enabled but its manifest failed to load — not registered\n`);
362
+ }
363
+
364
+ return registered;
365
+ }
@@ -0,0 +1,219 @@
1
+ /**
2
+ * lib/embed/capability-lifecycle.mjs — enable/disable/status/dry-run surface
3
+ * for embed capabilities (ADR-0061 §4, LMCP-P2).
4
+ *
5
+ * `enableCapability` / `disableCapability` persist the per-project tier
6
+ * manifest at `.cx/embed/<id>.manifest.json` (via lib/embed/capability-loader.mjs);
7
+ * a capability is only ever active when this file exists with `embed.enabled
8
+ * === true` — pack/builtin tiers ship it merely *available*, and enablement
9
+ * is explicit opt-in, never a default. `listCapabilities` and
10
+ * `capabilityStatus` read the merged D1 view plus the durable last-tick
11
+ * record the daemon-registration helper in lib/embed/capability-jobs.mjs
12
+ * writes. `resolveCapabilityChain` resolves the full binding chain
13
+ * (specialist → providers → filter → framework → authority → runtime) and
14
+ * returns it without calling a model or touching the last-tick record — a
15
+ * pure read, matching the ADR's "execute nothing" requirement.
16
+ */
17
+
18
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
19
+ import { join } from 'node:path';
20
+
21
+ import {
22
+ loadEmbedCapabilities,
23
+ readProjectEmbedManifest,
24
+ validateEmbedManifest,
25
+ writeProjectEmbedManifest,
26
+ } from './capability-loader.mjs';
27
+ import { resolveRuntime } from './capability-runtime.mjs';
28
+
29
+ /** Durable per-capability last-tick record directory. */
30
+ export function capabilityStatusDir(rootDir = process.cwd()) {
31
+ return join(rootDir, '.cx', 'runtime', 'embed-capabilities');
32
+ }
33
+
34
+ function capabilityStatusPath(id, rootDir = process.cwd()) {
35
+ return join(capabilityStatusDir(rootDir), `${id}.json`);
36
+ }
37
+
38
+ /**
39
+ * Read the durable last-tick record for a capability. Returns null when the
40
+ * capability has never ticked (e.g. just enabled, daemon not yet started).
41
+ */
42
+ export function readCapabilityTick(id, rootDir = process.cwd()) {
43
+ const p = capabilityStatusPath(id, rootDir);
44
+ if (!existsSync(p)) return null;
45
+ try {
46
+ return JSON.parse(readFileSync(p, 'utf8'));
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Write the durable last-tick record for a capability. Exported so the
54
+ * daemon's registered job body (this bead's stub, F5's real body) has one
55
+ * place to record `ran | skipped-with-reason | error` outcomes.
56
+ */
57
+ export function writeCapabilityTick(id, tick, rootDir = process.cwd()) {
58
+ const dir = capabilityStatusDir(rootDir);
59
+ mkdirSync(dir, { recursive: true });
60
+ writeFileSync(capabilityStatusPath(id, rootDir), `${JSON.stringify(tick, null, 2)}\n`);
61
+ }
62
+
63
+ /**
64
+ * List every discoverable embed capability with its merged manifest,
65
+ * project-tier enabled/override state, and loader errors. Each entry:
66
+ * { id, available: true, enabled, manifest, source: 'project'|'pack'|'builtin' }
67
+ */
68
+ export function listCapabilities({ rootDir = process.cwd(), packRoots, knownSpecialists } = {}) {
69
+ const { capabilities, errors } = loadEmbedCapabilities({ rootDir, packRoots, knownSpecialists });
70
+
71
+ const entries = capabilities.map((manifest) => {
72
+ const override = readProjectEmbedManifest(manifest.id, rootDir);
73
+ const enabled = override?.manifest?.embed?.enabled === true;
74
+ return {
75
+ id: manifest.id,
76
+ available: true,
77
+ enabled,
78
+ manifest,
79
+ source: override ? 'project' : (manifest._filePath?.includes(join('.cx', 'embed')) ? 'project' : 'pack-or-builtin'),
80
+ };
81
+ });
82
+
83
+ return { capabilities: entries, errors };
84
+ }
85
+
86
+ /**
87
+ * Enable a capability: merge the pack/builtin-default manifest (if any) with
88
+ * an optional override, stamp `embed.enabled: true`, validate the resulting
89
+ * manifest against the ADR-0061 schema, and write it to the project tier.
90
+ * Fails closed — an invalid manifest is never written; the returned result
91
+ * carries the JSON-schema-style error paths instead.
92
+ *
93
+ * @returns {{ ok: true, filePath: string, manifest: object } | { ok: false, errors: string[] }}
94
+ */
95
+ export function enableCapability(id, { rootDir = process.cwd(), overrides = {}, packRoots, knownSpecialists } = {}) {
96
+ const { capabilities, errors: loadErrors } = loadEmbedCapabilities({ rootDir, packRoots, knownSpecialists });
97
+ const defaultManifest = capabilities.find((c) => c.id === id);
98
+
99
+ if (!defaultManifest && Object.keys(overrides).length === 0) {
100
+ return {
101
+ ok: false,
102
+ errors: [`embed capability '${id}' not found in any tier (pack defaults or .cx/embed/) and no overrides supplied to define it`, ...loadErrors],
103
+ };
104
+ }
105
+
106
+ const base = defaultManifest ?? { id, type: 'embed', version: '1.0.0', defaultApprovalMode: 'proposal-only', embed: {} };
107
+ const candidate = {
108
+ ...base,
109
+ ...overrides,
110
+ embed: { ...base.embed, ...(overrides.embed ?? {}), enabled: true },
111
+ };
112
+ delete candidate._filePath;
113
+
114
+ const result = validateEmbedManifest(candidate, { filePath: `.cx/embed/${id}.manifest.json`, knownSpecialists });
115
+ if (!result.valid) {
116
+ return { ok: false, errors: result.errors };
117
+ }
118
+
119
+ const filePath = writeProjectEmbedManifest(id, candidate, rootDir);
120
+ return { ok: true, filePath, manifest: candidate };
121
+ }
122
+
123
+ /**
124
+ * Disable a capability: if a project-tier override exists, stamp
125
+ * `embed.enabled: false` in place (preserving any project overrides) rather
126
+ * than deleting the file — a subsequent `enable` should not silently lose
127
+ * prior project-tier customization. Idempotent: disabling an
128
+ * already-disabled or never-enabled capability succeeds without error.
129
+ */
130
+ export function disableCapability(id, { rootDir = process.cwd() } = {}) {
131
+ const existing = readProjectEmbedManifest(id, rootDir);
132
+ if (!existing) {
133
+ return { ok: true, filePath: null, wasEnabled: false };
134
+ }
135
+
136
+ const manifest = {
137
+ ...existing.manifest,
138
+ embed: { ...existing.manifest.embed, enabled: false },
139
+ };
140
+ const filePath = writeProjectEmbedManifest(id, manifest, rootDir);
141
+ return { ok: true, filePath, wasEnabled: existing.manifest?.embed?.enabled === true };
142
+ }
143
+
144
+ /**
145
+ * Resolve the full binding chain for a capability without side effects —
146
+ * no model call, no last-tick record written. Returns the same shape
147
+ * `construct embed dry-run <id>` prints.
148
+ */
149
+ export async function resolveCapabilityChain(id, { rootDir = process.cwd(), env = process.env, packRoots, knownSpecialists } = {}) {
150
+ const { capabilities, errors } = loadEmbedCapabilities({ rootDir, packRoots, knownSpecialists });
151
+ const manifest = capabilities.find((c) => c.id === id);
152
+ if (!manifest) {
153
+ return { ok: false, errors: [`embed capability '${id}' not found`, ...errors] };
154
+ }
155
+
156
+ const embed = manifest.embed;
157
+ const runtime = await resolveRuntime(embed.runtime, env);
158
+
159
+ return {
160
+ ok: true,
161
+ id,
162
+ chain: {
163
+ specialist: embed.specialist,
164
+ providerBindings: embed.providerBindings,
165
+ filter: embed.filter ?? null,
166
+ framework: embed.framework,
167
+ outputContract: embed.outputContract,
168
+ proposalAuthority: embed.proposalAuthority,
169
+ cadence: embed.cadence ?? null,
170
+ runtime: { declared: embed.runtime, ...runtime },
171
+ },
172
+ };
173
+ }
174
+
175
+ /**
176
+ * Per-specialist status: enabled state, bound providers, filter, runtime
177
+ * resolution, and the last durable tick record (`ran | skipped-with-reason |
178
+ * error`, or null if the capability has never ticked).
179
+ */
180
+ export async function capabilityStatus(id, { rootDir = process.cwd(), env = process.env, packRoots, knownSpecialists } = {}) {
181
+ const chain = await resolveCapabilityChain(id, { rootDir, env, packRoots, knownSpecialists });
182
+ if (!chain.ok) return chain;
183
+
184
+ const override = readProjectEmbedManifest(id, rootDir);
185
+ const enabled = override?.manifest?.embed?.enabled === true;
186
+ const lastTick = readCapabilityTick(id, rootDir);
187
+
188
+ return {
189
+ ok: true,
190
+ id,
191
+ enabled,
192
+ chain: chain.chain,
193
+ lastTick,
194
+ };
195
+ }
196
+
197
+ /**
198
+ * Enumerate the enabled-set as the daemon should register it: every
199
+ * discoverable capability whose project-tier manifest carries
200
+ * `embed.enabled === true`. Per ADR-0061, enablement is explicit opt-in —
201
+ * a project-tier file with no `enabled` field, or any value other than
202
+ * `true`, is not active. Both the CLI (`embed list`) and the daemon's
203
+ * startup registration read this same definition of "enabled", so there
204
+ * is one source of truth and no drift between what a user sees and what
205
+ * actually schedules.
206
+ */
207
+ export function enabledCapabilityIds({ rootDir = process.cwd(), packRoots, knownSpecialists } = {}) {
208
+ const { capabilities } = listCapabilities({ rootDir, packRoots, knownSpecialists });
209
+ return capabilities.filter((c) => c.enabled).map((c) => c.id);
210
+ }
211
+
212
+ /**
213
+ * Remove the durable last-tick record for a capability. Exported for test
214
+ * cleanup and for `disable` callers that want a clean status surface.
215
+ */
216
+ export function clearCapabilityTick(id, rootDir = process.cwd()) {
217
+ const p = capabilityStatusPath(id, rootDir);
218
+ if (existsSync(p)) rmSync(p, { force: true });
219
+ }