@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,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
@@ -20,6 +20,12 @@ export function enableSecretAuditTrail({ file } = {}) {
20
20
  setSecretAuditSink((event) => {
21
21
  if (event.event === 'secret.op_read') {
22
22
  if (event.cacheHit) return;
23
+
24
+ // Persisting the op:// ref (vault/item/field — not the value) into
25
+ // Construct's own audit JSONL is a recorded design decision (remediation
26
+ // plan Epic 6, construct-trxz.6), distinct from the mcp-platform-config.mjs
27
+ // prohibition on writing op:// refs into host-readable MCP config files.
28
+
23
29
  appendAuditRecord(
24
30
  {
25
31
  ts: new Date().toISOString(),