@geraldmaron/construct 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -0,0 +1,132 @@
1
+ /**
2
+ * lib/security/trust.mjs — Trust taxonomy and stamping primitives.
3
+ *
4
+ * Provides a machine-checkable trust hierarchy for all content that flows
5
+ * through Construct: ingested documents, recalled observations, provider
6
+ * responses, and built-in prompts. Callers stamp records at ingestion time
7
+ * and downstream consumers check the stamp before assembling model context.
8
+ *
9
+ * Integration into callers (ingest/embed/storage paths) is handled by
10
+ * follow-on beads (N2, N4). This module is additive — it exports utilities
11
+ * only; no existing code is modified.
12
+ *
13
+ * References: CX-AUDIT-LLMSEC-001, OWASP LLM01 [S12][S13], construct-9oi4.14.1
14
+ */
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Trust level taxonomy (ordered lowest → highest trust)
18
+ // ---------------------------------------------------------------------------
19
+
20
+ /** @type {Record<string, string>} */
21
+ export const TRUST_LEVELS = {
22
+ /** Scraped or parsed content with no authorship verification (web pages,
23
+ * docling-extracted text, unknown origin). Highest risk of injection. */
24
+ EXTERNAL_UNAUTHENTICATED: 'external-unauthenticated',
25
+
26
+ /** Content from an authenticated external actor: a known GitHub user's
27
+ * issue-tracker comment, a named Jira reporter, a Confluence page with
28
+ * attributed authorship. Lower risk but still untrusted. */
29
+ EXTERNAL_AUTHENTICATED: 'external-authenticated',
30
+
31
+ /** Authored by a member of the Construct team or project (e.g. a local
32
+ * markdown file committed by the team). Trusted but not built-in. */
33
+ TEAM_AUTHORED: 'team-authored',
34
+
35
+ /** Built-in prompts, validated skill packs, and internal system content.
36
+ * Full trust — assembled directly into the instruction channel. */
37
+ TRUSTED_INTERNAL: 'trusted-internal',
38
+ };
39
+
40
+ // Ordered list for comparison (index = rank, higher index = higher trust).
41
+ const LEVEL_ORDER = [
42
+ TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED,
43
+ TRUST_LEVELS.EXTERNAL_AUTHENTICATED,
44
+ TRUST_LEVELS.TEAM_AUTHORED,
45
+ TRUST_LEVELS.TRUSTED_INTERNAL,
46
+ ];
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Stamping
50
+ // ---------------------------------------------------------------------------
51
+
52
+ /**
53
+ * Stamp a trust label onto an observation or record object.
54
+ *
55
+ * @param {Record<string, unknown>} record Source object (not mutated).
56
+ * @param {string} level One of TRUST_LEVELS values.
57
+ * @param {string} source Human-readable source descriptor.
58
+ * @returns {Record<string, unknown>} New object with `_trust` metadata added.
59
+ */
60
+ export function stampTrust(record, level, source) {
61
+ if (!Object.values(TRUST_LEVELS).includes(level)) {
62
+ throw new TypeError(
63
+ `stampTrust: unknown trust level "${level}". ` +
64
+ `Valid values: ${Object.values(TRUST_LEVELS).join(', ')}`,
65
+ );
66
+ }
67
+ return {
68
+ ...record,
69
+ _trust: {
70
+ level,
71
+ source: String(source ?? 'unknown'),
72
+ stampedAt: Date.now(),
73
+ },
74
+ };
75
+ }
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // Level comparison
79
+ // ---------------------------------------------------------------------------
80
+
81
+ /**
82
+ * Return true if `level` meets or exceeds `required` in the trust hierarchy.
83
+ *
84
+ * @param {string} level The trust level of the content being checked.
85
+ * @param {string} required The minimum acceptable trust level.
86
+ * @returns {boolean}
87
+ */
88
+ export function meetsMinTrustLevel(level, required) {
89
+ const levelIdx = LEVEL_ORDER.indexOf(level);
90
+ const requiredIdx = LEVEL_ORDER.indexOf(required);
91
+ if (levelIdx === -1 || requiredIdx === -1) return false;
92
+ return levelIdx >= requiredIdx;
93
+ }
94
+
95
+ // ---------------------------------------------------------------------------
96
+ // Context assembly helpers
97
+ // ---------------------------------------------------------------------------
98
+
99
+ /**
100
+ * Wrap external content with explicit untrusted delimiters so the model
101
+ * context assembler can visually and programmatically distinguish data from
102
+ * instructions.
103
+ *
104
+ * @param {string} content Raw text content.
105
+ * @param {{ level: string, source: string }} trustMeta Trust metadata.
106
+ * @returns {string} Delimited content string.
107
+ */
108
+ export function wrapUntrusted(content, trustMeta) {
109
+ const level = trustMeta?.level ?? TRUST_LEVELS.EXTERNAL_UNAUTHENTICATED;
110
+ const source = trustMeta?.source ?? 'unknown';
111
+ return `[UNTRUSTED:${level}:${source}]\n${content}\n[/UNTRUSTED]`;
112
+ }
113
+
114
+ // ---------------------------------------------------------------------------
115
+ // Recall re-grading
116
+ // ---------------------------------------------------------------------------
117
+
118
+ /**
119
+ * Re-grade a recalled record — returns the `_trust` meta object if present,
120
+ * or null if the record was stored without a trust stamp.
121
+ *
122
+ * Callers should treat a null return as EXTERNAL_UNAUTHENTICATED per policy.
123
+ *
124
+ * @param {Record<string, unknown>} record A recalled observation or document.
125
+ * @returns {{ level: string, source: string, stampedAt: number } | null}
126
+ */
127
+ export function recallTrustGrade(record) {
128
+ if (record && typeof record._trust === 'object' && record._trust !== null) {
129
+ return record._trust;
130
+ }
131
+ return null;
132
+ }
@@ -6,13 +6,14 @@
6
6
  * spawns whatever is available and returns a result per service.
