@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
@@ -3,30 +3,53 @@
3
3
  * audit trail.
4
4
  *
5
5
  * enableSecretAuditTrail routes the value-free events from secret-resolver into the
6
- * append-only audit log (lib/audit-trail.mjs). Only actual op:// resolutions are
7
- * recorded the prompt-bearing, security-significant events — not cache hits or
8
- * high-frequency plain reads, so the resolve path is not serialized behind a file
9
- * lock. The materialized secret value is never available to this layer and is never
10
- * written. Wiring is per-process: the CLI entrypoint enables it; worker and daemon
11
- * processes are a tracked follow-up.
6
+ * append-only audit log (lib/audit-trail.mjs). Two event types are recorded:
7
+ * - secret.op_read: an actual op:// CLI read (prompt-bearing, never cached)
8
+ * - secret.resolve (non-op): any plaintext tier resolution (project-env, config-env,
9
+ * home-env, shell-rc, alt-store) records the source tier, never the value
10
+ * Cache hits on op reads are skipped; op-ref resolve events are skipped because
11
+ * their paired op_read event carries the canonical record. The materialized secret
12
+ * value is never available to this layer and is never written.
12
13
  */
13
14
 
14
15
  import { appendAuditRecord } from '../audit-trail.mjs';
15
16
  import { setSecretAuditSink } from './secret-resolver.mjs';
16
17
 
17
18
  export function enableSecretAuditTrail({ file } = {}) {
19
+ const sink = file ? { file } : {};
18
20
  setSecretAuditSink((event) => {
19
- if (event.event !== 'secret.op_read' || event.cacheHit) return;
20
- appendAuditRecord(
21
- {
22
- ts: new Date().toISOString(),
23
- tool: 'secret-resolver',
24
- action: 'op_read',
25
- ref: event.ref,
26
- ok: event.ok,
27
- code: event.code ?? null,
28
- },
29
- file ? { file } : {},
30
- );
21
+ if (event.event === 'secret.op_read') {
22
+ if (event.cacheHit) return;
23
+
24
+ // Persisting the op:// ref (vault/item/field — not the value) into
25
+ // Construct's own audit JSONL is a recorded design decision (remediation
26
+ // plan Epic 6, construct-trxz.6), distinct from the mcp-platform-config.mjs
27
+ // prohibition on writing op:// refs into host-readable MCP config files.
28
+
29
+ appendAuditRecord(
30
+ {
31
+ ts: new Date().toISOString(),
32
+ tool: 'secret-resolver',
33
+ action: 'op_read',
34
+ ref: event.ref,
35
+ ok: event.ok,
36
+ code: event.code ?? null,
37
+ },
38
+ sink,
39
+ );
40
+ } else if (event.event === 'secret.resolve' && !event.isOpRef) {
41
+ appendAuditRecord(
42
+ {
43
+ ts: new Date().toISOString(),
44
+ tool: 'secret-resolver',
45
+ action: 'resolve',
46
+ varName: event.varName,
47
+ source: event.source ?? null,
48
+ ok: event.ok,
49
+ code: event.code ?? null,
50
+ },
51
+ sink,
52
+ );
53
+ }
31
54
  });
32
55
  }
@@ -12,11 +12,12 @@
12
12
  * `$(op read 'op://...')` — it is resolved through the `op` CLI and cached for
13
13
  * the process lifetime so a single launch prompts at most once per reference.
14
14
  *
15
- * One divergence from loadConstructEnv is intentional and tracked separately: that
16
- * merge ranks the config files above process.env to defeat stale shell exports,
17
- * while this resolver honors a directly-passed env value first so a hermetic caller
18
- * can inject its own credentials. Unifying the process.env tier touches the live
19
- * LLM path and is deferred to a follow-up.
15
+ * The process.env tier follows one precedence ladder shared with loadConstructEnv:
16
+ * when file discovery is enabled (allowAmbient), the file tiers win over an ambient
17
+ * shell export so a stale export cannot shadow a managed config value, with the same
18
+ * carve-out that a materialized credential injected on the env beats a stored op://
19
+ * reference (preferInjectedCredential). A hermetic caller sets allowAmbient:false to
20
+ * suppress all file/rc discovery and use only the directly-injected env.
20
21
  *
