@geraldmaron/construct 1.4.2 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +57 -45
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +273 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +104 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -60,6 +60,6 @@ One token set across all distributable formats:
60
60
 
61
61
  - Export: `construct export … --to=pptx`
62
62
  - Regenerate: `npm run examples:deck`
63
- - Matrix: [Document I/O reference](/guides/reference/document-io)
63
+ - Matrix: [Document I/O reference](../../../docs/guides/reference/document-io.md)
64
64
 
65
65
  Review outputs in `.tmp/distribution-examples/`.
@@ -12,6 +12,10 @@
12
12
  * methods: `initialize`, `session/new`, `session/prompt`, `session/cancel`;
13
13
  * progress streams as `session/update` notifications. The prompt bridges to
14
14
  * planRun/executeRun and forwards run-lifecycle events as agent message chunks.
15
+ *
16
+ * `fetchImpl` is injectable (mirrors lib/orchestration/worker.mjs) and threads
17
+ * straight through to executeRun, so a test can drive the provider backend
18
+ * without a live key or a real network call.
15
19
  */
16
20
 
17
21
  import { createInterface } from 'node:readline';
@@ -20,16 +24,26 @@ const PROTOCOL_VERSION = 1;
20
24
 
21
25
  function describeEvent(event) {
22
26
  if (event.type === 'planned') return `Planned ${event.tasks ?? ''} specialist task(s).`;
23
- if (event.type === 'running') return 'Running orchestration…';
24
- if (event.type === 'task') return `· ${event.role ?? 'task'} — ${event.status ?? ''}`;
25
- if (event.type === 'completed') return `Completed: ${event.status ?? 'done'}.`;
27
+ if (event.type === 'running') return `Running orchestration… (workerBackend=${event.workerBackend ?? 'inline'})`;
28
+ if (event.type === 'task') return `· ${event.role ?? 'task'} — ${event.status ?? ''}${event.executor ? ` (${event.executor})` : ''}`;
29
+ if (event.type === 'completed') {
30
+ const status = event.status ?? 'done';
31
+ if (status === 'completed') return 'Completed (specialist tasks executed).';
32
+ if (status === 'completed-prepare-only') return 'Completed (tasks prepared only — no specialist execution).';
33
+ if (status === 'completed-with-failures') return 'Completed with failures (some tasks failed).';
34
+ if (status === 'degraded') return 'Degraded — the run could not fully execute; see the summary.';
35
+ if (status === 'cancelled') return 'Cancelled.';
36
+ return `Completed: ${status}.`;
37
+ }
26
38
  if (event.type === 'error') return `Error: ${event.error?.message ?? 'run failed'}.`;
27
39
  return null;
28
40
  }
29
41
 
30
42
  function summarize(run) {
31
- const tasks = (run.tasks || []).map((t) => `- ${t.role} (${t.status})`).join('\n');
32
- return `Orchestration ${run.status} executionMode=${run.execution?.executionMode}.\n${tasks}`;
43
+ const tasks = (run.tasks || []).map((t) => `- ${t.role} (${t.status}${t.executor ? `, ${t.executor}` : ''})`).join('\n');
44
+ const exec = run.execution || {};
45
+ const degraded = run.degraded ? ` — degraded: ${run.degradationReason}` : '';
46
+ return `Orchestration ${run.status} — executionMode=${exec.executionMode}, workerBackend=${run.workerBackend}${degraded}.\n${tasks}`;
33
47
  }
34
48
 