7
7
  */
8
8
  import { spawn, spawnSync } from 'node:child_process';
9
+ import { randomBytes } from 'node:crypto';
9
10
  import fs from 'node:fs';
10
11
  import net from 'node:net';
11
12
  import os from 'node:os';
12
13
  import path from 'node:path';
13
14
  import { fileURLToPath } from 'node:url';
14
15
 
15
- import { findAvailablePort } from './host-capabilities.mjs';
16
+ import { findAvailablePort, detectActiveSessions } from './host-capabilities.mjs';
16
17
  import { wrapWithOpRun } from './providers/op-run.mjs';
17
18
  import { getUserEnvPath, loadConstructEnv, parseEnvFile, writeEnvValues } from './env-config.mjs';
18
19
  import { runPressureRelease } from './runtime-pressure.mjs';
@@ -22,6 +23,7 @@ import { readHeartbeat } from './daemons/contract.mjs';
22
23
  import { heartbeatPath } from './oracle/index.mjs';
23
24
  import { stateDir } from './config/xdg.mjs';
24
25
  import { doctorRoot } from './config/xdg.mjs';
26
+ import { resolveStateDir } from './state-root.mjs';
25
27
 
26
28
  const INSTALL_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
27
29
 
@@ -163,13 +165,19 @@ async function isCopilotBridgeRunning(port) {
163
165
  return probeRuntimePort(port);
164
166
  }
165
167
 
166
- // Opt-in 1Password resolution (ADR-0049 Design A): each long-lived service launches
167
- // under `op run --env-file -- <cmd>` so op:// references resolve once at this stable
168
- // parent and every child inherits resolved provider keys. wrapWithOpRun returns the
169
- // command unchanged when CONSTRUCT_OP_ENV_FILE is unset, the file is missing, or `op`
170
- // is absent, so the un-opted path is byte-for-byte the prior behavior. The env that
171
- // carries CONSTRUCT_OP_ENV_FILE is options.env when a site sets one (doctor, oracle)
172
- // and process.env otherwise (cm, opencode, copilot bridge).
168
+ // Opt-in 1Password resolution (ADR-0049 Design A): under `construct dev` the process
169
+ // re-execs once under a single `op run --env-file` (maybeReExecUnderOpRun), so op://
170
+ // references resolve one time at that stable parent and every daemon here inherits the
171
+ // resolved keys through liveEnv the CONSTRUCT_OP_RUN_ACTIVE sentinel then makes
172
+ // wrapWithOpRun a no-op so no daemon nests a second op run. wrapWithOpRun stays the
173
+ // fallback for daemons started outside that parent (an opencode/copilot-bridge restart
174
+ // from a hook), wrapping them individually. It returns the command unchanged when
175
+ // CONSTRUCT_OP_ENV_FILE is unset, the file is missing, `op` is absent, or the sentinel
176
+ // is set, so the un-opted path is byte-for-byte the prior behavior. doctor, oracle, and
177
+ // cm consume no provider API keys (construct-0wmj) and are spawned unwrapped; every
178
+ // service is still spawned with the merged liveEnv, so config.env-only vars (including
179
+ // CONSTRUCT_OP_ENV_FILE and an OP_SERVICE_ACCOUNT_TOKEN) reach the child rather than
180
+ // only the wrapped op-run subset.
173
181
 
