@geraldmaron/construct 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (556) hide show
  1. package/.env.example +36 -0
  2. package/README.md +41 -7
  3. package/bin/construct +1029 -65
  4. package/bin/construct-postinstall.mjs +27 -2
  5. package/config/tag-vocabulary.json +264 -0
  6. package/examples/distribution/sources/deck-one-pager.md +1 -1
  7. package/lib/acp/server.mjs +21 -7
  8. package/lib/adapters-sync.mjs +2 -1
  9. package/lib/audit-trail.mjs +185 -2
  10. package/lib/beads/auto-close.mjs +2 -1
  11. package/lib/beads/drift.mjs +2 -1
  12. package/lib/bridges/copilot-proxy.mjs +20 -5
  13. package/lib/certification/skill-inventory.mjs +10 -1
  14. package/lib/cli/approvals.mjs +147 -0
  15. package/lib/cli-commands.mjs +105 -14
  16. package/lib/comment-lint.mjs +127 -8
  17. package/lib/config/schema.mjs +7 -30
  18. package/lib/config/source-target-registry.mjs +70 -0
  19. package/lib/config/source-targets.mjs +179 -205
  20. package/lib/contracts/coverage.mjs +77 -0
  21. package/lib/contracts/validate.mjs +102 -13
  22. package/lib/contracts/violation-log.mjs +50 -4
  23. package/lib/db/migrate.mjs +69 -0
  24. package/lib/db/migrations/001_orchestration_runs.sql +9 -0
  25. package/lib/db/migrations/002_queue_provider.sql +50 -0
  26. package/lib/db/migrations/003_worker_registry.sql +17 -0
  27. package/lib/db/migrations/004_trace_events.sql +16 -0
  28. package/lib/db/migrations/005_shared_memory.sql +15 -0
  29. package/lib/db/migrations/006_orchestration_runs_tenant.sql +5 -0
  30. package/lib/decisions/enforced-baseline.json +0 -2
  31. package/lib/decisions/registry.mjs +3 -2
  32. package/lib/deployment/parity-contract.mjs +1 -1
  33. package/lib/deployment-mode.mjs +78 -2
  34. package/lib/diagram-export.mjs +10 -1
  35. package/lib/distill.mjs +5 -2
  36. package/lib/docs-verify.mjs +2 -1
  37. package/lib/doctor/cli.mjs +1 -0
  38. package/lib/doctor/command-on-path.mjs +24 -0
  39. package/lib/doctor/diagnosis.mjs +109 -0
  40. package/lib/doctor/embedding-health.mjs +50 -0
  41. package/lib/doctor/engine-health.mjs +93 -0
  42. package/lib/doctor/graph-validate.mjs +47 -0
  43. package/lib/doctor/index.mjs +23 -4
  44. package/lib/doctor/sidecar-providers.mjs +56 -0
  45. package/lib/doctor/watchers/consistency.mjs +93 -1
  46. package/lib/doctor/watchers/cx-budget.mjs +1 -1
  47. package/lib/doctor/watchers/graph-staleness.mjs +1 -1
  48. package/lib/doctor/watchers/mcp-protocol.mjs +17 -0
  49. package/lib/doctor/watchers/oracle-liveness.mjs +92 -0
  50. package/lib/doctor/watchers/orchestration-runs.mjs +108 -0
  51. package/lib/doctor/watchers/provider-breaker.mjs +78 -0
  52. package/lib/document-export.mjs +52 -27
  53. package/lib/document-extract/docling-client.mjs +9 -5
  54. package/lib/document-extract/docling-sidecar.py +30 -0
  55. package/lib/document-extract.mjs +1 -1
  56. package/lib/document-ingest.mjs +9 -0
  57. package/lib/embed/approval-queue.mjs +184 -88
  58. package/lib/embed/authority-guard.mjs +65 -2
  59. package/lib/embed/capability-jobs.mjs +365 -0
  60. package/lib/embed/capability-lifecycle.mjs +219 -0
  61. package/lib/embed/capability-loader.mjs +303 -0
  62. package/lib/embed/capability-runtime.mjs +58 -0
  63. package/lib/embed/cli.mjs +185 -8
  64. package/lib/embed/config.mjs +4 -0
  65. package/lib/embed/daemon.mjs +125 -6
  66. package/lib/embed/demand-fetch.mjs +190 -54
  67. package/lib/embed/inbox.mjs +12 -10
  68. package/lib/embed/presets/ops-triage.mjs +236 -0
  69. package/lib/embed/presets/pm-feedback.mjs +341 -0
  70. package/lib/embed/presets/tpm.mjs +401 -0
  71. package/lib/embed/providers/jira.mjs +72 -1
  72. package/lib/embed/providers/registry.mjs +140 -33
  73. package/lib/embed/worker.mjs +5 -0
  74. package/lib/embedded-contract/capability.mjs +4 -0
  75. package/lib/embedded-contract/execution.mjs +1 -1
  76. package/lib/embedded-contract/model-resolve.mjs +53 -3
  77. package/lib/embedded-contract/workflow-defs.mjs +48 -73
  78. package/lib/embedded-contract/workflow-invoke.mjs +144 -4
  79. package/lib/embedded-contract/workflows/architecture-review.manifest.json +15 -0
  80. package/lib/embedded-contract/workflows/data-structure.manifest.json +14 -0
  81. package/lib/embedded-contract/workflows/evidence-ingest.manifest.json +14 -0
  82. package/lib/embedded-contract/workflows/memo-draft.manifest.json +14 -0
  83. package/lib/embedded-contract/workflows/operations-triage.manifest.json +18 -0
  84. package/lib/embedded-contract/workflows/operations.manifest.json +18 -0
  85. package/lib/embedded-contract/workflows/pm-feedback.manifest.json +18 -0
  86. package/lib/embedded-contract/workflows/prd-draft.manifest.json +15 -0
  87. package/lib/embedded-contract/workflows/proposal-review.manifest.json +15 -0
  88. package/lib/embedded-contract/workflows/research-synthesis.manifest.json +14 -0
  89. package/lib/embedded-contract/workflows/risk-review.manifest.json +15 -0
  90. package/lib/embedded-contract/workflows/structure-notes.manifest.json +14 -0
  91. package/lib/embedded-contract/workflows/transcript-process.manifest.json +14 -0
  92. package/lib/embedded-contract/workflows/triage.manifest.json +14 -0
  93. package/lib/env-config.mjs +60 -11
  94. package/lib/export-validate.mjs +34 -2
  95. package/lib/extensions/index.mjs +27 -0
  96. package/lib/extensions/loader.mjs +120 -0
  97. package/lib/extensions/manifest-schema.mjs +141 -0
  98. package/lib/extensions/manifests/anthropic.manifest.json +12 -0
  99. package/lib/extensions/manifests/atlassian-confluence.manifest.json +12 -0
  100. package/lib/extensions/manifests/atlassian-jira.manifest.json +53 -0
  101. package/lib/extensions/manifests/directory.manifest.json +12 -0
  102. package/lib/extensions/manifests/docling.manifest.json +37 -0
  103. package/lib/extensions/manifests/echo.manifest.json +8 -0
  104. package/lib/extensions/manifests/feedback.manifest.json +12 -0
  105. package/lib/extensions/manifests/github-copilot.manifest.json +12 -0
  106. package/lib/extensions/manifests/github.manifest.json +52 -0
  107. package/lib/extensions/manifests/linear.manifest.json +50 -0
  108. package/lib/extensions/manifests/local.manifest.json +12 -0
  109. package/lib/extensions/manifests/ollama.manifest.json +12 -0
  110. package/lib/extensions/manifests/openai.manifest.json +12 -0
  111. package/lib/extensions/manifests/openrouter-anthropic.manifest.json +12 -0
  112. package/lib/extensions/manifests/openrouter-deepseek.manifest.json +12 -0
  113. package/lib/extensions/manifests/openrouter-google.manifest.json +12 -0
  114. package/lib/extensions/manifests/openrouter-llama.manifest.json +12 -0
  115. package/lib/extensions/manifests/openrouter-qwen.manifest.json +12 -0
  116. package/lib/extensions/manifests/openrouter.manifest.json +12 -0
  117. package/lib/extensions/manifests/postgres.manifest.json +12 -0
  118. package/lib/extensions/manifests/salesforce.manifest.json +12 -0
  119. package/lib/extensions/manifests/slack.manifest.json +58 -0
  120. package/lib/extensions/manifests/whisper.manifest.json +36 -0
  121. package/lib/extensions/validate.mjs +175 -0
  122. package/lib/features.mjs +13 -9
  123. package/lib/flows/checkpoint.mjs +184 -0
  124. package/lib/flows/constants.mjs +27 -0
  125. package/lib/flows/define.mjs +146 -0
  126. package/lib/flows/engine.mjs +249 -0
  127. package/lib/flows/errors.mjs +19 -0
  128. package/lib/flows/index.mjs +27 -0
  129. package/lib/flows/joins.mjs +18 -0
  130. package/lib/flows/schema.mjs +90 -0
  131. package/lib/flows/state.mjs +31 -0
  132. package/lib/frameworks/loader.mjs +99 -0
  133. package/lib/frameworks/schema.mjs +157 -0
  134. package/lib/graph/build-from-corpus.mjs +67 -0
  135. package/lib/graph/build-from-embed.mjs +82 -0
  136. package/lib/graph/build-from-registry.mjs +164 -3
  137. package/lib/graph/cli.mjs +456 -12
  138. package/lib/graph/gap-queries.mjs +156 -0
  139. package/lib/graph/gaps.mjs +41 -0
  140. package/lib/graph/impacted.mjs +129 -0
  141. package/lib/graph/runtime-evidence.mjs +177 -0
  142. package/lib/graph/security-coverage.mjs +113 -0
  143. package/lib/graph/staleness.mjs +241 -10
  144. package/lib/graph/store.mjs +24 -7
  145. package/lib/graph/validate.mjs +161 -0
  146. package/lib/headhunt.mjs +9 -8
  147. package/lib/health-check.mjs +15 -7
  148. package/lib/hook-health.mjs +1 -1
  149. package/lib/hooks/agent-tracker.mjs +10 -4
  150. package/lib/hooks/edit-guard.mjs +3 -3
  151. package/lib/hooks/graph-impact-advisory.mjs +2 -2
  152. package/lib/hooks/guard-bash.mjs +41 -15
  153. package/lib/hooks/mcp-health-check.mjs +11 -4
  154. package/lib/hooks/model-fallback.mjs +50 -6
  155. package/lib/hooks/orchestration-dispatch-guard.mjs +14 -3
  156. package/lib/hooks/pre-compact.mjs +181 -173
  157. package/lib/hooks/rule-verifier.mjs +2 -1
  158. package/lib/hooks/session-reflect.mjs +2 -1
  159. package/lib/hooks/session-start.mjs +3 -3
  160. package/lib/host/readiness.mjs +109 -0
  161. package/lib/host-capabilities.mjs +13 -2
  162. package/lib/host-disposition.mjs +29 -8
  163. package/lib/identity.mjs +92 -0
  164. package/lib/ingest/degraded-extract.mjs +96 -0
  165. package/lib/ingest/docling-remote.mjs +2 -1
  166. package/lib/ingest/provider-extract.mjs +7 -19
  167. package/lib/ingest/sidecar-providers.mjs +196 -0
  168. package/lib/ingest-tooling.mjs +11 -5
  169. package/lib/init-unified.mjs +55 -38
  170. package/lib/init-update.mjs +2 -1
  171. package/lib/install/legacy-global-cleanup.mjs +25 -0
  172. package/lib/install/stage-project.mjs +41 -4
  173. package/lib/intake/daemon.mjs +99 -8
  174. package/lib/intake/git-queue.mjs +110 -38
  175. package/lib/intake/prepare.mjs +2 -0
  176. package/lib/intake/queue-registry.mjs +50 -0
  177. package/lib/intake/queue.mjs +133 -17
  178. package/lib/intake/session-prelude.mjs +57 -8
  179. package/lib/integrations/intake-integrations.mjs +61 -111
  180. package/lib/intent-classifier.mjs +43 -5
  181. package/lib/libreoffice-export.mjs +8 -8
  182. package/lib/logging/rotate.mjs +5 -4
  183. package/lib/mcp/broker.mjs +332 -17
  184. package/lib/mcp/denied-store.mjs +95 -0
  185. package/lib/mcp/destructive-approval.mjs +57 -0
  186. package/lib/mcp/destructive-gate.mjs +30 -0
  187. package/lib/mcp/dispatch-envelope.mjs +199 -0
  188. package/lib/mcp/memory-bridge.mjs +2 -1
  189. package/lib/mcp/server.mjs +185 -1268
  190. package/lib/mcp/tool-definitions-memory.mjs +376 -0
  191. package/lib/mcp/tool-definitions-project.mjs +271 -0
  192. package/lib/mcp/tool-definitions-skills.mjs +311 -0
  193. package/lib/mcp/tool-definitions-workflow.mjs +398 -0
  194. package/lib/mcp/tool-definitions.mjs +25 -0
  195. package/lib/mcp/tool-rate-limit.mjs +47 -0
  196. package/lib/mcp/tool-registry.mjs +107 -0
  197. package/lib/mcp/tool-safety.mjs +95 -0
  198. package/lib/mcp/tool-surface-parity.mjs +60 -0
  199. package/lib/mcp/tools/orchestration-delegation-next.tool.mjs +68 -0
  200. package/lib/mcp/tools/orchestration-run.mjs +197 -19
  201. package/lib/mcp/tools/orchestration-task-result.tool.mjs +77 -0
  202. package/lib/mcp/tools/project.mjs +25 -8
  203. package/lib/mcp/tools/provider-write.mjs +187 -0
  204. package/lib/mcp/tools/scope.mjs +0 -3
  205. package/lib/mcp/tools/skills.mjs +1 -1
  206. package/lib/mcp/tools/storage.mjs +2 -2
  207. package/lib/mcp/tools/web-search-governance.mjs +96 -0
  208. package/lib/mcp/tools/web-search.mjs +5 -76
  209. package/lib/mcp/transport/auth.mjs +238 -0
  210. package/lib/mcp/transport/http.mjs +136 -0
  211. package/lib/mcp/transport/mode.mjs +31 -0
  212. package/lib/mcp/transport/stdio.mjs +22 -0
  213. package/lib/mcp-catalog.json +4 -4
  214. package/lib/mcp-manager.mjs +34 -23
  215. package/lib/mcp-platform-config.mjs +53 -20
  216. package/lib/mode-capabilities.mjs +109 -0
  217. package/lib/model-router.mjs +42 -9
  218. package/lib/models/catalog.mjs +40 -1
  219. package/lib/net-guard.mjs +247 -0
  220. package/lib/observation-store.mjs +6 -2
  221. package/lib/ollama/provision-context.mjs +2 -1
  222. package/lib/opencode-config.mjs +22 -3
  223. package/lib/opencode-runtime-plugin.mjs +120 -2
  224. package/lib/oracle/actions.mjs +204 -10
  225. package/lib/oracle/cli.mjs +24 -3
  226. package/lib/oracle/daemon-entry.mjs +6 -0
  227. package/lib/oracle/dispatch.mjs +2 -1
  228. package/lib/oracle/execute.mjs +2 -2
  229. package/lib/oracle/gaps.mjs +3 -3
  230. package/lib/oracle/heartbeat.mjs +53 -0
  231. package/lib/oracle/read-model.mjs +69 -13
  232. package/lib/oracle/reconcile.mjs +3 -46
  233. package/lib/oracle/routing.mjs +37 -31
  234. package/lib/oracle/synthesize.mjs +1 -1
  235. package/lib/orchestration/classification.mjs +434 -0
  236. package/lib/orchestration/delegation-flow.mjs +132 -0
  237. package/lib/orchestration/flow-selection.mjs +418 -0
  238. package/lib/orchestration/gates.mjs +244 -0
  239. package/lib/orchestration/host-sampling.mjs +121 -0
  240. package/lib/orchestration/policy-constants.mjs +48 -0
  241. package/lib/orchestration/provider-outcome.mjs +197 -0
  242. package/lib/orchestration/readiness.mjs +114 -13
  243. package/lib/orchestration/run-store-postgres.mjs +32 -26
  244. package/lib/orchestration/run-store-sqlite.mjs +8 -14
  245. package/lib/orchestration/run-store.mjs +25 -12
  246. package/lib/orchestration/runtime.mjs +508 -62
  247. package/lib/orchestration/store.mjs +87 -12
  248. package/lib/orchestration/trace-store.mjs +125 -0
  249. package/lib/orchestration/web-capability.mjs +60 -0
  250. package/lib/orchestration/worker-runtime.mjs +162 -0
  251. package/lib/orchestration/worker.mjs +763 -80
  252. package/lib/orchestration-policy.mjs +67 -1111
  253. package/lib/output-quality.mjs +61 -2
  254. package/lib/packs/cli.mjs +144 -0
  255. package/lib/packs/core-pack.mjs +112 -0
  256. package/lib/packs/enablement.mjs +187 -0
  257. package/lib/packs/index.mjs +23 -0
  258. package/lib/packs/loader.mjs +176 -0
  259. package/lib/packs/manifest-schema.mjs +58 -0
  260. package/lib/packs/prompts.mjs +120 -0
  261. package/lib/packs/validate.mjs +208 -0
  262. package/lib/path-policy.mjs +56 -0
  263. package/lib/plugin-registry.mjs +4 -5
  264. package/lib/policy/audit-gate.mjs +42 -0
  265. package/lib/policy/consumption-budget.mjs +149 -0
  266. package/lib/policy/engine.mjs +81 -6
  267. package/lib/policy/role-authority.mjs +89 -0
  268. package/lib/prompt-composer.js +19 -3
  269. package/lib/providers/contract/adapters/confluence/governed-write.mjs +215 -0
  270. package/lib/providers/contract/adapters/confluence/manifest.json +17 -0
  271. package/lib/providers/contract/adapters/confluence/transport.mjs +135 -0
  272. package/lib/providers/contract/adapters/github/governed-write.mjs +121 -0
  273. package/lib/providers/contract/adapters/github/index.mjs +38 -3
  274. package/lib/providers/contract/adapters/jira/adf.mjs +151 -0
  275. package/lib/providers/contract/adapters/jira/createmeta.mjs +143 -0
  276. package/lib/providers/contract/adapters/jira/governed-write.mjs +182 -0
  277. package/lib/providers/contract/adapters/jira/manifest.json +17 -0
  278. package/lib/providers/contract/adapters/jira/transport.mjs +119 -0
  279. package/lib/providers/contract.mjs +207 -0
  280. package/lib/providers/credential-bootstrap.mjs +7 -4
  281. package/lib/providers/credential-sources.mjs +14 -2
  282. package/lib/providers/directory/index.mjs +261 -0
  283. package/lib/providers/feedback/index.mjs +392 -0
  284. package/lib/providers/filter-audit.mjs +68 -0
  285. package/lib/providers/filter-schema.mjs +54 -0
  286. package/lib/providers/github/index.mjs +31 -0
  287. package/lib/providers/instance-config.mjs +215 -0
  288. package/lib/providers/op-locate.mjs +96 -0
  289. package/lib/providers/op-run.mjs +64 -9
  290. package/lib/providers/registry.mjs +26 -3
  291. package/lib/providers/secret-audit-wiring.mjs +41 -18
  292. package/lib/providers/secret-resolver.mjs +263 -31
  293. package/lib/publish-template.mjs +6 -3
  294. package/lib/publish-tooling.mjs +4 -0
  295. package/lib/publish.mjs +32 -4
  296. package/lib/queue/pg-queue.mjs +395 -0
  297. package/lib/reflect.mjs +2 -1
  298. package/lib/registry/assemble.mjs +28 -2
  299. package/lib/registry/catalog.mjs +6 -0
  300. package/lib/registry/consolidation.mjs +8 -1
  301. package/lib/registry/custom-scaffold.mjs +200 -0
  302. package/lib/registry/custom-schema.mjs +137 -0
  303. package/lib/registry/loader.mjs +15 -4
  304. package/lib/registry/manifests/format-engines.default.json +15 -0
  305. package/lib/registry/manifests/surface-map.default.json +46 -0
  306. package/lib/registry/retired-paths.mjs +1 -0
  307. package/lib/registry/surface-map.mjs +100 -50
  308. package/lib/registry/validate.mjs +3 -3
  309. package/lib/render-visual-check.mjs +240 -0
  310. package/lib/resources/budget.mjs +40 -17
  311. package/lib/roles/flavor-bindings.mjs +36 -14
  312. package/lib/roles/gateway.mjs +15 -8
  313. package/lib/roots.mjs +107 -0
  314. package/lib/runtime/uv-bootstrap.mjs +32 -6
  315. package/lib/runtime/whisper-bootstrap.mjs +11 -3
  316. package/lib/runtime-env.mjs +5 -2
  317. package/lib/sandbox.mjs +1 -1
  318. package/lib/scheduler/index.mjs +8 -20
  319. package/lib/schema-infer.mjs +31 -2
  320. package/lib/scopes/lifecycle.mjs +29 -25
  321. package/lib/scopes/loader.mjs +49 -12
  322. package/lib/scopes/teams.mjs +1 -1
  323. package/lib/security/ingest-boundary.mjs +80 -0
  324. package/lib/security/recall-wrapper.mjs +99 -0
  325. package/lib/security/trust.mjs +132 -0
  326. package/lib/service-manager.mjs +93 -24
  327. package/lib/setup.mjs +41 -23
  328. package/lib/skills-apply.mjs +4 -2
  329. package/lib/specialists/postconditions.mjs +2 -2
  330. package/lib/state-root.mjs +147 -0
  331. package/lib/status.mjs +597 -6
  332. package/lib/storage/admin.mjs +84 -7
  333. package/lib/storage/backend-registry.mjs +73 -0
  334. package/lib/storage/backend.mjs +63 -9
  335. package/lib/storage/embeddings-openai.mjs +12 -2
  336. package/lib/storage/hybrid-query.mjs +11 -3
  337. package/lib/storage/retrieval-hardening.mjs +384 -0
  338. package/lib/storage/shared-memory.mjs +158 -0
  339. package/lib/storage/vector-client.mjs +69 -2
  340. package/lib/team/health.mjs +72 -0
  341. package/lib/telemetry/backends/local.mjs +9 -3
  342. package/lib/telemetry/client.mjs +3 -7
  343. package/lib/template-registry.mjs +10 -12
  344. package/lib/tenant/context.mjs +82 -0
  345. package/lib/tenant/isolation.mjs +129 -0
  346. package/lib/test-corpus-inventory.mjs +103 -2
  347. package/lib/uninstall/uninstall.mjs +78 -12
  348. package/lib/validator.mjs +4 -6
  349. package/lib/worker/entrypoint.mjs +2 -1
  350. package/lib/worker/run.mjs +2 -2
  351. package/lib/worker/trace.mjs +5 -11
  352. package/lib/workflow-state.mjs +52 -8
  353. package/lib/workflows/liveness.mjs +203 -0
  354. package/lib/workflows/loader.mjs +177 -0
  355. package/lib/workflows/manifest-schema.mjs +71 -0
  356. package/lib/workflows/surface-parity.mjs +118 -0
  357. package/lib/workflows/validate.mjs +127 -0
  358. package/lib/writes/control-plane.mjs +140 -0
  359. package/lib/writes/envelope.mjs +206 -0
  360. package/lib/writes/sent-log.mjs +86 -0
  361. package/lib/writes/write-intent.mjs +109 -0
  362. package/package.json +20 -7
  363. package/personas/construct.md +12 -3
  364. package/registry/agent-manifest.json +117 -0
  365. package/registry/capabilities.json +1987 -0
  366. package/rules/common/comments.md +1 -0
  367. package/schemas/brand-voice.schema.json +24 -0
  368. package/schemas/capability-registry.schema.json +72 -0
  369. package/schemas/certification-run.schema.json +130 -0
  370. package/schemas/demo-recording.schema.json +46 -0
  371. package/schemas/eval-dataset.schema.json +79 -0
  372. package/schemas/execution-capability-profile.schema.json +46 -0
  373. package/schemas/execution-policy.schema.json +114 -0
  374. package/schemas/improvement-proposal.schema.json +65 -0
  375. package/schemas/mcp-tool-output.schema.json +61 -0
  376. package/schemas/platform-capabilities.schema.json +83 -0
  377. package/schemas/project-config.schema.json +215 -0
  378. package/schemas/project-demo.schema.json +60 -0
  379. package/schemas/provider-behavior-matrix.schema.json +91 -0
  380. package/schemas/scope.schema.json +197 -0
  381. package/schemas/specialist-trace.schema.json +107 -0
  382. package/schemas/team.schema.json +99 -0
  383. package/schemas/unified-registry.schema.json +546 -0
  384. package/scripts/sync-specialists.mjs +336 -106
  385. package/skills/docs/adr-workflow.md +1 -1
  386. package/skills/docs/codebase-research-workflow.md +3 -3
  387. package/skills/docs/prd-workflow.md +5 -6
  388. package/skills/docs/runbook-workflow.md +2 -2
  389. package/skills/docs/user-research-workflow.md +3 -3
  390. package/skills/operating/fleet-health-routing.md +77 -0
  391. package/skills/roles/ai-engineer.md +1 -1
  392. package/skills/roles/architect.md +0 -1
  393. package/skills/roles/business-strategist.md +1 -1
  394. package/skills/roles/data-analyst.telemetry.md +1 -1
  395. package/skills/roles/data-engineer.md +1 -1
  396. package/skills/roles/data-engineer.pipeline.md +1 -1
  397. package/skills/roles/data-engineer.vector-retrieval.md +1 -2
  398. package/skills/roles/data-engineer.warehouse.md +1 -1
  399. package/skills/roles/designer.accessibility.md +1 -1
  400. package/skills/roles/designer.md +0 -1
  401. package/skills/roles/devil-advocate.md +1 -1
  402. package/skills/roles/docs-keeper.md +1 -1
  403. package/skills/roles/evaluator.md +1 -1
  404. package/skills/roles/explorer.md +1 -1
  405. package/skills/roles/platform-engineer.md +1 -1
  406. package/skills/roles/qa.ai-eval.md +1 -2
  407. package/skills/roles/qa.api-contract.md +0 -1
  408. package/skills/roles/qa.data-pipeline.md +0 -1
  409. package/skills/roles/qa.md +0 -1
  410. package/skills/roles/qa.web-ui.md +0 -1
  411. package/skills/roles/release-manager.md +1 -1
  412. package/skills/roles/researcher.md +0 -2
  413. package/skills/roles/reviewer.md +0 -3
  414. package/skills/roles/security.ai.md +1 -1
  415. package/skills/roles/security.legal-compliance.md +1 -1
  416. package/skills/roles/security.md +0 -1
  417. package/skills/roles/security.privacy.md +0 -1
  418. package/skills/roles/security.supply-chain.md +1 -1
  419. package/skills/roles/sre.md +1 -1
  420. package/skills/roles/test-automation.md +1 -1
  421. package/skills/roles/trace-reviewer.md +1 -1
  422. package/skills/roles/ux-researcher.md +1 -1
  423. package/specialists/artifact-manifest.json +36 -36
  424. package/specialists/org/contracts/accessibility-to-qa.json +11 -3
  425. package/specialists/org/contracts/any-to-business-strategist.json +19 -7
  426. package/specialists/org/contracts/any-to-debugger.json +7 -1
  427. package/specialists/org/contracts/any-to-designer.json +7 -1
  428. package/specialists/org/contracts/any-to-docs-keeper.json +18 -4
  429. package/specialists/org/contracts/any-to-explorer.json +13 -4
  430. package/specialists/org/contracts/any-to-sre-incident.json +6 -2
  431. package/specialists/org/contracts/any-to-trace-reviewer.json +16 -4
  432. package/specialists/org/contracts/architect-to-ai-engineer.json +6 -2
  433. package/specialists/org/contracts/architect-to-data-engineer.json +17 -5
  434. package/specialists/org/contracts/architect-to-devil-advocate.json +11 -3
  435. package/specialists/org/contracts/architect-to-engineer.json +20 -4
  436. package/specialists/org/contracts/architect-to-evaluator.json +15 -5
  437. package/specialists/org/contracts/architect-to-legal-compliance.json +15 -5
  438. package/specialists/org/contracts/architect-to-operations.json +17 -4
  439. package/specialists/org/contracts/architect-to-platform-engineer.json +20 -4
  440. package/specialists/org/contracts/construct-to-orchestrator.json +10 -2
  441. package/specialists/org/contracts/data-analyst-to-product-manager.json +11 -2
  442. package/specialists/org/contracts/engineer-to-qa.json +20 -4
  443. package/specialists/org/contracts/engineer-to-reviewer.json +29 -5
  444. package/specialists/org/contracts/explorer-to-engineer.json +11 -3
  445. package/specialists/org/contracts/legal-compliance-to-release-manager.json +12 -4
  446. package/specialists/org/contracts/operations-to-user.json +53 -0
  447. package/specialists/org/contracts/operations-triage-output.json +53 -0
  448. package/specialists/org/contracts/pm-requirements-candidates.json +53 -0
  449. package/specialists/org/contracts/product-manager-to-architect.json +30 -10
  450. package/specialists/org/contracts/product-manager-to-data-analyst.json +13 -3
  451. package/specialists/org/contracts/product-manager-to-ux-researcher.json +15 -5
  452. package/specialists/org/contracts/qa-to-release-manager.json +11 -3
  453. package/specialists/org/contracts/researcher-to-architect.json +10 -2
  454. package/specialists/org/contracts/researcher-to-product-manager.json +16 -5
  455. package/specialists/org/contracts/reviewer-to-security.json +17 -3
  456. package/specialists/org/contracts/test-automation-to-engineer.json +11 -3
  457. package/specialists/org/contracts/trace-reviewer-to-sre.json +12 -4
  458. package/specialists/org/contracts/user-to-construct.json +11 -4
  459. package/specialists/org/frameworks/cx-architect-constraint-option-failure.md +66 -0
  460. package/specialists/org/frameworks/cx-engineer-feasibility-blast-radius.md +66 -0
  461. package/specialists/org/frameworks/cx-ops-dependency-sequencing.md +74 -0
  462. package/specialists/org/frameworks/cx-pm-value-tradeoff.md +66 -0
  463. package/specialists/org/frameworks/cx-qa-risk-based-coverage.md +69 -0
  464. package/specialists/org/groups/engineering-group.json +2 -6
  465. package/specialists/org/groups/governance-group.json +2 -3
  466. package/specialists/org/groups/operations-group.json +5 -8
  467. package/specialists/org/groups/product-group.json +4 -8
  468. package/specialists/org/groups/quality-group.json +3 -7
  469. package/specialists/org/groups/strategy-group.json +6 -9
  470. package/specialists/org/models.json.example +8 -0
  471. package/specialists/org/scopes/creative.json +6 -1
  472. package/specialists/org/scopes/operations.json +7 -1
  473. package/specialists/org/scopes/research.json +6 -1
  474. package/specialists/org/scopes/rnd.json +7 -1
  475. package/specialists/org/specialists/cx-architect.json +27 -8
  476. package/specialists/org/specialists/cx-designer.json +22 -8
  477. package/specialists/org/specialists/cx-engineer.json +71 -7
  478. package/specialists/org/specialists/cx-operations.json +89 -15
  479. package/specialists/org/specialists/cx-orchestrator.json +16 -4
  480. package/specialists/org/specialists/cx-product-manager.json +28 -9
  481. package/specialists/org/specialists/cx-qa.json +16 -6
  482. package/specialists/org/specialists/cx-researcher.json +41 -7
  483. package/specialists/org/specialists/cx-reviewer.json +61 -11
  484. package/specialists/org/specialists/cx-security.json +30 -10
  485. package/specialists/org/teams/design-team.json +3 -4
  486. package/specialists/org/teams/engineering-team.json +3 -10
  487. package/specialists/org/teams/governance-team.json +3 -5
  488. package/specialists/org/teams/operations-team.json +6 -12
  489. package/specialists/org/teams/product-management-team.json +2 -3
  490. package/specialists/org/teams/quality-team.json +4 -12
  491. package/specialists/org/teams/research-team.json +3 -3
  492. package/specialists/org/teams/strategy-team.json +7 -14
  493. package/specialists/prompts/cx-architect.md +20 -1
  494. package/specialists/prompts/cx-data-analyst.md +1 -1
  495. package/specialists/prompts/cx-designer.md +12 -4
  496. package/specialists/prompts/cx-engineer.md +15 -1
  497. package/specialists/prompts/cx-operations.md +14 -2
  498. package/specialists/prompts/cx-orchestrator.md +9 -5
  499. package/specialists/prompts/cx-product-manager.md +5 -1
  500. package/specialists/prompts/cx-qa.md +6 -2
  501. package/specialists/prompts/cx-researcher.md +27 -31
  502. package/specialists/prompts/cx-reviewer.md +19 -1
  503. package/specialists/prompts/cx-security.md +5 -1
  504. package/templates/distribution/construct-brand.typ +123 -59
  505. package/templates/distribution/construct-decision.typ +6 -3
  506. package/templates/distribution/construct-pdf.typ +11 -4
  507. package/templates/distribution/construct-prd.typ +6 -3
  508. package/templates/distribution/construct-research.typ +7 -4
  509. package/vendor/pandoc-ext/README.md +3 -0
  510. package/vendor/pandoc-ext/diagram.lua +687 -0
  511. package/lib/providers/contract/adapters/git/index.mjs +0 -115
  512. package/lib/providers/contract/adapters/slack/index.mjs +0 -175
  513. package/specialists/org/contracts/business-strategist-to-product-manager.json +0 -39
  514. package/specialists/org/contracts/construct-to-rd-lead.json +0 -53
  515. package/specialists/org/contracts/data-engineer-to-platform-engineer.json +0 -36
  516. package/specialists/org/contracts/designer-to-accessibility.json +0 -36
  517. package/specialists/org/contracts/platform-engineer-to-engineer.json +0 -31
  518. package/specialists/org/contracts/qa-to-test-automation.json +0 -42
  519. package/specialists/org/contracts/rd-lead-to-architect.json +0 -38
  520. package/specialists/org/contracts/sre-to-release-manager.json +0 -36
  521. package/specialists/org/specialists/cx-accessibility.json +0 -69
  522. package/specialists/org/specialists/cx-ai-engineer.json +0 -75
  523. package/specialists/org/specialists/cx-business-strategist.json +0 -72
  524. package/specialists/org/specialists/cx-data-engineer.json +0 -71
  525. package/specialists/org/specialists/cx-devil-advocate.json +0 -71
  526. package/specialists/org/specialists/cx-docs-keeper.json +0 -82
  527. package/specialists/org/specialists/cx-evaluator.json +0 -69
  528. package/specialists/org/specialists/cx-explorer.json +0 -69
  529. package/specialists/org/specialists/cx-legal-compliance.json +0 -74
  530. package/specialists/org/specialists/cx-oracle.json +0 -46
  531. package/specialists/org/specialists/cx-platform-engineer.json +0 -80
  532. package/specialists/org/specialists/cx-rd-lead.json +0 -73
  533. package/specialists/org/specialists/cx-release-manager.json +0 -77
  534. package/specialists/org/specialists/cx-sre.json +0 -94
  535. package/specialists/org/specialists/cx-test-automation.json +0 -69
  536. package/specialists/org/specialists/cx-trace-reviewer.json +0 -69
  537. package/specialists/org/specialists/cx-ux-researcher.json +0 -68
  538. package/specialists/org/teams/accessibility-team.json +0 -39
  539. package/specialists/org/teams/ux-research-team.json +0 -39
  540. package/specialists/prompts/cx-accessibility.md +0 -55
  541. package/specialists/prompts/cx-ai-engineer.md +0 -124
  542. package/specialists/prompts/cx-business-strategist.md +0 -58
  543. package/specialists/prompts/cx-data-engineer.md +0 -55
  544. package/specialists/prompts/cx-devil-advocate.md +0 -59
  545. package/specialists/prompts/cx-docs-keeper.md +0 -164
  546. package/specialists/prompts/cx-evaluator.md +0 -49
  547. package/specialists/prompts/cx-explorer.md +0 -71
  548. package/specialists/prompts/cx-legal-compliance.md +0 -58
  549. package/specialists/prompts/cx-oracle.md +0 -98
  550. package/specialists/prompts/cx-platform-engineer.md +0 -97
  551. package/specialists/prompts/cx-rd-lead.md +0 -59
  552. package/specialists/prompts/cx-release-manager.md +0 -54
  553. package/specialists/prompts/cx-sre.md +0 -111
  554. package/specialists/prompts/cx-test-automation.md +0 -55
  555. package/specialists/prompts/cx-trace-reviewer.md +0 -101
  556. package/specialists/prompts/cx-ux-researcher.md +0 -53
