@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,238 @@
1
+ /**
2
+ * lib/mcp/transport/auth.mjs — remote (HTTP) MCP authorization primitives.
3
+ *
4
+ * Pure, network-free functions the http entrypoint composes at request time.
5
+ * Split out from the transport wiring so every rule is unit-testable without a
6
+ * socket. Four responsibilities, in the order the http entrypoint applies them:
7
+ *
8
+ * 1. resolveHttpAuthConfig — reads the http-remote auth config and FAILS
9
+ * CLOSED. http mode has no default identity; a missing bearer secret,
10
+ * missing audience, or missing allowed-origin list is a startup error,
11
+ * never a warning. There is no skip/allow env var — the absence of config
12
+ * is the refusal.
13
+ * 2. validateOrigin / validateHost — DNS-rebinding defense. A browser that
14
+ * resolves a trusted name to 127.0.0.1 still sends the attacker's Origin
15
+ * and the attacker's Host; both must be on the operator's allowlist or the
16
+ * request is rejected before any tool dispatch. Pairs with N7's SSRF work.
17
+ * 3. verifyBearer — RFC 8707 shaped: the inbound token must carry the exact
18
+ * audience (resource indicator) this server was configured for. A token
19
+ * minted for a different audience is rejected even if otherwise valid, so a
20
+ * token stolen from an upstream service cannot be replayed here.
21
+ * 4. mintScopedContext — the broker's internal actor context is derived from
22
+ * the VERIFIED claims and NEVER contains the inbound token. Token
23
+ * passthrough is structurally impossible: the raw bearer is consumed by
24
+ * verifyBearer and dropped; nothing downstream is handed the credential.
25
+ */
26
+
27
+ import crypto from 'node:crypto';
28
+
29
+ export class HttpAuthConfigError extends Error {
30
+ constructor(message) {
31
+ super(message);
32
+ this.name = 'HttpAuthConfigError';
33
+ }
34
+ }
35
+
36
+ export class HttpAuthError extends Error {
37
+ constructor(message, { status = 401 } = {}) {
38
+ super(message);
39
+ this.name = 'HttpAuthError';
40
+ this.status = status;
41
+ }
42
+ }
43
+
44
+ // The scoped context carries the token as a symbol-keyed, non-enumerable
45
+ // nothing: there is no field to hold it. mintScopedContext returns a frozen
46
+ // object with only the derived, non-secret identity. A downstream caller that
47
+ // tries to forward `ctx.token` finds `undefined`, and JSON.stringify(ctx) can
48
+ // never leak a credential because none was ever stored.
49
+
50
+ const CONTEXT_FIELDS = Object.freeze(['actor', 'audience', 'scopes', 'issuedAt', 'source']);
51
+
52
+ function parseList(raw) {
53
+ if (Array.isArray(raw)) return raw.map((s) => String(s).trim()).filter(Boolean);
54
+ if (typeof raw === 'string') return raw.split(',').map((s) => s.trim()).filter(Boolean);
55
+ return [];
56
+ }
57
+
58
+ /**
59
+ * Resolve the http-remote auth config from env, failing closed. Returns a
60
+ * frozen config when every required field is present; throws
61
+ * HttpAuthConfigError otherwise. The http entrypoint calls this BEFORE binding
62
+ * a socket so a misconfigured server never accepts a single request.
63
+ *
64
+ * Required (no defaults, no skip path):
65
+ * CONSTRUCT_MCP_HTTP_BEARER_SECRET — HMAC secret the inbound token is signed with
66
+ * CONSTRUCT_MCP_HTTP_AUDIENCE — this server's resource indicator (RFC 8707)
67
+ * CONSTRUCT_MCP_HTTP_ALLOWED_ORIGINS — comma list of allowed Origin header values
68
+ * Optional:
69
+ * CONSTRUCT_MCP_HTTP_ALLOWED_HOSTS — comma list of allowed Host header values
70
+ * (defaults to localhost forms)
71
+ * CONSTRUCT_MCP_HTTP_HOST / _PORT — bind address (defaults to 127.0.0.1)
72
+ */
73
+ export function resolveHttpAuthConfig(env = process.env) {
74
+ const missing = [];
75
+ const bearerSecret = env.CONSTRUCT_MCP_HTTP_BEARER_SECRET;
76
+ const audience = env.CONSTRUCT_MCP_HTTP_AUDIENCE;
77
+ const allowedOrigins = parseList(env.CONSTRUCT_MCP_HTTP_ALLOWED_ORIGINS);
78
+
79
+ if (!bearerSecret) missing.push('CONSTRUCT_MCP_HTTP_BEARER_SECRET');
80
+ if (!audience) missing.push('CONSTRUCT_MCP_HTTP_AUDIENCE');
81
+ if (allowedOrigins.length === 0) missing.push('CONSTRUCT_MCP_HTTP_ALLOWED_ORIGINS');
82
+
83
+ if (missing.length > 0) {
84
+ throw new HttpAuthConfigError(
85
+ `http-remote MCP refuses to start: missing required auth config (${missing.join(', ')}). `
86
+ + 'HTTP transport has no default identity; there is no skip flag — supply the config or run stdio-local.',
87
+ );
88
+ }
89
+
90
+ const allowedHosts = parseList(env.CONSTRUCT_MCP_HTTP_ALLOWED_HOSTS);
91
+ const bindHost = env.CONSTRUCT_MCP_HTTP_HOST || '127.0.0.1';
92
+ const bindPort = Number(env.CONSTRUCT_MCP_HTTP_PORT) || 7391;
93
+
94
+ return Object.freeze({
95
+ bearerSecret,
96
+ audience,
97
+ allowedOrigins: Object.freeze(allowedOrigins),
98
+ allowedHosts: Object.freeze(allowedHosts.length > 0 ? allowedHosts : defaultAllowedHosts(bindHost, bindPort)),
99
+ bindHost,
100
+ bindPort,
101
+ });
102
+ }
103
+
104
+ // Localhost-bind is the default; the default host allowlist is exactly the
105
+ // loopback forms for the bind address so a request whose Host names a public
106
+ // interface is rejected unless the operator explicitly widens allowedHosts.
107
+
108
+ function defaultAllowedHosts(bindHost, bindPort) {
109
+ const names = ['localhost', '127.0.0.1', '[::1]', bindHost];
110
+ const out = [];
111
+ for (const n of names) {
112
+ out.push(n);
113
+ out.push(`${n}:${bindPort}`);
114
+ }
115
+ return [...new Set(out)];
116
+ }
117
+
118
+ /**
119
+ * Validate the inbound Origin against the allowlist. A missing Origin is
120
+ * allowed only for non-browser clients (no Origin header at all); a present
121
+ * Origin that is not on the allowlist is a DNS-rebinding attempt and is
122
+ * rejected. Returns true when the request may proceed.
123
+ */
124
+ export function validateOrigin(origin, config) {
125
+ if (origin == null || origin === '') return true;
126
+ return config.allowedOrigins.includes(origin);
127
+ }
128
+
129
+ /**
130
+ * Validate the inbound Host header against the allowlist. A rebinding attack
131
+ * resolves a trusted name to a loopback IP but still carries the attacker's
132
+ * Host value; requiring an exact allowlist match closes that path. A missing
133
+ * Host is rejected — a well-formed HTTP/1.1 request always carries one.
134
+ */
135
+ export function validateHost(host, config) {
136
+ if (host == null || host === '') return false;
137
+ return config.allowedHosts.includes(host);
138
+ }
139
+
140
+ // Constant-time compare so a token-signature check cannot be turned into a
141
+ // timing oracle. Both operands are hex/base64url strings of the same length
142
+ // when the request is well-formed; a length mismatch short-circuits to false
143
+ // without leaking where the difference is.
144
+
145
+ function timingSafeEqualStr(a, b) {
146
+ const ab = Buffer.from(String(a));
147
+ const bb = Buffer.from(String(b));
148
+ if (ab.length !== bb.length) return false;
149
+ return crypto.timingSafeEqual(ab, bb);
150
+ }
151
+
152
+ function b64urlDecode(seg) {
153
+ return Buffer.from(seg.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
154
+ }
155
+
156
+ function b64urlEncode(buf) {
157
+ return Buffer.from(buf).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
158
+ }
159
+
160
+ /**
161
+ * Mint an internal HMAC bearer for tests and local tooling: a compact
162
+ * `<payloadB64url>.<sigB64url>` token whose payload is a JSON claims object —
163
+ * the token shape verifyBearer accepts. Not a full JWT — deliberately minimal
164
+ * so the audience/expiry/signature semantics are auditable in one file.
165
+ */
166
+ export function mintInternalBearer(claims, secret) {
167
+ const payload = b64urlEncode(JSON.stringify(claims));
168
+ const sig = b64urlEncode(crypto.createHmac('sha256', secret).update(payload).digest());
169
+ return `${payload}.${sig}`;
170
+ }
171
+
172
+ /**
173
+ * Verify an inbound bearer against the configured secret AND audience. Order:
174
+ * shape → signature → expiry → audience. Returns the verified claims on
175
+ * success; throws HttpAuthError (401) otherwise. The audience check is the
176
+ * resource-indicator binding: a token minted for a different resource is
177
+ * rejected here, which is what makes a stolen upstream token un-replayable.
178
+ *
179
+ * The returned claims are DATA ONLY. The caller passes them to
180
+ * mintScopedContext; the raw token string is never returned and never stored.
181
+ */
182
+ export function verifyBearer(rawAuthHeader, config, { now = () => Date.now() } = {}) {
183
+ if (typeof rawAuthHeader !== 'string' || !rawAuthHeader.startsWith('Bearer ')) {
184
+ throw new HttpAuthError('missing or malformed Authorization: Bearer header');
185
+ }
186
+ const token = rawAuthHeader.slice('Bearer '.length).trim();
187
+ const parts = token.split('.');
188
+ if (parts.length !== 2) throw new HttpAuthError('malformed bearer token');
189
+
190
+ const [payloadB64, sigB64] = parts;
191
+ const expectedSig = b64urlEncode(crypto.createHmac('sha256', config.bearerSecret).update(payloadB64).digest());
192
+ if (!timingSafeEqualStr(sigB64, expectedSig)) {
193
+ throw new HttpAuthError('bearer signature does not verify');
194
+ }
195
+
196
+ let claims;
197
+ try {
198
+ claims = JSON.parse(b64urlDecode(payloadB64));
199
+ } catch {
200
+ throw new HttpAuthError('bearer payload is not valid JSON');
201
+ }
202
+
203
+ if (typeof claims.exp === 'number' && now() >= claims.exp * 1000) {
204
+ throw new HttpAuthError('bearer token expired');
205
+ }
206
+
207
+ const aud = Array.isArray(claims.aud) ? claims.aud : (claims.aud == null ? [] : [claims.aud]);
208
+ if (!aud.includes(config.audience)) {
209
+ throw new HttpAuthError(
210
+ `bearer audience mismatch: token is for ${JSON.stringify(claims.aud)} but this server is ${JSON.stringify(config.audience)}`,
211
+ { status: 403 },
212
+ );
213
+ }
214
+
215
+ return claims;
216
+ }
217
+
218
+ /**
219
+ * Mint the broker's internal actor context from VERIFIED claims. Token
220
+ * passthrough is structurally impossible: the inbound token never reaches this
221
+ * function — only the already-verified claims do — and the returned object has
222
+ * no field capable of holding a credential.
223
+ *
224
+ * The result is frozen and carries exactly CONTEXT_FIELDS. A read of a token
225
+ * off it (ctx.token, ctx.bearer, ctx.authorization) yields undefined.
226
+ */
227
+ export function mintScopedContext(claims, config, { now = () => Date.now() } = {}) {
228
+ const ctx = {
229
+ actor: claims.sub || claims.actor || 'http-remote-unknown',
230
+ audience: config.audience,
231
+ scopes: Array.isArray(claims.scopes) ? [...claims.scopes] : parseList(claims.scope),
232
+ issuedAt: new Date(now()).toISOString(),
233
+ source: 'http-remote',
234
+ };
235
+ return Object.freeze(ctx);
236
+ }
237
+
238
+ export { CONTEXT_FIELDS };
@@ -0,0 +1,136 @@
1
+ /**
2
+ * lib/mcp/transport/http.mjs — http-remote MCP entrypoint (opt-in, fail-closed).
3
+ *
4
+ * The network transport. Every guard the stdio path does not need lives here,
5
+ * applied in order per request before the MCP SDK transport ever sees the body:
6
+ *
7
+ * 1. Host allowlist — DNS-rebinding defense (a rebound name still carries
8
+ * the attacker's Host).
9
+ * 2. Origin allowlist — DNS-rebinding defense for browser callers.
10
+ * 3. Bearer + audience — RFC 8707 resource-indicator binding; a token for a
11
+ * different audience is rejected.
12
+ * 4. Scoped-context mint — the broker actor context is derived from verified
13
+ * claims; the inbound token is dropped and never
14
+ * forwarded downstream (token passthrough impossible).
15
+ *
16
+ * Startup itself fails closed: constructHttpServer resolves the auth config
17
+ * first (resolveHttpAuthConfig throws HttpAuthConfigError on any missing field)
18
+ * and binds to localhost by default. TLS is expected to be terminated by a
19
+ * reverse proxy in front of this server; the localhost bind default keeps the
20
+ * cleartext hop on the loopback interface. See docs follow-up in the bead.
21
+ *
22
+ * authorizeHttpRequest is the pure decision function — headers in, a decision
23
+ * or a thrown HttpAuthError out — so the whole authorization pipeline is
24
+ * unit-testable without opening a socket. startHttpTransport wires it to a
25
+ * real node:http server only at the entrypoint.
26
+ */
27
+
28
+ import http from 'node:http';
29
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
30
+ import {
31
+ resolveHttpAuthConfig,
32
+ validateOrigin,
33
+ validateHost,
34
+ verifyBearer,
35
+ mintScopedContext,
36
+ HttpAuthError,
37
+ } from './auth.mjs';
38
+
39
+ /**
40
+ * Resolve config and refuse to continue if auth is unconfigured. Callers that
41
+ * want to know whether http mode CAN start call this and catch
42
+ * HttpAuthConfigError; the http entrypoint calls it before binding a socket.
43
+ */
44
+ export function constructHttpServer(env = process.env) {
45
+ return resolveHttpAuthConfig(env);
46
+ }
47
+
48
+ /**
49
+ * Decide whether a single inbound HTTP request may reach tool dispatch. Runs
50
+ * the four guards in order and throws HttpAuthError (with a 401/403 status) on
51
+ * the first failure. On success returns { context } — the scoped internal actor
52
+ * context minted from verified claims, with NO token field.
53
+ *
54
+ * @param {object} headers - lowercased request headers (host, origin, authorization)
55
+ * @param {object} config - the frozen config from resolveHttpAuthConfig
56
+ */
57
+ export function authorizeHttpRequest(headers, config, { now = () => Date.now() } = {}) {
58
+ const host = headers.host ?? headers.Host;
59
+ if (!validateHost(host, config)) {
60
+ throw new HttpAuthError(`host not allowed: ${JSON.stringify(host)}`, { status: 421 });
61
+ }
62
+
63
+ const origin = headers.origin ?? headers.Origin;
64
+ if (!validateOrigin(origin, config)) {
65
+ throw new HttpAuthError(`origin not allowed: ${JSON.stringify(origin)}`, { status: 403 });
66
+ }
67
+
68
+ const authHeader = headers.authorization ?? headers.Authorization;
69
+ const claims = verifyBearer(authHeader, config, { now });
70
+
71
+ const context = mintScopedContext(claims, config, { now });
72
+ return { context };
73
+ }
74
+
75
+ // The WWW-Authenticate challenge names the resource metadata location per the
76
+ // MCP authorization guidance so a compliant client can discover how to obtain
77
+ // a correctly-scoped token instead of guessing.
78
+
79
+ function writeAuthError(res, err, config) {
80
+ const status = err instanceof HttpAuthError ? err.status : 401;
81
+ res.statusCode = status;
82
+ res.setHeader('Content-Type', 'application/json');
83
+ if (status === 401) {
84
+ res.setHeader('WWW-Authenticate', `Bearer realm="construct-mcp", audience="${config.audience}"`);
85
+ }
86
+ res.end(JSON.stringify({ error: err.message }));
87
+ }
88
+
89
+ /**
90
+ * Start a real http-remote MCP server bound to localhost by default. Fails
91
+ * closed at the first line: a missing auth config throws before listen().
92
+ * Returns { server, transport, config } once listening.
93
+ *
94
+ * Each request is authorized by authorizeHttpRequest first; only an authorized
95
+ * request is handed to the SDK transport, and the scoped context (never the
96
+ * token) is attached for the dispatch layer to read.
97
+ */
98
+ export async function startHttpTransport(mcpServer, { env = process.env } = {}) {
99
+ const config = resolveHttpAuthConfig(env);
100
+
101
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
102
+ await mcpServer.connect(transport);
103
+
104
+ const httpServer = http.createServer(async (req, res) => {
105
+ let decision;
106
+ try {
107
+ decision = authorizeHttpRequest(req.headers, config);
108
+ } catch (err) {
109
+ writeAuthError(res, err, config);
110
+ return;
111
+ }
112
+
113
+ // The verified, token-free scoped context rides on the request object for
114
+ // the dispatch layer; the raw Authorization header is stripped here, so
115
+ // nothing downstream can forward the inbound credential.
116
+
117
+ req.constructScopedContext = decision.context;
118
+ delete req.headers.authorization;
119
+ delete req.headers.Authorization;
120
+
121
+ try {
122
+ await transport.handleRequest(req, res);
123
+ } catch (err) {
124
+ if (!res.headersSent) {
125
+ res.statusCode = 500;
126
+ res.end(JSON.stringify({ error: err?.message || 'internal error' }));
127
+ }
128
+ }
129
+ });
130
+
131
+ await new Promise((resolveListen) => {
132
+ httpServer.listen(config.bindPort, config.bindHost, resolveListen);
133
+ });
134
+
135
+ return { server: httpServer, transport, config };
136
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * lib/mcp/transport/mode.mjs — MCP transport-mode resolution.
3
+ *
4
+ * Two explicit, non-overlapping modes with separate entrypoints:
5
+ * stdio-local — the default. A local subprocess spoken to over stdin/stdout;
6
+ * identity comes from the local OS session, so no network authn.
7
+ * Behavior is unchanged from the pre-separation server.
8
+ * http-remote — opt-in only, via CONSTRUCT_MCP_TRANSPORT=http (or =http-remote).
9
+ * A network surface, so it REQUIRES authn config and refuses to
10
+ * start without it (see resolveHttpAuthConfig).
11
+ *
12
+ * There is no auto-detection and no implicit http: a server started with no
13
+ * transport env is always stdio-local. Selecting http is a deliberate operator
14
+ * act, and that act carries the fail-closed auth requirement with it.
15
+ */
16
+
17
+ export const TRANSPORT_STDIO = 'stdio-local';
18
+ export const TRANSPORT_HTTP = 'http-remote';
19
+
20
+ export const TRANSPORT_MODE_ENV_KEY = 'CONSTRUCT_MCP_TRANSPORT';
21
+
22
+ /**
23
+ * Resolve the transport mode from env. Anything but an explicit http selection
24
+ * resolves to stdio-local, so a typo or empty value degrades to the safe local
25
+ * mode rather than silently exposing a network surface.
26
+ */
27
+ export function resolveTransportMode(env = process.env) {
28
+ const raw = String(env?.[TRANSPORT_MODE_ENV_KEY] || '').trim().toLowerCase();
29
+ if (raw === 'http' || raw === 'http-remote' || raw === 'streamable-http') return TRANSPORT_HTTP;
30
+ return TRANSPORT_STDIO;
31
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * lib/mcp/transport/stdio.mjs — stdio-local MCP entrypoint.
3
+ *
4
+ * The default transport, factored out of server.mjs so the http entrypoint can
5
+ * live beside it under one mode selector. Behavior is byte-for-byte the same as
6
+ * the original inline `new StdioServerTransport(); await server.connect(...)`:
7
+ * a local subprocess over stdin/stdout, no network surface, no authn — identity
8
+ * is the local OS session. The server object (tool catalog + dispatch) is
9
+ * passed in already built, so this module never re-registers a handler.
10
+ */
11
+
12
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
13
+
14
+ /**
15
+ * Connect the pre-built MCP server to a stdio transport. Resolves once the
16
+ * transport is connected; the process then stays alive serving requests.
17
+ */
18
+ export async function startStdioTransport(server) {
19
+ const transport = new StdioServerTransport();
20
+ await server.connect(transport);
21
+ return transport;
22
+ }
@@ -6,11 +6,11 @@
6
6
  "name": "Context7",
7
7
  "category": "core",
8
8
  "description": "Library and framework documentation lookup. Agents use this before writing code against any dependency.",
9
- "package": "@upstash/context7-mcp@latest",
9
+ "package": "@upstash/context7-mcp@3.2.2",
10
10
  "command": "npx",
11
11
  "args": [
12
12
  "-y",
13
- "@upstash/context7-mcp@latest"
13
+ "@upstash/context7-mcp@3.2.2"
14
14
  ],
15
15
  "env": {},
16
16
  "requiredEnv": [],
@@ -31,11 +31,11 @@
31
31
  "name": "Sequential Thinking",
32
32
  "category": "optional",
33
33
  "description": "Structured multi-step reasoning chains. Helps with complex planning and analysis.",
34
- "package": "@modelcontextprotocol/server-sequential-thinking",
34
+ "package": "@modelcontextprotocol/server-sequential-thinking@2026.7.4",
35
35
  "command": "npx",
36
36
  "args": [
37
37
  "-y",
38
- "@modelcontextprotocol/server-sequential-thinking"
38
+ "@modelcontextprotocol/server-sequential-thinking@2026.7.4"
39
39
  ],
40
40
  "env": {},
41
41
  "requiredEnv": [],
@@ -29,8 +29,15 @@ import {
29
29
  } from './mcp-platform-config.mjs';
30
30
  import { getMcpById as getMcpByIdFromRegistry, loadPluginRegistry } from './plugin-registry.mjs';
31
31
 
32
- function getClaudeSettingsPath() {
33
- return join(homedir(), '.claude', 'settings.json');
32
+ // Claude Code does not read MCP server definitions from ~/.claude/settings.json
33
+ // (confirmed against code.claude.com/docs/en/mcp's installation-scopes table and
34
+ // code.claude.com/docs/en/managed-mcp, plus live `claude mcp list`/`claude mcp get`
35
+ // repro on v2.1.201 — construct-ranh). User-scope servers live in the top-level
36
+ // `mcpServers` object of ~/.claude.json; settings.json carries only policy keys
37
+ // (allowedMcpServers, deniedMcpServers, etc.) and hooks/permissions, untouched here.
38
+
39
+ function getClaudeUserConfigPath() {
40
+ return join(homedir(), '.claude.json');
34
41
  }
35
42
 
36
43
  // VS Code's "MCP: Open User Configuration" edits a per-profile mcp.json keyed
@@ -196,20 +203,24 @@ function getConfiguredEnvValues(mcp) {
196
203
  return values;
197
204
  }
198
205
 
199
- function loadSettings() {
200
- const claudeSettingsPath = getClaudeSettingsPath();
201
- if (!existsSync(claudeSettingsPath)) return {};
206
+ function loadClaudeUserConfig() {
207
+ const claudeUserConfigPath = getClaudeUserConfigPath();
208
+ if (!existsSync(claudeUserConfigPath)) return {};
202
209
  try {
203
- return JSON.parse(readFileSync(claudeSettingsPath, 'utf8'));
210
+ return JSON.parse(readFileSync(claudeUserConfigPath, 'utf8'));
204
211
  } catch {
205
212
  return {};
206
213
  }
207
214
  }
208
215
 
209
- function saveSettings(settings) {
210
- const claudeSettingsPath = getClaudeSettingsPath();
211
- mkdirSync(dirname(claudeSettingsPath), { recursive: true });
212
- writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + '\n');
216
+ // Rewrites the whole file: ~/.claude.json also holds unrelated Claude Code
217
+ // runtime state (oauthAccount, projects, numStartups, ...). Only the top-level
218
+ // `mcpServers` key is ever touched here; every other key round-trips untouched.
219
+
220
+ function saveClaudeUserConfig(config) {
221
+ const claudeUserConfigPath = getClaudeUserConfigPath();
222
+ mkdirSync(dirname(claudeUserConfigPath), { recursive: true });
223
+ writeFileSync(claudeUserConfigPath, JSON.stringify(config, null, 2) + '\n');
213
224
  }
214
225
 
215
226
  function loadOpenCodeConfig() {
@@ -226,12 +237,12 @@ function getMcpById(id) {
226
237
 
227
238
  function getInstalledMcps() {
228
239
  const registry = loadPluginRegistry({ cwd: process.cwd(), homeDir: homedir() });
229
- const settings = loadSettings();
240
+ const claudeUserConfig = loadClaudeUserConfig();
230
241
  const oc = loadOpenCodeConfig();
231
242
  const codexConfig = readCodexConfig();
232
243
  const codexServers = Array.from(codexConfig.matchAll(/^\[mcp_servers\.("?)([^"\]\n]+)\1\]/gm)).map((match) => match[2]);
233
244
  const set = new Set([
234
- ...Object.keys(settings.mcpServers ?? {}),
245
+ ...Object.keys(claudeUserConfig.mcpServers ?? {}),
235
246
  ...Object.keys(oc.mcp ?? {}),
236
247
  ...codexServers,
237
248
  ]);
@@ -300,7 +311,7 @@ export function cmdMcpList() {
300
311
 
301
312
  /**
302
313
  * construct mcp add <id>
303
- * Interactive Guided Wizard: collect required env vars and write to settings.json.
314
+ * Interactive Guided Wizard: collect required env vars and write to ~/.claude.json.
304
315
  */
305
316
  export async function cmdMcpAdd(id) {
306
317
  const mcp = getMcpById(id);
@@ -412,15 +423,15 @@ export async function cmdMcpAdd(id) {
412
423
  resolvedValues.MEMORY_PORT = process.env.MEMORY_PORT || '8765';
413
424
  }
414
425
 
415
- // Write to Claude settings
416
- const settings = loadSettings();
417
- if (!settings.mcpServers) settings.mcpServers = {};
426
+ // Write to Claude Code's user-scope MCP config (~/.claude.json), not settings.json
427
+ const claudeUserConfig = loadClaudeUserConfig();
428
+ if (!claudeUserConfig.mcpServers) claudeUserConfig.mcpServers = {};
418
429
  if (isManagedOnHost(mcp, 'claude')) {
419
- settings.mcpServers[id] = buildClaudeMcpEntry(id, mcp, resolvedValues, { auth: authMode });
430
+ claudeUserConfig.mcpServers[id] = buildClaudeMcpEntry(id, mcp, resolvedValues, { auth: authMode });
420
431
  } else {
421
- delete settings.mcpServers[id];
432
+ delete claudeUserConfig.mcpServers[id];
422
433
  }
423
- saveSettings(settings);
434
+ saveClaudeUserConfig(claudeUserConfig);
424
435
 
425
436
  // Write to OpenCode config
426
437
  const oc = loadOpenCodeConfig();
@@ -484,13 +495,13 @@ export async function cmdMcpAdd(id) {
484
495
  * construct mcp remove <id>
485
496
  */
486
497
  export function cmdMcpRemove(id) {
487
- const settings = loadSettings();
498
+ const claudeUserConfig = loadClaudeUserConfig();
488
499
  const openCodeState = readOpenCodeConfig();
489
500
  const oc = openCodeState.config ?? {};
490
501
  const openCodeId = getOpenCodeMcpId(id);
491
502
  const vscodePaths = getVSCodeUserMcpPaths();
492
503
  const cursorPath = getCursorMcpPath();
493
- const hasClaudeEntry = Boolean(settings.mcpServers?.[id]);
504
+ const hasClaudeEntry = Boolean(claudeUserConfig.mcpServers?.[id]);
494
505
  const hasOpenCodeEntry = Boolean(oc.mcp?.[openCodeId] || oc.mcp?.[id] || (id === 'memory' && oc.mcp?.cass));
495
506
  const hasCodexEntry = readCodexConfig().includes(`[mcp_servers.${id}]`) || readCodexConfig().includes(`[mcp_servers.${tomlString(id)}]`);
496
507
  const hasVscodeEntry = vscodePaths.some((p) => mcpFileHasEntry(p, 'servers', id));
@@ -503,8 +514,8 @@ export function cmdMcpRemove(id) {
503
514
  const mcp = getMcpById(id);
504
515
  const name = mcp?.name ?? id;
505
516
 
506
- if (settings.mcpServers) delete settings.mcpServers[id];
507
- saveSettings(settings);
517
+ if (claudeUserConfig.mcpServers) delete claudeUserConfig.mcpServers[id];
518
+ saveClaudeUserConfig(claudeUserConfig);
508
519
 
509
520
  if (oc.mcp) {
510
521
  delete oc.mcp[openCodeId];
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { dirname, resolve } from "node:path";
9
9
  import { fileURLToPath } from "node:url";
10
+ import { memoryPort } from "./home-namespace.mjs";
10
11
 
11
12
  const ROOT_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
12
13
 
@@ -31,7 +32,11 @@ function resolveTemplateObject(input, resolvedValues, fallback) {
31
32
  // A value is unsafe to write into a host MCP env block when it is an unresolved
32
33
  // `__NAME__` template or a 1Password `op://` reference. The op:// form must never
33
34
  // be written verbatim: a host that cannot run `op read` would pass the literal
34
- // reference to the child process, and writing it persists secret topology on disk.
35
+ // reference to the child process, and writing it persists secret topology into a
36
+ // host-readable MCP config file on disk (claude_desktop_config.json, OpenCode
37
+ // settings) — a different sink from Construct's own audit JSONL, where recording
38
+ // the ref (not the value) is the accepted design (secret-audit-wiring.mjs,
39
+ // remediation plan Epic 6, construct-trxz.6).
35
40
 
36
41
  function isUnwritableEnvValue(value) {
37
42
  return typeof value !== "string" || value.includes("__") || value.startsWith("op://");
@@ -58,8 +63,12 @@ const SECRET_REF = {
58
63
  // A stdio MCP env value that is a whole-value secret template (`__NAME__` as the
59
64
  // entire string) is a credential carrier: emit the host's env-reference form so the
60
65
  // live value never lands on disk, exactly as the remote `headers` path does. Claude
61
- // Code (`${VAR}`), VS Code (`${env:VAR}`), and OpenCode (`{env:VAR}`) each expand
62
- // their reference syntax inside a stdio env block, and the child still inherits the
66
+ // Code (`${VAR}`, documented at code.claude.com/docs/en/mcp under "Environment
67
+ // variable expansion in .mcp.json," confirmed 2026-07 for construct-trxz.12 --
68
+ // expansion applies to the file format, independent of which host-config file
69
+ // Construct currently targets; see construct-ranh) and OpenCode (`{env:VAR}`, live-
70
+ // validated against the installed binary for construct-trxz.12) each expand their
71
+ // reference syntax inside a stdio env block, and the child still inherits the
63
72
  // resolved env from the parent (config.env -> loadConstructEnv -> process.env) as a
64
73
  // backstop. A composed value (a URL with an embedded port template, a plain literal)
65
74
  // is not a secret and is materialized so the child receives the concrete string;
@@ -67,11 +76,21 @@ const SECRET_REF = {
67
76
 
68
77
  const WHOLE_SECRET_TEMPLATE = /^__([A-Z0-9_]+)__$/;
69
78
 
70
- function buildLocalEnvironment(mcpDef, resolvedValues, secretRef) {
79
+ // VS Code's own MCP configuration reference (code.visualstudio.com/docs/agents/
80
+ // reference/mcp-configuration, confirmed 2026-07 for construct-trxz.12) documents only
81
+ // `${workspaceFolder}`-style predefined variables and `${input:id}` prompts for
82
+ // mcp.json — no `${env:VAR}` substitution is listed as supported. Writing `${env:NAME}`
83
+ // into a VS Code stdio `env` block would hand the child process that literal,
84
+ // unexpanded string instead of the credential, so VS Code's local/stdio env keeps the
85
+ // Codex-style materialize-or-strip treatment (flipWholeSecret: false) rather than the
86
+ // reference flip Claude and OpenCode confirm via their own documented `${VAR}` /
87
+ // `{env:VAR}` expansion.
88
+
89
+ function buildLocalEnvironment(mcpDef, resolvedValues, secretRef, { flipWholeSecret = true } = {}) {
71
90
  const source = mcpDef.env ?? {};
72
91
  const out = {};
73
92
  for (const [key, raw] of Object.entries(source)) {
74
- const whole = typeof raw === "string" ? raw.match(WHOLE_SECRET_TEMPLATE) : null;
93
+ const whole = flipWholeSecret && typeof raw === "string" ? raw.match(WHOLE_SECRET_TEMPLATE) : null;
75
94
  if (whole) {
76
95
  out[key] = secretRef(whole[1]);
77
96
  continue;
@@ -86,10 +105,15 @@ function buildRemoteHeaders(mcpDef, secretRef) {
86
105
  return resolveTemplateObject(mcpDef.headers ?? {}, {}, secretRef);
87
106
  }
88
107
 
108
+ // The memory bridge URL is templated from MEMORY_PORT; when a caller does not supply
109
+ // one (an entry built outside a populated config.env), fall back to the derived per-home
110
+ // port from home-namespace, never the dead legacy 8765 literal that produced the
111
+ // split-brain against the actually-allocated port.
112
+
89
113
  function withMemoryDefaults(id, values) {
90
114
  if (id !== "memory") return values;
91
115
  if (values.MEMORY_PORT) return values;
92
- return { ...values, MEMORY_PORT: "8765" };
116
+ return { ...values, MEMORY_PORT: String(memoryPort()) };
93
117
  }
94
118
 
95
119
  // auth defaults to "oauth": remote MCP servers (e.g. GitHub's hosted server) are
@@ -115,7 +139,7 @@ export function buildClaudeMcpEntry(id, mcpDef, resolvedValues = {}, { host = "c
115
139
  };
116
140
  }
117
141
 
118
- const env = buildLocalEnvironment(mcpDef, values, secretRef);
142
+ const env = buildLocalEnvironment(mcpDef, values, secretRef, { flipWholeSecret: host !== "vscode" });
119
143
  return {
120
144
  command: mcpDef.command,
121
145
  args: resolveArgs(mcpDef.args, values),