21
22
  * resolveSecret performs the `op read` (and may surface a structured
22
23
  * SecretResolutionError); hasSecret only checks presence and never invokes the
@@ -24,14 +25,20 @@
24
25
  * value lives only in this module's cache and is never logged. allowAmbient lets
25
26
  * a hermetic caller (tests, embedded callers that inject their own env) suppress
26
27
  * file/rc discovery so a developer key never bleeds into an isolated run.
28
+ * peekRawCredential shares the same tiers for a non-secret display value (a repo
29
+ * slug, a host URL) — shape-only, never resolving an op:// reference. Any
30
+ * status/detection surface (ADR-0049 §2) should use hasSecret/hasAnySecret/
31
+ * peekRawCredential rather than reimplementing file or shell-rc scanning.
27
32
  */
28
33
 
29
34
  import fs from 'node:fs';
30
35
  import path from 'node:path';
31
36
  import os from 'node:os';
32
- import { spawnSync } from 'node:child_process';
37
+ import { spawnSync, spawn } from 'node:child_process';
33
38
  import { discoverAlternateRawForVar } from './credential-sources.mjs';
39
+ import { locateOpBinary } from './op-locate.mjs';
34
40
  import { configDir } from '../config/xdg.mjs';
41
+ import { preferInjectedCredential, parseEnvContent } from '../env-config.mjs';
35
42
 
36
43
  // The 1Password desktop integration prompts for biometric unlock on the first
37
44
  // read after the vault locks, and that interactive round-trip routinely takes
@@ -89,30 +96,145 @@ export function extractOpRef(rawValue) {
89
96
  return m ? m[2] : null;
90
97
  }
91
98
 