@@ -0,0 +1,17 @@
1
+ {
2
+ "id": "confluence-write",
3
+ "version": "1.0.0",
4
+ "kind": "data-source",
5
+ "capabilities": ["write", "search"],
6
+ "secretEnvKeys": ["CONFLUENCE_URL", "CONFLUENCE_EMAIL", "CONFLUENCE_TOKEN"],
7
+ "operations": ["write:page", "write:page-update", "search"],
8
+ "securityClassification": "trusted",
9
+ "owner": "construct-core",
10
+ "compatVersion": 1,
11
+ "installSource": "builtin",
12
+ "exposure": "envelope-only",
13
+ "envelopeModule": "lib/writes/envelope.mjs",
14
+ "adapterModule": "lib/providers/contract/adapters/confluence/governed-write.mjs",
15
+ "transportModule": "lib/providers/contract/adapters/confluence/transport.mjs",
16
+ "notes": "Colocated write-capability manifest for LMCP-J4. Follows the lib/extensions/manifests/*.manifest.json schema (see atlassian-confluence.manifest.json, which declares read/search only). Wiring this into the shared lib/extensions/manifests/ discovery directory is out of scope for this bead; capabilities here are reachable only via writeWithEnvelope(), never a direct provider-registry lookup."
17
+ }
@@ -0,0 +1,135 @@
1
+ /**
2
+ * lib/providers/contract/adapters/confluence/transport.mjs — real Confluence
3
+ * Cloud REST v2 transport for the governed write adapter.
4
+ *
5
+ * Implements the `confluenceTransport` shape governed-write.mjs depends on:
6
+ * `searchPagesByTitle`, `getPage`, `createPage`, `updatePage`,
7
+ * `createFooterComment`. Kept separate from governed-write.mjs so tests can
8
+ * inject a fake transport (tests/fakes/fake-confluence-transport.mjs)
9
+ * without any network dependency, and separate from the read-oriented
10
+ * adapter in ./index.mjs so this write path never shares mutable state with
11
+ * the read path.
12
+ *
13
+ * Auth: API token via CONFLUENCE_URL + CONFLUENCE_EMAIL + CONFLUENCE_TOKEN
14
+ * env vars, or passed directly via config.
15
+ */
16
+
17
+ import { AuthError, RateLimitError } from '../../errors.mjs';
18
+ import { guardedFetch } from '../../../../net-guard.mjs';
19
+
20
+ export function createConfluenceTransport(config = {}) {
21
+ const baseUrl = (config.baseUrl ?? process.env.CONFLUENCE_URL ?? '').replace(/\/$/, '');
22
+ const email = config.email ?? process.env.CONFLUENCE_EMAIL;
23
+ const token = config.token ?? process.env.CONFLUENCE_TOKEN;
24
+
25
+ if (!baseUrl || !email || !token) {
26
+ throw new AuthError(
27
+ 'Confluence transport requires CONFLUENCE_URL, CONFLUENCE_EMAIL, and CONFLUENCE_TOKEN (or config.baseUrl/email/token)',
28
+ { provider: 'confluence' },
29
+ );
30
+ }
31
+
32
+ const auth = `Basic ${Buffer.from(`${email}:${token}`).toString('base64')}`;
33
+
34
+ // Egress runs through the N7 guard (SSRF/rebinding); a private-address
35
+ // self-hosted instance is an explicit, audited opt-in, never the default.
36
+ const allowPrivate = config.allowPrivateEgress ?? process.env.CONSTRUCT_NET_ALLOW_PRIVATE_EGRESS === '1';
37
+
38
+ async function request(path, { method = 'GET', body } = {}) {
39
+ const res = await guardedFetch(`${baseUrl}${path}`, {
40
+ method,
41
+ headers: {
42
+ Authorization: auth,
43
+ 'Content-Type': 'application/json',
44
+ Accept: 'application/json',
45
+ },
46
+ body: body ? JSON.stringify(body) : undefined,
47
+ }, { allowPrivate });
48
+
49
+ if (res.status === 429) {
50
+ const retryAfter = parseInt(res.headers.get('Retry-After') ?? '60', 10);
51
+ throw new RateLimitError('Confluence rate limit hit', { provider: 'confluence', retryAfter });
52
+ }
53
+ if (!res.ok) {
54
+ const text = await res.text().catch(() => '');
55
+ const err = new Error(`Confluence API ${method} ${path} failed: ${res.status} ${text.slice(0, 300)}`);
56
+ err.status = res.status;
57
+ throw err;
58
+ }
59
+ if (res.status === 204) return null;
60
+ return res.json();
61
+ }
62
+
63
+ return {
64
+ async searchPagesByTitle(spaceId, title) {
65
+ const params = new URLSearchParams({ title, 'space-id': spaceId, limit: '10' });
66
+ const data = await request(`/wiki/api/v2/pages?${params.toString()}`);
67
+ return (data.results ?? []).map((p) => ({
68
+ id: p.id,
69
+ title: p.title,
70
+ spaceId: p.spaceId,
71
+ version: p.version?.number ?? null,
72
+ url: `${baseUrl}${p._links?.webui ?? ''}`,
73
+ }));
74
+ },
75
+
76
+ async getPage(pageId) {
77
+ const data = await request(`/wiki/api/v2/pages/${pageId}?body-format=storage`);
78
+ return {
79
+ id: data.id,
80
+ title: data.title,
81
+ spaceId: data.spaceId,
82
+ version: data.version?.number ?? null,
83
+ body: data.body?.storage?.value ?? '',
84
+ url: `${baseUrl}${data._links?.webui ?? ''}`,
85
+ };
86
+ },
87
+
88
+ async createPage({ spaceId, title, body, parentId }) {
89
+ const reqBody = {
90
+ spaceId,
91
+ status: 'current',
92
+ title,
93
+ parentId: parentId ?? undefined,
94
+ body: { representation: 'storage', value: body },
95
+ };
96
+ const data = await request('/wiki/api/v2/pages', { method: 'POST', body: reqBody });
97
+ return {
98
+ id: data.id,
99
+ title: data.title,
100
+ version: data.version?.number ?? 1,
101
+ url: `${baseUrl}${data._links?.webui ?? ''}`,
102
+ };
103
+ },
104
+
105
+ async updatePage({ pageId, title, body, version }) {
106
+ const reqBody = {
107
+ id: pageId,
108
+ status: 'current',
109
+ title,
110
+ version: { number: version },
111
+ body: { representation: 'storage', value: body },
112
+ };
113
+ const data = await request(`/wiki/api/v2/pages/${pageId}`, { method: 'PUT', body: reqBody });
114
+ return {
115
+ id: data.id,
116
+ title: data.title,
117
+ version: data.version?.number ?? version,
118
+ url: `${baseUrl}${data._links?.webui ?? ''}`,
119
+ };
120
+ },
121
+
122
+ async createFooterComment(pageId, body) {
123
+ const data = await request('/wiki/api/v2/footer-comments', {
124
+ method: 'POST',
125
+ body: { pageId, body: { representation: 'storage', value: body } },
126
+ });
127
+ return {
128
+ id: data.id,
129
+ url: `${baseUrl}${data._links?.webui ?? ''}`,
130
+ };
131
+ },
132
+ };
133
+ }
134
+
135
+ export default createConfluenceTransport;
@@ -0,0 +1,121 @@
1
+ /**
2
+ * lib/providers/contract/adapters/github/governed-write.mjs — envelope-shaped
3
+ * GitHub write adapter.
4
+ *
5
+ * Wraps the CLI-backed github adapter (./index.mjs) in the provider shape
6
+ * lib/writes/envelope.mjs expects: write(config, payload), meta.id, search().
7
+ * Adds two behaviors the raw CLI adapter cannot provide on its own:
8
+ *
9
+ * - Cross-run duplicate detection: every created issue carries a hidden
10
+ * idempotency marker (HTML comment) in its body. Before creating, this
11
+ * wrapper searches GitHub itself for an issue carrying that marker, so
12
+ * dedup survives across process restarts and machines, not just the
13
+ * local sent-log.
14
+ * - Secondary-rate-limit backoff: GitHub content-creation endpoints return
15
+ * 403 with a "secondary rate limit" message (no clean HTTP Retry-After
16
+ * on the gh CLI transport). This wrapper retries a bounded number of
17
+ * times honoring the parsed retry-after, then surfaces the error rather
18
+ * than spinning silently — the envelope's own generic retry/backoff is
19
+ * bypassed here because it is not retry-after aware.
20
+ *
21
+ * Only `type: 'issue'` writes get marker + dedup treatment; other write
22
+ * types pass straight through to the underlying adapter.
23
+ */
24
+
25
+ import { RateLimitError } from '../../errors.mjs';
26
+
27
+ const MARKER_PREFIX = 'construct:idempotency:';
28
+
29
+ function markerFor(key) {
30
+ return `<!-- ${MARKER_PREFIX}${key} -->`;
31
+ }
32
+
33
+ function extractMarkerKey(text) {
34
+ const match = new RegExp(`${MARKER_PREFIX}([a-zA-Z0-9_-]+)`).exec(text || '');
35
+ return match ? match[1] : null;
36
+ }
37
+
38
+ function sleep(ms) {
39
+ return new Promise((resolve) => setTimeout(resolve, ms));
40
+ }
41
+
42
+ /**
43
+ * @param {object} opts
44
+ * @param {object} opts.ghAdapter - underlying CLI adapter (default export of ./index.mjs)
45
+ * @param {number} [opts.maxRateLimitRetries] - bounded retry-after retries before surfacing (default 2)
46
+ * @param {function} [opts.sleepFn] - injectable sleep for tests
47
+ */
48
+ export function createGovernedGithubProvider({ ghAdapter, maxRateLimitRetries = 2, sleepFn = sleep } = {}) {
49
+ if (!ghAdapter) throw new Error('createGovernedGithubProvider: ghAdapter is required');
50
+
51
+ async function findExistingByMarker(config, { title, markerKey }) {
52
+ const results = await ghAdapter.search(`${title} in:title`, { scope: 'issues' });
53
+ for (const candidate of results || []) {
54
+ const body = candidate.body ?? candidate.textMatches?.map((m) => m.fragment).join(' ') ?? '';
55
+ const haystack = `${candidate.title ?? ''} ${body}`;
56
+ if (extractMarkerKey(haystack) === markerKey || candidate.title === title) {
57
+ return candidate;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+
63
+ async function writeIssueWithRetry(config, payload, markerKey) {
64
+ const bodyWithMarker = `${payload.body ?? ''}\n\n${markerFor(markerKey)}`;
65
+ let attempt = 0;
66
+ for (;;) {
67
+ try {
68
+ return await ghAdapter.write({
69
+ type: 'issue',
70
+ title: payload.title,
71
+ body: bodyWithMarker,
72
+ labels: payload.labels,
73
+ });
74
+ } catch (err) {
75
+ const isSecondaryRateLimit = err instanceof RateLimitError || err.status === 403;
76
+ if (!isSecondaryRateLimit || attempt >= maxRateLimitRetries) {
77
+ throw err;
78
+ }
79
+ attempt += 1;
80
+ const retryAfterMs = (err.retryAfter ?? 60) * 1000;
81
+ await sleepFn(retryAfterMs);
82
+ }
83
+ }
84
+ }
85
+
86
+ return {
87
+ meta: {
88
+ id: 'github',
89
+ displayName: 'GitHub (governed)',
90
+ capabilities: ['write', 'search'],
91
+ description: 'Envelope-routed GitHub writes with marker-based dedup and retry-after backoff.',
92
+ },
93
+
94
+ async write(config, payload) {
95
+ if (payload?.type !== 'issue') {
96
+ return ghAdapter.write(payload);
97
+ }
98
+ if (!payload.title) throw new Error('github governed write: payload.title is required for type "issue"');
99
+
100
+ const markerKey = payload.idempotencyKey || payload.title;
101
+ const existing = await findExistingByMarker(config, { title: payload.title, markerKey });
102
+ if (existing) {
103
+ return {
104
+ type: 'issue-duplicate',
105
+ id: existing.number ?? existing.id,
106
+ number: existing.number,
107
+ url: existing.url,
108
+ linkback: existing.url,
109
+ };
110
+ }
111
+
112
+ return writeIssueWithRetry(config, payload, markerKey);
113
+ },
114
+
115
+ async search(config, query) {
116
+ return ghAdapter.search(query, { scope: 'issues' });
117
+ },
118
+ };
119
+ }
120
+
121
+ export default createGovernedGithubProvider;
@@ -27,7 +27,26 @@
27
27
  */
28
28
 
29
29
  import { spawnSync } from 'node:child_process';
30
- import { AuthError, NotFoundError } from '../../errors.mjs';
30
+ import { AuthError, NotFoundError, RateLimitError } from '../../errors.mjs';
31
+
32
+ // gh CLI has no HTTP header access; secondary rate limits surface as a 403
33
+ // with "secondary rate limit" text in stderr and sometimes a suggested wait
34
+ // in seconds. Default to GitHub's documented minimum backoff when absent.
35
+ const SECONDARY_RATE_LIMIT_DEFAULT_RETRY_AFTER = 60;
36
+
37
+ function parseRetryAfterSeconds(stderr) {
38
+ const match = /retry.{0,10}?(\d+)\s*second/i.exec(stderr || '');
39
+ return match ? parseInt(match[1], 10) : SECONDARY_RATE_LIMIT_DEFAULT_RETRY_AFTER;
40
+ }
41
+
42
+ function throwIfRateLimited(stderr) {
43
+ if (/secondary rate limit|403/i.test(stderr || '') && /rate limit/i.test(stderr || '')) {
44
+ throw new RateLimitError(stderr.trim(), {
45
+ provider: 'github',
46
+ retryAfter: parseRetryAfterSeconds(stderr),
47
+ });
48
+ }
49
+ }
31
50
 
32
51
  function gh(args, { json = true, input } = {}) {
33
52
  const fullArgs = json ? [...args, '--json', ...ghJsonFields(args)] : args;
@@ -115,6 +134,10 @@ export default {
115
134
  const args = ['issue', 'create', ...repoFlag, '--title', item.title, '--body', item.body ?? ''];
116
135
  if (item.labels?.length) args.push('--label', item.labels.join(','));
117
136
  const out = spawnSync('gh', args, { encoding: 'utf8', env: process.env });
137
+ if (out.status !== 0) {
138
+ throwIfRateLimited(out.stderr);
139
+ throw new Error(`gh issue create failed: ${out.stderr?.trim() || `exit ${out.status}`}`);
140
+ }
118
141
  return { type: 'issue-created', url: out.stdout.trim() };
119
142
  }
120
143
 
@@ -127,21 +150,33 @@ export default {
127
150
  ];
128
151
  if (item.draft) args.push('--draft');
129
152
  const out = spawnSync('gh', args, { encoding: 'utf8', env: process.env });
153
+ if (out.status !== 0) {
154
+ throwIfRateLimited(out.stderr);
155
+ throw new Error(`gh pr create failed: ${out.stderr?.trim() || `exit ${out.status}`}`);
156
+ }
130
157
  return { type: 'pr-created', url: out.stdout.trim() };
131
158
  }
132
159
 
133
160
  if (item.type === 'comment') {
134
161
  const args = ['issue', 'comment', String(item.issue_number), ...repoFlag, '--body', item.body];
135
- spawnSync('gh', args, { encoding: 'utf8', env: process.env });
162
+ const out = spawnSync('gh', args, { encoding: 'utf8', env: process.env });
163
+ if (out.status !== 0) {
164
+ throwIfRateLimited(out.stderr);
165
+ throw new Error(`gh issue comment failed: ${out.stderr?.trim() || `exit ${out.status}`}`);
166
+ }
136
167
  return { type: 'comment-created', issue_number: item.issue_number };
137
168
  }
138
169
 
139
170
  if (item.type === 'pr-merge') {
140
171
  const method = item.merge_method ?? 'squash';
141
- spawnSync('gh', ['pr', 'merge', String(item.number), ...repoFlag, `--${method}`], {
172
+ const out = spawnSync('gh', ['pr', 'merge', String(item.number), ...repoFlag, `--${method}`], {
142
173
  encoding: 'utf8',
143
174
  env: process.env,
144
175
  });
176
+ if (out.status !== 0) {
177
+ throwIfRateLimited(out.stderr);
178
+ throw new Error(`gh pr merge failed: ${out.stderr?.trim() || `exit ${out.status}`}`);
179
+ }
145
180
  return { type: 'pr-merged', number: item.number };
146
181
  }
147
182
 
@@ -0,0 +1,151 @@
1
+ /**
2
+ * lib/providers/contract/adapters/jira/adf.mjs — Atlassian Document Format
3
+ * (ADF) construction for Jira Cloud write fields.
4
+ *
5
+ * Jira Cloud REST API v3 requires `description`, `comment.body`, and any
6
+ * multiline custom field of type `doc` to be submitted as an ADF document
7
+ * rather than plain text. Covers the minimal ADF subset issue/comment write
8
+ * ops need: paragraphs split on blank lines, single-level bullet lists for
9
+ * lines starting with "- " or "* ", and inline text nodes. Full markdown
10
+ * fidelity (headings, tables, nested lists) is out of scope.
11
+ *
12
+ * `renderAdfPreview(adf)` renders an ADF document back to a readable plain
13
+ * string for dry-run display, so a human reviewing a dry-run payload sees
14
+ * text rather than a raw ADF tree.
15
+ */
16
+
17
+ const ADF_VERSION = 1;
18
+
19
+ function textNode(text) {
20
+ return { type: 'text', text: String(text) };
21
+ }
22
+
23
+ function paragraphNode(text) {
24
+ return { type: 'paragraph', content: text ? [textNode(text)] : [] };
25
+ }
26
+
27
+ function bulletListNode(items) {
28
+ return {
29
+ type: 'bulletList',
30
+ content: items.map((item) => ({
31
+ type: 'listItem',
32
+ content: [paragraphNode(item)],
33
+ })),
34
+ };
35
+ }
36
+
37
+ // Blank-line-separated blocks become paragraphs; a contiguous run of lines
38
+ // each starting with "- " or "* " becomes a single bullet list. This covers
39
+ // the shapes specialists actually produce (short prose + a checklist) without
40
+ // pretending to be a markdown parser.
41
+
42
+ function blocksFromText(text) {
43
+ const lines = String(text ?? '').split('\n');
44
+ const blocks = [];
45
+ let paragraphLines = [];
46
+ let listItems = [];
47
+
48
+ function flushParagraph() {
49
+ if (paragraphLines.length) {
50
+ blocks.push(paragraphNode(paragraphLines.join(' ').trim()));
51
+ paragraphLines = [];
52
+ }
53
+ }
54
+ function flushList() {
55
+ if (listItems.length) {
56
+ blocks.push(bulletListNode(listItems));
57
+ listItems = [];
58
+ }
59
+ }
60
+
61
+ for (const rawLine of lines) {
62
+ const line = rawLine.trim();
63
+ const bulletMatch = /^[-*]\s+(.*)$/.exec(line);
64
+ if (bulletMatch) {
65
+ flushParagraph();
66
+ listItems.push(bulletMatch[1]);
67
+ } else if (line === '') {
68
+ flushParagraph();
69
+ flushList();
70
+ } else {
71
+ flushList();
72
+ paragraphLines.push(line);
73
+ }
74
+ }
75
+ flushParagraph();
76
+ flushList();
77
+
78
+ return blocks.length ? blocks : [paragraphNode('')];
79
+ }
80
+
81
+ /**
82
+ * Build a full ADF document from a plain-text (or already-ADF) input.
83
+ * Passing an object that already looks like an ADF doc (`{ type: 'doc' }`)
84
+ * returns it unchanged so callers can pass through pre-built documents.
85
+ *
86
+ * @param {string|object} content
87
+ * @returns {object} ADF document
88
+ */
89
+ export function buildAdfDocument(content) {
90
+ if (content && typeof content === 'object' && content.type === 'doc') {
91
+ return content;
92
+ }
93
+ return {
94
+ type: 'doc',
95
+ version: ADF_VERSION,
96
+ content: blocksFromText(content),
97
+ };
98
+ }
99
+
100
+ /**
101
+ * Render an ADF document (or plain text) back to a flat human-readable
102
+ * string, for dry-run display. Not a full ADF renderer — only understands
103
+ * the node types buildAdfDocument produces plus a defensive fallback for
104
+ * unknown node types (renders as `[<type>]`).
105
+ *
106
+ * @param {object|string} adf
107
+ * @returns {string}
108
+ */
109
+ export function renderAdfPreview(adf) {
110
+ if (typeof adf === 'string') return adf;
111
+ if (!adf || typeof adf !== 'object') return '';
112
+
113
+ function renderNode(node) {
114
+ if (!node || typeof node !== 'object') return '';
115
+ switch (node.type) {
116
+ case 'doc':
117
+ return (node.content ?? []).map(renderNode).join('\n');
118
+ case 'paragraph':
119
+ return (node.content ?? []).map(renderNode).join('');
120
+ case 'text':
121
+ return node.text ?? '';
122
+ case 'bulletList':
123
+ return (node.content ?? []).map((item) => `- ${renderNode(item)}`).join('\n');
124
+ case 'listItem':
125
+ return (node.content ?? []).map(renderNode).join('').trim();
126
+ default:
127
+ return `[${node.type ?? 'unknown'}]`;
128
+ }
129
+ }
130
+
131
+ return renderNode(adf);
132
+ }
133
+
134
+ /**
135
+ * True when the given value is a well-formed ADF document envelope
136
+ * (`{ type: 'doc', version, content: [...] }`). Distinguishes a doc node
137
+ * from a bare string, for rejecting fields the API requires as ADF but
138
+ * were submitted as plain text.
139
+ *
140
+ * @param {unknown} value
141
+ * @returns {boolean}
142
+ */
143
+ export function isAdfDocument(value) {
144
+ return (
145
+ !!value &&
146
+ typeof value === 'object' &&
147
+ value.type === 'doc' &&
148
+ typeof value.version === 'number' &&
149
+ Array.isArray(value.content)
150
+ );
151
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * lib/providers/contract/adapters/jira/createmeta.mjs — Jira Cloud createmeta
3
+ * validation for write payloads.
4
+ *
5
+ * Jira Cloud's `GET /rest/api/3/issue/createmeta` (expanded with
6
+ * `projects.issuetypes.fields`) reports, per project + issue type, which
7
+ * fields exist, which are required, and which accept a `doc` (ADF) shape
8
+ * versus a plain scalar. `validateAgainstCreatemeta` runs before any issue
9
+ * create/comment submit so a bad project key, unknown issue type, missing
10
+ * required field, or wrong ADF/plain-text shape surfaces as a client-side
11
+ * error with an actionable message instead of a raw 400 from Jira after the
12
+ * request has already gone out.
13
+ *
14
+ * Providers inject a `fetchCreatemeta(projectKey, issueTypeName)` function
15
+ * so this module has no transport dependency and the fake-jira test double
16
+ * can simulate arbitrary createmeta shapes and failure modes.
17
+ */
18
+
19
+ import { isAdfDocument } from './adf.mjs';
20
+
21
+ /**
22
+ * Normalize a raw createmeta API response into a lookup map of
23
+ * `{ [fieldKey]: { required, schema } }` for the requested issue type.
24
+ *
25
+ * @param {object} raw - createmeta response body
26
+ * @param {string} projectKey
27
+ * @param {string} issueTypeName
28
+ * @returns {{ issueTypeId: string|null, fields: Record<string, {required: boolean, schema: object}> } | null}
29
+ */
30
+ export function extractIssueTypeMeta(raw, projectKey, issueTypeName) {
31
+ const project = (raw?.projects ?? []).find((p) => p.key === projectKey);
32
+ if (!project) return null;
33
+
34
+ const issueType = (project.issuetypes ?? []).find(
35
+ (it) => it.name?.toLowerCase() === issueTypeName?.toLowerCase(),
36
+ );
37
+ if (!issueType) return null;
38
+
39
+ const fields = {};
40
+ for (const [key, def] of Object.entries(issueType.fields ?? {})) {
41
+ fields[key] = { required: !!def.required, schema: def.schema ?? {} };
42
+ }
43
+ return { issueTypeId: issueType.id ?? null, fields };
44
+ }
45
+
46
+ /**
47
+ * Validate an issue-create payload against createmeta for its project +
48
+ * issue type before submit. Returns a structured result rather than
49
+ * throwing, so callers can map failures to actionable messages themselves.
50
+ *
51
+ * @param {object} opts
52
+ * @param {function(string, string): Promise<object>} opts.fetchCreatemeta
53
+ * @param {string} opts.projectKey
54
+ * @param {string} opts.issueTypeName
55
+ * @param {object} opts.fieldValues - candidate field values keyed by createmeta field key
56
+ * (e.g. { summary, description, labels }). `description` is checked for
57
+ * ADF shape when createmeta reports its schema type as `doc`.
58
+ * @returns {Promise<{ ok: true } | { ok: false, reason: string, errors: string[] }>}
59
+ */
60
+ export async function validateAgainstCreatemeta({ fetchCreatemeta, projectKey, issueTypeName, fieldValues }) {
61
+ let raw;
62
+ try {
63
+ raw = await fetchCreatemeta(projectKey, issueTypeName);
64
+ } catch (err) {
65
+ return {
66
+ ok: false,
67
+ reason: 'createmeta-fetch-failed',
68
+ errors: [mapCreatemetaTransportError(err, { projectKey })],
69
+ };
70
+ }
71
+
72
+ const meta = extractIssueTypeMeta(raw, projectKey, issueTypeName);
73
+ if (!meta) {
74
+ const knownProjects = (raw?.projects ?? []).map((p) => p.key);
75
+ if (!knownProjects.includes(projectKey)) {
76
+ return {
77
+ ok: false,
78
+ reason: 'unknown-project',
79
+ errors: [
80
+ `Project "${projectKey}" was not found or you lack the Browse Projects / Create Issues permission for it.` +
81
+ (knownProjects.length ? ` Accessible projects: ${knownProjects.join(', ')}.` : ''),
82
+ ],
83
+ };
84
+ }
85
+ return {
86
+ ok: false,
87
+ reason: 'unknown-issue-type',
88
+ errors: [`Issue type "${issueTypeName}" is not available in project "${projectKey}".`],
89
+ };
90
+ }
91
+
92
+ const errors = [];
93
+
94
+ for (const [fieldKey, def] of Object.entries(meta.fields)) {
95
+ if (!def.required) continue;
96
+ const present = fieldValues[fieldKey] !== undefined && fieldValues[fieldKey] !== null && fieldValues[fieldKey] !== '';
97
+ if (!present) {
98
+ errors.push(`Field "${fieldKey}" is required by project "${projectKey}" / issue type "${issueTypeName}" but was not provided.`);
99
+ }
100
+ }
101
+
102
+ for (const [fieldKey, value] of Object.entries(fieldValues)) {
103
+ const def = meta.fields[fieldKey];
104
+ if (!def) continue;
105
+ if (def.schema?.type === 'doc' && value !== undefined && value !== null && !isAdfDocument(value)) {
106
+ errors.push(`Field "${fieldKey}" requires an Atlassian Document Format (ADF) body, but a plain value was submitted.`);
107
+ }
108
+ }
109
+
110
+ if (errors.length) {
111
+ return { ok: false, reason: 'field-validation-failed', errors };
112
+ }
113
+
114
+ return { ok: true, issueTypeId: meta.issueTypeId };
115
+ }
116
+
117
+ /**
118
+ * Map a createmeta transport failure (401/403/404/5xx) to an actionable
119
+ * message. Jira Cloud returns 404 for both "project does not exist" and
120
+ * "no permission to view project" — the same ambiguity the create-issue
121
+ * endpoint itself has — so the message names both possibilities rather
122
+ * than asserting one.
123
+ *
124
+ * @param {Error & {status?: number}} err
125
+ * @param {{ projectKey: string }} ctx
126
+ * @returns {string}
127
+ */
128
+ export function mapCreatemetaTransportError(err, { projectKey }) {
129
+ const status = err?.status;
130
+ if (status === 401) {
131
+ return 'Jira authentication failed while fetching createmeta. Check JIRA_EMAIL / JIRA_TOKEN.';
132
+ }
133
+ if (status === 403) {
134
+ return `Forbidden: the authenticated account lacks permission to create issues in project "${projectKey}".`;
135
+ }
136
+ if (status === 404) {
137
+ return `Project "${projectKey}" was not found, or the authenticated account cannot browse it.`;
138
+ }
139
+ if (status && status >= 500) {
140
+ return `Jira createmeta endpoint returned a server error (${status}); retry later.`;
141
+ }
142
+ return `Failed to fetch createmeta for project "${projectKey}": ${err?.message ?? String(err)}`;
143
+ }