@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
@@ -114,18 +114,6 @@
114
114
  }
115
115
  }
116
116
  },
117
- "hosts": {
118
- "type": "object",
119
- "description": "Granular control over editor and platform adapter sets.",
120
- "properties": {
121
- "claude": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
122
- "opencode": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
123
- "codex": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
124
- "copilot": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
125
- "vscode": { "type": "object", "properties": { "enabled": { "type": "boolean" } } },
126
- "cursor": { "type": "object", "properties": { "enabled": { "type": "boolean" } } }
127
- }
128
- },
129
117
  "providers": {
130
118
  "type": "object",
131
119
  "description": "Provider-specific settings (e.g., Anthropic, OpenAI, GitHub). Values starting with '$' are interpolated from environment variables.",
@@ -367,10 +367,8 @@
367
367
  },
368
368
  "postconditions": {
369
369
  "type": "array",
370
- "description": "Conditions that must be true after this contract completes.",
371
- "items": {
372
- "type": "string"
373
- }
370
+ "description": "Conditions that must be true after this contract completes. Each entry is a structured object with a required `postconditionType` of 'executable' (carries a `check` — or an `enforcedVia` pointer to lib/specialists/postconditions.mjs / contract.output — that lib/contracts/validate.mjs actually runs) or 'advisory' (documented, not mechanically checked). Bare prose strings are a legacy, deprecated shape.",
371
+ "items": {}
374
372
  },
375
373
  "teamBoundary": {
376
374
  "type": "object",
@@ -10,11 +10,16 @@
10
10
  * `orchestrator` entry). Specialists, slash commands, and skills do NOT
11
11
  * land at global scope — they are project content.
12
12
  * - Plus the hook installer in `~/.claude/settings.json`, which has to be
13
- * global so hooks fire in every Claude Code session.
13
+ * global so hooks fire in every Claude Code session. MCP server
14
+ * definitions are a separate file: Claude Code reads user-scope MCP
15
+ * servers from the top-level `mcpServers` object in `~/.claude.json`,
16
+ * not from settings.json (construct-ranh).
14
17
  *
15
18
  * Project scope (`<project>/.claude/`, `<project>/.codex/`, `<project>/.github/`, …)
16
19
  * - `construct` front door only (Single Front Door), slash commands, skills, MCP
17
20
  * wiring. Specialists dispatch internally via orchestration MCP tools.
21
+ * MCP server definitions land in `<project>/.mcp.json` (Claude Code's
22
+ * documented project scope), not `.claude/settings.json`.
18
23
  *
19
24
  * Flags:
20
25
  * --dry-run Print a diff of what would change without writing anything.
@@ -47,6 +52,7 @@ import {
47
52
  import { findOpenCodeConfigPath, readOpenCodeConfig, writeOpenCodeConfig, ensureOpenRouterProviderAuth } from "../lib/opencode-config.mjs";
48
53
  import { HEAVY_EXTERNAL_MCP_IDS, LOCAL_SURFACE_MODES, decideTrim, isLocalModel } from "../lib/mcp/tool-budget.mjs";
49
54
  import { emitCursorRules } from "../lib/rules-delivery.mjs";
55
+ import { memoryPort } from "../lib/home-namespace.mjs";
50
56
  import { resolvePromptContract, readPromptBody } from "../lib/prompt-composer.js";
51
57
  import { renderPersonaForTier } from "../lib/persona-sections.mjs";
52
58
  import { getModelVerdict } from "../lib/ollama/capability-store.mjs";
@@ -453,7 +459,7 @@ const standardConstructTools = [
453
459
  // agent's tools list or the call is blocked, leaving the orchestrator unable to route.
454
460
  // Single source of truth so every host's orchestrator grant stays in parity.
455
461
 
456
- const ORCHESTRATOR_DISPATCH_TOOLS = ["orchestration_policy", "orchestration_run"];
462
+ const ORCHESTRATOR_DISPATCH_TOOLS = ["orchestration_policy", "orchestration_run", "orchestration_delegation_next"];
457
463
  const managedStart = `# BEGIN ${systemName.toUpperCase()} AGENTS`;
458
464
  const managedEnd = `# END ${systemName.toUpperCase()} AGENTS`;
459
465
  const mdManagedStart = `<!-- BEGIN ${systemName.toUpperCase()} AGENTS -->`;
@@ -485,10 +491,17 @@ function resolveArgs(args) {
485
491
  : a));
486
492
  }
487
493
 
