@geraldmaron/construct 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -0,0 +1,68 @@
1
+ /**
2
+ * lib/providers/filter-audit.mjs — per-poll provider filter audit log.
3
+ *
4
+ * ADR-0060 requires every poll to record, per source, the filter that was
5
+ * enforced and how many items it dropped. Appends one JSONL line per source
6
+ * per poll to `<rootDir>/.cx/providers/filter-audit.jsonl` with the
7
+ * acceptance-criteria field names `{provider, instance, filterHash, matched,
8
+ * dropped}`, plus the ADR's own `fetched`/`admitted` and `pushdown` fields
9
+ * for the fuller audit shape. Rotation uses `appendWithRotationSync`
10
+ * directly (not the shared `appendBounded` channel registry) to keep this
11
+ * change scoped to `lib/providers/*`.
12
+ */
13
+ import { existsSync, readFileSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+
16
+ import { appendWithRotationSync } from '../logging/rotate.mjs';
17
+
18
+ const AUDIT_SUBDIR = ['.cx', 'providers'];
19
+ const AUDIT_FILE = 'filter-audit.jsonl';
20
+
21
+ // 10 MB per shard, 5 shards kept — a filter-audit line is written once per
22
+ // source per poll, so this comfortably covers years of daemon uptime.
23
+
24
+ const MAX_BYTES = 10 * 1024 * 1024;
25
+ const MAX_SEGMENTS = 5;
26
+
27
+ export function filterAuditPath(rootDir) {
28
+ return join(rootDir, ...AUDIT_SUBDIR, AUDIT_FILE);
29
+ }
30
+
31
+ /**
32
+ * Append one filter-audit record. `matched`/`dropped` are the
33
+ * acceptance-criteria field names; `fetched`/`admitted` mirror the ADR-0060
34
+ * audit shape (`admitted` === `matched`) so both vocabularies are queryable
35
+ * from the same line without a reader needing to know which one shipped.
36
+ */
37
+ export function recordFilterAudit(rootDir, {
38
+ provider,
39
+ instance = null,
40
+ filterHash: hash = null,
41
+ scope = null,
42
+ predicates = null,
43
+ nativeQuery = null,
44
+ pushdown = false,
45
+ fetched,
46
+ matched,
47
+ }) {
48
+ const record = {
49
+ ts: new Date().toISOString(),
50
+ provider,
51
+ instance,
52
+ filterHash: hash,
53
+ filterApplied: { scope, predicates, nativeQuery, pushdown, fetched, admitted: matched },
54
+ matched,
55
+ dropped: fetched - matched,
56
+ };
57
+ appendWithRotationSync(filterAuditPath(rootDir), JSON.stringify(record) + '\n', {
58
+ maxBytes: MAX_BYTES,
59
+ maxSegments: MAX_SEGMENTS,
60
+ });
61
+ return record;
62
+ }
63
+
64
+ export function readFilterAudit(rootDir) {
65
+ const path = filterAuditPath(rootDir);
66
+ if (!existsSync(path)) return [];
67
+ return readFileSync(path, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line));
68
+ }
@@ -0,0 +1,54 @@
1
+ /**
2
+ * lib/providers/filter-schema.mjs — provider filter block JSON schema (ADR-0060).
3
+ *
4
+ * Ships the normalized filter grammar every provider kind may accept:
5
+ * `scope` (per-kind container allowlist), `predicates` (portable field-level
6
+ * filter core), and `nativeQuery` (provider-native passthrough escape hatch).
7
+ * Per-kind manifest `configSchema` fields reference this via `$ref`; per-kind
8
+ * `SCOPE_FIELDS_BY_PROVIDER` in `lib/providers/contract.mjs` declares which
9
+ * `scope` keys a given provider kind actually honors.
10
+ *
11
+ * Data-only module (schema + enums), consumed by `validateFilterConfig()`
12
+ * in `lib/providers/contract.mjs`. No JSON-Schema engine runs here — the
13
+ * shape below is the source of truth the hand-rolled validator checks
14
+ * against, keeping schema and validator from drifting silently apart.
15
+ */
16
+
17
+ export const FILTER_SCHEMA = Object.freeze({
18
+ $id: 'construct:provider-filter',
19
+ type: 'object',
20
+ additionalProperties: false,
21
+ properties: {
22
+ scope: {
23
+ type: 'object',
24
+ additionalProperties: false,
25
+ properties: {
26
+ projects: { type: 'array', items: { type: 'string' } },
27
+ repos: { type: 'array', items: { type: 'string' } },
28
+ repoGlob: { type: 'array', items: { type: 'string' } },
29
+ channels: { type: 'array', items: { type: 'string' } },
30
+ spaces: { type: 'array', items: { type: 'string' } },
31
+ },
32
+ },
33
+ predicates: {
34
+ type: 'object',
35
+ additionalProperties: false,
36
+ properties: {
37
+ assignee: { type: 'array', items: { type: 'string' } },
38
+ statusCategory: { type: 'array', items: { enum: ['to-do', 'in-progress', 'done'] } },
39
+ priority: { type: 'array', items: { type: 'string' } },
40
+ label: { type: 'array', items: { type: 'string' } },
41
+ updatedSince: { type: 'string' },
42
+ },
43
+ },
44
+ nativeQuery: { type: 'string' },
45
+ },
46
+ });
47
+
48
+ export const SCOPE_KEYS = Object.freeze(['projects', 'repos', 'repoGlob', 'channels', 'spaces']);
49
+
50
+ export const PREDICATE_KEYS = Object.freeze(['assignee', 'statusCategory', 'priority', 'label', 'updatedSince']);
51
+
52
+ export const STATUS_CATEGORIES = Object.freeze(['to-do', 'in-progress', 'done']);
53
+
54
+ export const TOP_LEVEL_KEYS = Object.freeze(['scope', 'predicates', 'nativeQuery']);
@@ -14,10 +14,21 @@
14
14
  * The provider is intentionally narrow in v1 — issue + PR + code search +