174
182
  function wrapServiceSpawn(command, args, homeDir, options = {}) {
175
183
  const env = options.env || process.env;
@@ -225,9 +233,9 @@ export function startDoctor({ rootDir, homeDir = os.homedir() } = {}) {
225
233
  if (existing) return { started: false, reused: true, pid: existing.pid };
226
234
  const indexPath = path.join(INSTALL_ROOT, 'lib', 'doctor', 'index.mjs');
227
235
  if (!fs.existsSync(indexPath)) return { started: false, reason: 'missing-binary' };
236
+ // doctor consumes no provider API keys (construct-0wmj) — no op-run wrap needed.
228
237
  const doctorEnv = { ...process.env };
229
- const launch = wrapServiceSpawn('node', [indexPath], homeDir, { env: doctorEnv });
230
- const { logPath } = spawnDetached(launch.command, launch.args, homeDir, 'doctor.log', { cwd: rootDir, env: doctorEnv });
238
+ const { logPath } = spawnDetached('node', [indexPath], homeDir, 'doctor.log', { cwd: rootDir, env: doctorEnv });
231
239
  return { started: true, logPath };
232
240
  }
233
241
 
@@ -244,8 +252,8 @@ export function startOracle({ rootDir, homeDir = os.homedir(), projectDir = root
244
252
  CONSTRUCT_ORACLE_ROOT: rootDir,
245
253
  CONSTRUCT_ORACLE_PROJECT: projectDir,
246
254
  };
247
- const launch = wrapServiceSpawn('node', [entry], homeDir, { env: oracleEnv });
248
- const { logPath } = spawnDetached(launch.command, launch.args, homeDir, 'oracle-daemon.log', {
255
+ // oracle consumes no provider API keys (construct-0wmj) no op-run wrap needed.
256
+ const { logPath } = spawnDetached('node', [entry], homeDir, 'oracle-daemon.log', {
249
257
  cwd: projectDir,
250
258
  env: oracleEnv,
251
259
  });
@@ -338,6 +346,17 @@ export async function startServices({
338
346
  const operatorKeepAlive = resolveOllamaKeepAlive(liveEnv);
339
347
  if (operatorKeepAlive) liveEnv.OLLAMA_KEEP_ALIVE = operatorKeepAlive;
340
348
 
349
+ // The Copilot bridge fronts the user's Copilot entitlement over loopback HTTP, so it
350
+ // requires a bearer token. Generate a stable token once, persist it to the 0600
351
+ // config.env, and set it on liveEnv before OpenCode (the only caller) spawns so both
352
+ // the bridge and OpenCode inherit it; writeOpenCodeConfig wires it as an {env:} auth
353
+ // header. Reusing the persisted value keeps a restarted bridge and OpenCode in sync.
354
+ if (wants('copilot-bridge') && support.gh && detectActiveSessions().includes('github-copilot') && !liveEnv.CONSTRUCT_COPILOT_BRIDGE_TOKEN) {
355
+ const copilotBridgeToken = randomBytes(24).toString('hex');
356
+ liveEnv.CONSTRUCT_COPILOT_BRIDGE_TOKEN = copilotBridgeToken;
357
+ writeEnvValues(envPath, { CONSTRUCT_COPILOT_BRIDGE_TOKEN: copilotBridgeToken });
358
+ }
359
+
341
360
  const pressureReport = runPressureReleaseFn({ env: { ...liveEnv, HOME: homeDir } });
342
361
  if (pressureReport?.killed?.length) {
343
362
  results.push({
@@ -354,14 +373,14 @@ export async function startServices({
354
373
  if (traceBackend === 'local') {
355
374
  results.push({
356
375
  name: 'Telemetry',
357
- url: path.join(rootDir, '.cx', 'traces'),
376
+ url: resolveStateDir(rootDir, 'traces', { ensureDir: false }),
358
377
  status: 'configured',
359
378
  note: 'local JSONL traces; remote export not configured',
360
379
  });
361
380
  } else if (traceBackend === 'none') {
362
381
  results.push({
363
382
  name: 'Telemetry',
364
- url: path.join(rootDir, '.cx', 'traces'),
383
+ url: resolveStateDir(rootDir, 'traces', { ensureDir: false }),
365
384
  status: 'configured',
366
385
  note: 'remote export disabled; local JSONL traces preserved',
367
386
  });
@@ -390,8 +409,17 @@ export async function startServices({
390
409
  if (await memoryProbeFn(ports.memory)) {
391
410
  results.push({ name: 'Memory (cm)', url: `http://127.0.0.1:${ports.memory}`, status: 'reused' });
392
411
  } else {
393
- const cm = wrapServiceSpawn('cm', ['serve', '--port', String(ports.memory)], homeDir, { env: liveEnv });
394
- spawnDetachedFn(cm.command, cm.args, homeDir, 'cm.log');
412
+ // cm consumes no provider API keys (construct-0wmj) no op-run wrap needed.
413
+ const cmSpawn = spawnDetachedFn('cm', ['serve', '--port', String(ports.memory)], homeDir, 'cm.log', { env: liveEnv });
414
+ try {
415
+ if (cmSpawn?.child?.pid) {
416
+ writeJson(portOwnershipPath(ports.memory, homeDir), {
417
+ pid: cmSpawn.child.pid, command: `cm serve --port ${ports.memory}`,
418
+ cwd: process.cwd(), marker: 'construct', constructManaged: true,
419
+ startedAt: new Date().toISOString(),
420
+ });
421
+ }
422
+ } catch { /* ownership record is advisory */ }
395
423
  results.push({ name: 'Memory (cm)', url: `http://127.0.0.1:${ports.memory}`, status: 'started' });
396
424
  }
397
425
  } else if (wants('memory')) {
@@ -404,7 +432,16 @@ export async function startServices({
404
432
  results.push({ name: 'OpenCode', url: `http://127.0.0.1:${ports.bridge}`, status: 'reused' });
405
433
  } else {
406
434
  const opencode = wrapServiceSpawn('opencode', ['serve', '--port', String(ports.bridge)], homeDir, { env: liveEnv });
407
- spawnDetachedFn(opencode.command, opencode.args, homeDir, 'opencode.log');
435
+ const ocSpawn = spawnDetachedFn(opencode.command, opencode.args, homeDir, 'opencode.log', { env: liveEnv });
436
+ try {
437
+ if (ocSpawn?.child?.pid) {
438
+ writeJson(portOwnershipPath(ports.bridge, homeDir), {
439
+ pid: ocSpawn.child.pid, command: `${opencode.command} ${opencode.args.join(' ')}`,
440
+ cwd: process.cwd(), marker: 'construct', constructManaged: true,
441
+ startedAt: new Date().toISOString(),
442
+ });
443
+ }
444
+ } catch { /* ownership record is advisory */ }
408
445
  results.push({ name: 'OpenCode', url: `http://127.0.0.1:${ports.bridge}`, status: 'started' });
409
446
  }
410
447
  }
@@ -420,7 +457,16 @@ export async function startServices({
420
457
  const proxyPath = path.join(INSTALL_ROOT, 'lib', 'bridges', 'copilot-proxy.mjs');
421
458
  if (fs.existsSync(proxyPath)) {
422
459
  const copilot = wrapServiceSpawn('node', [proxyPath, '--port', String(ports.copilotBridge)], homeDir, { env: liveEnv });
423
- spawnDetachedFn(copilot.command, copilot.args, homeDir, 'copilot-bridge.log');
460
+ const copilotSpawn = spawnDetachedFn(copilot.command, copilot.args, homeDir, 'copilot-bridge.log', { env: liveEnv });
461
+ try {
462
+ if (copilotSpawn?.child?.pid) {
463
+ writeJson(portOwnershipPath(ports.copilotBridge, homeDir), {
464
+ pid: copilotSpawn.child.pid, command: `${copilot.command} ${copilot.args.join(' ')}`,
465
+ cwd: process.cwd(), marker: 'construct', constructManaged: true,
466
+ startedAt: new Date().toISOString(),
467
+ });
468
+ }
469
+ } catch { /* ownership record is advisory */ }
424
470
  results.push({ name: 'Copilot Bridge', url: `http://127.0.0.1:${ports.copilotBridge}`, status: 'started' });
425
471
  } else {
426
472
  results.push({ name: 'Copilot Bridge', status: 'unavailable', note: 'copilot-proxy binary missing in lib/bridges/' });
@@ -476,16 +522,39 @@ async function emitServiceFailures(results) {
476
522
  } catch { /* best effort */ }
477
523
  }
478
524
 
479
- function killPortOwners(port, spawnSyncFn = spawnSync) {
525
+ // Ownership record path for a port. startServices writes one when it spawns a service;
526
+ // killPortOwners reads it before sending SIGTERM so a foreign process that happens to
527
+ // occupy a configured port is never signalled.
528
+
529
+ function portOwnershipPath(port, homeDir) {
530
+ return path.join(runtimeStateDir(homeDir), `port-${port}.json`);
531
+ }
532
+
533
+ // Predicate: does a process record represent a Construct-owned service? A record is
534
+ // owned when it carries a Construct env/marker set at spawn time. A foreign process or
535
+ // a record from before the ownership contract was introduced is not owned.
536
+
537
+ export function isConstructOwnedPort(processRecord, { port } = {}) {
538
+ if (!processRecord || typeof processRecord !== 'object') return false;
539
+ return Boolean(processRecord.marker === 'construct' || processRecord.constructManaged === true);
540
+ }
541
+
542
+ function killPortOwners(port, spawnSyncFn = spawnSync, homeDir = os.homedir()) {
480
543
  if (!port || !Number.isInteger(port) || port <= 0) return false;
481
544
  try {
482
545
  const result = spawnSyncFn('lsof', ['-t', `-i:${port}`], { encoding: 'utf8' });
483
546
  const pids = (result.stdout || '').trim().split(/\s+/).filter(Boolean).map(Number).filter((n) => n > 0);
484
547
  if (pids.length === 0) return false;
548
+
549
+ // Only signal a PID if we have a durable Construct ownership record for this port
550
+ // AND it names this exact PID. A foreign owner gets reported, not killed.
551
+ const ownerRecord = readJson(portOwnershipPath(port, homeDir));
552
+ let killed = false;
485
553
  for (const pid of pids) {
486
- try { process.kill(pid, 'SIGTERM'); } catch { /* already gone */ }
554
+ if (!ownerRecord || ownerRecord.pid !== pid || !isConstructOwnedPort(ownerRecord)) continue;
555
+ try { process.kill(pid, 'SIGTERM'); killed = true; } catch { /* already gone */ }
487
556
  }
488
- return true;
557
+ return killed;
489
558
  } catch {
490
559
  return false;
491
560
  }
@@ -507,15 +576,15 @@ export async function stopServices({
507
576
  const envPath = getUserEnvPath(homeDir);
508
577
  const envValues = parseEnvFile(envPath);
509
578
  const memoryPort = Number(envValues.MEMORY_PORT) || derivedMemoryPort();
510
- const cmKilled = killPortOwners(memoryPort, spawnSyncFn);
579
+ const cmKilled = killPortOwners(memoryPort, spawnSyncFn, homeDir);
511
580
  results.push({ name: 'Memory (cm)', status: cmKilled ? 'stopped' : 'not-running' });
512
581
 
513
582
  const bridgePort = Number(envValues.BRIDGE_PORT) || 5173;
514
- const openCodeKilled = killPortOwners(bridgePort, spawnSyncFn);
583
+ const openCodeKilled = killPortOwners(bridgePort, spawnSyncFn, homeDir);
515
584
  results.push({ name: 'OpenCode', status: openCodeKilled ? 'stopped' : 'not-running' });
516
585
 
517
586
  const copilotBridgePort = Number(envValues.COPILOT_BRIDGE_PORT) || 5174;
518
- const copilotBridgeKilled = killPortOwners(copilotBridgePort, spawnSyncFn);
587
+ const copilotBridgeKilled = killPortOwners(copilotBridgePort, spawnSyncFn, homeDir);
519
588
  results.push({ name: 'Copilot Bridge', status: copilotBridgeKilled ? 'stopped' : 'not-running' });
520
589
 
521
590
  const stopped = results.filter((r) => r.status === 'stopped' || r.status === 'cleaned').map((r) => r.name);
package/lib/setup.mjs CHANGED
@@ -2,10 +2,14 @@
2
2
  /**
3
3
  * lib/setup.mjs — Machine-scoped first-run setup wizard for Construct.
4
4
  *
5
- * Installs cm/cass, configures managed defaults, pre-warms the embedding model,
6
- * wires MCP integrations, and writes the XDG user config (~/.config/construct/config.env).
7
- * Invoked by `construct install` and by lib/install/first-invocation.mjs
8
- * when a TTY user is missing resources.
5
+ * Installs cm/cass, configures managed defaults, wires MCP integrations, and
6
+ * writes the XDG user config (~/.config/construct/config.env). Invoked by
7
+ * `construct install` and by lib/install/first-invocation.mjs when a TTY
8
+ * user is missing resources.
9
+ *
10
+ * The embedding model and LanceDB vector store provision lazily by default
11
+ * (construct-rf26.17) — pass --with-embeddings to warm both up during
12
+ * install instead of at first semantic-search use, mirroring --with-docling.
9
13
  *
10
14
  * Project-scoped scaffolding (`.cx/`, AGENTS.md, plan.md, adapters) lives
11
15
  * in lib/init-unified.mjs — invoked by `construct init`. These two flows
@@ -35,6 +39,7 @@ import { getEmbeddingModelInfo, warmupEmbeddingModel } from './storage/embedding
35
39
  import { buildPressureGuardValues, installPressureGuardLaunchAgent, loadPressureGuardLaunchAgent } from './runtime-pressure.mjs';
36
40
  import { consentToInstall } from './setup-prompts.mjs';
37
41
  import { cleanupLegacyGlobalConstruct } from './install/legacy-global-cleanup.mjs';
42
+ import { isMainModule } from './roots.mjs';
38
43
 
39
44
  const ROOT_DIR = path.resolve(import.meta.dirname, '..');
40
45
  const HOME = os.homedir();
@@ -59,6 +64,9 @@ Flags:
59
64
  --with-docling eagerly provision the docling document-extraction venv now
60
65
  (heavy, ~10 min via uv; otherwise provisioned lazily on
61
66
  first document ingest)
67
+ --with-embeddings warm the embedding model + LanceDB vector store dir now
68
+ (loads from local cache only, offline-safe; otherwise
69
+ both provision lazily on first semantic-search use)
62
70
  --cleanup-legacy-global
63
71
  remove legacy user-global Construct adapters/MCP/history
64
72
  before setup; does not uninstall a global construct CLI
@@ -330,7 +338,7 @@ function runLegacyGlobalCleanup({ homeDir, dryRun }) {
330
338
  // network call, or service registration that the real run would perform — and
331
339
  // returns before touching disk so the preview is provably side-effect-free.
332
340
 
333
- function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent, cleanupLegacyGlobal }) {
341
+ function printInstallPlan({ scope, homeDir, withDocling, withEmbeddings, noLaunchAgent, cleanupLegacyGlobal }) {
334
342
  console.log('Construct install — dry-run (no changes written)');
335
343
  console.log('────────────────────────────────────────────────');
336
344
  console.log(`Scope: ${scope}`);
@@ -350,11 +358,11 @@ function printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent, cleanupL
350
358
  console.log(` · ${getCanonicalOpenCodeConfigPath(homeDir)} (OpenCode config)`);
351
359
  console.log(` · ${path.join(configDir(homeDir), 'lib')} → ${path.join(ROOT_DIR, 'lib')} (hook lib symlink)`);
352
360
  console.log(` · ${path.join(stateDir(homeDir), 'workspace')} (workspace docs scaffold)`);
353
- console.log(` · ${path.dirname(defaultVectorIndexPath(homeDir))} (LanceDB vector store dir)`);
361
+ if (withEmbeddings) console.log(` · ${path.dirname(defaultVectorIndexPath(homeDir))} (LanceDB vector store dir)`);
354
362
  console.log('\nWould check / install runtime tooling:');
355
363
  console.log(' · cm (memory CLI) and cass (session search) when available on PATH');
356
- console.log('\nWould reach the network / external services:');
357
- console.log(' · embedding model warmup (loads from local cache; offline-safe, no download)');
364
+ if (withEmbeddings || withDocling) console.log('\nWould reach the network / external services:');
365
+ if (withEmbeddings) console.log(' · embedding model warmup (loads from local cache; offline-safe, no download)');
358
366
  if (withDocling) console.log(' · docling Python venv provisioning via uv (~10 min)');
359
367
  console.log(' · construct sync --quiet, construct sync --global, construct doctor');
360
368
  if (process.platform === 'darwin' && !noLaunchAgent) {
@@ -375,9 +383,10 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
375
383
  return;
376
384
  }
377
385
 
378
- const KNOWN_FLAGS = new Set(['--yes', '--dry-run', '--no-launch-agent', '--reconfigure', '--with-docling', '--cleanup-legacy-global', '--help', '-h']);
386
+ const KNOWN_FLAGS = new Set(['--yes', '--dry-run', '--no-launch-agent', '--reconfigure', '--with-docling', '--with-embeddings', '--cleanup-legacy-global', '--help', '-h']);
379
387
  const dryRun = argSet.has('--dry-run');
380
388
  const withDocling = argSet.has('--with-docling');
389
+ const withEmbeddings = argSet.has('--with-embeddings');
381
390
  const unknownFlags = args.filter((a) => {
382
391
  if (!a.startsWith('-')) return false;
383
392
  if (a.startsWith('--scope=') || a === '--scope') return false;
@@ -402,7 +411,7 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
402
411
  printProjectScopeGuidance();
403
412
  return;
404
413
  }
405
- printInstallPlan({ scope, homeDir, withDocling, noLaunchAgent: argSet.has('--no-launch-agent'), cleanupLegacyGlobal });
414
+ printInstallPlan({ scope, homeDir, withDocling, withEmbeddings, noLaunchAgent: argSet.has('--no-launch-agent'), cleanupLegacyGlobal });
406
415
  return;
407
416
  }
408
417
 
@@ -470,20 +479,29 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
470
479
 
471
480
  const telemetryResult = process.env.CONSTRUCT_TELEMETRY_URL
472
481
  ? { status: 'configured', note: `remote export configured (${process.env.CONSTRUCT_TELEMETRY_URL})` }
473
- : { status: 'local', note: 'local JSONL traces in .cx/traces; remote export optional' };
482
+ : { status: 'local', note: 'local JSONL traces in the machine-scoped state root; remote export optional' };
474
483
  console.log(`Telemetry: ${telemetryResult.note}`);
475
484
 
476
- fs.mkdirSync(path.dirname(defaultVectorIndexPath(homeDir)), { recursive: true });
477
-
478
- try {
479
- const warm = await warmupEmbeddingModel({ env: process.env });
480
- if (warm.degraded) {
481
- console.log(`Embeddings: degraded fallback (${warm.fallbackReason || 'model unavailable'}) semantic search will use the hashing backend`);
482
- } else {
483
- console.log(`Embeddings: ${warm.model} ready (${warm.dimensions}d, warmed in ${warm.durationMs}ms)`);
485
+ // The embedding model and LanceDB vector store dir are provisioned lazily
486
+ // by default (construct-rf26.17): a project that never runs semantic
487
+ // search never pays for the ONNX model cache or an index directory. Both
488
+ // provision on first real use (`construct ingest`, first observation
489
+ // store/search) exactly like docling below. --with-embeddings opts into
490
+ // warming both up now instead of at first query.
491
+ if (withEmbeddings) {
492
+ fs.mkdirSync(path.dirname(defaultVectorIndexPath(homeDir)), { recursive: true });
493
+ try {
494
+ const warm = await warmupEmbeddingModel({ env: process.env });
495
+ if (warm.degraded) {
496
+ console.log(`Embeddings: degraded fallback (${warm.fallbackReason || 'model unavailable'}) — semantic search will use the hashing backend`);
497
+ } else {
498
+ console.log(`Embeddings: ${warm.model} ready (${warm.dimensions}d, warmed in ${warm.durationMs}ms)`);
499
+ }
500
+ } catch (err) {
501
+ console.log(`Embeddings: warmup skipped (${err?.message || 'unknown error'}) — model will load on first use`);
484
502
  }
485
- } catch (err) {
486
- console.log(`Embeddings: warmup skipped (${err?.message || 'unknown error'}) model will load on first use`);
503
+ } else {
504
+ console.log('Embeddings: will provision on first semantic-search use (pass --with-embeddings to warm up now)');
487
505
  }
488
506
 
489
507
  // Docling (document/PDF extraction) provisions a pinned Python venv via uv,
@@ -592,7 +610,7 @@ export async function runSetup({ rootDir = ROOT_DIR, args = [], homeDir = HOME }
592
610
  console.log('Setup complete.');
593
611
 
594
612
  console.log('\nLocal services:');
595
- console.log(' Traces: local JSONL (.cx/traces)');
613
+ console.log(' Traces: local JSONL (machine-scoped state root)');
596
614
  console.log(` Vector: LanceDB (embedded)`);
597
615
  console.log(` ${managedValues.CONSTRUCT_LANCEDB_PATH}`);
598
616
  console.log(` Credentials are saved to ${getUserEnvPath(homeDir)} for later reference.`);
@@ -623,6 +641,6 @@ export function ensureWorkspace(homeDir = HOME) {
623
641
  return wsPath;
624
642
  }
625
643
 
626
- if (import.meta.url === `file://${process.argv[1]}`) {
644
+ if (isMainModule(import.meta.url)) {
627
645
  runSetup({ args: process.argv.slice(2) });
628
646
  }
@@ -87,8 +87,10 @@ export function writeConstructManifest(cwd, profile, classification) {
87
87
  * that records the recommended per-project scoping for Claude Code.
88
88
  *
89
89
  * Why a sidecar and not `settings.json`? Claude Code today only reads
90
- * `permissions`, `hooks`, and `mcpServers` from settings.json. A
91
- * `disabledPlugins` key would be ignored silently. Writing a sidecar is
90
+ * `permissions`, `hooks`, and policy keys (`allowedMcpServers`,
91
+ * `deniedMcpServers`, etc.) from settings.json MCP server definitions
92
+ * live in `~/.claude.json` or `.mcp.json` instead (construct-ranh). A
93
+ * `disabledPlugins` key would be ignored silently either way. Writing a sidecar is
92
94
  * honest — it documents the recommendation without pretending the harness
93
95
  * enforces it. The construct-mcp session-start path can read this sidecar
94
96
  * and inject a "skills out of scope for this project" note.
@@ -18,7 +18,7 @@
18
18
  * - cx-reviewer prevents silent rubber-stamp reviews
19
19
  * - cx-security prevents post-hoc threat models
20
20
  * - cx-debugger prevents symptom-only fixes
21
- * - cx-docs-keeper prevents stale-doc PRs
21
+ * - cx-operations prevents stale-doc PRs (docs-keeper folded in, construct-rf26.11)
22
22
  * - cx-designer prevents post-hoc accessibility
23
23
  */
24
24
 
@@ -75,7 +75,7 @@ export const POSTCONDITIONS = {
75
75
  reason: `rootCauseConfirmedVia must be one of: ${[...ROOT_CAUSE_SOURCES].join(', ')}.`,
76
76
  },
77
77
  ],
78
- 'cx-docs-keeper': [
78
+ 'cx-operations': [
79
79
  {
80
80
  id: 'docs-keeper.cross-doc-coherence-check-ran',
81
81
  description: 'Docs-keeper must run the cross-doc coherence check and attach a named diff.',