488
- function resolveTemplateStrings(value) {
494
+ // A secret-suffixed placeholder (TOKEN/SECRET/API_KEY/PUBLIC_KEY/PRIVATE_KEY) always
495
+ // resolves to OpenCode's `{env:NAME}` reference form, even when the named var is set
496
+ // in process.env, so a live credential never lands in a generated config file. This
497
+ // guard runs before the general env lookup below, mirroring the buildLocalEnvironment
498
+ // value->ref flip in lib/mcp-platform-config.mjs. GITHUB_TOKEN is an intentional alias
499
+ // that still materializes GITHUB_PERSONAL_ACCESS_TOKEN's value: that branch stays ahead
500
+ // of the suffix guard since it targets a different source var, not GITHUB_TOKEN itself.
501
+
502
+ export function resolveTemplateStrings(value) {
489
503
  if (typeof value === "string") {
490
504
  return value.replace(/__([A-Z0-9_]+)__/g, (_, name) => {
491
- if (process.env[name] !== undefined && process.env[name] !== "") return process.env[name];
492
505
  if (name === "CX_TOOLKIT_DIR") return root;
493
506
  if (name === "MEMORY_PORT") return process.env.MEMORY_PORT || "8765";
494
507
  if (name === "CONSTRUCT_MEMORY_BRIDGE_URL") {
@@ -500,6 +513,7 @@ function resolveTemplateStrings(value) {
500
513
  if (/(?:TOKEN|SECRET|API_KEY|PUBLIC_KEY|PRIVATE_KEY)$/.test(name)) {
501
514
  return `{env:${name}}`;
502
515
  }
516
+ if (process.env[name] !== undefined && process.env[name] !== "") return process.env[name];
503
517
  return `__${name}__`;
504
518
  });
505
519
  }
@@ -778,7 +792,7 @@ function orchestrationMicroPrompt(platform) {
778
792
 
779
793
  // Directive for the local editor agent (construct-local). It executes bounded work on
780
794
  // the cheap local model and hands planning/reasoning back to the construct architect —
781
- // the aider architect/editor split. Kept short: a small model must actually obey it.
795
+ // a cheap-editor/capable-architect split. Kept short: a small model must actually obey it.
782
796
 
783
797
  const LOCAL_EDITOR_DIRECTIVE =
784
798
  `You are a focused execution agent running on a local model, dispatched by construct to do one bounded job. Do well-scoped edits for the current task and verify them; make the smallest correct change, never a broad rewrite.\n` +
@@ -1016,7 +1030,7 @@ ${buildPrompt(entry, allEntries, "claude")}
1016
1030
  * Rewrite the home-mode hook command pattern
1017
1031
  * node "$HOME/.config/construct/lib/hooks/<name>.mjs"
1018
1032
  * into the project-portable form
1019
- * node "${CLAUDE_PROJECT_DIR:-.}/.construct/run.mjs" hook <name>
1033
+ * node "${CLAUDE_PROJECT_DIR:-<absRoot>}/.construct/run.mjs" hook <name>
1020
1034
  * so the resulting settings.json works on any clone where the project ships
1021
1035
  * the .construct/ launcher (committed by `npm install`'s postinstall or by
1022
1036
  * `construct init`). The launcher resolves Construct via node_modules → npx
@@ -1024,21 +1038,29 @@ ${buildPrompt(entry, allEntries, "claude")}
1024
1038
  * works for non-Node ecosystems too. Other commands (inline node -e
1025
1039
  * snippets, npx block-no-verify@…) are left untouched.
1026
1040
  *
1027
- * The `${CLAUDE_PROJECT_DIR:-.}` anchor matters: Claude Code invokes hooks with
1041
+ * The `${CLAUDE_PROJECT_DIR:-<absRoot>}` anchor matters: hosts invoke hooks with
1028
1042
  * a working directory that is not guaranteed to be the project root (observed:
1029
- * $HOME), and a bare relative `.construct/run.mjs` then fails with MODULE_NOT_FOUND
1030
- * at node:internal/modules/cjs/loader. CLAUDE_PROJECT_DIR is the project root Claude
1031
- * Code exports to every hook (same var lib/hooks/*.mjs already read); the `:-.`
1032
- * fallback preserves the prior relative behavior wherever the var is unset.
1043
+ * $HOME, a `cd`-ed scratch dir), and a bare relative `.construct/run.mjs` then
1044
+ * fails with MODULE_NOT_FOUND at node:internal/modules/cjs/loader before any
1045
+ * code in run.mjs can execute, so the shim cannot self-correct. CLAUDE_PROJECT_DIR
1046
+ * is the project root Claude Code exports to every hook (same var lib/hooks/*.mjs
1047
+ * already read) and stays correct if the checkout moves; the fallback is the
1048
+ * absolute project root baked at sync time, so tools that do not export
1049
+ * CLAUDE_PROJECT_DIR (or any host with a drifted cwd) still resolve the launcher.
1050
+ * `.claude/settings.json` is gitignored and regenerated per machine by
1051
+ * `construct sync`, so an absolute fallback is machine-correct without leaking a
1052
+ * shared path. The prior `:-.` fallback was cwd-relative and broke on any drift.
1033
1053
  */
1034
- function makeHooksPortable(hooksJson) {
1054
+ function makeHooksPortable(hooksJson, projectRoot) {
1055
+ const anchor = `\${CLAUDE_PROJECT_DIR:-${path.resolve(projectRoot)}}`;
1056
+
1035
1057
  // Operate on the in-memory object so we don't fight JSON string escaping.
1036
1058
  const replaceCommand = (cmd) => {
1037
1059
  if (typeof cmd !== 'string') return cmd;
1038
1060
  const m = cmd.match(/^node\s+"?\$HOME\/\.config\/construct\/lib\/hooks\/([a-z0-9-]+)\.mjs"?\s*(.*)$/);
1039
1061
  if (!m) return cmd;
1040
1062
  const [, name, rest] = m;
1041
- return `node "\${CLAUDE_PROJECT_DIR:-.}/.construct/run.mjs" hook ${name}${rest ? ' ' + rest.trim() : ''}`;
1063
+ return `node "${anchor}/.construct/run.mjs" hook ${name}${rest ? ' ' + rest.trim() : ''}`;
1042
1064
  };
1043
1065
 
1044
1066
  const walk = (node) => {
@@ -1091,14 +1113,9 @@ function syncGlobalClaudeMcpServers(settings, registryMcp) {
1091
1113
  for (const [id, mcpDef] of Object.entries(registryMcp)) {
1092
1114
  if (!GLOBAL_CLAUDE_MCP_IDS.has(id)) continue;
1093
1115
  const existingEntry = settings.mcpServers[id];
1094
- const existing = JSON.stringify(existingEntry ?? "");
1095
- const hasPlaceholder = existing.includes("__");
1096
- const hasFloatingVersion = existing.includes("@latest");
1097
- const registryWantsCommand = !mcpDef.type && Array.isArray(mcpDef.args);
1098
- const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
1099
- const transportMismatch = registryWantsCommand && existingIsRemote;
1100
- if (existingEntry && !hasPlaceholder && !hasFloatingVersion && !transportMismatch) continue;
1101
- settings.mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env);
1116
+ const desiredEntry = buildClaudeMcpEntry(id, mcpDef, process.env);
1117
+ if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
1118
+ settings.mcpServers[id] = desiredEntry;
1102
1119
  }
1103
1120
  }
1104
1121
 
@@ -1107,6 +1124,12 @@ function syncGlobalClaudeMcpServers(settings, registryMcp) {
1107
1124
  * with hook commands rewritten to be path-relative to whatever Construct
1108
1125
  * install the project carries. Merges into an existing settings.json
1109
1126
  * if one is already in the project; otherwise creates a fresh one.
1127
+ *
1128
+ * Carries hooks and permissions only — Claude Code does not read MCP server
1129
+ * definitions from settings.json at any scope (confirmed against
1130
+ * code.claude.com/docs/en/mcp's installation-scopes table and live `claude
1131
+ * mcp list` repro, construct-ranh). Project-scope MCP wiring is
1132
+ * writeProjectMcpJson's job, targeting `.mcp.json`.
1110
1133
  */
1111
1134
  function writeProjectClaudeSettings(targetDir) {
1112
1135
  const settingsPath = path.join(targetDir, ".claude", "settings.json");
@@ -1120,17 +1143,42 @@ function writeProjectClaudeSettings(targetDir) {
1120
1143
  : {};
1121
1144
 
1122
1145
  if (template.hooks) {
1123
- existing.hooks = JSON.parse(makeHooksPortable(template.hooks));
1146
+ existing.hooks = JSON.parse(makeHooksPortable(template.hooks, targetDir));
1124
1147
  }
1125
1148
  if (template.permissions) {
1126
1149
  existing.permissions ??= template.permissions;
1127
1150
  }
1128
- if (template.mcpServers) {
1129
- existing.mcpServers ??= {};
1130
- for (const [id, mcpDef] of Object.entries(template.mcpServers)) {
1131
- if (existing.mcpServers[id]) continue;
1132
- if (!PROJECT_DEFAULT_MCP_IDS.has(id)) continue;
1133
- existing.mcpServers[id] = mcpDef;
1151
+
1152
+ if (DRY_RUN) return;
1153
+ mkdirp(path.dirname(settingsPath));
1154
+ writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n");
1155
+ }
1156
+
1157
+ /**
1158
+ * Materialise project-scope MCP wiring at `.mcp.json` in the project root —
1159
+ * the scope Claude Code documents for team-shared, version-controlled MCP
1160
+ * server definitions (settings.json only carries policy keys and hooks).
1161
+ * Merges into an existing `.mcp.json` if the project already has one;
1162
+ * existing entries win so manual edits or a prior `claude mcp add --scope
1163
+ * project` stick.
1164
+ */
1165
+ function writeProjectMcpJson(targetDir) {
1166
+ const mcpJsonPath = path.join(targetDir, ".mcp.json");
1167
+ const templatePath = path.join(root, "platforms", "claude", "settings.template.json");
1168
+
1169
+ const existing = fs.existsSync(mcpJsonPath)
1170
+ ? JSON.parse(fs.readFileSync(mcpJsonPath, "utf8"))
1171
+ : {};
1172
+ existing.mcpServers ??= {};
1173
+
1174
+ if (fs.existsSync(templatePath)) {
1175
+ const template = JSON.parse(fs.readFileSync(templatePath, "utf8"));
1176
+ if (template.mcpServers) {
1177
+ for (const [id, mcpDef] of Object.entries(template.mcpServers)) {
1178
+ if (existing.mcpServers[id]) continue;
1179
+ if (!PROJECT_DEFAULT_MCP_IDS.has(id)) continue;
1180
+ existing.mcpServers[id] = mcpDef;
1181
+ }
1134
1182
  }
1135
1183
  }
1136
1184
 
@@ -1139,19 +1187,23 @@ function writeProjectClaudeSettings(targetDir) {
1139
1187
  // the server exposing project_context/get_skill/get_template/orchestration_policy
1140
1188
  // that the specialist loop depends on. The curated template omits it, which left
1141
1189
  // Claude Code as the only selected tool without the construct config. Merge the
1142
- // registry on top of the template seed; existing entries win so user edits stick,
1143
- // and buildClaudeMcpEntry resolves each server's path/env.
1190
+ // registry on top of the template seed via needsRefresh(): an existing entry
1191
+ // wins as long as it still matches the registry (env keys, pinned version,
1192
+ // transport, toolkit path), so a manual opt-in sticks but drift — a missing
1193
+ // registry env key, a stale pinned version — gets corrected.
1144
1194
  const registryMcp = scopedManagedMcpDefs({ projectScope: true });
1145
- existing.mcpServers ??= {};
1146
1195
  for (const [id, mcpDef] of Object.entries(registryMcp)) {
1147
- if (existing.mcpServers[id]) continue;
1148
1196
  if (!PROJECT_DEFAULT_MCP_IDS.has(id)) continue;
1149
- existing.mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env);
1197
+ const existingEntry = existing.mcpServers[id];
1198
+ const desiredEntry = buildClaudeMcpEntry(id, mcpDef, process.env);
1199
+ if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
1200
+ existing.mcpServers[id] = desiredEntry;
1150
1201
  }
1202
+ reconcileStaleManagedEntries(existing.mcpServers, { registryMcp, rebuildEntry: (id, def) => buildClaudeMcpEntry(id, def, process.env) });
1151
1203
 
1152
1204
  if (DRY_RUN) return;
1153
- mkdirp(path.dirname(settingsPath));
1154
- writeFile(settingsPath, JSON.stringify(existing, null, 2) + "\n");
1205
+ mkdirp(path.dirname(mcpJsonPath));
1206
+ writeFile(mcpJsonPath, JSON.stringify(existing, null, 2) + "\n");
1155
1207
  }
1156
1208
 
1157
1209
  function syncClaude(entries, targetDir = null, wants = true) {
@@ -1182,6 +1234,7 @@ function syncClaude(entries, targetDir = null, wants = true) {
1182
1234
 
1183
1235
  if (targetDir) {
1184
1236
  writeProjectClaudeSettings(targetDir);
1237
+ writeProjectMcpJson(targetDir);
1185
1238
  return;
1186
1239
  }
1187
1240
 
@@ -1199,7 +1252,7 @@ ${personaList}
1199
1252
  // User-managed file with our managed-block carved out — never doc-stamp.
1200
1253
  writeFile(claudeMdPath, replaceManagedBlock(existing, note, mdManagedStart, mdManagedEnd), { stamp: false });
1201
1254
 
1202
- // Sync MCP servers into ~/.claude/settings.json if it exists
1255
+ // Sync hooks into ~/.claude/settings.json if it exists
1203
1256
  const claudeSettingsPath = path.join(home, ".claude", "settings.json");
1204
1257
  if (fs.existsSync(claudeSettingsPath)) {
1205
1258
  const settings = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf8"));
@@ -1219,10 +1272,22 @@ ${personaList}
1219
1272
  settings.hooks = JSON.parse(hookStr);
1220
1273
  }
1221
1274
  }
1222
- const registryMcp = scopedManagedMcpDefs({ projectScope: false });
1223
- syncGlobalClaudeMcpServers(settings, registryMcp);
1224
1275
  if (!DRY_RUN) fs.writeFileSync(claudeSettingsPath, JSON.stringify(settings, null, 2) + "\n");
1225
1276
  }
1277
+
1278
+ // Sync user-scope MCP servers into ~/.claude.json's top-level `mcpServers`
1279
+ // — Claude Code does not read MCP server definitions from settings.json at
1280
+ // any scope (confirmed against code.claude.com/docs/en/mcp and live `claude
1281
+ // mcp list` repro, construct-ranh). Gated on the file already existing, same
1282
+ // as the hooks sync above: only write into Claude Code's own state file for
1283
+ // a user who has actually run the CLI at least once.
1284
+ const claudeUserConfigPath = path.join(home, ".claude.json");
1285
+ if (fs.existsSync(claudeUserConfigPath)) {
1286
+ const claudeUserConfig = JSON.parse(fs.readFileSync(claudeUserConfigPath, "utf8"));
1287
+ const registryMcp = scopedManagedMcpDefs({ projectScope: false });
1288
+ syncGlobalClaudeMcpServers(claudeUserConfig, registryMcp);
1289
+ if (!DRY_RUN) fs.writeFileSync(claudeUserConfigPath, JSON.stringify(claudeUserConfig, null, 2) + "\n");
1290
+ }
1226
1291
  }
1227
1292
  }
1228
1293
 
@@ -1484,6 +1549,86 @@ function getVSCodeUserMcpPaths() {
1484
1549
  .filter((file) => fs.existsSync(file));
1485
1550
  }
1486
1551
 
1552
+ // One comparator every host dialect (global Claude, project Claude, VS Code,
1553
+ // Cursor, OpenCode) calls instead of a bespoke preserve/refresh predicate per
1554
+ // surface — so the construct-mcp telemetry env passthrough and a pinned package
1555
+ // version converge identically everywhere rather than drifting per host.
1556
+ // needsRefresh() is the one question every dialect asks: does this host's
1557
+ // existing entry still match what the registry wants, across placeholder
1558
+ // resolution, transport, registry-declared env keys, pinned package versions,
1559
+ // and toolkit path. Callers
1560
+ // build the desired entry with their own host-specific builder (buildClaudeMcpEntry
1561
+ // / buildOpenCodeMcpEntry) first — both a Claude-shape entry (`args`/`env`) and an
1562
+ // OpenCode-shape entry (`command` array with the binary as element 0 / `environment`)
1563
+ // are normalized here so one comparator serves both shapes.
1564
+
1565
+ function entryArgs(entry) {
1566
+ if (Array.isArray(entry?.args)) return entry.args;
1567
+ if (Array.isArray(entry?.command)) return entry.command;
1568
+ return [];
1569
+ }
1570
+
1571
+ function entryEnv(entry) {
1572
+ return entry?.env ?? entry?.environment ?? {};
1573
+ }
1574
+
1575
+ function entryIsRemote(entry) {
1576
+ return entry?.type === "http" || entry?.type === "remote";
1577
+ }
1578
+
1579
+ // A registry env block that declares a key (e.g. the construct-mcp telemetry
1580
+ // passthrough) must be present on the host entry by name; the host-resolved
1581
+ // value differs per host (a literal, a `${VAR}`, a `{env:VAR}`) so only key
1582
+ // presence is compared. Absence — including "no env block at all" — means stale.
1583
+
1584
+ function envKeysMissing(existingEnv, desiredEnv) {
1585
+ const desiredKeys = Object.keys(desiredEnv ?? {});
1586
+ if (desiredKeys.length === 0) return false;
1587
+ const existingKeys = new Set(Object.keys(existingEnv ?? {}));
1588
+ return desiredKeys.some((key) => !existingKeys.has(key));
1589
+ }
1590
+
1591
+ // A version-pinned package arg (`@scope/name@version`, including the `@latest`
1592
+ // anti-pin) is compared as an exact string: if the registry's desired pin string
1593
+ // is not present verbatim anywhere in the existing args/command, the host is
1594
+ // carrying a different (or floating) version and must be refreshed. Unversioned
1595
+ // packages are not compared — pinning is opt-in per package via the catalog.
1596
+
1597
+ function argsVersionDiffers(existingArgs, desiredArgs) {
1598
+ const isPinned = (arg) => typeof arg === "string" && /^@[^/]+\/[^@]+@[\w.-]+$/.test(arg);
1599
+ const desiredPins = (desiredArgs ?? []).filter(isPinned);
1600
+ if (desiredPins.length === 0) return false;
1601
+ const existingPins = new Set((existingArgs ?? []).filter(isPinned));
1602
+ return desiredPins.some((pin) => !existingPins.has(pin));
1603
+ }
1604
+
1605
+ export function needsRefresh(existingEntry, desiredEntry, { root } = {}) {
1606
+ if (!existingEntry) return true;
1607
+ if (JSON.stringify(existingEntry).includes("__")) return true;
1608
+ if (!entryIsRemote(desiredEntry) && entryIsRemote(existingEntry)) return true;
1609
+ if (envKeysMissing(entryEnv(existingEntry), entryEnv(desiredEntry))) return true;
1610
+ if (argsVersionDiffers(entryArgs(existingEntry), entryArgs(desiredEntry))) return true;
1611
+ if (root && mcpEntryPointsOutsideToolkit({ args: entryArgs(existingEntry) }, root)) return true;
1612
+ if (desiredEntry?.cwd !== undefined && existingEntry.cwd !== desiredEntry.cwd) return true;
1613
+ return false;
1614
+ }
1615
+
1616
+ // VS Code's orchestration tools default their working directory to the SERVER
1617
+ // process's cwd, which is host-launch-dependent (construct-6y6w.9) — the same
1618
+ // tool call can read/write a different project depending on which host
1619
+ // launched the server and from where. VS Code resolves `${workspaceFolder}`
1620
+ // against the workspace that owns the `.vscode/mcp.json` the entry lives in,
1621
+ // so pinning it there removes the ambiguity for exactly the host that has a
1622
+ // single fixed workspace folder per config file. A remote/http entry has no
1623
+ // process cwd to pin and is left untouched.
1624
+
1625
+ const VSCODE_WORKSPACE_CWD = "${workspaceFolder}";
1626
+
1627
+ function withVscodeWorkspaceCwd(entry) {
1628
+ if (!entry || entry.type === "http" || entry.type === "remote") return entry;
1629
+ return { ...entry, cwd: VSCODE_WORKSPACE_CWD };
1630
+ }
1631
+
1487
1632
  // A merged mcp.json preserves existing entries so user customizations survive a
1488
1633
  // re-sync. A construct-owned server path is a fully-resolved, non-placeholder
1489
1634
  // path, so the preserve rule keeps it even when it points at a different toolkit
@@ -1491,8 +1636,13 @@ function getVSCodeUserMcpPaths() {
1491
1636
  // exist. Treat a construct toolkit path outside the current root as stale so the
1492
1637
  // sync refreshes it; user-owned servers carry no lib/mcp toolkit path and stay.
1493
1638
 
1639
+ // Claude/VS Code/Cursor entries carry the script path in `args`; OpenCode's local
1640
+ // entry shape (buildOpenCodeMcpEntry) instead carries `command: [bin, ...args]`.
1641
+ // Falling back to `command` when `args` is absent lets this one predicate serve
1642
+ // both shapes without OpenCode's caller having to reshape the entry first.
1643
+
1494
1644
  export function mcpEntryPointsOutsideToolkit(entry, root) {
1495
- const args = Array.isArray(entry?.args) ? entry.args : [];
1645
+ const args = Array.isArray(entry?.args) ? entry.args : (Array.isArray(entry?.command) ? entry.command : []);
1496
1646
  return args.some((arg) => {
1497
1647
  if (typeof arg !== "string") return false;
1498
1648
  const normalArg = arg.replace(/\\/g, "/");
@@ -1502,6 +1652,57 @@ export function mcpEntryPointsOutsideToolkit(entry, root) {
1502
1652
  });
1503
1653
  }
1504
1654
 
1655
+ // The desired-set loop only visits registryMcp ids, so a construct-managed entry
1656
+ // present in the host config but OUTSIDE the current sync set (an optional server
1657
+ // like `memory` the user opted into) is never revisited — a stale toolkit path in it
1658
+ // becomes immortal. This second pass rewrites those in place: the double guard (id is
1659
+ // construct-managed AND its path points outside the toolkit) never touches an
1660
+ // unmanaged/user entry, and rewriting to a path under root makes it idempotent (a
1661
+ // second run finds nothing stale). Rewrite, never delete, never seed — opt-in sticks.
1662
+
1663
+ // A memory entry can be stale without pointing outside the toolkit: pinned to the dead
1664
+ // legacy port, to a port other than the one currently allocated, or to a bridge script
1665
+ // path that does not exist (an old checkout). Any of these means the entry cannot reach
1666
+ // the running memory server — the split-brain — so it must be rewritten to the current
1667
+ // port and path.
1668
+
1669
+ function memoryEntryIsStale(entry) {
1670
+ if (!entry) return false;
1671
+ const want = String(memoryPort(process.env));
1672
+ const portOf = (s) => String(s || "").match(/:(\d+)\/?$/)?.[1];
1673
+ const bridgeUrlEnv = entry.env?.CONSTRUCT_MEMORY_BRIDGE_URL ?? entry.environment?.CONSTRUCT_MEMORY_BRIDGE_URL;
1674
+ const ports = [portOf(entry.url), portOf(bridgeUrlEnv)];
1675
+ const portStale = ports.some((p) => p && p !== want);
1676
+ const commandArgs = Array.isArray(entry.args) ? entry.args : (Array.isArray(entry.command) ? entry.command : []);
1677
+ const scriptArg = commandArgs.find((a) => typeof a === "string" && a.endsWith("memory-bridge.mjs"));
1678
+ const pathMissing = scriptArg ? !fs.existsSync(scriptArg) : false;
1679
+ return portStale || pathMissing;
1680
+ }
1681
+
1682
+ // OpenCode keys config.mcp by getOpenCodeMcpId(id), not the catalog id (identity
1683
+ // today, but the indirection exists for a future per-host rename) — so the host
1684
+ // config key this loop sees is not always the id `registryMcp`/`managedMcpDefs()`
1685
+ // know. `mapId` translates the host key back to the catalog id for the ownership
1686
+ // and sync-set checks; the write-back stays keyed on the original host key so no
1687
+ // entry is renamed, only rewritten in place.
1688
+
1689
+ export function reconcileStaleManagedEntries(configMap, { registryMcp, rebuildEntry, mapId = (key) => key }) {
1690
+ if (!configMap) return false;
1691
+ const managed = managedMcpDefs();
1692
+ let changed = false;
1693
+ for (const [key, entry] of Object.entries(configMap)) {
1694
+ const catalogId = mapId(key);
1695
+ if (catalogId in registryMcp) continue;
1696
+ const mcpDef = managed[catalogId];
1697
+ if (!mcpDef) continue;
1698
+ const stale = mcpEntryPointsOutsideToolkit(entry, root) || (catalogId === "memory" && memoryEntryIsStale(entry));
1699
+ if (!stale) continue;
1700
+ configMap[key] = rebuildEntry(catalogId, mcpDef);
1701
+ changed = true;
1702
+ }
1703
+ return changed;
1704
+ }
1705
+
1505
1706
  // Workspace defaults Construct manages for VS Code chat. `chat.agentFilesLocations`
1506
1707
  // pins the agent scan to `.github/agents` so the orchestrator does not list twice
1507
1708
  // (VS Code also scans `.claude/agents`, whose Claude tool names it ignores); its
@@ -1595,15 +1796,11 @@ function syncVSCode(targetDir = null, wants = true) {
1595
1796
  if (!config.servers) config.servers = {};
1596
1797
  for (const [id, mcpDef] of Object.entries(registryMcp)) {
1597
1798
  const existingEntry = config.servers[id];
1598
- const existing = JSON.stringify(existingEntry ?? "");
1599
- const hasPlaceholder = existing.includes("__");
1600
- const registryWantsCommand = !mcpDef.type && Array.isArray(mcpDef.args);
1601
- const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
1602
- const transportMismatch = registryWantsCommand && existingIsRemote;
1603
- const staleToolkitPath = mcpEntryPointsOutsideToolkit(existingEntry, root);
1604
- if (existingEntry && !hasPlaceholder && !transportMismatch && !staleToolkitPath) continue;
1605
- config.servers[id] = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
1799
+ const desiredEntry = withVscodeWorkspaceCwd(buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" }));
1800
+ if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
1801
+ config.servers[id] = desiredEntry;
1606
1802
  }
1803
+ reconcileStaleManagedEntries(config.servers, { registryMcp, rebuildEntry: (id, def) => withVscodeWorkspaceCwd(buildClaudeMcpEntry(id, def, process.env, { host: "vscode" })) });
1607
1804
  if (!DRY_RUN) {
1608
1805
  mkdirp(path.dirname(mcpPath));
1609
1806
  fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
@@ -1621,14 +1818,9 @@ function syncVSCode(targetDir = null, wants = true) {
1621
1818
  if (!config.servers) config.servers = {};
1622
1819
  for (const [id, mcpDef] of Object.entries(registryMcp)) {
1623
1820
  const existingEntry = config.servers[id];
1624
- const existing = JSON.stringify(existingEntry ?? "");
1625
- const hasPlaceholder = existing.includes("__");
1626
- const registryWantsCommand = !mcpDef.type && Array.isArray(mcpDef.args);
1627
- const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
1628
- const transportMismatch = registryWantsCommand && existingIsRemote;
1629
- const staleToolkitPath = mcpEntryPointsOutsideToolkit(existingEntry, root);
1630
- if (existingEntry && !hasPlaceholder && !transportMismatch && !staleToolkitPath) continue;
1631
- config.servers[id] = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
1821
+ const desiredEntry = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
1822
+ if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
1823
+ config.servers[id] = desiredEntry;
1632
1824
  }
1633
1825
  if (!DRY_RUN) fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + "\n");
1634
1826
  synced = true;
@@ -1692,14 +1884,11 @@ function syncCursor(targetDir = null, wants = true) {
1692
1884
  if (!config.mcpServers) config.mcpServers = {};
1693
1885
  for (const [id, mcpDef] of Object.entries(registryMcp)) {
1694
1886
  const existingEntry = config.mcpServers[id];
1695
- const existing = JSON.stringify(existingEntry ?? "");
1696
- const hasPlaceholder = existing.includes("__");
1697
- const registryWantsCommand = !mcpDef.type && Array.isArray(mcpDef.args);
1698
- const existingIsRemote = existingEntry && (existingEntry.type === 'http' || existingEntry.type === 'remote');
1699
- const transportMismatch = registryWantsCommand && existingIsRemote;
1700
- if (existingEntry && !hasPlaceholder && !transportMismatch) continue;
1701
- config.mcpServers[id] = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
1887
+ const desiredEntry = buildClaudeMcpEntry(id, mcpDef, process.env, { host: "vscode" });
1888
+ if (!needsRefresh(existingEntry, desiredEntry, { root })) continue;
1889
+ config.mcpServers[id] = desiredEntry;
1702
1890
  }
1891
+ reconcileStaleManagedEntries(config.mcpServers, { registryMcp, rebuildEntry: (id, def) => buildClaudeMcpEntry(id, def, process.env, { host: "vscode" }) });
1703
1892
  if (!DRY_RUN) {
1704
1893
  mkdirp(path.dirname(cursorMcpPath));
1705
1894
  fs.writeFileSync(cursorMcpPath, JSON.stringify(config, null, 2) + "\n");
@@ -1971,18 +2160,32 @@ function syncOpencode(entries, targetDir = null, wants = true) {
1971
2160
  // any remote/http memory entry must be rewritten when the registry
1972
2161
  // defines a command-based bridge.
1973
2162
  const existingEntry = config.mcp[openCodeId];
1974
- const registryWantsCommand = !mcpDef.type && Array.isArray(mcpDef.args);
1975
- const existingIsRemote = existingEntry && (existingEntry.type === 'remote' || existingEntry.type === 'http');
1976
- const transportMismatch = registryWantsCommand && existingIsRemote;
2163
+ const desiredEntry = buildOpenCodeMcpEntry(id, mcpDef, process.env).entry;
1977
2164
 
1978
- const existing = JSON.stringify(existingEntry ?? "");
1979
- const hasPlaceholder = existing.includes("__");
2165
+ // The registry's own args can carry an unresolved `__NAME__` template (a
2166
+ // secret var not yet set in this environment); needsRefresh only inspects
2167
+ // built entries, so a still-templated registry def is checked separately
2168
+ // and always wins a refresh once the var resolves.
1980
2169
  const argsHaveTemplates = (mcpDef.args ?? []).some((a) => typeof a === 'string' && a.includes('__'));
1981
- if (!existingEntry || hasPlaceholder || argsHaveTemplates || transportMismatch) {
1982
- config.mcp[openCodeId] = buildOpenCodeMcpEntry(id, mcpDef, process.env).entry;
2170
+ if (argsHaveTemplates || needsRefresh(existingEntry, desiredEntry, { root })) {
2171
+ config.mcp[openCodeId] = desiredEntry;
1983
2172
  }
1984
2173
  }
1985
2174
 
2175
+ // The sync-set loop above only visits `registryMcp` ids, so a managed-but-optional
2176
+ // entry (e.g. `memory` in project scope) with a stale toolkit path is never
2177
+ // revisited here either — the same immortal-entry gap fixed for VS Code/Cursor/
2178
+ // Claude project (construct-6y6w.1). `getOpenCodeMcpId` keys config.mcp by the
2179
+ // host id rather than the catalog id, so map it back before checking ownership.
2180
+ const catalogIdForOpenCodeKey = new Map(
2181
+ Object.keys(managedMcpDefs()).map((catalogId) => [getOpenCodeMcpId(catalogId), catalogId]),
2182
+ );
2183
+ reconcileStaleManagedEntries(config.mcp, {
2184
+ registryMcp,
2185
+ mapId: (key) => catalogIdForOpenCodeKey.get(key) ?? key,
2186
+ rebuildEntry: (catalogId, def) => buildOpenCodeMcpEntry(catalogId, def, process.env).entry,
2187
+ });
2188
+
1986
2189
  // Heavy external MCP servers serialize a measured ~37k tokens of tool schema
1987
2190
  // into EVERY agent's request — github ~30k alone (fixtures 2026-06-22) —
1988
2191
  // agent's request — including the built-in Build/Plan agents the per-agent
@@ -2060,7 +2263,7 @@ function syncOpencode(entries, targetDir = null, wants = true) {
2060
2263
  };
2061
2264
  }
2062
2265
 
2063
- // Hybrid split (aider architect/editor). When the fast tier is a LOCAL model, emit a
2266
+ // Hybrid split (cheap local editor, capable architect). When the fast tier is a LOCAL model, emit a
2064
2267
  // narrow `construct-local` editor: it does bounded edits on a cheap local model and hands
2065
2268
  // planning/reasoning back to `construct` (the architect, which stays on the user's chosen
2066
2269
  // model — we never pin it). The editor's model is NOT the generic fast-tier default
@@ -2123,17 +2326,17 @@ function syncOpencode(entries, targetDir = null, wants = true) {
2123
2326
  delete config.model;
2124
2327
  }
2125
2328
 
2126
- // OpenCode's built-in helper agents drive session naming, summaries, and
2127
- // compaction. Keep them on a stronger auxiliary model so those surfaces do not
2128
- // inherit the main chat model's selection history or a low-end coder default.
2129
- const auxiliaryModel = resolvedModels.reasoning || resolvedModels.standard || resolvedModels.fast;
2130
- if (auxiliaryModel) {
2131
- for (const key of ["title", "summary", "compaction"]) {
2132
- config.agent[key] = {
2133
- ...(config.agent[key] && typeof config.agent[key] === "object" ? config.agent[key] : {}),
2134
- model: auxiliaryModel,
2135
- };
2136
- }
2329
+ // OpenCode's built-in helper agents (session naming, summaries, compaction)
2330
+ // must follow the host's live routing an absolute model pin here made
2331
+ // compaction call a provider the user had no key/credits for, failing hard
2332
+ // even though a working model was selected for the session. Keep the entries
2333
+ // present (hosts and tests rely on them existing) but strip any model pin a
2334
+ // previous sync wrote, whether the old weak coder default or a strong tier
2335
+ // pin, so OpenCode falls back to its own session-model/small_model routing.
2336
+ for (const key of ["title", "summary", "compaction"]) {
2337
+ const existing = config.agent[key] && typeof config.agent[key] === "object" ? config.agent[key] : {};
2338
+ delete existing.model;
2339
+ config.agent[key] = existing;
2137
2340
  }
2138
2341
 
2139
2342
  writeOpenCodeConfig(config, configPath);
@@ -21,7 +21,7 @@ Use when: an architectural decision is made that affects the system structure, d
21
21
  2. **Write to `docs/decisions/adr/ADR-{NNN}-{slug}.md`** using the template from `get_template("adr")`: resolves `.cx/templates/docs/adr.md` (override) then `templates/docs/adr.md` (shipped)
22
22
  - NNN = next sequential number (check existing files)
23
23
  3. **Also write a shorter entry to `.cx/decisions/`** for session context
24
- 4. **cx-docs-keeper** updates `.cx/context.md` Architecture Notes with a one-line summary and link
24
+ 4. **cx-operations** updates `.cx/context.md` Architecture Notes with a one-line summary and link
25
25
 
26
26
  ## File naming
27
27
  - `docs/decisions/adr/ADR-001-use-postgres-over-mysql.md`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: docs-codebase-research-workflow
3
- description: "Use when: cx-explorer maps the repo — entry points, dependencies, hot paths, or unfamiliar subsystems."
3
+ description: "Use when: cx-researcher maps the repo — entry points, dependencies, hot paths, or unfamiliar subsystems."
4
4
  inputs: [repository-path]
5
5
  artifactType: research-brief
6
6
  toneDefault: pedagogical
@@ -9,7 +9,7 @@ verificationBar: "Every load-bearing claim cites a verifiable primary source; la
9
9
  ---
10
10
  # Codebase Research Workflow
11
11
 
12
- Use when: cx-explorer investigates **this repository** — structure, dependencies, behavior. Not for external vendor research or user interviews.
12
+ Use when: cx-researcher investigates **this repository** — structure, dependencies, behavior. Not for external vendor research or user interviews.
13
13
 
14
14
  Call `get_skill("roles/researcher")` and `get_skill("exploration/repo-map")` before deep dives.
15
15
 
@@ -37,7 +37,7 @@ Call `get_skill("roles/researcher")` and `get_skill("exploration/repo-map")` bef
37
37
 
38
38
  - Every architectural claim traceable to file:line.
39
39
  - Unknown paths marked `[unverified]` until read.
40
- - cx-explorer must **not** answer product prioritization or user preference questions.
40
+ - cx-researcher must **not** answer product prioritization or user preference questions.
41
41
  ## Release gate
42
42
 
43
43
  Run `construct artifact validate <path> --type=<type>` before marking the artifact approved.