@geraldmaron/construct 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (519) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1027 -64
  4. package/examples/distribution/sources/deck-one-pager.md +1 -1
  5. package/lib/acp/server.mjs +21 -7
  6. package/lib/adapters-sync.mjs +2 -1
  7. package/lib/audit-trail.mjs +185 -2
  8. package/lib/beads/auto-close.mjs +2 -1
  9. package/lib/beads/drift.mjs +2 -1
  10. package/lib/bridges/copilot-proxy.mjs +20 -5
  11. package/lib/certification/skill-inventory.mjs +10 -1
  12. package/lib/cli/approvals.mjs +147 -0
  13. package/lib/cli-commands.mjs +105 -14
  14. package/lib/comment-lint.mjs +127 -8
  15. package/lib/config/schema.mjs +6 -29
  16. package/lib/config/source-target-registry.mjs +70 -0
  17. package/lib/config/source-targets.mjs +179 -205
  18. package/lib/contracts/coverage.mjs +77 -0
  19. package/lib/contracts/validate.mjs +102 -13
  20. package/lib/contracts/violation-log.mjs +50 -4
  21. package/lib/db/migrate.mjs +69 -0
  22. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  23. package/lib/db/migrations/002_queue_provider.sql +50 -0
  24. package/lib/db/migrations/003_worker_registry.sql +17 -0
  25. package/lib/db/migrations/004_trace_events.sql +16 -0
  26. package/lib/db/migrations/005_shared_memory.sql +15 -0
  27. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  28. package/lib/decisions/enforced-baseline.json +0 -2
  29. package/lib/decisions/registry.mjs +3 -2
  30. package/lib/deployment/parity-contract.mjs +1 -1
  31. package/lib/deployment-mode.mjs +78 -2
  32. package/lib/diagram-export.mjs +10 -1
  33. package/lib/distill.mjs +5 -2
  34. package/lib/docs-verify.mjs +2 -1
  35. package/lib/doctor/cli.mjs +1 -0
  36. package/lib/doctor/command-on-path.mjs +24 -0
  37. package/lib/doctor/diagnosis.mjs +89 -0
  38. package/lib/doctor/embedding-health.mjs +50 -0
  39. package/lib/doctor/engine-health.mjs +93 -0
  40. package/lib/doctor/graph-validate.mjs +47 -0
  41. package/lib/doctor/index.mjs +18 -4
  42. package/lib/doctor/sidecar-providers.mjs +56 -0
  43. package/lib/doctor/watchers/consistency.mjs +93 -1
  44. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  45. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  46. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  47. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  48. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  49. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  50. package/lib/document-export.mjs +52 -27
  51. package/lib/document-extract/docling-client.mjs +9 -5
  52. package/lib/document-extract/docling-sidecar.py +30 -0
  53. package/lib/document-extract.mjs +1 -1
  54. package/lib/document-ingest.mjs +9 -0
  55. package/lib/embed/approval-queue.mjs +184 -88
  56. package/lib/embed/authority-guard.mjs +65 -2
  57. package/lib/embed/capability-jobs.mjs +365 -0
  58. package/lib/embed/capability-lifecycle.mjs +219 -0
  59. package/lib/embed/capability-loader.mjs +303 -0
  60. package/lib/embed/capability-runtime.mjs +58 -0
  61. package/lib/embed/cli.mjs +185 -8
  62. package/lib/embed/config.mjs +4 -0
  63. package/lib/embed/daemon.mjs +125 -6
  64. package/lib/embed/demand-fetch.mjs +190 -54
  65. package/lib/embed/inbox.mjs +12 -10
  66. package/lib/embed/presets/ops-triage.mjs +236 -0
  67. package/lib/embed/presets/pm-feedback.mjs +341 -0
  68. package/lib/embed/presets/tpm.mjs +401 -0
  69. package/lib/embed/providers/jira.mjs +72 -1
  70. package/lib/embed/providers/registry.mjs +140 -33
  71. package/lib/embedded-contract/capability.mjs +4 -0
  72. package/lib/embedded-contract/execution.mjs +1 -1
  73. package/lib/embedded-contract/model-resolve.mjs +53 -3
  74. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  75. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  76. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  77. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  78. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  79. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  80. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  81. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  82. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  83. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  84. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  85. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  86. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  88. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  90. package/lib/env-config.mjs +50 -9
  91. package/lib/extensions/index.mjs +27 -0
  92. package/lib/extensions/loader.mjs +120 -0
  93. package/lib/extensions/manifest-schema.mjs +141 -0
  94. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  95. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  96. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  97. package/lib/extensions/manifests/directory.manifest.json +12 -0
  98. package/lib/extensions/manifests/docling.manifest.json +37 -0
  99. package/lib/extensions/manifests/echo.manifest.json +8 -0
  100. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  101. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  102. package/lib/extensions/manifests/github.manifest.json +52 -0
  103. package/lib/extensions/manifests/linear.manifest.json +50 -0
  104. package/lib/extensions/manifests/local.manifest.json +12 -0
  105. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  106. package/lib/extensions/manifests/openai.manifest.json +12 -0
  107. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  108. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  109. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  110. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  113. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  114. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  115. package/lib/extensions/manifests/slack.manifest.json +58 -0
  116. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  117. package/lib/extensions/validate.mjs +175 -0
  118. package/lib/features.mjs +13 -9
  119. package/lib/flows/checkpoint.mjs +184 -0
  120. package/lib/flows/constants.mjs +27 -0
  121. package/lib/flows/define.mjs +146 -0
  122. package/lib/flows/engine.mjs +249 -0
  123. package/lib/flows/errors.mjs +19 -0
  124. package/lib/flows/index.mjs +27 -0
  125. package/lib/flows/joins.mjs +18 -0
  126. package/lib/flows/schema.mjs +90 -0
  127. package/lib/flows/state.mjs +31 -0
  128. package/lib/frameworks/loader.mjs +99 -0
  129. package/lib/frameworks/schema.mjs +157 -0
  130. package/lib/graph/build-from-corpus.mjs +67 -0
  131. package/lib/graph/build-from-embed.mjs +82 -0
  132. package/lib/graph/build-from-registry.mjs +164 -3
  133. package/lib/graph/cli.mjs +456 -12
  134. package/lib/graph/gap-queries.mjs +156 -0
  135. package/lib/graph/gaps.mjs +41 -0
  136. package/lib/graph/impacted.mjs +129 -0
  137. package/lib/graph/runtime-evidence.mjs +177 -0
  138. package/lib/graph/security-coverage.mjs +113 -0
  139. package/lib/graph/staleness.mjs +241 -10
  140. package/lib/graph/store.mjs +24 -7
  141. package/lib/graph/validate.mjs +161 -0
  142. package/lib/headhunt.mjs +9 -8
  143. package/lib/health-check.mjs +15 -7
  144. package/lib/hook-health.mjs +1 -1
  145. package/lib/hooks/agent-tracker.mjs +10 -4
  146. package/lib/hooks/edit-guard.mjs +3 -3
  147. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  148. package/lib/hooks/guard-bash.mjs +41 -15
  149. package/lib/hooks/mcp-health-check.mjs +11 -4
  150. package/lib/hooks/model-fallback.mjs +50 -6
  151. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  152. package/lib/hooks/pre-compact.mjs +181 -173
  153. package/lib/hooks/rule-verifier.mjs +2 -1
  154. package/lib/hooks/session-reflect.mjs +2 -1
  155. package/lib/hooks/session-start.mjs +3 -3
  156. package/lib/host-capabilities.mjs +13 -2
  157. package/lib/host-disposition.mjs +19 -7
  158. package/lib/identity.mjs +92 -0
  159. package/lib/ingest/degraded-extract.mjs +96 -0
  160. package/lib/ingest/docling-remote.mjs +2 -1
  161. package/lib/ingest/provider-extract.mjs +7 -19
  162. package/lib/ingest/sidecar-providers.mjs +196 -0
  163. package/lib/ingest-tooling.mjs +11 -5
  164. package/lib/init-unified.mjs +55 -38
  165. package/lib/init-update.mjs +2 -1
  166. package/lib/install/legacy-global-cleanup.mjs +25 -0
  167. package/lib/intake/daemon.mjs +99 -8
  168. package/lib/intake/git-queue.mjs +110 -38
  169. package/lib/intake/queue-registry.mjs +50 -0
  170. package/lib/intake/queue.mjs +133 -17
  171. package/lib/intake/session-prelude.mjs +57 -8
  172. package/lib/integrations/intake-integrations.mjs +61 -111
  173. package/lib/intent-classifier.mjs +43 -5
  174. package/lib/libreoffice-export.mjs +8 -8
  175. package/lib/logging/rotate.mjs +5 -4
  176. package/lib/mcp/broker.mjs +332 -17
  177. package/lib/mcp/denied-store.mjs +95 -0
  178. package/lib/mcp/destructive-gate.mjs +30 -0
  179. package/lib/mcp/dispatch-envelope.mjs +199 -0
  180. package/lib/mcp/memory-bridge.mjs +2 -1
  181. package/lib/mcp/server.mjs +154 -1411
  182. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  183. package/lib/mcp/tool-definitions-project.mjs +271 -0
  184. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  185. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  186. package/lib/mcp/tool-definitions.mjs +25 -0
  187. package/lib/mcp/tool-registry.mjs +107 -0
  188. package/lib/mcp/tool-safety.mjs +1 -0
  189. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  190. package/lib/mcp/tools/orchestration-run.mjs +165 -25
  191. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  192. package/lib/mcp/tools/provider-write.mjs +187 -0
  193. package/lib/mcp/tools/scope.mjs +0 -3
  194. package/lib/mcp/tools/skills.mjs +1 -1
  195. package/lib/mcp/tools/storage.mjs +0 -8
  196. package/lib/mcp/transport/auth.mjs +238 -0
  197. package/lib/mcp/transport/http.mjs +136 -0
  198. package/lib/mcp/transport/mode.mjs +31 -0
  199. package/lib/mcp/transport/stdio.mjs +22 -0
  200. package/lib/mcp-catalog.json +4 -4
  201. package/lib/mcp-manager.mjs +34 -23
  202. package/lib/mcp-platform-config.mjs +31 -7
  203. package/lib/mode-capabilities.mjs +109 -0
  204. package/lib/model-router.mjs +42 -9
  205. package/lib/models/catalog.mjs +40 -1
  206. package/lib/net-guard.mjs +247 -0
  207. package/lib/observation-store.mjs +6 -2
  208. package/lib/ollama/provision-context.mjs +2 -1
  209. package/lib/opencode-config.mjs +22 -3
  210. package/lib/opencode-runtime-plugin.mjs +120 -2
  211. package/lib/oracle/actions.mjs +204 -10
  212. package/lib/oracle/cli.mjs +24 -3
  213. package/lib/oracle/dispatch.mjs +2 -1
  214. package/lib/oracle/execute.mjs +2 -2
  215. package/lib/oracle/gaps.mjs +3 -3
  216. package/lib/oracle/heartbeat.mjs +53 -0
  217. package/lib/oracle/read-model.mjs +69 -13
  218. package/lib/oracle/reconcile.mjs +3 -46
  219. package/lib/oracle/routing.mjs +37 -31
  220. package/lib/oracle/synthesize.mjs +1 -1
  221. package/lib/orchestration/classification.mjs +434 -0
  222. package/lib/orchestration/delegation-flow.mjs +132 -0
  223. package/lib/orchestration/flow-selection.mjs +418 -0
  224. package/lib/orchestration/gates.mjs +244 -0
  225. package/lib/orchestration/host-sampling.mjs +121 -0
  226. package/lib/orchestration/policy-constants.mjs +48 -0
  227. package/lib/orchestration/provider-outcome.mjs +197 -0
  228. package/lib/orchestration/readiness.mjs +114 -13
  229. package/lib/orchestration/run-store-postgres.mjs +32 -26
  230. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  231. package/lib/orchestration/run-store.mjs +25 -12
  232. package/lib/orchestration/runtime.mjs +446 -39
  233. package/lib/orchestration/store.mjs +87 -12
  234. package/lib/orchestration/trace-store.mjs +125 -0
  235. package/lib/orchestration/web-capability.mjs +3 -2
  236. package/lib/orchestration/worker-runtime.mjs +162 -0
  237. package/lib/orchestration/worker.mjs +528 -89
  238. package/lib/orchestration-policy.mjs +67 -1111
  239. package/lib/packs/cli.mjs +144 -0
  240. package/lib/packs/core-pack.mjs +112 -0
  241. package/lib/packs/enablement.mjs +187 -0
  242. package/lib/packs/index.mjs +23 -0
  243. package/lib/packs/loader.mjs +176 -0
  244. package/lib/packs/manifest-schema.mjs +58 -0
  245. package/lib/packs/prompts.mjs +120 -0
  246. package/lib/packs/validate.mjs +208 -0
  247. package/lib/plugin-registry.mjs +4 -5
  248. package/lib/policy/audit-gate.mjs +42 -0
  249. package/lib/policy/consumption-budget.mjs +149 -0
  250. package/lib/policy/engine.mjs +81 -6
  251. package/lib/policy/role-authority.mjs +89 -0
  252. package/lib/prompt-composer.js +19 -3
  253. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  254. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  255. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  256. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  257. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  258. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  259. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  260. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  261. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  262. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  263. package/lib/providers/contract.mjs +207 -0
  264. package/lib/providers/credential-bootstrap.mjs +7 -4
  265. package/lib/providers/credential-sources.mjs +14 -2
  266. package/lib/providers/directory/index.mjs +261 -0
  267. package/lib/providers/feedback/index.mjs +392 -0
  268. package/lib/providers/filter-audit.mjs +68 -0
  269. package/lib/providers/filter-schema.mjs +54 -0
  270. package/lib/providers/github/index.mjs +31 -0
  271. package/lib/providers/instance-config.mjs +215 -0
  272. package/lib/providers/op-locate.mjs +96 -0
  273. package/lib/providers/op-run.mjs +64 -9
  274. package/lib/providers/registry.mjs +26 -3
  275. package/lib/providers/secret-audit-wiring.mjs +6 -0
  276. package/lib/providers/secret-resolver.mjs +240 -23
  277. package/lib/publish-template.mjs +6 -3
  278. package/lib/publish-tooling.mjs +4 -0
  279. package/lib/publish.mjs +32 -4
  280. package/lib/queue/pg-queue.mjs +395 -0
  281. package/lib/reflect.mjs +2 -1
  282. package/lib/registry/assemble.mjs +28 -2
  283. package/lib/registry/consolidation.mjs +8 -1
  284. package/lib/registry/custom-scaffold.mjs +200 -0
  285. package/lib/registry/custom-schema.mjs +137 -0
  286. package/lib/registry/loader.mjs +8 -3
  287. package/lib/registry/manifests/format-engines.default.json +15 -0
  288. package/lib/registry/manifests/surface-map.default.json +46 -0
  289. package/lib/registry/retired-paths.mjs +1 -0
  290. package/lib/registry/surface-map.mjs +100 -50
  291. package/lib/render-visual-check.mjs +240 -0
  292. package/lib/resources/budget.mjs +40 -17
  293. package/lib/roles/flavor-bindings.mjs +36 -14
  294. package/lib/roles/gateway.mjs +15 -8
  295. package/lib/roots.mjs +107 -0
  296. package/lib/runtime/uv-bootstrap.mjs +32 -6
  297. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  298. package/lib/runtime-env.mjs +5 -2
  299. package/lib/scheduler/index.mjs +8 -20
  300. package/lib/schema-infer.mjs +31 -2
  301. package/lib/scopes/lifecycle.mjs +29 -25
  302. package/lib/scopes/loader.mjs +49 -12
  303. package/lib/scopes/teams.mjs +1 -1
  304. package/lib/security/ingest-boundary.mjs +80 -0
  305. package/lib/security/recall-wrapper.mjs +99 -0
  306. package/lib/security/trust.mjs +132 -0
  307. package/lib/service-manager.mjs +38 -19
  308. package/lib/setup.mjs +41 -23
  309. package/lib/skills-apply.mjs +4 -2
  310. package/lib/specialists/postconditions.mjs +2 -2
  311. package/lib/state-root.mjs +147 -0
  312. package/lib/status.mjs +597 -6
  313. package/lib/storage/admin.mjs +77 -5
  314. package/lib/storage/backend-registry.mjs +73 -0
  315. package/lib/storage/backend.mjs +63 -9
  316. package/lib/storage/embeddings-openai.mjs +12 -2
  317. package/lib/storage/hybrid-query.mjs +11 -3
  318. package/lib/storage/retrieval-hardening.mjs +384 -0
  319. package/lib/storage/shared-memory.mjs +158 -0
  320. package/lib/storage/vector-client.mjs +69 -2
  321. package/lib/team/health.mjs +72 -0
  322. package/lib/telemetry/backends/local.mjs +9 -3
  323. package/lib/telemetry/client.mjs +3 -7
  324. package/lib/template-registry.mjs +10 -12
  325. package/lib/tenant/context.mjs +82 -0
  326. package/lib/tenant/isolation.mjs +129 -0
  327. package/lib/test-corpus-inventory.mjs +103 -2
  328. package/lib/uninstall/uninstall.mjs +78 -12
  329. package/lib/validator.mjs +4 -6
  330. package/lib/worker/entrypoint.mjs +2 -1
  331. package/lib/worker/run.mjs +2 -2
  332. package/lib/worker/trace.mjs +5 -11
  333. package/lib/workflow-state.mjs +52 -8
  334. package/lib/workflows/liveness.mjs +203 -0
  335. package/lib/workflows/loader.mjs +177 -0
  336. package/lib/workflows/manifest-schema.mjs +71 -0
  337. package/lib/workflows/surface-parity.mjs +118 -0
  338. package/lib/workflows/validate.mjs +127 -0
  339. package/lib/writes/control-plane.mjs +140 -0
  340. package/lib/writes/envelope.mjs +206 -0
  341. package/lib/writes/sent-log.mjs +86 -0
  342. package/lib/writes/write-intent.mjs +109 -0
  343. package/package.json +16 -8
  344. package/personas/construct.md +12 -3
  345. package/registry/capabilities.json +143 -36
  346. package/rules/common/comments.md +1 -0
  347. package/schemas/project-config.schema.json +0 -12
  348. package/schemas/unified-registry.schema.json +2 -4
  349. package/scripts/sync-specialists.mjs +284 -81
  350. package/skills/docs/adr-workflow.md +1 -1
  351. package/skills/docs/codebase-research-workflow.md +3 -3
  352. package/skills/docs/prd-workflow.md +5 -6
  353. package/skills/docs/runbook-workflow.md +2 -2
  354. package/skills/docs/user-research-workflow.md +3 -3
  355. package/skills/operating/fleet-health-routing.md +77 -0
  356. package/skills/roles/ai-engineer.md +1 -1
  357. package/skills/roles/architect.md +0 -1
  358. package/skills/roles/business-strategist.md +1 -1
  359. package/skills/roles/data-analyst.telemetry.md +1 -1
  360. package/skills/roles/data-engineer.md +1 -1
  361. package/skills/roles/data-engineer.pipeline.md +1 -1
  362. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  363. package/skills/roles/data-engineer.warehouse.md +1 -1
  364. package/skills/roles/designer.accessibility.md +1 -1
  365. package/skills/roles/designer.md +0 -1
  366. package/skills/roles/devil-advocate.md +1 -1
  367. package/skills/roles/docs-keeper.md +1 -1
  368. package/skills/roles/evaluator.md +1 -1
  369. package/skills/roles/explorer.md +1 -1
  370. package/skills/roles/platform-engineer.md +1 -1
  371. package/skills/roles/qa.ai-eval.md +1 -2
  372. package/skills/roles/qa.api-contract.md +0 -1
  373. package/skills/roles/qa.data-pipeline.md +0 -1
  374. package/skills/roles/qa.md +0 -1
  375. package/skills/roles/qa.web-ui.md +0 -1
  376. package/skills/roles/release-manager.md +1 -1
  377. package/skills/roles/researcher.md +0 -2
  378. package/skills/roles/reviewer.md +0 -3
  379. package/skills/roles/security.ai.md +1 -1
  380. package/skills/roles/security.legal-compliance.md +1 -1
  381. package/skills/roles/security.md +0 -1
  382. package/skills/roles/security.privacy.md +0 -1
  383. package/skills/roles/security.supply-chain.md +1 -1
  384. package/skills/roles/sre.md +1 -1
  385. package/skills/roles/test-automation.md +1 -1
  386. package/skills/roles/trace-reviewer.md +1 -1
  387. package/skills/roles/ux-researcher.md +1 -1
  388. package/specialists/artifact-manifest.json +36 -36
  389. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  390. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  391. package/specialists/org/contracts/any-to-debugger.json +7 -1
  392. package/specialists/org/contracts/any-to-designer.json +7 -1
  393. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  394. package/specialists/org/contracts/any-to-explorer.json +13 -4
  395. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  396. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  397. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  398. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  399. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  400. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  401. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  402. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  403. package/specialists/org/contracts/architect-to-operations.json +17 -4
  404. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  405. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  406. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  407. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  408. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  409. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  410. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  411. package/specialists/org/contracts/operations-to-user.json +53 -0
  412. package/specialists/org/contracts/operations-triage-output.json +53 -0
  413. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  414. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  415. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  416. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  417. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  418. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  419. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  420. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  421. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  422. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  423. package/specialists/org/contracts/user-to-construct.json +11 -4
  424. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  425. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  426. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  427. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  428. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  429. package/specialists/org/groups/engineering-group.json +2 -6
  430. package/specialists/org/groups/governance-group.json +2 -3
  431. package/specialists/org/groups/operations-group.json +5 -8
  432. package/specialists/org/groups/product-group.json +4 -8
  433. package/specialists/org/groups/quality-group.json +3 -7
  434. package/specialists/org/groups/strategy-group.json +6 -9
  435. package/specialists/org/models.json.example +8 -0
  436. package/specialists/org/scopes/creative.json +6 -1
  437. package/specialists/org/scopes/operations.json +7 -1
  438. package/specialists/org/scopes/research.json +6 -1
  439. package/specialists/org/scopes/rnd.json +7 -1
  440. package/specialists/org/specialists/cx-architect.json +27 -8
  441. package/specialists/org/specialists/cx-designer.json +22 -8
  442. package/specialists/org/specialists/cx-engineer.json +71 -7
  443. package/specialists/org/specialists/cx-operations.json +89 -15
  444. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  445. package/specialists/org/specialists/cx-product-manager.json +28 -9
  446. package/specialists/org/specialists/cx-qa.json +16 -6
  447. package/specialists/org/specialists/cx-researcher.json +40 -7
  448. package/specialists/org/specialists/cx-reviewer.json +61 -11
  449. package/specialists/org/specialists/cx-security.json +30 -10
  450. package/specialists/org/teams/design-team.json +3 -4
  451. package/specialists/org/teams/engineering-team.json +3 -10
  452. package/specialists/org/teams/governance-team.json +3 -5
  453. package/specialists/org/teams/operations-team.json +6 -12
  454. package/specialists/org/teams/product-management-team.json +2 -3
  455. package/specialists/org/teams/quality-team.json +4 -12
  456. package/specialists/org/teams/research-team.json +3 -3
  457. package/specialists/org/teams/strategy-team.json +7 -14
  458. package/specialists/prompts/cx-architect.md +20 -1
  459. package/specialists/prompts/cx-data-analyst.md +1 -1
  460. package/specialists/prompts/cx-designer.md +12 -4
  461. package/specialists/prompts/cx-engineer.md +15 -1
  462. package/specialists/prompts/cx-operations.md +14 -2
  463. package/specialists/prompts/cx-orchestrator.md +9 -5
  464. package/specialists/prompts/cx-product-manager.md +5 -1
  465. package/specialists/prompts/cx-qa.md +6 -2
  466. package/specialists/prompts/cx-researcher.md +25 -30
  467. package/specialists/prompts/cx-reviewer.md +19 -1
  468. package/specialists/prompts/cx-security.md +5 -1
  469. package/templates/distribution/construct-brand.typ +123 -59
  470. package/templates/distribution/construct-decision.typ +6 -3
  471. package/templates/distribution/construct-pdf.typ +11 -4
  472. package/templates/distribution/construct-prd.typ +6 -3
  473. package/templates/distribution/construct-research.typ +7 -4
  474. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  475. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  476. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  477. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  478. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  479. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  480. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  481. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  482. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  483. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  484. package/specialists/org/specialists/cx-accessibility.json +0 -69
  485. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  486. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  487. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  488. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  489. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  490. package/specialists/org/specialists/cx-evaluator.json +0 -69
  491. package/specialists/org/specialists/cx-explorer.json +0 -69
  492. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  493. package/specialists/org/specialists/cx-oracle.json +0 -46
  494. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  495. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  496. package/specialists/org/specialists/cx-release-manager.json +0 -77
  497. package/specialists/org/specialists/cx-sre.json +0 -94
  498. package/specialists/org/specialists/cx-test-automation.json +0 -69
  499. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  500. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  501. package/specialists/org/teams/accessibility-team.json +0 -39
  502. package/specialists/org/teams/ux-research-team.json +0 -39
  503. package/specialists/prompts/cx-accessibility.md +0 -55
  504. package/specialists/prompts/cx-ai-engineer.md +0 -124
  505. package/specialists/prompts/cx-business-strategist.md +0 -58
  506. package/specialists/prompts/cx-data-engineer.md +0 -55
  507. package/specialists/prompts/cx-devil-advocate.md +0 -59
  508. package/specialists/prompts/cx-docs-keeper.md +0 -164
  509. package/specialists/prompts/cx-evaluator.md +0 -49
  510. package/specialists/prompts/cx-explorer.md +0 -71
  511. package/specialists/prompts/cx-legal-compliance.md +0 -58
  512. package/specialists/prompts/cx-oracle.md +0 -98
  513. package/specialists/prompts/cx-platform-engineer.md +0 -97
  514. package/specialists/prompts/cx-rd-lead.md +0 -59
  515. package/specialists/prompts/cx-release-manager.md +0 -54
  516. package/specialists/prompts/cx-sre.md +0 -111
  517. package/specialists/prompts/cx-test-automation.md +0 -55
  518. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  519. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -13,12 +13,14 @@