92
- function defaultOpRead(opRef) {
93
- const result = spawnSync('op', ['read', opRef], { encoding: 'utf8', timeout: OP_READ_TIMEOUT_MS });
94
- if (result.error && result.error.code === 'ENOENT') {
99
+ // A 1Password service account (OP_SERVICE_ACCOUNT_TOKEN) makes `op read`/`op run`
100
+ // non-interactive no desktop app, no biometric, no `op signin` which fits a
101
+ // multi-process / daemon / headless runtime and CI. It is opt-in: with no token the
102
+ // desktop app integration remains the default. The `op` CLI auto-detects the token
103
+ // from its process env; forwarding it from the caller's env (not just the ambient
104
+ // shell) lets Construct source it from the OS keychain at runtime rather than
105
+ // storing it in plaintext. Note: a service account cannot read the built-in
106
+ // Personal/Private vault and is rate-limited (1k reads/day on the Individual tier),
107
+ // so it pairs with resolve-once, not per-process op read.
108
+ // Docs: https://developer.1password.com/docs/service-accounts/
109
+
110
+ export function opServiceAccountToken(env = process.env) {
111
+ const token = env?.OP_SERVICE_ACCOUNT_TOKEN;
112
+ return typeof token === 'string' && token.trim() ? token.trim() : null;
113
+ }
114
+
115
+ function defaultOpWhoami() {
116
+ const opPath = locateOpBinary();
117
+ if (!opPath) return { installed: false, signedIn: false };
118
+ const result = spawnSync(opPath, ['whoami'], { encoding: 'utf8', timeout: 5000 });
119
+ if (result.error && result.error.code === 'ENOENT') return { installed: false, signedIn: false };
120
+ return { installed: true, signedIn: result.status === 0 };
121
+ }
122
+
123
+ // Which 1Password auth mode `op` will use for a resolution, for doctor/diagnostics.
124
+ // A service-account token is non-interactive; otherwise `op` relies on the desktop
125
+ // app session, and `op whoami` reveals whether that session is live (a cold session
126
+ // is the usual cause of repeated biometric prompts). `whoami` is checked, not `read`,
127
+ // so it never prompts. Injectable for tests.
128
+
129
+ export function describeOpAuthMode(env = process.env, { whoami = defaultOpWhoami } = {}) {
130
+ if (opServiceAccountToken(env)) {
131
+ return { mode: 'service-account', signedIn: true, detail: 'OP_SERVICE_ACCOUNT_TOKEN set — op read/op run authenticate non-interactively.' };
132
+ }
133
+ const state = whoami();
134
+ if (!state.installed) {
135
+ return { mode: 'op-absent', signedIn: false, detail: '1Password CLI (op) not installed — set keys directly or install op.' };
136
+ }
137
+ if (state.signedIn) {
138
+ return { mode: 'desktop-session', signedIn: true, detail: 'op is signed in via the desktop app session.' };
139
+ }
140
+ return {
141
+ mode: 'desktop-session',
142
+ signedIn: false,
143
+ detail: 'op is not signed in — enable Settings → Developer → Integrate with 1Password CLI, or set OP_SERVICE_ACCOUNT_TOKEN for non-interactive auth.',
144
+ };
145
+ }
146
+
147
+ function opReadChildEnv(env) {
148
+ const token = opServiceAccountToken(env);
149
+ return token ? { ...process.env, OP_SERVICE_ACCOUNT_TOKEN: token } : process.env;
150
+ }
151
+
152
+ // Map an `op read` outcome — from the sync spawnSync reader or the async spawn reader —
153
+ // to the resolved secret or a classified SecretResolutionError. Both readers funnel
154
+ // through here so their error taxonomy (OP_NOT_INSTALLED / OP_NOT_SIGNED_IN /
155
+ // OP_READ_FAILED / OP_EMPTY) cannot drift apart.
156
+
157
+ function interpretOpReadResult(opRef, { enoent, status, stdout, stderr }) {
158
+ if (enoent) {
95
159
  throw new SecretResolutionError('1Password CLI not found — install `op` or set the key directly.', 'OP_NOT_INSTALLED');
96
160
  }
97
- if (result.status !== 0) {
98
- const stderr = String(result.stderr || '').toLowerCase();
99
- if (/sign in|signin|session|not currently|authenticate|authorization|no account/.test(stderr)) {
100
- throw new SecretResolutionError('1Password CLI is not signed in — run `op signin` and retry.', 'OP_NOT_SIGNED_IN');
161
+ if (status !== 0) {
162
+ const err = String(stderr || '').toLowerCase();
163
+ if (/sign in|signin|session|not currently|authenticate|authorization|no account/.test(err)) {
164
+ throw new SecretResolutionError('1Password CLI is not signed in — enable the desktop app CLI integration (Settings → Developer) and unlock, or set OP_SERVICE_ACCOUNT_TOKEN for non-interactive access.', 'OP_NOT_SIGNED_IN');
101
165
  }
102
- throw new SecretResolutionError(`op read failed for ${opRef}: ${String(result.stderr || '').trim().slice(0, 160)}`, 'OP_READ_FAILED');
166
+ throw new SecretResolutionError(`op read failed for ${opRef}: ${String(stderr || '').trim().slice(0, 160)}`, 'OP_READ_FAILED');
103
167
  }
104
- const secret = String(result.stdout || '').trim();
168
+ const secret = String(stdout || '').trim();
105
169
  if (!secret) throw new SecretResolutionError(`op read returned an empty value for ${opRef}`, 'OP_EMPTY');
106
170
  return secret;
107
171
  }
108
172
 
109
- export function resolveOpRef(opRef, { opRead = defaultOpRead } = {}) {
173
+ function defaultOpRead(opRef, { env = process.env } = {}) {
174
+ const opPath = locateOpBinary();
175
+ if (!opPath) {
176
+ throw new SecretResolutionError('1Password CLI not found — install `op` or set the key directly.', 'OP_NOT_INSTALLED');
177
+ }
178
+ const result = spawnSync(opPath, ['read', opRef], { encoding: 'utf8', timeout: OP_READ_TIMEOUT_MS, env: opReadChildEnv(env) });
179
+ return interpretOpReadResult(opRef, {
180
+ enoent: Boolean(result.error && result.error.code === 'ENOENT'),
181
+ status: result.status,
182
+ stdout: result.stdout,
183
+ stderr: result.stderr,
184
+ });
185
+ }
186
+
187
+ // The MCP CallTool path resolves provider keys inside the single-threaded server, so
188
+ // a spawnSync `op read` freezes the event loop — and its own Promise.race timeout
189
+ // guard — for up to OP_READ_TIMEOUT_MS on the first op:// touch. This async reader
190
+ // uses spawn so the loop keeps turning, killing the child at the same timeout and
191
+ // mapping the outcome through the same interpretOpReadResult.
192
+
193
+ function defaultOpReadAsync(opRef, { env = process.env } = {}) {
194
+ return new Promise((resolve, reject) => {
195
+ const opPath = locateOpBinary();
196
+ if (!opPath) {
197
+ reject(new SecretResolutionError('1Password CLI not found — install `op` or set the key directly.', 'OP_NOT_INSTALLED'));
198
+ return;
199
+ }
200
+ const child = spawn(opPath, ['read', opRef], { env: opReadChildEnv(env) });
201
+ let stdout = '';
202
+ let stderr = '';
203
+ let settled = false;
204
+ let timer = null;
205
+ const finish = (fn) => {
206
+ if (settled) return;
207
+ settled = true;
208
+ if (timer) clearTimeout(timer);
209
+ fn();
210
+ };
211
+ timer = setTimeout(() => {
212
+ try { child.kill('SIGKILL'); } catch { /* already exited */ }
213
+ finish(() => reject(new SecretResolutionError(`op read failed for ${opRef}: timed out after ${OP_READ_TIMEOUT_MS}ms`, 'OP_READ_FAILED')));
214
+ }, OP_READ_TIMEOUT_MS);
215
+ child.stdout?.on('data', (d) => { stdout += String(d); });
216
+ child.stderr?.on('data', (d) => { stderr += String(d); });
217
+ child.on('error', (opErr) => finish(() => {
218
+ if (opErr && opErr.code === 'ENOENT') {
219
+ reject(new SecretResolutionError('1Password CLI not found — install `op` or set the key directly.', 'OP_NOT_INSTALLED'));
220
+ return;
221
+ }
222
+ reject(opErr);
223
+ }));
224
+ child.on('close', (code) => finish(() => {
225
+ try { resolve(interpretOpReadResult(opRef, { enoent: false, status: code, stdout, stderr })); }
226
+ catch (err) { reject(err); }
227
+ }));
228
+ });
229
+ }
230
+
231
+ export function resolveOpRef(opRef, { opRead = defaultOpRead, env = process.env } = {}) {
110
232
  if (opCache.has(opRef)) {
111
233
  emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: true, ok: true });
112
234
  return opCache.get(opRef);
113
235
  }
114
236
  try {
115
- const secret = opRead(opRef);
237
+ const secret = opRead(opRef, { env });
116
238
  opCache.set(opRef, secret);
117
239
  emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: false, ok: true });
118
240
  return secret;
@@ -127,11 +249,17 @@ function materialize(rawValue, opts) {
127
249
  return ref ? resolveOpRef(ref, opts) : unquote(rawValue);
128
250
  }
129
251
 
252
+ // Delegates to env-config's parseEnvContent so a dotenv value never diverges
253
+ // from loadConstructEnv's reading of the same file (construct-192h.10) — in
254
+ // particular an unpaired quote (`KEY="abc`) resolves to the same byte-identical
255
+ // value on both paths instead of the resolver's own quote-pairing regex leaving
256
+ // the stray quote in place.
257
+
130
258
  function readDotenvVar(file, varName) {
131
259
  try {
132
260
  if (!fs.existsSync(file)) return null;
133
- const m = fs.readFileSync(file, 'utf8').match(new RegExp(`^${varName}=(.+)$`, 'm'));
134
- return m ? m[1].trim() : null;
261
+ const parsed = parseEnvContent(fs.readFileSync(file, 'utf8'));
262
+ return Object.prototype.hasOwnProperty.call(parsed, varName) ? parsed[varName] : null;
135
263
  } catch {
136
264
  return null;
137
265
  }
@@ -181,9 +309,8 @@ function readFromOpEnvCatalog(varName, { env, home }) {
181
309
  }
182
310
 
183
311
  function rawCandidate(varName, { env, cwd, home }) {
184
- const direct = env?.[varName];
185
- if (typeof direct === 'string' && direct.length > 0) return { raw: direct, source: 'env' };
186
-
312
+ // Pure file/store discovery: the ambient env tier is ranked by the callers
313
+ // (resolveSecret / hasSecret) so one precedence ladder governs env-vs-file.
187
314
  // File tier matches loadConstructEnv: the project .env (closest to the work)
188
315
  // wins over the machine-wide XDG config.env, which wins over a generic ~/.env.
189
316
  const files = [
@@ -218,18 +345,32 @@ function resolveAndAudit(varName, raw, source, opts) {
218
345
 
219
346
  export function resolveSecret(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true, opRead } = {}) {
220
347
  const direct = env?.[varName];
221
- if (typeof direct === 'string' && direct.length > 0) return resolveAndAudit(varName, direct, 'env', { opRead });
348
+ const hasDirect = typeof direct === 'string' && direct.length > 0;
349
+
350
+ // Hermetic mode: only the directly-injected env is consulted, no file/rc discovery,
351
+ // so a developer key never bleeds into an isolated run.
222
352
  if (!allowAmbient) {
353
+ if (hasDirect) return resolveAndAudit(varName, direct, 'env', { opRead, env });
223
354
  emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
224
355
  return null;
225
356
  }
357
+
226
358
  const home = os.homedir();
227
359
  const found = rawCandidate(varName, { env, cwd, home });
228
- if (!found) {
229
- emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
230
- return null;
360
+
361
+ // Precedence parity with loadConstructEnv: the file tiers win over an ambient shell
362
+ // export, except a materialized credential injected on the env beats a stored op://
363
+ // reference so a single `op run` auth suffices.
364
+ if (found) {
365
+ if (hasDirect && preferInjectedCredential(direct, found.raw)) {
366
+ return resolveAndAudit(varName, direct, 'env', { opRead, env });
367
+ }
368
+ return resolveAndAudit(varName, found.raw, found.source, { opRead, env });
231
369
  }
232
- return resolveAndAudit(varName, found.raw, found.source, { opRead });
370
+
371
+ if (hasDirect) return resolveAndAudit(varName, direct, 'env', { opRead, env });
372
+ emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
373
+ return null;
233
374
  }
234
375
 
235
376
  export function resolveFirstSecret(varNames, opts = {}) {
@@ -240,15 +381,81 @@ export function resolveFirstSecret(varNames, opts = {}) {
240
381
  return null;
241
382
  }
242
383
 
384
+ // Async twins for the MCP CallTool hot path: file/rc discovery stays synchronous
385
+ // (small local fs reads, not the blocking step), but op:// materialization uses
386
+ // defaultOpReadAsync (spawn) instead of spawnSync so the single-threaded MCP server's
387
+ // event loop keeps turning during a slow `op read`. The shared opCache means a value
388
+ // resolved by either the sync or async path serves both, so a warm ref never re-spawns.
389
+
390
+ export async function resolveOpRefAsync(opRef, { opRead = defaultOpReadAsync, env = process.env } = {}) {
391
+ if (opCache.has(opRef)) {
392
+ emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: true, ok: true });
393
+ return opCache.get(opRef);
394
+ }
395
+ try {
396
+ const secret = await opRead(opRef, { env });
397
+ opCache.set(opRef, secret);
398
+ emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: false, ok: true });
399
+ return secret;
400
+ } catch (err) {
401
+ emitAudit({ event: 'secret.op_read', ref: opRef, cacheHit: false, ok: false, code: err?.code });
402
+ throw err;
403
+ }
404
+ }
405
+
406
+ async function materializeAsync(rawValue, opts) {
407
+ const ref = extractOpRef(rawValue);
408
+ return ref ? resolveOpRefAsync(ref, opts) : unquote(rawValue);
409
+ }
410
+
411
+ async function resolveAndAuditAsync(varName, raw, source, opts) {
412
+ const isOpRef = Boolean(extractOpRef(raw));
413
+ try {
414
+ const value = await materializeAsync(raw, opts);
415
+ emitAudit({ event: 'secret.resolve', varName, source, isOpRef, ok: true });
416
+ return value;
417
+ } catch (err) {
418
+ emitAudit({ event: 'secret.resolve', varName, source, isOpRef, ok: false, code: err?.code });
419
+ throw err;
420
+ }
421
+ }
422
+
423
+ export async function resolveSecretAsync(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true, opRead } = {}) {
424
+ const direct = env?.[varName];
425
+ const hasDirect = typeof direct === 'string' && direct.length > 0;
426
+
427
+ if (!allowAmbient) {
428
+ if (hasDirect) return resolveAndAuditAsync(varName, direct, 'env', { opRead, env });
429
+ emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
430
+ return null;
431
+ }
432
+
433
+ const home = os.homedir();
434
+ const found = rawCandidate(varName, { env, cwd, home });
435
+
436
+ if (found) {
437
+ if (hasDirect && preferInjectedCredential(direct, found.raw)) {
438
+ return resolveAndAuditAsync(varName, direct, 'env', { opRead, env });
439
+ }
440
+ return resolveAndAuditAsync(varName, found.raw, found.source, { opRead, env });
441
+ }
442
+
443
+ if (hasDirect) return resolveAndAuditAsync(varName, direct, 'env', { opRead, env });
444
+ emitAudit({ event: 'secret.resolve', varName, source: null, isOpRef: false, ok: false });
445
+ return null;
446
+ }
447
+
243
448
  // Presence check that never runs `op read`: a stored op:// reference counts as
244
- // configured because the plaintext is resolved lazily at call time.
449
+ // configured because the plaintext is resolved lazily at call time. `home`
450
+ // defaults to os.homedir() but is overridable so a status/detection caller can
451
+ // point at an injected homeDir without mutating process.env.HOME.
245
452
 
246
- export function hasSecret(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true } = {}) {
453
+ export function hasSecret(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true, home } = {}) {
247
454
  const direct = env?.[varName];
248
455
  if (typeof direct === 'string' && direct.length > 0) return true;
249
456
  if (!allowAmbient) return false;
250
- const home = os.homedir();
251
- const found = rawCandidate(varName, { env, cwd, home });
457
+ const resolvedHome = home || os.homedir();
458
+ const found = rawCandidate(varName, { env, cwd, home: resolvedHome });
252
459
  return Boolean(found && typeof found.raw === 'string' && found.raw.length > 0);
253
460
  }
254
461
 
@@ -256,6 +463,31 @@ export function hasAnySecret(varNames, opts = {}) {
256
463
  return varNames.some((name) => hasSecret(name, opts));
257
464
  }
258
465
 
466
+ // A non-secret configuration value (a repo slug, a Jira host URL, a Confluence
467
+ // space key) still needs shape-based detection — file/rc presence and its raw
468
+ // text — without ever resolving an op:// reference, so a display-only read
469
+ // shares the same tiers as hasSecret rather than a caller re-implementing
470
+ // dotenv/rc scanning. Returns null when nothing is found; never invokes `op read`.
471
+
472
+ export function peekRawCredential(varName, { env = process.env, cwd = process.cwd(), allowAmbient = true, home } = {}) {
473
+ const direct = env?.[varName];
474
+ if (typeof direct === 'string' && direct.length > 0) return { raw: unquote(direct), source: 'env' };
475
+ if (!allowAmbient) return null;
476
+ const resolvedHome = home || os.homedir();
477
+ const found = rawCandidate(varName, { env, cwd, home: resolvedHome });
478
+ return found ? { raw: unquote(found.raw), source: found.source } : null;
479
+ }
480
+
259
481
  export function __clearSecretCache() {
260
482
  opCache.clear();
261
483
  }
484
+
485
+ // Read-only accessor for the materialized op:// cache. Callers that redact
486
+ // durable output (lib/audit-trail.mjs) need to compare candidate strings
487
+ // against known-resolved secret values without this module logging or
488
+ // otherwise changing its own resolution behavior; this returns a snapshot
489
+ // array, never the live Map, so a consumer cannot mutate the cache.
490
+
491
+ export function __resolvedSecretValues() {
492
+ return Array.from(opCache.values());
493
+ }
@@ -102,7 +102,7 @@ function stripCoverDuplicatesFromBody(body, metadata = {}) {
102
102
  out = out.replace(h1Re, '');
103
103
  }
104
104
  const lines = out.split('\n');
105
- while (lines.length && /^-\s+\*\*(Date|Owner|Status)\*\*:/i.test(lines[0].trim())) {
105
+ while (lines.length && /^-\s+\*\*(Date|Owner|Status):?\*\*:?/i.test(lines[0].trim())) {
106
106
  lines.shift();
107
107
  }
108
108
  while (lines.length && lines[0].trim() === '') lines.shift();
@@ -135,9 +135,12 @@ export function parseArtifactMetadata(inputPath) {
135
135
  }
136
136
  }
137
137
 
138
+ // Authors write both `**Date**: x` and `**Date:** x`; the contract accepts
139
+ // either so the masthead never silently loses a field to colon placement.
140
+
138
141
  const h1 = body.match(/^#\s+(.+)$/m);
139
- const ownerMatch = body.match(/\*\*Owner\*\*:\s*(\S+)/);
140
- const dateMatch = body.match(/\*\*Date\*\*:\s*(\S+)/);
142
+ const ownerMatch = body.match(/\*\*Owner:?\*\*:?\s*(\S+)/i);
143
+ const dateMatch = body.match(/\*\*Date:?\*\*:?\s*(\S+)/i);
141
144
 
142
145
  return {
143
146
  title: fm.title || (h1 ? h1[1].trim() : path.basename(inputPath, path.extname(inputPath))),
@@ -14,6 +14,7 @@ import { detect as detectExport } from './document-export.mjs';
14
14
  import { locateRenderer } from './diagram.mjs';
15
15
  import { locateRecorder } from './demo.mjs';
16
16
  import { detectIngestPipeline } from './ingest-tooling.mjs';
17
+ import { resolvePuppeteerExecutable } from './diagram-export.mjs';
17
18
 
18
19
  const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
20
 
@@ -32,11 +33,13 @@ export function detectFigureBinaries(env = process.env) {
32
33
  const d2 = whichBin('d2');
33
34
  const mmdc = whichBin('mmdc');
34
35
  const dot = whichBin('dot');
36
+ const chrome = resolvePuppeteerExecutable(env);
35
37
  const filter = fs.existsSync(diagramFilterPath());
36
38
  const missing = [];
37
39
  if (!filter) missing.push('diagram.lua (vendor/pandoc-ext/diagram.lua)');
38
40
  if (!d2 && !dot) missing.push('d2 or graphviz dot');
39
41
  if (!mmdc) missing.push('mmdc (@mermaid-js/mermaid-cli)');
42
+ if (mmdc && !chrome) missing.push('Chrome for mermaid-cli (set PUPPETEER_EXECUTABLE_PATH or install Google Chrome)');
40
43
  return {
41
44
  ok: true,
42
45
  present: missing.length === 0,
@@ -44,6 +47,7 @@ export function detectFigureBinaries(env = process.env) {
44
47
  d2,
45
48
  dot,
46
49
  mmdc,
50
+ chrome,
47
51
  missing,
48
52
  message: missing.length === 0
49
53
  ? 'Figure tooling ready (pandoc-ext/diagram + d2/mmdc)'
package/lib/publish.mjs CHANGED
@@ -193,13 +193,21 @@ export function runPublish({
193
193
  }
194
194
  }
195
195
 
196
+ const exportOk = ledger.export?.ok !== false;
197
+ const validationOk = ledger.validation?.ok !== false;
198
+ const relOutput = ledger.export?.outputPath ? path.relative(cwd, ledger.export.outputPath) : null;
199
+
196
200
  return {
197
- ok: ledger.export?.ok !== false,
201
+ ok: exportOk && validationOk,
198
202
  ledger,
199
203
  message: ledger.export?.skipped
200
204
  ? 'Publish source-only complete'
201
- : ledger.export?.ok
205
+ : exportOk && validationOk
202
206
  ? `Published ${path.relative(cwd, ledger.export.outputPath)}`
207
+ : !validationOk && relOutput
208
+ ? preview
209
+ ? `Preview rendered with validation failures: ${relOutput}`
210
+ : `Publish blocked: output validation failed for ${relOutput}`
203
211
  : ledger.export?.message || detection.message,
204
212
  };
205
213
  }
@@ -235,12 +243,17 @@ Examples:
235
243
  `);
236
244
  }
237
245
 
238
- export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot } = {}) {
246
+ // Every flag the publish spec declares maps to a behavior here; an unrecognized
247
+ // `--flag` is collected into `unknown` so the CLI can warn instead of silently
248
+ // dropping it (the pre-fix parser discarded --recording/--figures without a trace).
249
+
250
+ export function parsePublishArgs(argv = []) {
239
251
  const options = {
240
252
  format: 'pdf',
241
253
  output: null,
242
254
  type: null,
243
255
  demos: [],
256
+ recordings: [],
244
257
  figures: true,
245
258
  strict: true,
246
259
  gate: true,
@@ -249,6 +262,7 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
249
262
  preview: false,
250
263
  help: false,
251
264
  input: null,
265
+ unknown: [],
252
266
  };
253
267
 
254
268
  for (let i = 0; i < argv.length; i += 1) {
@@ -257,6 +271,7 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
257
271
  if (arg === '--strict') { options.strict = true; continue; }
258
272
  if (arg === '--no-strict') { options.strict = false; continue; }
259
273
  if (arg === '--source-only') { options.sourceOnly = true; continue; }
274
+ if (arg === '--figures') { options.figures = true; continue; }
260
275
  if (arg === '--no-figures') { options.figures = false; continue; }
261
276
  if (arg === '--no-gate') { options.gate = false; continue; }
262
277
  if (arg === '--preview') { options.preview = true; continue; }
@@ -269,8 +284,17 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
269
284
  if (arg.startsWith('--type=')) { options.type = arg.slice(7); continue; }
270
285
  if (arg === '--demo') { options.demos.push(argv[++i]); continue; }
271
286
  if (arg.startsWith('--demo=')) { options.demos.push(arg.slice(7)); continue; }
272
- if (!arg.startsWith('--')) options.input = arg;
287
+ if (arg === '--recording') { options.recordings.push(argv[++i]); continue; }
288
+ if (arg.startsWith('--recording=')) { options.recordings.push(arg.slice('--recording='.length)); continue; }
289
+ if (!arg.startsWith('--')) { options.input = arg; continue; }
290
+ options.unknown.push(arg);
273
291
  }
292
+ return options;
293
+ }
294
+
295
+ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot } = {}) {
296
+ const options = parsePublishArgs(argv);
297
+ for (const flag of options.unknown) console.error(`[publish] Ignoring unknown flag: ${flag}`);
274
298
 
275
299
  if (options.help) {
276
300
  printPublishHelp();
@@ -299,6 +323,7 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
299
323
  format: options.format,
300
324
  outputPath: options.output,
301
325
  demos: options.demos,
326
+ recordings: options.recordings,
302
327
  figures: options.figures,
303
328
  strict: options.strict,
304
329
  gate: options.gate,
@@ -346,6 +371,9 @@ export async function runPublishCli(argv = [], { cwd = process.cwd(), repoRoot }
346
371
  }
347
372
 
348
373
  console.error(result.message);
374
+ if (result.ledger?.validation?.ok === false) {
375
+ for (const failure of result.ledger.validation.failures || []) console.error(` - ${failure}`);
376
+ }
349
377
  const exitCode = result.strict && (result.missing?.length || result.gateBlocked) ? 2 : 1;
350
378
  return { exitCode, result };
351
379
  }