35
49
  function textFromPrompt(prompt) {
@@ -37,7 +51,7 @@ function textFromPrompt(prompt) {
37
51
  return prompt.filter((b) => b && b.type === 'text' && typeof b.text === 'string').map((b) => b.text).join('\n').trim();
38
52
  }
39
53
 
40
- export function runAcpServer({ input, output, env = process.env, defaultCwd = process.cwd() } = {}) {
54
+ export function runAcpServer({ input, output, env = process.env, defaultCwd = process.cwd(), fetchImpl } = {}) {
41
55
  const sessions = new Map();
42
56
  let sessionCounter = 0;
43
57
 
@@ -63,7 +77,7 @@ export function runAcpServer({ input, output, env = process.env, defaultCwd = pr
63
77
  const line = describeEvent(event);
64
78
  if (line) update(sessionId, line);
65
79
  });
66
- const run = await executeRun(session.cwd, planned.runId, { env, workerBackend: 'inline' });
80
+ const run = await executeRun(session.cwd, planned.runId, { env, fetchImpl });
67
81
  off();
68
82
  update(sessionId, summarize(run));
69
83
  respond(id, { stopReason: isCancelRequested(planned.runId) ? 'cancelled' : 'end_turn' });
@@ -12,6 +12,7 @@ import { fileURLToPath } from 'node:url';
12
12
  import { detectHostCapabilities } from './host-capabilities.mjs';
13
13
  import { isConstructPackageRepo } from './host-disposition.mjs';
14
14
  import { stageProjectAdapters } from './install/stage-project.mjs';
15
+ import { isMainModule } from './roots.mjs';
15
16
 
16
17
  const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
17
18
  const PKG_ROOT = path.resolve(MODULE_DIR, '..');
@@ -58,7 +59,7 @@ export function runAdaptersScript({ cwd = process.cwd(), hosts = null } = {}) {
58
59
  return result.synced ? 0 : 1;
59
60
  }
60
61
 
61
- if (import.meta.url === `file://${process.argv[1]}`) {
62
+ if (isMainModule(import.meta.url)) {
62
63
  const args = new Set(process.argv.slice(2));
63
64
  const forceAll = args.has('--all-hosts');
64
65
  const hosts = forceAll ? resolveAdapterHosts({ forceAll: true }) : null;
@@ -17,14 +17,31 @@
17
17
  * the damaged prefix as immutable legacy and re-bases the live chain;
18
18
  * verifyChain validates only from the last boundary forward. See
19
19
  * docs/guides/concepts/observability.md.
20
+ *
21
+ * Every record passes through redactRecord before the chain hash is computed
22
+ * (redact-then-sign), so prev_line_hash always chains off the redacted form —
23
+ * a secret can never enter the signed chain even transiently. Redaction
24
+ * covers: (a) values resolved by lib/providers/secret-resolver.mjs, consulted
25
+ * read-only via its resolved-cache snapshot; (b) common token shapes (Bearer,
26
+ * ghp_, xoxb/xoxp/xoxa, sk-, op://); (c) custom patterns registered with
27
+ * configureRedactionPatterns. Matches are replaced with `<redacted:kind>`;
28
+ * the pass itself is audited as a count, never the matched text.
29
+ *
30
+ * LMCP-H5: checkAuditSinkAvailable performs a real write-probe against the
31
+ * audit directory (mkdir + append + unlink a sidecar file) so a caller can
32
+ * distinguish "sink healthy" from "sink down" (read-only filesystem, missing
33
+ * mount, permissions) before deciding whether to proceed. Enterprise mode's
34
+ * mandatory-audit gate (lib/policy/audit-gate.mjs) fails closed on this
35
+ * check; solo/team never call it.
20
36
  */
21
- import { readFileSync, existsSync, statSync, mkdirSync } from 'node:fs';
37
+ import { readFileSync, existsSync, statSync, mkdirSync, appendFileSync, unlinkSync } from 'node:fs';
22
38
  import { createHash } from 'node:crypto';
23
39
  import { join, dirname } from 'node:path';
24
40
 
25
41
  import { appendBounded, readLastLineAcrossSegments } from './logging/rotate.mjs';
26
42
  import { withFileLockSync } from './storage/file-lock.mjs';
27
43
  import { doctorRoot } from './config/xdg.mjs';
44
+ import { __resolvedSecretValues } from './providers/secret-resolver.mjs';
28
45
 
29
46
  const AUDIT_FILE = join(doctorRoot(), 'audit-trail.jsonl');
30
47
 
@@ -32,17 +49,183 @@ function sha256(input) {
32
49
  return createHash('sha256').update(input).digest('hex');
33
50
  }
34
51
 
52
+ export class AuditSinkUnavailableError extends Error {
53
+ constructor(message, { file, reason } = {}) {
54
+ super(message);
55
+ this.name = 'AuditSinkUnavailableError';
56
+ this.file = file ?? null;
57
+ this.reason = reason ?? null;
58
+ }
59
+ }
60
+
61
+ // A real write-probe, not a stat/existsSync check: a directory can exist and
62
+ // still refuse writes (read-only remount, quota, permission drop after
63
+ // mkdir). Writing and removing a sidecar file physically exercises the same
64
+ // path appendAuditRecord will take next, so "available" here means the next
65
+ // real append is actually expected to succeed.
66
+
67
+ export function checkAuditSinkAvailable({ file = AUDIT_FILE } = {}) {
68
+ const dir = dirname(file);
69
+ const probePath = join(dir, `.audit-sink-probe-${process.pid}-${Date.now()}`);
70
+ try {
71
+ mkdirSync(dir, { recursive: true });
72
+ appendFileSync(probePath, 'probe\n', 'utf8');
73
+ unlinkSync(probePath);
74
+ return { available: true, reason: null };
75
+ } catch (err) {
76
+ return { available: false, reason: err?.code || err?.message || 'unknown-error' };
77
+ }
78
+ }
79
+
80
+ // Built-in token shapes seen in the wild across providers: generic Bearer
81
+ // headers, GitHub PATs (ghp_), Slack bot/user/app tokens (xoxb/xoxp/xoxa),
82
+ // OpenAI-style secret keys (sk-), and 1Password references (op://...). Each
83
+ // pattern is tagged with a `kind` so the replacement reads `<redacted:kind>`
84
+ // rather than a single opaque marker, which keeps audit records diagnosable.
85
+
86
+ const BUILTIN_PATTERNS = [
87
+ { kind: 'bearer-token', re: /\bBearer\s+[A-Za-z0-9\-._~+/]+=*/g },
88
+ { kind: 'github-token', re: /\bghp_[A-Za-z0-9]{20,}\b/g },
89
+ { kind: 'slack-token', re: /\bxox[bpa]-[A-Za-z0-9-]{10,}\b/g },
90
+ { kind: 'api-key', re: /\bsk-[A-Za-z0-9]{16,}\b/g },
91
+ { kind: 'op-reference', re: /\bop:\/\/[^\s'"]+/g },
92
+ ];
93
+
94
+ // Custom patterns a caller wires in at startup (e.g. an org-specific token
95
+ // shape). Kept process-local and additive over BUILTIN_PATTERNS; never
96
+ // persisted, so a test or embedder can register/reset freely.
97
+
98
+ let customPatterns = [];
99
+
100
+ /**
101
+ * Register additional redaction patterns beyond the built-in token shapes.
102
+ * Each entry is `{ kind, re }` where `re` is a global RegExp. Replaces the
103
+ * full custom set on every call so a caller can reconfigure cleanly.
104
+ *
105
+ * @param {{kind:string, re:RegExp}[]} patterns
106
+ */
107
+ export function configureRedactionPatterns(patterns = []) {
108
+ customPatterns = Array.isArray(patterns) ? patterns.filter((p) => p && p.re instanceof RegExp && typeof p.kind === 'string') : [];
109
+ }
110
+
111
+ export function __resetRedactionPatterns() {
112
+ customPatterns = [];
113
+ }
114
+
115
+ // Secret values below this length are excluded from the known-value pass:
116
+ // short resolved values (flags, single-char tokens) produce false-positive
117
+ // redactions in ordinary prose.
118
+
119
+ const MIN_KNOWN_SECRET_LENGTH = 8;
120
+
121
+ function escapeRegExp(str) {
122
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
123
+ }
124
+
125
+ // A resolved secret is redacted by exact substring match, not by first
126
+ // hashing the haystack — a hash comparison can only ever confirm equality of
127
+ // two already-known values, never locate an unknown-length substring inside
128
+ // arbitrary free text. Instead, the "hashed comparison" the caller performs
129
+ // (recorded in the audit count, see below) is against sha256(value) so the
130
+ // count event that leaves the process never carries a byte of the plaintext,
131
+ // even though the in-process match itself must touch the plaintext to redact
132
+ // it — the same value already lives in secret-resolver's own cache in-process.
133
+
134
+ function knownSecretPatterns() {
135
+ const values = __resolvedSecretValues().filter(
136
+ (v) => typeof v === 'string' && v.length >= MIN_KNOWN_SECRET_LENGTH,
137
+ );
138
+ return values.map((value) => ({
139
+ kind: 'known-secret',
140
+ re: new RegExp(escapeRegExp(value), 'g'),
141
+ hash: sha256(value),
142
+ }));
143
+ }
144
+
145
+ // lib/providers/secret-audit-wiring.mjs already records the op:// reference
146
+ // itself (never the resolved value) under these exact key names as its
147
+ // value-free diagnostic contract — a bare op://vault/item/field pointer is
148
+ // not a secret, so the op-reference pattern must not clobber it there. Any
149
+ // other field carrying an op:// string (a tool result body, a raw env dump)
150
+ // is still redacted.
151
+
152
+ const OP_REF_EXEMPT_KEYS = new Set(['ref']);
153
+
154
+ // Walk every string reachable in `value`, applying every pattern in turn and
155
+ // replacing matches with `<redacted:kind>`. Returns the redacted clone plus a
156
+ // count-only tally (kind → number of matches) so the caller can audit that
157
+ // redaction occurred without ever recording the matched text.
158
+
159
+ function redactString(str, patterns, tally) {
160
+ let out = str;
161
+ for (const { kind, re } of patterns) {
162
+ re.lastIndex = 0;
163
+ const matches = out.match(re);
164
+ if (matches && matches.length) {
165
+ tally[kind] = (tally[kind] || 0) + matches.length;
166
+ out = out.replace(re, `<redacted:${kind}>`);
167
+ }
168
+ }
169
+ return out;
170
+ }
171
+
172
+ function redactValue(value, patterns, tally, key = null) {
173
+ if (typeof value === 'string') {
174
+ if (key && OP_REF_EXEMPT_KEYS.has(key)) {
175
+ const rest = patterns.filter((p) => p.kind !== 'op-reference');
176
+ return redactString(value, rest, tally);
177
+ }
178
+ return redactString(value, patterns, tally);
179
+ }
180
+ if (Array.isArray(value)) return value.map((v) => redactValue(v, patterns, tally));
181
+ if (value && typeof value === 'object') {
182
+ const out = {};
183
+ for (const [k, v] of Object.entries(value)) {
184
+ // Schema-marked redacted:true fields are already elided upstream by the
185
+ // caller's own schema; this pass is a second, pattern-based net over
186
+ // whatever free-text fields (detail, args, result, payload) remain.
187
+ out[k] = redactValue(v, patterns, tally, k);
188
+ }
189
+ return out;
190
+ }
191
+ return value;
192
+ }
193
+
194
+ /**
195
+ * Redact known secret values and token-shaped strings from an audit record.
196
+ * Runs BEFORE the chain hash is computed so the signature always covers the
197
+ * redacted form (redact-then-sign). Returns `{ record, tally }` where tally
198
+ * is a count-only map of kind → matches (never the matched text).
199
+ *
200
+ * @param {object} partial
201
+ * @returns {{record: object, tally: Record<string, number>}}
202
+ */
203
+ export function redactRecord(partial) {
204
+ const patterns = [...BUILTIN_PATTERNS, ...customPatterns, ...knownSecretPatterns()];
205
+ const tally = {};
206
+ const record = redactValue(partial, patterns, tally);
207
+ return { record, tally };
208
+ }
209
+
35
210
  // prev_line_hash and the append must be one atomic step or concurrent writers
36
211
  // race; the file lock serializes them across processes.
37
212
 
38
213
  export function appendAuditRecord(partial, { file = AUDIT_FILE } = {}) {
214
+ const { record: redacted, tally } = redactRecord(partial);
215
+
216
+ // Redaction is itself audited as a count only — never the secret — folded
217
+ // into the record's own field before it is hashed and written, so the
218
+ // signed line and the returned record always agree.
219
+
220
+ if (Object.keys(tally).length) redacted.redaction_counts = tally;
221
+
39
222
  return withFileLockSync(file, () => {
40
223
  let prev = null;
41
224
  try {
42
225
  const last = readLastLineAcrossSegments(file);
43
226
  if (last !== null) prev = sha256(last);
44
227
  } catch { /* first record, or unreadable tail */ }
45
- const record = { ...partial, prev_line_hash: prev };
228
+ const record = { ...redacted, prev_line_hash: prev };
46
229
  mkdirSync(dirname(file), { recursive: true });
47
230
  appendBounded('audit-trail', file, JSON.stringify(record) + '\n');
48
231
  return record;
@@ -21,6 +21,7 @@
21
21
  */
22
22
 
23
23
  import { spawnSync } from 'node:child_process';
24
+ import { isMainModule } from '../roots.mjs';
24
25
 
25
26
  const CLOSES_RE = /\b(?:closes?|close|fix(?:es)?|fixed|resolves?|resolved)\s+(construct-[a-z0-9]+)/gi;
26
27
 
@@ -105,7 +106,7 @@ export async function runAutoClose({ range, runner = spawnSync } = {}) {
105
106
  }
106
107
 
107
108
  // CLI entry: read --range, run, print summary.
108
- const invokedDirectly = import.meta.url === `file://${process.argv[1]}`;
109
+ const invokedDirectly = isMainModule(import.meta.url);
109
110
  if (invokedDirectly) {
110
111
  const args = process.argv.slice(2);
111
112
  const rangeArg = args.find((a) => a.startsWith('--range='));
@@ -25,6 +25,7 @@
25
25
  */
26
26
 
27
27
  import { spawnSync } from 'node:child_process';
28
+ import { isMainModule } from '../roots.mjs';
28
29
 
29
30
  const DEFAULT_STALE_OPEN_DAYS = 14;
30
31
  const DEFAULT_STUCK_IN_PROGRESS_DAYS = 3;
@@ -166,7 +167,7 @@ export function formatDriftReport(report) {
166
167
  }
167
168
 
168
169
  // CLI: print report, exit non-zero when thresholds breached unless --report-only.
169
- const invokedDirectly = import.meta.url === `file://${process.argv[1]}`;
170
+ const invokedDirectly = isMainModule(import.meta.url);
170
171
  if (invokedDirectly) {
171
172
  const args = process.argv.slice(2);
172
173
  const json = args.includes('--json');
@@ -23,18 +23,33 @@ async function getCopilotToken() {
23
23
  }
24
24
  }
25
25
 
26
- const server = http.createServer(async (req, res) => {
27
- // CORS
28
- res.setHeader('Access-Control-Allow-Origin', '*');
29
- res.setHeader('Access-Control-Allow-Methods', 'POST, GET, OPTIONS');
30
- res.setHeader('Access-Control-Allow-Headers', '*');
26
+ // Fail closed: the bridge fronts the user's Copilot entitlement, so every request
27
+ // must carry the per-launch bearer token service-manager injects, and an unconfigured
28
+ // token means no access at all. No CORS headers are emitted — the only legitimate
29
+ // caller is a loopback server process (OpenCode), never a browser page, so a wildcard
30
+ // Access-Control-Allow-Origin would just let any open tab drive the entitlement.
31
+
32
+ const EXPECTED_TOKEN = process.env.CONSTRUCT_COPILOT_BRIDGE_TOKEN || null;
33
+
34
+ function isAuthorized(req) {
35
+ if (!EXPECTED_TOKEN) return false;
36
+ const match = String(req.headers.authorization || '').match(/^Bearer\s+(.+)$/i);
37
+ return Boolean(match) && match[1] === EXPECTED_TOKEN;
38
+ }
31
39
 
40
+ const server = http.createServer(async (req, res) => {
32
41
  if (req.method === 'OPTIONS') {
33
42
  res.writeHead(204);
34
43
  res.end();
35
44
  return;
36
45
  }
37
46
 
47
+ if (!isAuthorized(req)) {
48
+ res.writeHead(401, { 'Content-Type': 'application/json' });
49
+ res.end(JSON.stringify({ error: 'Unauthorized: missing or invalid bridge bearer token.' }));
50
+ return;
51
+ }
52
+
38
53
  if (req.url === '/v1/chat/completions' && req.method === 'POST') {
39
54
  const token = await getCopilotToken();
40
55
  if (!token) {
@@ -13,6 +13,7 @@ import { createHash } from 'node:crypto';
13
13
  import { artifactTypes, loadArtifactManifest } from '../artifact-manifest.mjs';
14
14
  import { validateSkillEffectiveness } from '../validators/skill-effectiveness.mjs';
15
15
  import { collectScopeRoleIds } from '../scopes/enrich.mjs';
16
+ import { bindingForSpecialist } from '../roles/flavor-bindings.mjs';
16
17
 
17
18
  function findConstructRoot(startPath = process.cwd()) {
18
19
  let current = path.resolve(startPath);
@@ -95,7 +96,7 @@ function verificationHooks(meta, body) {
95
96
  }
96
97
 
97
98
  function resolveOwners(skillId, { registry, declaredSkills, ownerBases }) {
98
- if (skillId.startsWith('brand/')) return ['cx-docs-keeper'];
99
+ if (skillId.startsWith('brand/')) return ['cx-operations'];
99
100
  const owners = new Set();
100
101
  for (const agent of Object.values(registry.specialists ?? {})) {
101
102
  if ((agent.skills ?? []).includes(skillId)) owners.add(`cx-${agent.name}`);
@@ -107,6 +108,14 @@ function resolveOwners(skillId, { registry, declaredSkills, ownerBases }) {
107
108
  }
108
109
  if (!owners.size) owners.add(`scope-role:${base}`);
109
110
  }
111
+ // A roles/*.md overlay's base name is not always a live specialist name —
112
+ // some overlays are skill bundles loaded onto a differently-named anchor.
113
+ // lib/roles/flavor-bindings.mjs is the source of truth for which
114
+ // specialist actually loads a given overlay; fall back to it here.
115
+ if (base && owners.size === 0) {
116
+ const binding = bindingForSpecialist(base);
117
+ if (binding?.specialistId) owners.add(binding.specialistId);
118
+ }
110
119
  if (declaredSkills.has(skillId) && owners.size === 0) {
111
120
  for (const agent of Object.values(registry.specialists ?? {})) {
112
121
  if ((agent.skills ?? []).includes(skillId)) owners.add(`cx-${agent.name}`);
@@ -0,0 +1,147 @@
1
+ /**
2
+ * lib/cli/approvals.mjs — `construct approvals` CLI handler.
3
+ *
4
+ * Loads the ApprovalQueue from the appropriate persistence path and
5
+ * implements list / approve / deny / status subcommands.
6
+ *
7
+ * `approve` is the operator's decision point and the sole production drain
8
+ * for J6 governed write intents: once a record whose tool names a known
9
+ * governed provider (jira/github/confluence) is approved, the control plane
10
+ * (lib/writes/control-plane.mjs) executes it through the J2 envelope — the
11
+ * only place recommend becomes execute. Non-write-intent approvals (embed
12
+ * tool proposals) are left for their own runtime to act on and are not
13
+ * touched here.
14
+ */
15
+
16
+ import { ApprovalQueue } from '../embed/approval-queue.mjs';
17
+ import { getDeploymentMode } from '../deployment-mode.mjs';
18
+ import { executeApprovedWriteIntent } from '../writes/control-plane.mjs';
19
+ import { parseWriteIntentToolName, KNOWN_PROVIDERS } from '../writes/write-intent.mjs';
20
+
21
+ /**
22
+ * Run the approvals CLI command.
23
+ * @param {string[]} args
24
+ * @param {object} ctx
25
+ * @param {string} ctx.rootDir
26
+ * @param {string} ctx.homeDir
27
+ * @param {object} ctx.env
28
+ * @param {Function} ctx.println
29
+ * @param {Function} ctx.errorln
30
+ * @returns {number} exit code
31
+ */
32
+ export async function runApprovalsCli(args, { rootDir, homeDir, env, println, errorln }) {
33
+ const sub = args[0];
34
+ if (!sub || sub === '--help' || sub === '-h') {
35
+ println('Usage: construct approvals <list|approve|deny|status>');
36
+ println('');
37
+ println('Subcommands:');
38
+ println(' list List pending approvals');
39
+ println(' approve <id> Approve a pending approval by id');
40
+ println(' deny <id> Deny a pending approval by id');
41
+ println(' status <id> Show full status of a specific approval');
42
+ return 0;
43
+ }
44
+
45
+ const deploymentMode = getDeploymentMode(env, { cwd: rootDir });
46
+ const persistPath = ApprovalQueue.resolvePersistPath(rootDir, deploymentMode);
47
+ const queue = new ApprovalQueue({ persistPath });
48
+
49
+ if (sub === 'list') {
50
+ const pending = queue.getPending();
51
+ if (pending.length === 0) {
52
+ println('No pending approvals.');
53
+ return 0;
54
+ }
55
+ for (const r of pending) {
56
+ const rb = r.requestedBy || {};
57
+ const by = rb.role || rb.userId || rb.serviceId || 'unknown';
58
+ println(`${r.approvalId}`);
59
+ println(` tool: ${r.toolCall?.tool || '?'}`);
60
+ println(` requestedAt: ${r.requestedAt}`);
61
+ println(` requestedBy: ${by}`);
62
+ println(` expiresAt: ${r.expiresAt}`);
63
+ println('');
64
+ }
65
+ return 0;
66
+ }
67
+
68
+ if (sub === 'approve') {
69
+ const id = args[1];
70
+ if (!id) {
71
+ errorln('Usage: construct approvals approve <id>');
72
+ return 1;
73
+ }
74
+ try {
75
+ const record = queue.approve(id, {
76
+ decidedBy: {
77
+ role: process.env.CONSTRUCT_ROLE || 'cli-operator',
78
+ userId: process.env.USER || process.env.USERNAME || null,
79
+ source: 'cli',
80
+ },
81
+ });
82
+ println(`Approved: ${record.approvalId} (tool: ${record.toolCall?.tool})`);
83
+
84
+ // Only a governed write intent (tool "<provider>.<kind>" naming a known
85
+ // governed provider) is executed here; anything else is another
86
+ // runtime's proposal and stays untouched.
87
+
88
+ const parsed = parseWriteIntentToolName(record.toolCall?.tool);
89
+ if (parsed && KNOWN_PROVIDERS.includes(parsed.providerId)) {
90
+ try {
91
+ const result = await executeApprovedWriteIntent(record, { rootDir });
92
+ println(`Executed ${parsed.providerId}.${parsed.writeKind} via control plane (status: ${result?.status ?? 'unknown'})`);
93
+ } catch (execErr) {
94
+ errorln(`Approved, but control-plane execution failed: ${execErr.message}`);
95
+ return 1;
96
+ }
97
+ }
98
+ return 0;
99
+ } catch (err) {
100
+ errorln(`Error: ${err.message}`);
101
+ return 1;
102
+ }
103
+ }
104
+
105
+ if (sub === 'deny') {
106
+ const id = args[1];
107
+ if (!id) {
108
+ errorln('Usage: construct approvals deny <id> [--reason=...]');
109
+ return 1;
110
+ }
111
+ const reasonFlag = args.find((a) => a.startsWith('--reason='));
112
+ const reason = reasonFlag ? reasonFlag.slice('--reason='.length) : 'denied by operator';
113
+ try {
114
+ const record = queue.deny(id, {
115
+ decidedBy: {
116
+ role: process.env.CONSTRUCT_ROLE || 'cli-operator',
117
+ userId: process.env.USER || process.env.USERNAME || null,
118
+ source: 'cli',
119
+ },
120
+ reason,
121
+ });
122
+ println(`Denied: ${record.approvalId} (reason: ${reason})`);
123
+ return 0;
124
+ } catch (err) {
125
+ errorln(`Error: ${err.message}`);
126
+ return 1;
127
+ }
128
+ }
129
+
130
+ if (sub === 'status') {
131
+ const id = args[1];
132
+ if (!id) {
133
+ errorln('Usage: construct approvals status <id>');
134
+ return 1;
135
+ }
136
+ const record = queue.getById(id);
137
+ if (!record) {
138
+ errorln(`Approval not found: ${id}`);
139
+ return 1;
140
+ }
141
+ println(JSON.stringify(record, null, 2));
142
+ return 0;
143
+ }
144
+
145
+ errorln(`Unknown approvals subcommand: ${sub}. Available: list, approve, deny, status`);
146
+ return 1;
147
+ }