13
13
  import { createIntakeQueue } from './queue.mjs';
14
14
  import { formatTriageLine } from './classify.mjs';
15
15
  import { isBrokered } from '../mcp/broker.mjs';
16
+ import { getDeploymentMode } from '../deployment-mode.mjs';
16
17
  import { getRebrand } from '../scopes/rebrand.mjs';
17
18
  import { homedir as osHomedir } from 'node:os';
18
19
  import { readLastTick } from '../oracle/index.mjs';
19
20
  import { listPending } from '../oracle/actions.mjs';
20
21
  import { readLatestVerdict } from '../oracle/verdicts.mjs';
21
22
  import { isVerdictOnlyGap } from '../oracle/policy.mjs';
23
+ import { heartbeatFreshness, formatAge } from '../oracle/heartbeat.mjs';
22
24
 
23
25
  export function buildIntakePrelude({ cwd, env = process.env } = {}) {
24
26
  if (!cwd) return '';
@@ -41,9 +43,9 @@ export function buildIntakePrelude({ cwd, env = process.env } = {}) {
41
43
  }
42
44
  }
43
45
 
44
- export function buildBrokerStatusLine({ env = process.env } = {}) {
45
- const active = isBrokered(env);
46
- const mode = env?.CONSTRUCT_DEPLOYMENT_MODE || 'solo';
46
+ export function buildBrokerStatusLine({ env = process.env, cwd } = {}) {
47
+ const active = isBrokered(env, { cwd });
48
+ const mode = getDeploymentMode(env, { cwd });
47
49
  if (!active) {
48
50
  return `MCP broker: off · deployment mode: ${mode} (set CONSTRUCT_MCP_BROKER=on to engage in solo mode).`;
49
51
  }
@@ -52,7 +54,7 @@ export function buildBrokerStatusLine({ env = process.env } = {}) {
52
54
 
53
55
  export function buildSessionPrelude({ cwd, env = process.env } = {}) {
54
56
  const intake = buildIntakePrelude({ cwd, env });
55
- const broker = buildBrokerStatusLine({ env });
57
+ const broker = buildBrokerStatusLine({ env, cwd });
56
58
  const oracle = buildOraclePrelude({ cwd, env });
57
59
  if (!intake && !broker && !oracle) return '';
58
60
  const parts = [];
@@ -62,6 +64,26 @@ export function buildSessionPrelude({ cwd, env = process.env } = {}) {
62
64
  return parts.join('\n\n');
63
65
  }
64
66
 
67
+ /**
68
+ * lib/oracle/synthesize.mjs renders gap detail text against a 24h lookback
69
+ * window ("in the last 24h"); a verdict older than that window describes a
70
+ * period disjoint from the present, so the same threshold marks it stale.
71
+ * Missing/unparseable `at` resolves to Infinity (stale, not fresh) rather
72
+ * than defaulting to zero age.
73
+ */
74
+ const VERDICT_STALE_MS = 24 * 60 * 60 * 1000;
75
+
76
+ function verdictFreshness(verdict, now = Date.now()) {
77
+ const at = verdict?.at;
78
+ const parsed = at ? Date.parse(at) : NaN;
79
+ const ageMs = Number.isFinite(parsed) ? now - parsed : Infinity;
80
+ return {
81
+ ageMs,
82
+ stale: !Number.isFinite(ageMs) || ageMs > VERDICT_STALE_MS,
83
+ asOf: Number.isFinite(parsed) ? new Date(parsed).toISOString() : null,
84
+ };
85
+ }
86
+
65
87
  /**
66
88
  * Oracle verdict + pending queue for session-start (mirror doctor warnings).
67
89
  */
@@ -75,7 +97,19 @@ export function buildOraclePrelude({ cwd, env = process.env, homeDir } = {}) {
75
97
  const pending = listPending(cwd).filter((p) => p.status === 'pending');
76
98
  const v = verdict?.verdict ?? last?.verdict ?? 'unknown';
77
99
  if (v === 'healthy' && pending.length === 0) return '';
78
- const lines = [`\n## Oracle overseer · verdict: **${v}**`];
100
+ const source = verdict ?? last;
101
+ const { ageMs, stale, asOf } = verdictFreshness(source);
102
+ const verdictHead = v === 'unknown'
103
+ ? `\n## Oracle overseer · verdict: **${v}**`
104
+ : `\n## Oracle overseer · verdict: **${v}** (as of ${asOf ?? 'unknown'}, ${formatAge(ageMs)} ago)`;
105
+ const lines = [verdictHead];
106
+ if (v !== 'unknown' && stale) {
107
+ lines.push(`- STALE: verdict is older than ${formatAge(VERDICT_STALE_MS)}; gap details below describe the verdict date, not now.`);
108
+ }
109
+ const heartbeat = heartbeatFreshness(last);
110
+ if (heartbeat.stale && pending.length > 0) {
111
+ lines.push(`- [high] oracle-producer-stalled: no oracle tick in ${formatAge(heartbeat.ageMs)} while ${pending.length} approval(s) wait — the daemon looks stalled, not idle; run \`construct oracle status\` / \`construct oracle start\`.`);
112
+ }
79
113
  const gapSource = verdict?.gaps ?? last?.gaps ?? [];
80
114
  const top = gapSource.filter((g) => g.severity === 'high').slice(0, 3);
81
115
  for (const g of top) {
@@ -83,7 +117,7 @@ export function buildOraclePrelude({ cwd, env = process.env, homeDir } = {}) {
83
117
  const hint = g.id === 'beads-hygiene'
84
118
  ? 'run `construct beads drift`'
85
119
  : g.id === 'workflow-misaligned'
86
- ? 'run `construct workflow init` or `construct init`'
120
+ ? 'run `construct workflow new` or `construct init`'
87
121
  : 'see `construct oracle gaps`';
88
122
  lines.push(`- [${g.severity}] ${g.id} (verdict-only): ${g.detail} — ${hint}`);
89
123
  } else {
@@ -102,7 +136,7 @@ export function buildOraclePrelude({ cwd, env = process.env, homeDir } = {}) {
102
136
 
103
137
  export function readOracleDockState({ cwd, env = process.env, homeDir } = {}) {
104
138
  if (!cwd || env.CONSTRUCT_ORACLE === 'off' || env.CONSTRUCT_ORACLE === '0') {
105
- return { visible: false, verdict: null, pendingCount: 0, topGaps: [], summary: '' };
139
+ return { visible: false, verdict: null, pendingCount: 0, topGaps: [], summary: '', asOf: null, stale: false, producerStalled: false, producerAgeMs: null };
106
140
  }
107
141
  try {
108
142
  const home = homeDir ?? osHomedir();
@@ -113,23 +147,38 @@ export function readOracleDockState({ cwd, env = process.env, homeDir } = {}) {
113
147
  const gapSource = verdictDoc?.gaps ?? last?.gaps ?? [];
114
148
  const topGaps = gapSource.filter((g) => g.severity === 'high').slice(0, 3);
115
149
  const visible = verdict !== 'healthy' || pending.length > 0;
150
+ const { stale, asOf } = verdictFreshness(verdictDoc ?? last);
151
+ const heartbeat = heartbeatFreshness(last);
152
+ const producerStalled = heartbeat.stale && pending.length > 0;
116
153
  const parts = [`verdict ${verdict}`];
117
154
  if (pending.length) parts.push(`${pending.length} pending`);
155
+ if (verdict !== 'unknown' && stale) parts.push('STALE');
156
+ if (producerStalled) parts.push('PRODUCER STALLED');
118
157
  return {
119
158
  visible,
120
159
  verdict,
121
160
  pendingCount: pending.length,
122
161
  topGaps,
123
162
  summary: parts.join(' · '),
163
+ asOf,
164
+ stale: verdict !== 'unknown' && stale,
165
+ producerStalled,
166
+ producerAgeMs: heartbeat.ageMs,
124
167
  };
125
168
  } catch {
126
- return { visible: false, verdict: null, pendingCount: 0, topGaps: [], summary: '' };
169
+ return { visible: false, verdict: null, pendingCount: 0, topGaps: [], summary: '', asOf: null, stale: false, producerStalled: false, producerAgeMs: null };
127
170
  }
128
171
  }
129
172
 
130
173
  export function formatOracleDockDetail(state) {
131
174
  if (!state?.visible) return 'Oracle: healthy — no pending approvals.';
132
175
  const lines = [`Oracle overseer · ${state.summary}`];
176
+ if (state.stale) {
177
+ lines.push(` STALE: verdict as of ${state.asOf ?? 'unknown'}; gap details describe the verdict date, not now.`);
178
+ }
179
+ if (state.producerStalled) {
180
+ lines.push(` PRODUCER STALLED: no oracle tick in ${formatAge(state.producerAgeMs)} while ${state.pendingCount} approval(s) wait — the daemon may be down.`);
181
+ }
133
182
  for (const g of state.topGaps || []) {
134
183
  if (isVerdictOnlyGap(g)) {
135
184
  lines.push(` [${g.severity}] ${g.id} (verdict-only): ${g.detail}`);
@@ -19,7 +19,7 @@ import { join } from 'node:path';
19
19
  import { homedir } from 'node:os';
20
20
  import { spawnSync } from 'node:child_process';
21
21
  import { configDir } from '../config/xdg.mjs';
22
- import { resolveOpRef } from '../providers/secret-resolver.mjs';
22
+ import { resolveOpRef, hasSecret, hasAnySecret, peekRawCredential } from '../providers/secret-resolver.mjs';
23
23
 
24
24
  // ── Multi-method auth helpers ────────────────────────────────────────────
25
25
 
@@ -588,8 +588,13 @@ export function tagIntakeWithExternalRef(rootDir, intakeId, system, externalUrl,
588
588
  }
589
589
 
590
590
  /**
591
- * Synchronous credential resolver for use in constructors and sync contexts.
592
- * Mirrors the async resolveCredential above. Checks env → .env → config.env → shell rc.
591
+ * Credential resolver for the actual publish paths (createGitHubIssue,
592
+ * createJiraTicket, publishArtifactToConfluence) that need a real, materialized
593
+ * value to make an API call. Checks env → .env → config.env → shell rc,
594
+ * resolving a 1Password op:// reference through resolveOpRef when the rc tier
595
+ * carries one. Not for status/detection — detectIntegrationConfig below uses
596
+ * hasSecret/hasAnySecret/peekRawCredential instead, which never resolve op:// or
597
+ * source a shell rc file (ADR-0049 §2: presence-first in short-lived paths).
593
598
  */
594
599
  function resolveCredentialSync(varName, homeDir) {
595
600
  if (process.env[varName]) return process.env[varName];
@@ -640,126 +645,71 @@ function resolveCredentialSync(varName, homeDir) {
640
645
  // ── Configuration detection ──────────────────────────────────────────────
641
646
 
642
647
  /**
643
- * Check which integrations are configured.
644
- * Scopes environment, .env files, ~/.construct/config.env, and shell rc
645
- * files (including 1Password op:// refs).
648
+ * Check which integrations are configured. A status/detection surface must
649
+ * never materialize a credential (ADR-0049 §2: "never to merely list or
650
+ * check") this reports presence by shape only: env var set, a project/config/
651
+ * home .env entry exists, or a shell rc `export VAR=...` line matches. It never
652
+ * calls resolveOpRef and never sources a shell rc file (contrast
653
+ * resolveCredentialSync above, used only when an actual API call needs the
654
+ * real value). `gh auth status` / `gh repo view` are read-only CLI queries,
655
+ * not secret materialization — gh manages its own token internally and never
656
+ * hands Construct a plaintext value here.
646
657
  *
647
658
  * @param {object} [opts]
648
659
  * @param {string} [opts.homeDir]
649
- * @returns {{ github: boolean, jira: boolean, confluence: boolean, githubToken: boolean, jiraToken: boolean, confluenceToken: boolean }}
660
+ * @returns {{ github: boolean, jira: boolean, confluence: boolean, githubMethod: string|null, githubRepo: string|null, jiraHost: string|null, confluenceSpace: string|null }}
650
661
  */
651
662
  export function detectIntegrationConfig({ homeDir } = {}) {
652
663
  const hd = homeDir || homedir();
653
664
 
654
- // GitHub — try gh CLI first, then env vars
655
- const ghAuth = resolveGitHubAuth(hd);
656
-
657
- // Jira
658
- const jiraHost = resolveCredentialSync('JIRA_HOST', hd);
659
- const jiraUser = resolveCredentialSync('JIRA_USER', hd);
660
- const jiraToken = resolveCredentialSync('JIRA_API_TOKEN', hd);
661
- const jiraProject = resolveCredentialSync('JIRA_PROJECT', hd);
662
-
663
- // Confluence
664
- const confHost = resolveCredentialSync('CONFLUENCE_HOST', hd);
665
- const confUser = resolveCredentialSync('CONFLUENCE_USER', hd);
666
- const confToken = resolveCredentialSync('CONFLUENCE_API_TOKEN', hd);
667
- const confSpace = resolveCredentialSync('CONFLUENCE_SPACE', hd);
668
-
669
- return {
670
- github: ghAuth.method === 'gh' || Boolean(ghAuth.token && ghAuth.repo),
671
- githubMethod: ghAuth.method || null,
672
- githubRepo: ghAuth.repo || null,
673
- jira: Boolean(jiraHost && jiraUser && jiraToken && jiraProject),
674
- jiraHost: jiraHost || null,
675
- confluence: Boolean(confHost && confUser && confToken && confSpace),
676
- confluenceSpace: confSpace || null,
677
- };
678
- }
679
-
680
- /**
681
- * Resolve a credential from env, .env files, config.env, or shell rc files.
682
- * Mirrors the approach in lib/health-check.mjs.
683
- */
684
- function resolveCredential(varName, homeDir) {
685
- if (process.env[varName]) return process.env[varName];
686
-
687
- // Project .env
665
+ // GitHub
666
+ let ghCliAuthenticated = false;
688
667
  try {
689
- const projectEnv = join(process.cwd(), '.env');
690
- if (existsSync(projectEnv)) {
691
- const content = readFileSync(projectEnv, 'utf8');
692
- const m = content.match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
693
- if (m) return m[1].trim();
694
- }
695
- } catch { /* skip */ }
668
+ const status = spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', timeout: 5000 });
669
+ ghCliAuthenticated = status.status === 0;
670
+ } catch { /* gh not available */ }
696
671
 
697
- // ~/.env
698
- try {
699
- const homeEnv = join(homeDir, '.env');
700
- if (existsSync(homeEnv)) {
701
- const content = readFileSync(homeEnv, 'utf8');
702
- const m = content.match(new RegExp(`^${varName}=["']?(.+?)["']?$`, 'm'));
703
- if (m) return m[1].trim();
704
- }
705
- } catch { /* skip */ }
672
+ const githubTokenPresent = hasAnySecret(['GITHUB_TOKEN', 'GH_TOKEN'], { home: hd });
673
+ let githubRepo = process.env.GITHUB_REPO || peekRawCredential('GITHUB_REPO', { home: hd })?.raw || null;
674
+ let githubMethod = null;
706
675
 
707
- // config.env in the XDG config dir
708
- try {
709
- const cfgPath = join(configDir(homeDir), 'config.env');
710
- if (existsSync(cfgPath)) {
711
- const content = readFileSync(cfgPath, 'utf8');
712
- const m = content.match(new RegExp(`^${varName}=(.+)$`, 'm'));
713
- if (m) return m[1].trim();
676
+ if (ghCliAuthenticated) {
677
+ githubMethod = 'gh';
678
+ if (!githubRepo) {
679
+ try {
680
+ const remote = spawnSync('gh', ['repo', 'view', '--json', 'nameWithOwner', '--jq', '.nameWithOwner'], { encoding: 'utf8', timeout: 5000 });
681
+ if (remote.status === 0 && remote.stdout?.trim()) githubRepo = remote.stdout.trim();
682
+ } catch { /* gh not available */ }
714
683
  }
715
- } catch { /* skip */ }
684
+ } else if (githubTokenPresent) {
685
+ githubMethod = 'env';
686
+ }
716
687
 
717
- // Shell rc files (including 1Password op:// refs and command substitutions)
718
- try {
719
- const shellFiles = [join(homeDir, '.zshrc'), join(homeDir, '.bashrc'), join(homeDir, '.bash_profile'), join(homeDir, '.profile')];
720
- for (const rcPath of shellFiles) {
721
- if (!existsSync(rcPath)) continue;
722
- try {
723
- const content = readFileSync(rcPath, 'utf8');
724
- // Direct export (simple values only, not command substitutions)
725
- const directRe = new RegExp(`^\\s*export\\s+${varName}="?([^"'\\$\\(]+)"?$`, 'm');
726
- const directMatch = content.match(directRe);
727
- if (directMatch) return directMatch[1].trim();
728
-
729
- // op:// ref — try 1Password CLI
730
- const opRe = new RegExp(`export\\s+${varName}=["']?\\$\\(op read '([^']+)'\\)["']?`, 'm');
731
- const opMatch = content.match(opRe);
732
- if (opMatch) {
733
- // op read failures often mean the rc-file field name doesn't match the
734
- // actual 1Password item field; surface them under the debug flag.
735
- try {
736
- return resolveOpRef(opMatch[1]);
737
- } catch (err) {
738
- if (process.env.CONSTRUCT_DEBUG_CREDENTIALS === '1') {
739
- process.stderr.write(`[credentials] op read failed for ${varName}: ${String(err.message).slice(0, 200)}\n`);
740
- }
741
- }
742
- }
688
+ // Jira
689
+ const jiraConfigured = Boolean(
690
+ hasSecret('JIRA_HOST', { home: hd })
691
+ && hasSecret('JIRA_USER', { home: hd })
692
+ && hasSecret('JIRA_API_TOKEN', { home: hd })
693
+ && hasSecret('JIRA_PROJECT', { home: hd }),
694
+ );
695
+ const jiraHost = peekRawCredential('JIRA_HOST', { home: hd })?.raw || null;
743
696
 
744
- // If the value uses command substitution ($(...)), try evaluating via
745
- // a shell that sources the rc file. This catches any $(...) pattern
746
- // including op read with different field names or other secret managers.
747
- const cmdSubRe = new RegExp(`^export\\s+${varName}="?\\$\\((.+)\\)"?`, 'm');
748
- const cmdMatch = content.match(cmdSubRe);
749
- if (cmdMatch) {
750
- try {
751
- const shellResult = spawnSync('zsh', ['-c', `source "${rcPath}" 2>/dev/null; echo "\${${varName}}"`], {
752
- encoding: 'utf8', timeout: 5000,
753
- });
754
- if (shellResult.status === 0 && shellResult.stdout?.trim()) {
755
- const val = shellResult.stdout.trim().split('\n').pop()?.trim();
756
- if (val && !val.includes('ERROR') && !val.includes('could not read')) return val;
757
- }
758
- } catch { /* shell eval failed */ }
759
- }
760
- } catch { /* skip unreadable */ }
761
- }
762
- } catch { /* skip */ }
697
+ // Confluence
698
+ const confluenceConfigured = Boolean(
699
+ hasSecret('CONFLUENCE_HOST', { home: hd })
700
+ && hasSecret('CONFLUENCE_USER', { home: hd })
701
+ && hasSecret('CONFLUENCE_API_TOKEN', { home: hd })
702
+ && hasSecret('CONFLUENCE_SPACE', { home: hd }),
703
+ );
704
+ const confluenceSpace = peekRawCredential('CONFLUENCE_SPACE', { home: hd })?.raw || null;
763
705
 
764
- return null;
706
+ return {
707
+ github: ghCliAuthenticated || Boolean(githubTokenPresent && githubRepo),
708
+ githubMethod,
709
+ githubRepo,
710
+ jira: jiraConfigured,
711
+ jiraHost,
712
+ confluence: confluenceConfigured,
713
+ confluenceSpace,
714
+ };
765
715
  }
@@ -27,6 +27,7 @@
27
27
  import crypto from 'node:crypto';
28
28
  import { readCurrentModels, readOpenRouterApiKeyFromOpenCodeConfig } from './model-router.mjs';
29
29
  import { getUserEnvPath } from './env-config.mjs';
30
+ import { extractOpRef, hasSecret, resolveOpRefAsync, resolveSecretAsync } from './providers/secret-resolver.mjs';
30
31
  import { logIntentVerification } from './telemetry/intent-verifications.mjs';
31
32
 
32
33
  export const INTENT_VERIFY_TIMEOUT_MS = 3000;
@@ -66,12 +67,49 @@ export function resetCache() {
66
67
  CACHE.clear();
67
68
  }
68
69
 
70
+ async function resolveSecretOrNull(varName, { env = process.env, allowAmbient = true } = {}) {
71
+ try {
72
+ return await resolveSecretAsync(varName, { env, allowAmbient });
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ async function materializeConfiguredKey(rawValue) {
79
+ if (typeof rawValue !== 'string' || rawValue.length === 0) return null;
80
+ const ref = extractOpRef(rawValue);
81
+ try {
82
+ return ref ? await resolveOpRefAsync(ref) : rawValue;
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ async function resolveOpenRouterApiKey() {
89
+ const envKey = await resolveSecretOrNull('OPENROUTER_API_KEY');
90
+ if (envKey) return envKey;
91
+ try {
92
+ return await materializeConfiguredKey(readOpenRouterApiKeyFromOpenCodeConfig());
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
97
+
98
+ async function resolveAnthropicApiKey() {
99
+ return resolveSecretOrNull('ANTHROPIC_API_KEY');
100
+ }
101
+
69
102
  function isApiKeyConfigured() {
70
- if (process.env.ANTHROPIC_API_KEY) return true;
71
- if (process.env.OPENROUTER_API_KEY) return true;
103
+ // Presence gate only — hasSecret detects a configured key (including an op://
104
+ // reference) without materializing it, so deciding whether to run the background
105
+ // intent verifier never spawns `op read` or a 1Password biometric prompt on the
106
+ // routing hot path. Materialization happens lazily in defaultModelCaller when a
107
+ // verification call is actually made.
108
+ if (hasSecret('ANTHROPIC_API_KEY')) return true;
109
+ if (hasSecret('OPENROUTER_API_KEY')) return true;
72
110
  try {
73
111
  if (readOpenRouterApiKeyFromOpenCodeConfig()) return true;
74
- } catch { /* ignore */ }
112
+ } catch { /* OpenCode provider config is optional */ }
75
113
  return false;
76
114
  }
77
115
 
@@ -115,8 +153,8 @@ async function fetchWithTimeout(url, options, timeoutMs = INTENT_VERIFY_TIMEOUT_
115
153
  }
116
154
 
117
155
  async function defaultModelCaller({ system, user }) {
118
- const orKey = process.env.OPENROUTER_API_KEY || readOpenRouterApiKeyFromOpenCodeConfig();
119
- const anthropicKey = process.env.ANTHROPIC_API_KEY;
156
+ const orKey = await resolveOpenRouterApiKey();
157
+ const anthropicKey = await resolveAnthropicApiKey();
120
158
  if (!orKey && !anthropicKey) throw new Error('no model api key configured');
121
159
 
122
160
  const envPath = getUserEnvPath();
@@ -15,26 +15,26 @@ const SOFFICE_CANDIDATES = [
15
15
  '/Applications/LibreOffice.app/Contents/MacOS/soffice',
16
16
  ];
17
17
 
18
- function whichBin(name) {
18
+ function whichBin(name, env) {
19
19
  const cmd = process.platform === 'win32' ? 'where' : 'which';
20
- const result = spawnSync(cmd, [name], { encoding: 'utf8' });
20
+ const result = spawnSync(cmd, [name], { encoding: 'utf8', env });
21
21
  if (result.status !== 0) return null;
22
22
  return (result.stdout || '').trim().split('\n')[0] || null;
23
23
  }
24
24
 
25
- export function resolveLibreOfficeBin(env = process.env) {
25
+ export function resolveLibreOfficeBin(env = process.env, { existsSyncFn = fs.existsSync } = {}) {
26
26
  const fromEnv = (env.CONSTRUCT_LIBREOFFICE_BIN || env.SOFFICE_BIN || '').trim();
27
- if (fromEnv && fs.existsSync(fromEnv)) return fromEnv;
27
+ if (fromEnv && existsSyncFn(fromEnv)) return fromEnv;
28
28
  for (const candidate of SOFFICE_CANDIDATES) {
29
- if (candidate.includes('/') && fs.existsSync(candidate)) return candidate;
30
- const found = whichBin(candidate);
29
+ if (candidate.includes('/') && existsSyncFn(candidate)) return candidate;
30
+ const found = whichBin(candidate, env);
31
31
  if (found) return found;
32
32
  }
33
33
  return null;
34
34
  }
35
35
 
36
- export function libreOfficePresent(env = process.env) {
37
- return Boolean(resolveLibreOfficeBin(env));
36
+ export function libreOfficePresent(env = process.env, opts) {
37
+ return Boolean(resolveLibreOfficeBin(env, opts));
38
38
  }
39
39
 
40
40
  export function libreOfficeInstallHint() {
@@ -2,8 +2,9 @@
2
2
  * lib/logging/rotate.mjs — shared file-rotation primitive.
3
3
  *
4
4
  * Two consumers need bounded log files: the trace writer
5
- * (`.cx/traces/<date>.jsonl`, capped to avoid >100MB single-file commits
6
- * that GitHub rejects) and the embed daemon stdout log
5
+ * (state-root `traces/<date>.jsonl` ADR-0066, machine-scoped, not
6
+ * project-local capped to avoid >100MB single-file commits that GitHub
7
+ * would reject if ever committed) and the embed daemon stdout log
7
8
  * (`~/.cx/runtime/embed-daemon.log`, capped so a stuck "Telemetry skipped"
8
9
  * message can't fill the disk).
9
10
  *
@@ -183,8 +184,8 @@ export function appendWithRotationSync(filePath, line, { maxBytes, maxSegments =
183
184
  * background daemons).
184
185
  */
185
186
  export const LIMITS = {
186
- // Trace shards under .cx/traces/<date>.jsonl — capped below GitHub's
187
- // 100 MB single-file ceiling. Bead construct-1vv5.
187
+ // Trace shards under the state-root's traces/<date>.jsonl — capped below
188
+ // GitHub's 100 MB single-file ceiling. Bead construct-1vv5.
188
189
 
189
190
  trace: {
190
191
  maxBytes: 100 * 1024 * 1024,