15
15
  * a single repo metadata read. Webhook signature verification is in place
16
16
  * so consumers can mount a receiver without re-implementing it.
17
+ *
18
+ * Scope enforcement: when `repoAllowlist` or `repoAllowGlob` is present in
19
+ * config, `read()` validates `config.repo` and `search()` validates every
20
+ * `repo:` qualifier in the query through `validateAllowlist()`
21
+ * (lib/providers/contract.mjs) before any network call, throwing a typed
22
+ * `OUT_OF_SCOPE` error on a blocked target. A search query with no `repo:`
23
+ * qualifier is rejected outright when an allowlist is configured — an
24
+ * unscoped query could otherwise span the whole credential's visible
25
+ * surface and defeat the allowlist.
17
26
  */
18
27
 
19
28
  import crypto from 'node:crypto';
20
29
 
30
+ import { validateAllowlist } from '../contract.mjs';
31
+
21
32
  const API = 'https://api.github.com';
22
33
 
23
34
  function authHeader(env) {
@@ -25,6 +36,10 @@ function authHeader(env) {
25
36
  return token ? { Authorization: `Bearer ${token}` } : {};
26
37
  }
27
38
 
39
+ function outOfScopeError(reason) {
40
+ return Object.assign(new Error(reason), { code: 'OUT_OF_SCOPE' });
41
+ }
42
+
28
43
  async function ghFetch(pathOrUrl, env, init = {}) {
29
44
  const url = pathOrUrl.startsWith('http') ? pathOrUrl : `${API}${pathOrUrl}`;
30
45
  const headers = {
@@ -83,6 +98,8 @@ export function create({ env = process.env } = {}) {
83
98
 
84
99
  async read(config) {
85
100
  if (!config?.repo) throw new Error('github.read: config.repo required (owner/name)');
101
+ const check = validateAllowlist('github', config.repo, config);
102
+ if (!check.allowed) throw outOfScopeError(check.reason);
86
103
  return ghFetch(`/repos/${config.repo}`, env);
87
104
  },
88
105
 
@@ -90,6 +107,20 @@ export function create({ env = process.env } = {}) {
90
107
  const kind = config?.kind || 'issues';
91
108
  const query = config?.query;
92
109
  if (!query) throw new Error('github.search: config.query required');
110
+
111
+ const hasAllowlistConfig = (Array.isArray(config?.repoAllowlist) && config.repoAllowlist.length > 0)
112
+ || (typeof config?.repoAllowGlob === 'string' && config.repoAllowGlob.length > 0);
113
+ const repoQualifiers = [...query.matchAll(/\brepo:(\S+)/g)].map((m) => m[1]);
114
+
115
+ if (hasAllowlistConfig && repoQualifiers.length === 0) {
116
+ throw outOfScopeError('github.search: repoAllowlist/repoAllowGlob is configured; query must include a repo: qualifier to enforce scope');
117
+ }
118
+ for (const repo of repoQualifiers) {
119
+ const target = repo.includes('/') ? repo.split('/').pop() : repo;
120
+ const check = validateAllowlist('github', target, config);
121
+ if (!check.allowed) throw outOfScopeError(check.reason);
122
+ }
123
+
93
124
  const path = kind === 'code'
94
125
  ? `/search/code?q=${encodeURIComponent(query)}`
95
126
  : kind === 'prs'
@@ -0,0 +1,215 @@
1
+ /**
2
+ * lib/providers/instance-config.mjs — provider instance config persistence + validation.
3
+ *
4
+ * Backs `construct provider add|configure` (LMCP-B12). An "instance config"
5
+ * is the per-project, per-provider-id settings block (Jira project keys,
6
+ * GitHub repo, Slack channel, plus the ADR-0060 `filter` block) — distinct
7
+ * from `lib/providers/registry.mjs`'s `.cx/providers.json`, which registers
8
+ * *which provider module* answers for an id, not its call-time config.
9
+ *
10
+ * Storage: one JSON file per provider id at `<rootDir>/.cx/providers/<id>.json`,
11
+ * `{ providerId, config, updatedAt }`. Human-reviewable, git-trackable, and
12
+ * namespaced per id so concurrent `configure` calls on different providers
13
+ * never collide on one shared file.
14
+ *
15
+ * Validation walks the provider's `configSchema` (a JSON-Schema draft
16
+ * 2020-12 subset: `type`, `properties`, `required`, `enum`, `pattern`,
17
+ * `minimum`/`maximum`, `additionalProperties`) by hand — no schema engine
18
+ * dependency, mirroring the hand-rolled approach `contract.mjs` already
19
+ * takes for the ADR-0060 filter block. `config.filter`, when present, is
20
+ * delegated to `validateFilterConfig()` from `contract.mjs` (LMCP-B11) so
21
+ * the two validators can never drift on filter-block semantics. Every
22
+ * rejection carries a JSON-Pointer-style path (`config.<field>` or
23
+ * `config.filter.<...>`) so the caller can name the offending key.
24
+ */
25
+
26
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
27
+ import { dirname, join } from 'node:path';
28
+
29
+ import { validateFilterConfig } from './contract.mjs';
30
+ import { SCOPE_KEYS, PREDICATE_KEYS } from './filter-schema.mjs';
31
+
32
+ // `filter.scope.*` and most `filter.predicates.*` leaves are always arrays
33
+ // per the ADR-0060 grammar (filter-schema.mjs); `updatedSince` is the one
34
+ // scalar-string predicate. A single `--filter.scope.projects ABC` must
35
+ // therefore land as `["ABC"]`, not the bare string, even on first occurrence.
36
+ const ALWAYS_ARRAY_FILTER_LEAVES = new Set([
37
+ ...SCOPE_KEYS.map((k) => `filter.scope.${k}`),
38
+ ...PREDICATE_KEYS.filter((k) => k !== 'updatedSince').map((k) => `filter.predicates.${k}`),
39
+ ]);
40
+
41
+ export function instanceConfigPath(rootDir, providerId) {
42
+ return join(rootDir, '.cx', 'providers', `${providerId}.json`);
43
+ }
44
+
45
+ export function readInstanceConfig(rootDir, providerId) {
46
+ const filePath = instanceConfigPath(rootDir, providerId);
47
+ if (!existsSync(filePath)) return null;
48
+ return JSON.parse(readFileSync(filePath, 'utf8'));
49
+ }
50
+
51
+ export function writeInstanceConfig(rootDir, providerId, config) {
52
+ const filePath = instanceConfigPath(rootDir, providerId);
53
+ mkdirSync(dirname(filePath), { recursive: true });
54
+ const record = { providerId, config, updatedAt: new Date().toISOString() };
55
+ writeFileSync(filePath, JSON.stringify(record, null, 2) + '\n');
56
+ return record;
57
+ }
58
+
59
+ /**
60
+ * Build the default config object from a JSON-Schema `properties` map:
61
+ * every property that declares a `default` is seeded with it. Properties
62
+ * without a default are omitted — `provider add` scaffolds only what the
63
+ * schema actually commits to, not placeholder nulls.
64
+ */
65
+ export function defaultsFromSchema(configSchema) {
66
+ const defaults = {};
67
+ const properties = configSchema?.properties || {};
68
+ for (const [key, propSchema] of Object.entries(properties)) {
69
+ if (propSchema && Object.prototype.hasOwnProperty.call(propSchema, 'default')) {
70
+ defaults[key] = propSchema.default;
71
+ }
72
+ }
73
+ return defaults;
74
+ }
75
+
76
+ function typeMatches(value, type) {
77
+ switch (type) {
78
+ case 'string': return typeof value === 'string';
79
+ case 'number': return typeof value === 'number' && Number.isFinite(value);
80
+ case 'integer': return typeof value === 'number' && Number.isInteger(value);
81
+ case 'boolean': return typeof value === 'boolean';
82
+ case 'array': return Array.isArray(value);
83
+ case 'object': return value !== null && typeof value === 'object' && !Array.isArray(value);
84
+ default: return true;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Validate one property value against its JSON-Schema property descriptor.
90
+ * Returns an array of error strings (empty when valid); each string is
91
+ * prefixed with `path` so nested callers can build a JSON-Pointer trail.
92
+ */
93
+ function validateProperty(path, value, propSchema) {
94
+ const errors = [];
95
+ if (!propSchema) return errors;
96
+
97
+ if (propSchema.enum && !propSchema.enum.includes(value)) {
98
+ errors.push(`${path}: must be one of [${propSchema.enum.join(', ')}], got ${JSON.stringify(value)}`);
99
+ return errors;
100
+ }
101
+
102
+ if (propSchema.type && !typeMatches(value, propSchema.type)) {
103
+ errors.push(`${path}: must be of type ${propSchema.type}, got ${JSON.stringify(value)}`);
104
+ return errors;
105
+ }
106
+
107
+ if (propSchema.pattern && typeof value === 'string' && !new RegExp(propSchema.pattern).test(value)) {
108
+ errors.push(`${path}: does not match pattern ${propSchema.pattern}`);
109
+ }
110
+
111
+ if (typeof value === 'number') {
112
+ if (propSchema.minimum !== undefined && value < propSchema.minimum) {
113
+ errors.push(`${path}: must be >= ${propSchema.minimum}, got ${value}`);
114
+ }
115
+ if (propSchema.maximum !== undefined && value > propSchema.maximum) {
116
+ errors.push(`${path}: must be <= ${propSchema.maximum}, got ${value}`);
117
+ }
118
+ }
119
+
120
+ return errors;
121
+ }
122
+
123
+ /**
124
+ * Validate a merged instance config against a provider's declared
125
+ * `configSchema`, plus (when the config carries a `filter` key) the
126
+ * ADR-0060 filter block via `validateFilterConfig()`. Returns
127
+ * `{ valid: true }` or `{ valid: false, errors: string[] }` — never throws,
128
+ * so callers (CLI + tests) get a uniform result shape to report from.
129
+ *
130
+ * `filter` is accepted on every provider config regardless of whether the
131
+ * schema's `properties` declares it: the ADR-0060 block is a cross-cutting
132
+ * concern layered on top of the provider-specific schema, not a provider-
133
+ * specific property.
134
+ */
135
+ export function validateInstanceConfig(providerId, configSchema, config = {}) {
136
+ const errors = [];
137
+ const schema = configSchema || {};
138
+ const properties = schema.properties || {};
139
+ const { filter, ...rest } = config || {};
140
+
141
+ for (const required of schema.required || []) {
142
+ if (!(required in rest)) {
143
+ errors.push(`config.${required}: required property missing`);
144
+ }
145
+ }
146
+
147
+ for (const [key, value] of Object.entries(rest)) {
148
+ const path = `config.${key}`;
149
+ if (!(key in properties)) {
150
+ if (schema.additionalProperties === false) {
151
+ errors.push(`${path}: unknown property (not declared in configSchema)`);
152
+ }
153
+ continue;
154
+ }
155
+ errors.push(...validateProperty(path, value, properties[key]));
156
+ }
157
+
158
+ if (filter !== undefined) {
159
+ try {
160
+ validateFilterConfig(providerId, filter);
161
+ } catch (err) {
162
+ errors.push(`config.filter: ${err.message}`);
163
+ }
164
+ }
165
+
166
+ if (errors.length > 0) return { valid: false, errors };
167
+ return { valid: true };
168
+ }
169
+
170
+ /**
171
+ * Apply `--key value` CLI overrides onto a base config object. Dotted keys
172
+ * (`scope.projects`) address nested objects; repeated flags for an array
173
+ * leaf (`filter.scope.projects`) accumulate rather than overwrite, so
174
+ * `--filter.scope.projects ABC --filter.scope.projects DEF` yields
175
+ * `["ABC", "DEF"]` in one `configure` call.
176
+ */
177
+ export function applyOverrides(base, overrides) {
178
+ const result = JSON.parse(JSON.stringify(base || {}));
179
+
180
+ // Accumulation only fires for a keyPath repeated within *this* override
181
+ // batch — a pre-existing base value (a schema default or a prior
182
+ // `configure` call) is a plain overwrite, never folded into an array.
183
+ const touched = new Set();
184
+
185
+ for (const { keyPath, value } of overrides) {
186
+ const segments = keyPath.split('.');
187
+ let cursor = result;
188
+ for (let i = 0; i < segments.length - 1; i++) {
189
+ const segment = segments[i];
190
+ if (typeof cursor[segment] !== 'object' || cursor[segment] === null || Array.isArray(cursor[segment])) {
191
+ cursor[segment] = {};
192
+ }
193
+ cursor = cursor[segment];
194
+ }
195
+ const leaf = segments[segments.length - 1];
196
+ const parsed = parseFlagValue(value);
197
+ if (touched.has(keyPath)) {
198
+ cursor[leaf] = Array.isArray(cursor[leaf]) ? [...cursor[leaf], parsed] : [cursor[leaf], parsed];
199
+ } else if (ALWAYS_ARRAY_FILTER_LEAVES.has(keyPath)) {
200
+ cursor[leaf] = [parsed];
201
+ touched.add(keyPath);
202
+ } else {
203
+ cursor[leaf] = parsed;
204
+ touched.add(keyPath);
205
+ }
206
+ }
207
+ return result;
208
+ }
209
+
210
+ function parseFlagValue(raw) {
211
+ if (raw === 'true') return true;
212
+ if (raw === 'false') return false;
213
+ if (raw !== '' && !Number.isNaN(Number(raw))) return Number(raw);
214
+ return raw;
215
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * lib/providers/op-locate.mjs — resolve the 1Password `op` CLI binary once.
3
+ *
4
+ * A GUI-launched MCP host (OpenCode, Claude Desktop, Cursor) spawns
5
+ * `node lib/mcp/server.mjs` with a minimal PATH that omits Homebrew, so a bare
6
+ * `spawnSync('op', …)` ENOENTs and `op` is misreported as not installed despite
7
+ * being present at a well-known path like /opt/homebrew/bin/op. Resolution walks three
8
+ * tiers — the process PATH (cheap, no shell), then a login-shell probe that re-runs
9
+ * the user's rc/profile (macOS path_helper included, the way credential-bootstrap
10
+ * recovers PATH), then well-known install dirs — and caches the absolute path for
11
+ * the process lifetime. Every op-invocation site (secret-resolver, op-run,
12
+ * credential-bootstrap) resolves through here so PATH semantics stay consistent
13
+ * across call sites. env/runShell/wellKnown are injectable so tests exercise each
14
+ * tier without a real 1Password install.
15
+ */
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import { spawnSync } from 'node:child_process';
20
+
21
+ const WELL_KNOWN_POSIX = ['/opt/homebrew/bin/op', '/usr/local/bin/op', '/usr/bin/op'];
22
+
23
+ function wellKnownWin32() {
24
+ const local = process.env.LOCALAPPDATA;
25
+ return [
26
+ local ? path.join(local, 'Microsoft', 'WinGet', 'Links', 'op.exe') : null,
27
+ 'C:\\ProgramData\\chocolatey\\bin\\op.exe',
28
+ ].filter(Boolean);
29
+ }
30
+
31
+ function defaultWellKnown() {
32
+ return process.platform === 'win32' ? wellKnownWin32() : WELL_KNOWN_POSIX;
33
+ }
34
+
35
+ let cached;
36
+
37
+ function isFile(candidate) {
38
+ try {
39
+ return fs.statSync(candidate).isFile();
40
+ } catch {
41
+ return false;
42
+ }
43
+ }
44
+
45
+ // Scan the process PATH for `op` without spawning a shell — the common case where
46
+ // PATH is already correct resolves here at no cost.
47
+
48
+ function scanProcessPath(env) {
49
+ const dirs = String(env?.PATH || '').split(path.delimiter).filter(Boolean);
50
+ const names = process.platform === 'win32' ? ['op.exe', 'op.cmd', 'op.bat'] : ['op'];
51
+ for (const dir of dirs) {
52
+ for (const name of names) {
53
+ const candidate = path.join(dir, name);
54
+ if (isFile(candidate)) return candidate;
55
+ }
56
+ }
57
+ return null;
58
+ }
59
+
60
+ // Login-shell probe: `sh -lc 'command -v op'` re-runs the user's shell init, which
61
+ // on macOS is where path_helper adds Homebrew — recovering the PATH a GUI-launched
62
+ // child never inherited. `where op` is the Windows analogue.
63
+
64
+ function defaultRunShell() {
65
+ if (process.platform === 'win32') {
66
+ const r = spawnSync('where', ['op'], { encoding: 'utf8', timeout: 3000 });
67
+ return r.status === 0 ? String(r.stdout || '').split(/\r?\n/)[0].trim() : '';
68
+ }
69
+ const r = spawnSync('sh', ['-lc', 'command -v op'], { encoding: 'utf8', timeout: 3000 });
70
+ return r.status === 0 ? String(r.stdout || '').trim() : '';
71
+ }
72
+
73
+ /**
74
+ * Resolve the `op` binary (process PATH → login-shell PATH → well-known dirs),
75
+ * returning an absolute path or null. Cached for the process lifetime; pass
76
+ * fresh:true to bypass the cache. env/runShell/wellKnown are injectable for tests.
77
+ */
78
+ export function locateOpBinary({ env = process.env, runShell = defaultRunShell, wellKnown = defaultWellKnown(), fresh = false } = {}) {
79
+ if (!fresh && cached !== undefined) return cached;
80
+
81
+ const onPath = scanProcessPath(env);
82
+ if (onPath) return (cached = onPath);
83
+
84
+ const viaShell = runShell();
85
+ if (viaShell && isFile(viaShell)) return (cached = viaShell);
86
+
87
+ for (const candidate of wellKnown) {
88
+ if (candidate && isFile(candidate)) return (cached = candidate);
89
+ }
90
+
91
+ return (cached = null);
92
+ }
93
+
94
+ export function __resetOpLocateCache() {
95
+ cached = undefined;
96
+ }
@@ -1,13 +1,21 @@
1
1
  /**
2
- * lib/providers/op-run.mjs — optionally launch a child under 1Password `op run`.
2
+ * lib/providers/op-run.mjs — optionally launch Construct under 1Password `op run`.
3
3
  *
4
- * When CONSTRUCT_OP_ENV_FILE points to an `op run` env-file (KEY=op://… lines)
5
- * and the `op` CLI is installed, long-lived Construct services are spawned through
6
- * `op run --env-file <file> -- <cmd>`, so op:// references resolve once at startup
7
- * into the process env. lib/service-manager.mjs calls wrapWithOpRun at each service
8
- * spawn, so the memory server, OpenCode, the copilot bridge, the doctor daemon, and
9
- * the oracle daemon all inherit resolved provider keys without a per-invocation
10
- * shell wrapper. Masking stays on so resolved secrets are not echoed to service logs.
4
+ * When CONSTRUCT_OP_ENV_FILE points to an `op run` env-file (KEY=op://… lines) and
5
+ * the `op` CLI is installed, `construct dev` re-execs itself once under
6
+ * `op run --env-file <file> -- …` (maybeReExecUnderOpRun). Every op:// reference then
7
+ * resolves a single time at that stable parent one biometric unlock — and the
8
+ * detached daemons the re-exec'd process spawns inherit the resolved keys through the
9
+ * parent env. A CONSTRUCT_OP_RUN_ACTIVE sentinel marks the wrapped process so nested
10
+ * launches inside it do not re-wrap and re-prompt.
11
+ *
12
+ * wrapWithOpRun stays the per-service fallback for daemons started outside the
13
+ * re-exec'd parent (a doctor or oracle restart from a hook), where no parent
14
+ * resolution has run; under the parent (sentinel set) it returns the command
15
+ * unchanged. The trade-off of resolving once at the parent: `op run` masks the
16
+ * parent's own stdout but not the separate log files of the daemons it detaches, so a
17
+ * daemon that echoes a key logs it unmasked (ADR-0049). The per-service fallback still
18
+ * masks the daemons it wraps directly.
11
19
  *
12
20
  * Strictly opt-in: with the var unset, the file missing, or `op` absent, the
13
21
  * command is returned unchanged and 1Password is never invoked. No other setup
@@ -17,6 +25,13 @@
17
25
  import fs from 'node:fs';
18
26
  import os from 'node:os';
19
27
  import { spawnSync } from 'node:child_process';
28
+ import { locateOpBinary } from './op-locate.mjs';
29
+
30
+ // Set on the process env when Construct has already re-exec'd under a parent `op run`.
31
+ // Both the parent re-exec and the per-service wrap check it so a single resolution at
32
+ // the top of the tree is never nested (which would re-prompt and double-resolve).
33
+
34
+ export const OP_RUN_ACTIVE_ENV = 'CONSTRUCT_OP_RUN_ACTIVE';
20
35
 
21
36
  function expandHome(file, homeDir) {
22
37
  if (file === '~') return homeDir;
@@ -36,8 +51,10 @@ export function resolveOpEnvFile(env = process.env, homeDir = os.homedir()) {
36
51
  }
37
52
 
38
53
  export function opCliAvailable() {
54
+ const opPath = locateOpBinary();
55
+ if (!opPath) return false;
39
56
  try {
40
- return spawnSync('op', ['--version'], { stdio: 'ignore', timeout: 3000 }).status === 0;
57
+ return spawnSync(opPath, ['--version'], { stdio: 'ignore', timeout: 3000 }).status === 0;
41
58
  } catch {
42
59
  return false;
43
60
  }
@@ -48,6 +65,10 @@ export function opCliAvailable() {
48
65
  * to the original command unchanged otherwise. `hasOp` is injectable for tests.
49
66
  */
50
67
  export function wrapWithOpRun(command, args = [], { env = process.env, homeDir = os.homedir(), hasOp = opCliAvailable } = {}) {
68
+ // A parent `op run` already resolved every reference into this env; wrapping again
69
+ // would spawn a nested op run that re-prompts, so hand back the command untouched.
70
+ if (env[OP_RUN_ACTIVE_ENV]) return { command, args, wrapped: false, envFile: null };
71
+
51
72
  const file = resolveOpEnvFile(env, homeDir);
52
73
  if (!file || !hasOp()) return { command, args, wrapped: false, envFile: null };
53
74
  return {
@@ -57,3 +78,37 @@ export function wrapWithOpRun(command, args = [], { env = process.env, homeDir =
57
78
  envFile: file,
58
79
  };
59
80
  }
81
+
82
+ /**
83
+ * Re-exec the current process once under `op run` so every op:// reference in the
84
+ * env-file resolves a single time into the process env; the detached daemons the
85
+ * re-exec'd process then spawns inherit the resolved keys without their own op run.
86
+ * The blocking spawnSync makes the outer process a thin wrapper that exits with the
87
+ * inner status. Returns { reExecuted: false, reason } on every opt-out path — sentinel
88
+ * already set, no resolvable env-file, `op` absent, or a spawn error — so the caller
89
+ * falls through to the normal (per-service-wrap) launch unchanged. `spawnFn` and the
90
+ * process descriptors are injectable for tests.
91
+ */
92
+ export function maybeReExecUnderOpRun({
93
+ argv = process.argv,
94
+ execPath = process.execPath,
95
+ env = process.env,
96
+ homeDir = os.homedir(),
97
+ hasOp = opCliAvailable,
98
+ spawnFn = spawnSync,
99
+ } = {}) {
100
+ if (env[OP_RUN_ACTIVE_ENV]) return { reExecuted: false, reason: 'already-active' };
101
+ const file = resolveOpEnvFile(env, homeDir);
102
+ if (!file) return { reExecuted: false, reason: 'not-opted-in' };
103
+ if (!hasOp()) return { reExecuted: false, reason: 'op-missing' };
104
+
105
+ const scriptArgs = argv.slice(1);
106
+ const childEnv = { ...env, [OP_RUN_ACTIVE_ENV]: '1' };
107
+ const result = spawnFn('op', ['run', `--env-file=${file}`, '--', execPath, ...scriptArgs], {
108
+ stdio: 'inherit',
109
+ env: childEnv,
110
+ });
111
+ if (result?.error) return { reExecuted: false, reason: 'spawn-failed', error: result.error };
112
+ const code = typeof result?.status === 'number' ? result.status : 1;
113
+ return { reExecuted: true, code, envFile: file };
114
+ }
@@ -26,10 +26,20 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
26
26
  import { assertProviderContract } from './contract.mjs';
27
27
  import { getBreaker } from './circuit-breaker.mjs';
28
28
  import { configDir } from '../config/xdg.mjs';
29
+ import { loadManifestsFromDir, resolveManifestDirs } from '../extensions/loader.mjs';
30
+ import { describeModelProviders } from '../models/catalog.mjs';
29
31
 
30
32
  const HERE = dirname(fileURLToPath(import.meta.url));
31
33
 
32
- export const BUILT_INS = ['github', 'atlassian-jira', 'atlassian-confluence', 'slack', 'salesforce'];
34
+ /**
35
+ * @deprecated Use manifest-driven provider discovery via resolveProviders() instead.
36
+ * Will be removed in a future version.
37
+ */
38
+ export const BUILT_INS = (() => {
39
+ const { builtin: dir } = resolveManifestDirs();
40
+ const { manifests } = loadManifestsFromDir(dir);
41
+ return manifests.filter((m) => m.kind === 'data-source').map((m) => m.id);
42
+ })();
33
43
 
34
44
  function readJsonIfExists(path) {
35
45
  try {
@@ -109,7 +119,13 @@ export async function resolveProviders({ rootDir = process.cwd(), env = process.
109
119
  const sources = {};
110
120
  const errors = [];
111
121
 
112
- for (const id of BUILT_INS) {
122
+ const { builtin: builtinManifestDir } = resolveManifestDirs();
123
+ const { manifests: allManifests } = loadManifestsFromDir(builtinManifestDir);
124
+ const builtInIds = allManifests
125
+ .filter((m) => m.kind === 'data-source')
126
+ .map((m) => m.id);
127
+
128
+ for (const id of builtInIds) {
113
129
  try {
114
130
  const mod = await loadBuiltIn(id);
115
131
  if (!mod) continue;
@@ -145,6 +161,12 @@ export async function resolveProviders({ rootDir = process.cwd(), env = process.
145
161
  return { providers, sources, errors };
146
162
  }
147
163
 
164
+ // kind='model' manifests (LMCP-B4) enumerate through this same registry but
165
+ // are never instantiated as data-source contract instances — model provider
166
+ // families keep their cache/poll/visibility logic in lib/models/catalog.mjs
167
+ // and lib/model-router.mjs. This appends their manifest-derived summary rows
168
+ // so `construct provider list` shows one merged surface across both kinds.
169
+
148
170
  export async function describeProviders({ rootDir = process.cwd(), env = process.env } = {}) {
149
171
  const { providers, sources, errors } = await resolveProviders({ rootDir, env });
150
172
  const summary = await Promise.all(
@@ -161,7 +183,8 @@ export async function describeProviders({ rootDir = process.cwd(), env = process
161
183
  };
162
184
  })
163
185
  );
164
- return { summary, errors };
186
+ const modelSummary = describeModelProviders({ rootDir, env });
187
+ return { summary: [...summary, ...modelSummary], errors };
165
188
  }
166
189
 
167
190
  // Standalone validation for a candidate override entry: imports the package