@geraldmaron/construct 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -1,9 +1,19 @@
1
1
  /**
2
2
  * lib/runtime/uv-bootstrap.mjs — idempotent uv + docling venv provisioner.
3
3
  *
4
- * On first use: downloads uv via the official Astral installer, creates
5
- * `<rootDir>/.cx/runtime/docling/.venv`, installs the pinned docling release.
6
- * Subsequent calls are no-ops if the venv is already present.
4
+ * One venv per machine, not one per project (construct-rf26.16; ADR-0066
5
+ * called this out as the docling venv's terminal shape). On first use:
6
+ * downloads uv via the official Astral installer, creates
7
+ * `~/.construct/runtime/docling/.venv` (or `$CX_TOOLKIT_DIR/runtime/docling/.venv`)
8
+ * via `resolveSharedRuntimeDir`, installs the pinned docling release.
9
+ * Subsequent calls from any project on this machine are no-ops once the
10
+ * venv is present — there is no project key anywhere in this path.
11
+ *
12
+ * Version pin is the lockfile: `.install-marker.json`'s `doclingVersion`
13
+ * field is compared against DOCLING_PIN on every call. A mismatch (a
14
+ * construct upgrade that bumps the pin) is not silent drift — it clears the
15
+ * stale venv directory and re-provisions from scratch under `force`
16
+ * semantics, rather than layering a new install over an old one.
7
17
  *
8
18
  * Best-practice notes (2026-06):
9
19
  * - uv (Astral) is the consensus Python tooling, ~10× faster than pip, single
@@ -16,19 +26,25 @@
16
26
  * Returns the path to the venv's python binary so callers can spawn the
17
27
  * sidecar without relying on PATH state.
18
28
  */
19
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
29
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
20
30
  import { spawn, spawnSync } from 'node:child_process';
21
31
  import path from 'node:path';
22
32
  import os from 'node:os';
23
33
 
34
+ import { resolveSharedRuntimeDir } from '../state-root.mjs';
35
+
24
36
  const DOCLING_PIN = '2.45.0';
25
37
  const UV_INSTALL_URL = 'https://astral.sh/uv/install.sh';
26
38
  const UV_TIMEOUT_MS = 120_000;
27
39
  const VENV_TIMEOUT_MS = 240_000;
28
40
  const INSTALL_TIMEOUT_MS = 600_000;
29
41
 
30
- export function defaultRuntimeDir(cwd = process.cwd()) {
31
- return path.join(cwd, '.cx', 'runtime', 'docling');
42
+ // ensureDir:false shared by the provisioner (ensureDoclingVenv, which
43
+ // already mkdirs before it writes) and the read-only detector
44
+ // (describeDoclingRuntime); a detect-only call must not conjure the dir.
45
+
46
+ export function defaultRuntimeDir() {
47
+ return resolveSharedRuntimeDir('docling', { ensureDir: false });
32
48
  }
33
49
 
34
50
  function which(bin) {
@@ -127,6 +143,16 @@ export async function ensureDoclingVenv({ runtimeDir = defaultRuntimeDir(), forc
127
143
  return { pythonBin: existingPython, venvDir, runtimeDir, fresh: false };
128
144
  }
129
145
 
146
+ // A version-pin mismatch (existingMarker present but doclingVersion !== DOCLING_PIN)
147
+ // is a controlled re-provision, not silent drift: clear the stale venv outright
148
+ // rather than trust `uv venv` to reconcile an existing directory left by a
149
+ // different docling/Python pin.
150
+
151
+ if (existingMarker && existingMarker.doclingVersion !== DOCLING_PIN) {
152
+ progress(`Pinned docling version changed (${existingMarker.doclingVersion} → ${DOCLING_PIN}) — re-provisioning.`);
153
+ try { rmSync(venvDir, { recursive: true, force: true }); } catch { /* best effort */ }
154
+ }
155
+
130
156
  progress('Provisioning the docling document extractor (first run). This downloads a Python runtime and ML dependencies and can take several minutes; later runs are instant.');
131
157
  const uv = await ensureUv(runtimeDir);
132
158
 
@@ -12,8 +12,9 @@
12
12
  *
13
13
  * Model files are downloaded directly from Hugging Face the first time the
14
14
  * default `base.en` (or env-overridden) model is requested. Models cache at
15
- * <runtimeDir>/models/. base.en is ~150MB; smaller than the small.en or
16
- * medium.en model that callers can opt into via CONSTRUCT_WHISPER_MODEL.
15
+ * <stateRoot>/runtime/whisper/models/ (ADR-0066: machine-scoped, not
16
+ * project-local). base.en is ~150MB; smaller than the small.en or medium.en
17
+ * model that callers can opt into via CONSTRUCT_WHISPER_MODEL.
17
18
  *
18
19
  * No bundled distribution: whisper.cpp upstream does not publish stable
19
20
  * pre-built CLI binaries across platforms, and building from source via
@@ -25,12 +26,19 @@ import { pipeline } from 'node:stream/promises';
25
26
  import path from 'node:path';
26
27
  import os from 'node:os';
27
28
 
29
+ import { resolveStateDir } from '../state-root.mjs';
30
+
28
31
  const HF_MODEL_BASE_URL = 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main';
29
32
  const DEFAULT_MODEL = process.env.CONSTRUCT_WHISPER_MODEL || 'base.en';
30
33
  const KNOWN_MODELS = new Set(['tiny', 'tiny.en', 'base', 'base.en', 'small', 'small.en', 'medium', 'medium.en', 'large-v3', 'large-v3-turbo']);
31
34
 
35
+ // ensureDir:false — shared by the provisioner (ensureWhisperRuntime, which
36
+ // already mkdirs before it writes) and the read-only detector
37
+ // (describeWhisperRuntime/locateWhisperBinary); a detect-only call must not
38
+ // conjure the dir.
39
+
32
40
  export function defaultRuntimeDir(cwd = process.cwd()) {
33
- return path.join(cwd, '.cx', 'runtime', 'whisper');
41
+ return resolveStateDir(cwd, 'runtime', 'whisper', { ensureDir: false });
34
42
  }
35
43
 
36
44
  function which(bin) {
@@ -1,8 +1,11 @@
1
1
  /**
2
2
  * lib/runtime-env.mjs — Construct runtime environment with credential bootstrap.
3
3
  *
4
- * prepareConstructEnv merges ~/.config/construct/config.env into the process environment.
5
- * Credential auto-link from 1Password runs only when autoLink:true (setup-credentials).
4
+ * prepareConstructEnv merges the user's ~/.config/construct/config.env and,
5
+ * when the caller passes `rootDir` (the resolved project root, not the
6
+ * toolkit install checkout), the project .env into the process environment —
7
+ * project .env wins. Credential auto-link from 1Password runs only when
8
+ * autoLink:true (setup-credentials).
6
9
  */
7
10
 
8
11
  import { loadConstructEnv } from './env-config.mjs';
package/lib/sandbox.mjs CHANGED
@@ -55,7 +55,7 @@ export function createSandbox({ profile = null, seedContext = null } = {}) {
55
55
  fs.writeFileSync(path.join(root, '.cx', 'context.md'), ctx);
56
56
 
57
57
  const cfg = { version: 1, alias: 'Construct', deployment: { mode: 'solo' } };
58
- if (profile) cfg.profile = profile;
58
+ if (profile) cfg.scope = profile;
59
59
  fs.writeFileSync(path.join(root, 'construct.config.json'), JSON.stringify(cfg, null, 2) + '\n');
60
60
 
61
61
  spawnSync('git', ['init', '--quiet'], { cwd: root, stdio: 'ignore' });
@@ -8,11 +8,15 @@
8
8
  *
9
9
  * Jobs registered here: 'tag-candidate-mining', 'skill-usage-rollup', 'doc-hygiene-scan'
10
10
  *
11
- * Deployment mode is determined by getDeploymentMode(env), which inspects env vars.
11
+ * Deployment mode is resolved through the canonical lib/deployment-mode.mjs
12
+ * getDeploymentMode (CONSTRUCT_DEPLOYMENT_MODE env, then project config, then solo),
13
+ * so the scheduler agrees with the broker, session prelude, and status surfaces.
12
14
  * Registration is in-memory: no persistence across process restarts beyond the
13
15
  * native trigger files written by solo.mjs or the DB rows in 009_scheduler.sql.
14
16
  */
15
17
 
18
+ import { getDeploymentMode } from '../deployment-mode.mjs';
19
+
16
20
  // ---------------------------------------------------------------------------
17
21
  // In-memory job registry
18
22
  // ---------------------------------------------------------------------------
@@ -57,22 +61,6 @@ export function listJobs() {
57
61
  }));
58
62
  }
59
63
 
60
- /**
61
- * Derives the deployment mode from environment variables.
62
- * Returns 'enterprise' | 'team' | 'solo'.
63
- *
64
- * CONSTRUCT_DEPLOYMENT=enterprise or CONSTRUCT_ENTERPRISE=1 => enterprise
65
- * CONSTRUCT_DEPLOYMENT=team or CONSTRUCT_TEAM=1 => team
66
- * Otherwise => solo
67
- */
68
- export function getDeploymentMode(env = process.env) {
69
- const explicit = env.CONSTRUCT_DEPLOYMENT;
70
- if (explicit === 'enterprise' || env.CONSTRUCT_ENTERPRISE === '1') return 'enterprise';
71
- if (explicit === 'team' || env.CONSTRUCT_TEAM === '1') return 'team';
72
- if (explicit === 'solo') return 'solo';
73
- return 'solo';
74
- }
75
-
76
64
  // ---------------------------------------------------------------------------
77
65
  // Built-in job registrations
78
66
  // ---------------------------------------------------------------------------
@@ -104,8 +92,8 @@ registerJob({
104
92
  const DOC_HYGIENE_SOLO_CRON = '0 2 * * *';
105
93
  const DOC_HYGIENE_TEAM_CRON = '0 * * * *';
106
94
 
107
- export function resolveDocHygieneSchedule(env = process.env) {
108
- const mode = getDeploymentMode(env);
95
+ export function resolveDocHygieneSchedule(env = process.env, { cwd } = {}) {
96
+ const mode = getDeploymentMode(env, { cwd });
109
97
  const isTeamish = mode === 'team' || mode === 'enterprise';
110
98
  return {
111
99
  mode: isTeamish ? 'team' : 'solo',
@@ -121,7 +109,7 @@ registerJob({
121
109
  mode: docHygiene.mode,
122
110
  handler: async ({ cwd, env }) => {
123
111
  const { findHygieneCandidates } = await import('../hygiene/scan.mjs');
124
- const { limit } = resolveDocHygieneSchedule(env);
112
+ const { limit } = resolveDocHygieneSchedule(env, { cwd });
125
113
  const candidates = findHygieneCandidates({ cwd, limit });
126
114
  return {
127
115
  status: 'ok',
@@ -25,6 +25,7 @@ import { extractDocumentTextNodeNative, extractDocumentTextAsync } from './docum
25
25
  import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig } from './model-router.mjs';
26
26
  import { getUserEnvPath } from './env-config.mjs';
27
27
  import { pollFreeModels, topForTier } from './model-free-selector.mjs';
28
+ import { extractOpRef, resolveOpRef, resolveSecret } from './providers/secret-resolver.mjs';
28
29
  import { execSync } from 'node:child_process';
29
30
 
30
31
  // ---------------------------------------------------------------------------
@@ -187,9 +188,37 @@ async function fetchWithTimeout(url, options) {
187
188
  }
188
189
  }
189
190
 
191
+ function resolveSecretOrNull(varName, { env = process.env, allowAmbient = true } = {}) {
192
+ try {
193
+ return resolveSecret(varName, { env, allowAmbient });
194
+ } catch {
195
+ return null;
196
+ }
197
+ }
198
+
199
+ function materializeConfiguredKey(rawValue) {
200
+ if (typeof rawValue !== 'string' || rawValue.length === 0) return null;
201
+ const ref = extractOpRef(rawValue);
202
+ try {
203
+ return ref ? resolveOpRef(ref) : rawValue;
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+
209
+ function resolveOpenRouterApiKey() {
210
+ const envKey = resolveSecretOrNull('OPENROUTER_API_KEY');
211
+ if (envKey) return envKey;
212
+ try {
213
+ return materializeConfiguredKey(readOpenRouterApiKeyFromOpenCodeConfig());
214
+ } catch {
215
+ return null;
216
+ }
217
+ }
218
+
190
219
  async function callModel(userContent, { traceId } = {}) {
191
- const orKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
192
- const anthropicKey = process.env.ANTHROPIC_API_KEY;
220
+ const orKey = resolveOpenRouterApiKey();
221
+ const anthropicKey = resolveSecretOrNull('ANTHROPIC_API_KEY');
193
222
 
194
223
  if (!orKey && !anthropicKey) {
195
224
  throw new Error('No API key found. Set ANTHROPIC_API_KEY or OPENROUTER_API_KEY to use schema inference.');
@@ -10,7 +10,11 @@
10
10
  *
11
11
  * Lifecycle stages:
12
12
  * draft - in `.cx/scopes/draft-<id>/` with requirements.md + scope.json
13
- * active - in `specialists/org/scopes/<id>.json` (curated) or `.cx/scope.json` (custom)
13
+ * active - in `specialists/org/scopes/<id>.json` (curated), or a named
14
+ * custom profile under the construct-rf26.13 config-layer tiers
15
+ * (`~/.construct/org/scopes/<id>.json` or
16
+ * `<project>/.cx/org/scopes/<id>.json`), or the single-file
17
+ * `.cx/scope.json` escape hatch (custom, anonymous)
14
18
  * archived - in `archive/scopes/<id>/` with the final state and an
15
19
  * archive-note.md explaining why it was retired
16
20
  *
@@ -41,23 +45,22 @@ function archiveDir(cwd, id) {
41
45
 
42
46
  const REQUIREMENTS_BRIEF = (id, displayName) => `# Scope requirements: ${displayName} (${id})
43
47
 
44
- This brief is a discovery and design contract for a new Construct scope. Methodology lives in \`docs/guides/concepts/persona-research.md\` and lifecycle in \`docs/guides/concepts/scope-lifecycle.md\`. Treat each section as a task to dispatch to the named specialist. A draft scope is not ready to promote until every section has evidence backing it. The aim is research-grounded, not opinion-grounded.
48
+ This brief is a discovery and design contract for a new Construct scope. Methodology lives in \`docs/guides/concepts/persona-research.md\` and lifecycle in \`docs/guides/concepts/profile-lifecycle.md\`. Treat each section as a task to dispatch to the named specialist. A draft scope is not ready to promote until every section has evidence backing it. The aim is research-grounded, not opinion-grounded.
45
49
 
46
50
  Specialists used here are the existing Construct cx-* personas (already in the registry). They are dispatched the standard way; this doc just names which questions each one owns.
47
51
 
52
+ A scope selects flows and skill emphasis over the shared, fixed 12-role core roster (\`specialists/org/specialists/\`) — it does not invent new roles or departments. If this org genuinely needs a role absent from that roster, author it as a custom specialist via the construct-rf26.13 config layer (\`construct specialist create <id> --custom\`, docs/guides/cookbook/custom-specialists-and-teams.md) rather than declaring it inline here.
53
+
48
54
  This draft scaffolding produces:
49
- - \`scope.json\` — the structural definition (departments, roles, intake)
55
+ - \`scope.json\` — the structural definition (intake taxonomy, doc templates, skill emphasis)
50
56
  - \`requirements.md\` — this brief
51
- - \`personas/<role>.md\` — one persona artifact per role (filled during the discover phase)
52
- - \`departments/<dept>.md\` — one charter doc per department (filled during the frame phase)
53
-
54
- The persona and department files start as templates. Filling them from evidence is the actual work; the JSON is just a manifest.
57
+ - \`personas/<role>.md\` — optional, only when \`seedRoles\` names a role genuinely absent from the core roster (rare; most orgs route entirely through the existing 12)
55
58
 
56
- ## 1. Discovery (cx-ux-researcher)
59
+ ## 1. Discovery (cx-researcher)
57
60
 
58
61
  Goal: characterize the people, the work, and the outputs of this org type.
59
62
 
60
- - Who are the typical roles in this org? Name 4 to 8 with one-line responsibilities.
63
+ - Which of the 12 core roles (architect, reviewer, engineer, debugger, qa, security, operations, product-manager, data-analyst, designer, researcher, orchestrator) are primary for this org, and which are rarely if ever needed?
61
64
  - What is the dominant work loop? Describe in 5 to 8 stages.
62
65
  - What are the recurring signals that enter the loop (briefs, bug reports, customer messages, etc.)?
63
66
  - What are the canonical output artifacts (campaigns, runbooks, papers, etc.)?
@@ -67,23 +70,23 @@ Goal: characterize the people, the work, and the outputs of this org type.
67
70
 
68
71
  Goal: turn discovery into an intake taxonomy and a stage sequence.
69
72
 
70
- - Propose intake types (max 24). Each must be: distinct, observable, and routable to a primary owner.
73
+ - Propose intake types (max 24). Each must be: distinct, observable, and routable to a primary owner among the 12 core roles.
71
74
  - Propose stages (max 12). Order matters; each stage must answer "what changed?" not just "what happened next?".
72
- - For each intake type, name the primary owner role and the recommended chain (max 3 hops).
75
+ - For each intake type, name the primary owner role (one of the 12) and the recommended chain (max 3 hops).
73
76
  - For each stage, name the dominant artifact produced.
74
77
  - Acceptance: a representative real signal classifies into a single intake type with confidence > 0.6.
75
78
 
76
- ## 3. Architecture (cx-architect)
79
+ ## 3. Skill emphasis (cx-architect)
77
80
 
78
- Goal: define the role set and how it interlocks with the curated cx-* registry.
81
+ Goal: pick the flows and skill bundles this profile should emphasize from the shared registry — not a new role set.
79
82
 
80
- - List role ids the scope will reference (max 80, grouped into max 12 departments with max 20 roles per department). For each, indicate one of:
81
- reuse-existing (name the cx-* agent), create-new (specify scope), or compose-overlay (which base role + which flavor).
82
- - Identify cross-role handoffs. Where can ambiguity stall the loop? Name the orchestrator role.
83
- - Validate against the per-role cap (max 6 flavors per role per scope).
84
- - Acceptance: every declared role either exists in specialists/org or has a written scope statement.
83
+ - For each doc template the profile ships, confirm a docTemplates entry exists and name its owning role.
84
+ - List the \`defaultSkills\` (skill ids under \`skills/\`) this org's work loop leans on most — these become the profile's baseline skill entitlement (lib/skills/router.mjs), on top of whatever an individual specialist already carries.
85
+ - Set \`researchProfiles\` per intake type (external, user, codebase, market, compliance) and \`toneDefaults\` per doc template.
86
+ - Name the orchestrator's role in this loop (routing/dispatch is always cx-orchestrator; this just confirms nothing here bypasses it).
87
+ - Acceptance: every \`defaultSkills\` entry resolves to a real file under \`skills/\`; every doc template maps to an owning role among the 12.
85
88
 
86
- ## 4. Validation (cx-evaluator)
89
+ ## 4. Validation (cx-reviewer)
87
90
 
88
91
  Goal: prove the draft works on real signals before promotion.
89
92
 
@@ -93,13 +96,14 @@ Goal: prove the draft works on real signals before promotion.
93
96
 
94
97
  ## 5. Promotion (operator decision)
95
98
 
96
- Goal: move the draft into the active catalog.
99
+ Goal: move the draft into the active catalog, or leave it as a durable custom profile.
97
100
 
98
101
  - Curated path: hand-edit \`specialists/org/scopes/${id}.json\` from this draft, open a PR, run \`npm run lint:scopes\`.
99
- - Custom path: copy the draft scope.json to \`<project>/.cx/scope.json\` with \`"custom": true\`; \`construct scope set\` for switching between curated.
100
- - Either path requires the validation acceptance criteria above to be met.
102
+ - Custom path (named, reusable): copy the draft scope.json to \`<project>/.cx/org/scopes/${id}.json\` (project tier, git-tracked) or \`~/.construct/org/scopes/${id}.json\` (user tier, shared across projects) — the same builtin -> user -> project precedence construct-rf26.13 gives custom specialists and teams. Then \`construct scope set ${id}\` switches to it exactly like a curated scope.
103
+ - Custom path (anonymous, one-off): copy the draft scope.json to \`<project>/.cx/scope.json\` with \`"custom": true\` — the pre-rf26.13 escape hatch, still supported for a single ad hoc override.
104
+ - Any path requires the validation acceptance criteria above to be met.
101
105
 
102
- ## 6. Health monitoring (cx-evaluator + cx-trace-reviewer)
106
+ ## 6. Health monitoring (cx-reviewer)
103
107
 
104
108
  Goal: keep the scope honest after it ships.
105
109
 
@@ -107,7 +111,7 @@ Goal: keep the scope honest after it ships.
107
111
  - Any role with success-rate < 0.5 across 10+ runs triggers a review: is the role wrong, the prompt wrong, or the routing wrong?
108
112
  - Health data is the input for future scope revisions; never edit a scope without a health report first.
109
113
 
110
- ## 7. Archive (cx-docs-keeper + operator)
114
+ ## 7. Archive (cx-operations + operator)
111
115
 
112
116
  Goal: retire a scope cleanly without losing the learning.
113
117
 
@@ -397,7 +401,7 @@ export function scopeHealth(cwd, scopeId, { windowDays = 30 } = {}) {
397
401
 
398
402
  return {
399
403
  scope: scopeId,
400
- scopeExists: !!loadScope(scopeId),
404
+ scopeExists: !!loadScope(scopeId, { cwd }),
401
405
  windowDays,
402
406
  observationCount,
403
407
  roles: roleSummary,
@@ -1,12 +1,23 @@
1
1
  /**
2
2
  * lib/scopes/loader.mjs — Work-scope loader (intake lens + org enrichment).
3
3
  *
4
- * Curated scopes live in specialists/org/scopes/<id>.json. Custom overrides
5
- * use <cwd>/.cx/scope.json with custom: true. construct.config.json scope
6
- * field selects the active scope id (default rnd).
4
+ * Curated scopes live in specialists/org/scopes/<id>.json. Named custom
5
+ * scopes reuse the builtin -> user -> project precedence construct-rf26.13
6
+ * established for specialists/teams (lib/registry/assemble.mjs): an id also
7
+ * resolves from ~/.construct/org/scopes/<id>.json (shared across every
8
+ * project on the machine) and <cwd>/.cx/org/scopes/<id>.json (git-tracked,
9
+ * highest precedence), instead of a scope-specific mechanism. A later tier's
10
+ * fields shallow-merge over an earlier tier's on id collision.
7
11
  *
8
- * Teams and roles are not authored in scope files enrich.mjs attaches them
9
- * from specialists/org at load time.
12
+ * The single-file <cwd>/.cx/scope.json escape hatch (custom: true) still
13
+ * works for an anonymous, one-off override — it predates rf26.13 and stays
14
+ * for the "just this project, no id to remember" case — but a named, reusable
15
+ * custom profile should live under the tiered org/scopes/ layer so it can be
16
+ * switched to with `construct scope set <id>` like a curated one.
17
+ *
18
+ * construct.config.json's scope field selects the active curated/custom id
19
+ * (default rnd). Teams and roles are not authored in scope files — enrich.mjs
20
+ * attaches them from specialists/org at load time.
10
21
  */
11
22
 
12
23
  import { existsSync, readFileSync, readdirSync } from 'node:fs';
@@ -14,6 +25,7 @@ import { dirname, join, resolve } from 'node:path';
14
25
  import { fileURLToPath } from 'node:url';
15
26
 
16
27
  import { enrichScope } from './enrich.mjs';
28
+ import { homeDir } from '../paths.mjs';
17
29
 
18
30
  const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
19
31
  const REPO_ROOT = resolve(MODULE_DIR, '..', '..');
@@ -21,18 +33,43 @@ const SCOPES_DIR = join(REPO_ROOT, 'specialists', 'org', 'scopes');
21
33
 
22
34
  export const DEFAULT_SCOPE_ID = 'rnd';
23
35
 
24
- export function loadScope(id, opts = {}) {
25
- if (!id || typeof id !== 'string') return null;
26
- const path = join(SCOPES_DIR, `${id}.json`);
27
- if (!existsSync(path)) return null;
36
+ function userScopesDir() {
37
+ return join(homeDir(), '.construct', 'org', 'scopes');
38
+ }
39
+
40
+ function projectScopesDir(cwd) {
41
+ return join(cwd, '.cx', 'org', 'scopes');
42
+ }
43
+
44
+ function readScopeTier(dir, id) {
45
+ const p = join(dir, `${id}.json`);
46
+ if (!existsSync(p)) return null;
28
47
  try {
29
- const raw = JSON.parse(readFileSync(path, 'utf8'));
30
- return enrichScope(raw, opts);
48
+ return JSON.parse(readFileSync(p, 'utf8'));
31
49
  } catch {
32
50
  return null;
33
51
  }
34
52
  }
35
53
 
54
+ function resolveScopeRaw(id, cwd) {
55
+ const tiers = [SCOPES_DIR, userScopesDir(), projectScopesDir(cwd)];
56
+ let merged = null;
57
+ for (const dir of tiers) {
58
+ const raw = readScopeTier(dir, id);
59
+ if (!raw) continue;
60
+ merged = merged ? { ...merged, ...raw } : raw;
61
+ }
62
+ return merged;
63
+ }
64
+
65
+ export function loadScope(id, opts = {}) {
66
+ if (!id || typeof id !== 'string') return null;
67
+ const cwd = opts.cwd || process.cwd();
68
+ const raw = resolveScopeRaw(id, cwd);
69
+ if (!raw) return null;
70
+ return enrichScope(raw, opts);
71
+ }
72
+
36
73
  export function listScopes() {
37
74
  if (!existsSync(SCOPES_DIR)) return [];
38
75
  return readdirSync(SCOPES_DIR)
@@ -64,7 +101,7 @@ export function loadCustomScope(cwd) {
64
101
 
65
102
  export function resolveActiveScope(cwd, configScopeId = null) {
66
103
  const rootDir = REPO_ROOT;
67
- const enrichOpts = { rootDir };
104
+ const enrichOpts = { rootDir, cwd };
68
105
  if (configScopeId) {
69
106
  const s = loadScope(configScopeId, enrichOpts);
70
107
  if (s) return s;
@@ -10,7 +10,7 @@ const OPERATIONS_OBJECTIVE_RULES = [
10
10
  {
11
11
  teamId: 'operations-team',
12
12
  focus: 'reliability',
13
- attachTo: ['cx-sre', 'cx-docs-keeper'],
13
+ attachTo: ['cx-operations'],
14
14
  pattern: /\b(incident|outage|deploy|deployment|rollback|reliability|runbook|postmortem|sre|on-?call)\b/i,
15
15
  },
16
16
  {
@@ -0,0 +1,80 @@
1
+ /**
2
+ * lib/security/ingest-boundary.mjs — Source-kind → trust-level mapping at the
3
+ * ingest boundary.
4
+ *
5
+ * Maps named source kinds (provider type, pipeline stage, ingestion method)
6
+ * to canonical trust levels and stamps them onto records using the primitives
7
+ * in lib/security/trust.mjs.
8
+ *
9
+ * The module is additive — existing ingest, embed, or storage code is not modified.
10
+ * Callers that want labeling import `stampIngestBoundary` and
11
+ * apply it at the point where external content enters Construct's data layer.
12
+ *
13
+ * References: CX-AUDIT-LLMSEC-001, construct-9oi4.14.1
14
+ */
15
+
16
+ import { TRUST_LEVELS, stampTrust } from './trust.mjs';
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Source-kind registry
20
+ // ---------------------------------------------------------------------------
21
+
22
+ /**
23
+ * Mapping from well-known source kind strings to trust levels.
24
+ * All entries are lowercase; callers should normalise before lookup.
25
+ */
26
+ const SOURCE_KIND_MAP = {
27
+ // Built-in / internal
28
+ 'builtin-prompt': TRUST_LEVELS.TRUSTED_INTERNAL,
29
+ 'validated-pack': TRUST_LEVELS.TRUSTED_INTERNAL,
30
+
31
+ // Team-authored local content
32
+ 'team-authored': TRUST_LEVELS.TEAM_AUTHORED,
33
+ 'local-committed': TRUST_LEVELS.TEAM_AUTHORED,
34
+
35
+ // Authenticated external providers
36
+ 'github-issue': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
37
+ 'github-pr': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
38
+ 'github-comment': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
39
+ 'jira-ticket': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
40
+ 'jira-comment': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
41
+ 'confluence-page': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
42
+ 'linear-issue': TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
43
+
44
+ // Unauthenticated / parsed / scraped content
45
+ 'docling-parsed': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
46
+ 'web-fetched': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
47
+ 'web-search-result': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
48
+ 'pdf-extracted': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
49
+ 'unknown': TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
50
+ };
51
+
52
+ /**
53
+ * Resolve the trust level for a given source kind string.
54
+ * Unrecognised kinds default to EXTERNAL_UNAUTHENTICATED (fail-safe).
55
+ *
56
+ * @param {string} sourceKind
57
+ * @returns {string} TRUST_LEVELS value
58
+ */
59
+ export function resolveTrustLevel(sourceKind) {
60
+ const key = (sourceKind ?? 'unknown').toLowerCase();
61
+ return SOURCE_KIND_MAP[key] ?? TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED;
62
+ }
63
+
64
+ /**
65
+ * Stamp a trust label onto a record at the ingest boundary.
66
+ *
67
+ * Maps `sourceKind` to the appropriate trust level and delegates to
68
+ * `stampTrust`. The resulting record has a `_trust` field that downstream
69
+ * consumers (recall wrappers, context assemblers) can inspect.
70
+ *
71
+ * @param {Record<string, unknown>} record The record being ingested.
72
+ * @param {string} sourceKind Well-known source kind string.
73
+ * @param {{ sourceRef?: string }} [options] Optional extra metadata.
74
+ * @returns {Record<string, unknown>} Stamped record (source record not mutated).
75
+ */
76
+ export function stampIngestBoundary(record, sourceKind, options = {}) {
77
+ const level = resolveTrustLevel(sourceKind);
78
+ const source = options.sourceRef ?? sourceKind ?? 'unknown';
79
+ return stampTrust(record, level, source);
80
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * lib/security/recall-wrapper.mjs — Trust-aware context assembly for recalled
3
+ * records.
4
+ *
5
+ * Before a set of recalled observations or documents is assembled into model
6
+ * context, callers pass them through `wrapForContextAssembly`. Records
7
+ * stamped as external (authenticated or unauthenticated) are wrapped with
8
+ * explicit untrusted delimiters. Internal and team-authored records pass
9
+ * through unchanged. Unstamped records are warned and treated as the most
10
+ * restrictive level: EXTERNAL_UNAUTHENTICATED.
11
+ *
12
+ * The module is additive — existing memory, recall, or context assembly code is not modified.
13
+ * Integration is left to follow-on beads (N2, N4).
14
+ *
15
+ * References: CX-AUDIT-LLMSEC-001, construct-9oi4.14.1
16
+ */
17
+
18
+ import { TRUST_LEVELS, recallTrustGrade, stampTrust, wrapUntrusted } from './trust.mjs';
19
+
20
+ // Trust levels that require the untrusted-delimiter treatment.
21
+ const UNTRUSTED_LEVELS = new Set([
22
+ TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
23
+ TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
24
+ ]);
25
+
26
+ /**
27
+ * Wrap a single recalled record for safe context assembly.
28
+ *
29
+ * - TRUSTED_INTERNAL / TEAM_AUTHORED → returned as-is.
30
+ * - EXTERNAL_AUTHENTICATED / EXTERNAL_UNAUTHENTICATED → content wrapped with
31
+ * `[UNTRUSTED:…]` delimiters in both the `content` field (if present) and
32
+ * as a `_wrappedContent` convenience field.
33
+ * - No `_trust` stamp → warned, treated as EXTERNAL_UNAUTHENTICATED.
34
+ *
35
+ * @param {Record<string, unknown>} record A recalled observation/document.
36
+ * @param {{ warn?: (msg: string) => void }} [opts]
37
+ * @returns {Record<string, unknown>} Wrapped or passthrough record.
38
+ */
39
+ export function wrapRecordForContext(record, opts = {}) {
40
+ const warn = opts.warn ?? ((msg) => console.warn('[recall-wrapper]', msg));
41
+ let trustMeta = recallTrustGrade(record);
42
+
43
+ if (trustMeta === null) {
44
+ warn(
45
+ `Recalled record has no _trust stamp — treating as ${TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED}. ` +
46
+ `Record keys: ${Object.keys(record ?? {}).join(', ') || '(empty)'}`,
47
+ );
48
+ // Re-stamp defensively so downstream consumers always see _trust.
49
+ const stamped = stampTrust(
50
+ record,
51
+ TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
52
+ 'unstamped-recall',
53
+ );
54
+ trustMeta = stamped._trust;
55
+ record = stamped;
56
+ }
57
+
58
+ if (!UNTRUSTED_LEVELS.has(trustMeta.level)) {
59
+ // TRUSTED_INTERNAL or TEAM_AUTHORED — pass through unchanged.
60
+ return record;
61
+ }
62
+
63
+ // External content: wrap any present content field and attach _wrappedContent.
64
+ const rawContent = record.content ?? record.text ?? record.body ?? null;
65
+ const wrapped = rawContent != null
66
+ ? wrapUntrusted(String(rawContent), trustMeta)
67
+ : null;
68
+
69
+ return {
70
+ ...record,
71
+ ...(wrapped != null ? { _wrappedContent: wrapped } : {}),
72
+ };
73
+ }
74
+
75
+ /**
76
+ * Wrap an array of recalled records for safe context assembly.
77
+ *
78
+ * Iterates each record through `wrapRecordForContext`. The returned array
79
+ * preserves original order. Non-object entries are returned as-is with a
80
+ * warning (defensive).
81
+ *
82
+ * @param {unknown[]} records Array of recalled observations or documents.
83
+ * @param {{ warn?: (msg: string) => void }} [opts]
84
+ * @returns {unknown[]} Array of wrapped/passthrough records.
85
+ */
86
+ export function wrapForContextAssembly(records, opts = {}) {
87
+ if (!Array.isArray(records)) {
88
+ throw new TypeError('wrapForContextAssembly: records must be an array');
89
+ }
90
+ const warn = opts.warn ?? ((msg) => console.warn('[recall-wrapper]', msg));
91
+
92
+ return records.map((record, idx) => {
93
+ if (record === null || typeof record !== 'object') {
94
+ warn(`Record at index ${idx} is not an object — skipping wrap (type: ${typeof record})`);
95
+ return record;
96
+ }
97
+ return wrapRecordForContext(record, { warn });
98
+ });
99
+ }