@geraldmaron/construct 1.1.1 → 1.2.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 (408) hide show
  1. package/README.md +29 -19
  2. package/bin/construct +311 -81
  3. package/bin/construct-postinstall.mjs +30 -2
  4. package/examples/distribution/README.md +42 -0
  5. package/examples/distribution/manifest.json +53 -0
  6. package/examples/distribution/sources/adr.md +84 -0
  7. package/examples/distribution/sources/deck-one-pager.md +65 -0
  8. package/examples/distribution/sources/prd-platform.md +161 -0
  9. package/examples/distribution/sources/research-brief.md +80 -0
  10. package/examples/distribution/sources/rfc-platform.md +82 -0
  11. package/examples/distribution/sources/runbook.md +103 -0
  12. package/examples/distribution/sources/strategy.md +88 -0
  13. package/lib/adapters-sync.mjs +67 -0
  14. package/lib/artifact-gate-notice.mjs +38 -0
  15. package/lib/artifact-manifest.mjs +112 -0
  16. package/lib/artifact-release-gate.mjs +171 -0
  17. package/lib/artifact-reviewers.mjs +68 -0
  18. package/lib/artifact-type-from-path.mjs +79 -0
  19. package/lib/audit-skills.mjs +4 -0
  20. package/lib/audit-specialists.mjs +285 -0
  21. package/lib/audit-trail.mjs +1 -1
  22. package/lib/auto-docs.mjs +87 -18
  23. package/lib/beads/drift.mjs +27 -6
  24. package/lib/beads-client.mjs +49 -121
  25. package/lib/beads-optimistic.mjs +5 -12
  26. package/lib/brand-fonts.mjs +93 -0
  27. package/lib/brand-prose.mjs +214 -0
  28. package/lib/brand-tokens.mjs +92 -0
  29. package/lib/bridges/copilot-proxy.mjs +13 -26
  30. package/lib/capability-ledger.mjs +156 -0
  31. package/lib/certification/artifact-fixtures.mjs +132 -0
  32. package/lib/certification/artifact-gates.mjs +63 -0
  33. package/lib/certification/artifact-provenance.mjs +97 -0
  34. package/lib/certification/canonical-scenarios.mjs +78 -0
  35. package/lib/certification/cli.mjs +191 -0
  36. package/lib/certification/dashboard-api.mjs +71 -0
  37. package/lib/certification/demo-parity.mjs +116 -0
  38. package/lib/certification/document-io-fixtures.mjs +246 -0
  39. package/lib/certification/document-workflow.mjs +97 -0
  40. package/lib/certification/eval-bridge.mjs +77 -0
  41. package/lib/certification/model-routing.mjs +149 -0
  42. package/lib/certification/prompt-budget.mjs +119 -0
  43. package/lib/certification/rc-gate.mjs +206 -0
  44. package/lib/certification/real-llm-scenarios.mjs +303 -0
  45. package/lib/certification/role-cards.mjs +113 -0
  46. package/lib/certification/role-overlays.mjs +117 -0
  47. package/lib/certification/run.mjs +122 -0
  48. package/lib/certification/runner.mjs +323 -0
  49. package/lib/certification/scenarios.mjs +51 -0
  50. package/lib/certification/skill-inventory.mjs +289 -0
  51. package/lib/certification/skill-scenarios.mjs +147 -0
  52. package/lib/certification/specialist-contracts.mjs +85 -0
  53. package/lib/certification/specialist-scenarios.mjs +175 -0
  54. package/lib/certification/stale-impact.mjs +146 -0
  55. package/lib/certification/status.mjs +252 -0
  56. package/lib/certification/store.mjs +77 -0
  57. package/lib/chat/cli.mjs +333 -0
  58. package/lib/chat/command-suggest.mjs +161 -0
  59. package/lib/chat/commands.mjs +215 -0
  60. package/lib/chat/config.mjs +142 -0
  61. package/lib/chat/context-compactor.mjs +250 -0
  62. package/lib/chat/context-continuation.mjs +253 -0
  63. package/lib/chat/continuation-source.mjs +58 -0
  64. package/lib/chat/demo-guide.mjs +61 -0
  65. package/lib/chat/design-tokens.mjs +91 -0
  66. package/lib/chat/desktop-binary.mjs +79 -0
  67. package/lib/chat/desktop-build.mjs +130 -0
  68. package/lib/chat/desktop-launcher.mjs +133 -0
  69. package/lib/chat/evidence.mjs +145 -0
  70. package/lib/chat/export.mjs +74 -0
  71. package/lib/chat/harness/driver.mjs +91 -0
  72. package/lib/chat/list-picker.mjs +112 -0
  73. package/lib/chat/model-picker.mjs +356 -0
  74. package/lib/chat/openrouter-fallback.mjs +151 -0
  75. package/lib/chat/permission-prompt.mjs +33 -0
  76. package/lib/chat/picker-catalog.mjs +45 -0
  77. package/lib/chat/policy-telemetry.mjs +34 -0
  78. package/lib/chat/present.mjs +245 -0
  79. package/lib/chat/session-context.mjs +39 -0
  80. package/lib/chat/session-persist.mjs +73 -0
  81. package/lib/chat/session-restore.mjs +71 -0
  82. package/lib/chat/session-settings.mjs +53 -0
  83. package/lib/chat/system-prompt.mjs +52 -0
  84. package/lib/chat/transparency.mjs +93 -0
  85. package/lib/chat/tui/color-scheme.mjs +42 -0
  86. package/lib/chat/tui/markdown.mjs +123 -0
  87. package/lib/chat/tui/presentation.mjs +100 -0
  88. package/lib/chat/tui/render.mjs +500 -0
  89. package/lib/chat/tui/turn-block.mjs +284 -0
  90. package/lib/chat/tui/turn-present.mjs +18 -0
  91. package/lib/chat/tui/turn-state.mjs +88 -0
  92. package/lib/chat/tui/usage.mjs +122 -0
  93. package/lib/chat/web-commands.mjs +146 -0
  94. package/lib/chat/web-launcher.mjs +63 -0
  95. package/lib/chat/web-picker-keys.mjs +46 -0
  96. package/lib/chat/web-session.mjs +159 -0
  97. package/lib/cli-commands.mjs +129 -15
  98. package/lib/comment-lint.mjs +6 -4
  99. package/lib/config/schema.mjs +36 -5
  100. package/lib/contract-schemas/decision.json +50 -0
  101. package/lib/contract-schemas/implementation.json +51 -0
  102. package/lib/contract-schemas/review-report.json +32 -0
  103. package/lib/contract-schemas/test-report.json +43 -0
  104. package/lib/contracts/construct-handoff.mjs +60 -0
  105. package/lib/contracts/validate.mjs +32 -3
  106. package/lib/contracts/violation-log.mjs +58 -5
  107. package/lib/dashboard-demo.mjs +71 -0
  108. package/lib/dashboard-static.mjs +19 -5
  109. package/lib/decisions/registry.mjs +5 -3
  110. package/lib/deck-export-pptx.mjs +1152 -0
  111. package/lib/demo-recording.mjs +142 -0
  112. package/lib/demo-script.mjs +114 -0
  113. package/lib/demo-surface.mjs +249 -0
  114. package/lib/demo.mjs +598 -140
  115. package/lib/diagram-export.mjs +192 -0
  116. package/lib/diagram.mjs +13 -11
  117. package/lib/docs-verify.mjs +25 -7
  118. package/lib/doctor/index.mjs +3 -1
  119. package/lib/doctor/project-adapters.mjs +44 -0
  120. package/lib/doctor/watchers/cx-budget.mjs +98 -0
  121. package/lib/doctor/watchers/graph-staleness.mjs +52 -0
  122. package/lib/doctor/watchers/process-pressure.mjs +4 -3
  123. package/lib/document-export.mjs +360 -36
  124. package/lib/document-extract.mjs +44 -4
  125. package/lib/document-ingest.mjs +29 -20
  126. package/lib/embed/cli.mjs +2 -2
  127. package/lib/embed/daemon.mjs +18 -46
  128. package/lib/embed/worker.mjs +2 -2
  129. package/lib/embedded-contract/model-resolve.mjs +10 -9
  130. package/lib/env-config.mjs +26 -0
  131. package/lib/evals/dataset.mjs +137 -0
  132. package/lib/evals/gates.mjs +175 -0
  133. package/lib/graph/build-co-change.mjs +54 -0
  134. package/lib/graph/build-from-registry.mjs +136 -0
  135. package/lib/graph/build-import-graph.mjs +153 -0
  136. package/lib/graph/cli.mjs +110 -0
  137. package/lib/graph/impact-cli.mjs +89 -0
  138. package/lib/graph/impact.mjs +108 -0
  139. package/lib/graph/staleness.mjs +44 -0
  140. package/lib/graph/store.mjs +172 -0
  141. package/lib/health-check.mjs +90 -76
  142. package/lib/hooks/artifact-release-gate.mjs +43 -0
  143. package/lib/hooks/brand-prose-lint.mjs +38 -0
  144. package/lib/hooks/graph-impact-advisory.mjs +62 -0
  145. package/lib/hooks/session-optimize.mjs +84 -207
  146. package/lib/hooks/session-start.mjs +4 -5
  147. package/lib/host-disposition.mjs +7 -0
  148. package/lib/improvement/cli.mjs +189 -0
  149. package/lib/improvement/controller.mjs +137 -0
  150. package/lib/improvement/proposal.mjs +120 -0
  151. package/lib/improvement/specialist-loop.mjs +192 -0
  152. package/lib/improvement/store.mjs +89 -0
  153. package/lib/improvement/surface.mjs +219 -0
  154. package/lib/ingest-tooling.mjs +97 -0
  155. package/lib/init/doc-lanes.mjs +165 -0
  156. package/lib/init-docs.mjs +31 -178
  157. package/lib/init-unified.mjs +27 -97
  158. package/lib/init-update-guide.mjs +102 -0
  159. package/lib/init-update.mjs +32 -1
  160. package/lib/init.mjs +8 -0
  161. package/lib/install/desktop-binary-download.mjs +85 -0
  162. package/lib/install/legacy-global-cleanup.mjs +189 -0
  163. package/lib/intake/daemon.mjs +2 -0
  164. package/lib/intake/git-queue.mjs +9 -8
  165. package/lib/intake/queue.mjs +2 -1
  166. package/lib/intake/session-prelude.mjs +90 -1
  167. package/lib/libreoffice-export.mjs +97 -0
  168. package/lib/logging/rotate.mjs +9 -0
  169. package/lib/maintenance/docker-reclaim.mjs +206 -0
  170. package/lib/mcp/external-schema-cost.mjs +74 -0
  171. package/lib/mcp/server.mjs +84 -9
  172. package/lib/mcp/stdio-mcp-probe.mjs +188 -0
  173. package/lib/mcp/tool-budget.mjs +55 -4
  174. package/lib/mcp/tools/document.mjs +18 -4
  175. package/lib/mcp/tools/skills.mjs +32 -3
  176. package/lib/mcp/tools/workflow.mjs +13 -1
  177. package/lib/model-free-selector.mjs +18 -0
  178. package/lib/model-registry.mjs +13 -229
  179. package/lib/model-router.mjs +373 -92
  180. package/lib/models/behavior-matrix.mjs +289 -0
  181. package/lib/models/catalog.mjs +209 -0
  182. package/lib/models/execution-capability-profile.mjs +196 -0
  183. package/lib/models/execution-policy.mjs +307 -0
  184. package/lib/models/provider-poll.mjs +383 -0
  185. package/lib/npm-spawn-env.mjs +17 -0
  186. package/lib/ollama/installed-models.mjs +129 -0
  187. package/lib/oracle/actions.mjs +187 -0
  188. package/lib/oracle/artifact-gate.mjs +99 -0
  189. package/lib/oracle/cli.mjs +204 -0
  190. package/lib/oracle/daemon-entry.mjs +14 -0
  191. package/lib/oracle/dispatch.mjs +81 -0
  192. package/lib/oracle/execute.mjs +143 -0
  193. package/lib/oracle/gaps.mjs +76 -0
  194. package/lib/oracle/index.mjs +84 -0
  195. package/lib/oracle/issues.mjs +164 -0
  196. package/lib/oracle/org-graph.mjs +170 -0
  197. package/lib/oracle/policy.mjs +80 -0
  198. package/lib/oracle/read-model.mjs +398 -0
  199. package/lib/oracle/reconcile.mjs +191 -0
  200. package/lib/oracle/routing.mjs +89 -0
  201. package/lib/oracle/synthesize.mjs +384 -0
  202. package/lib/oracle/verdicts.mjs +51 -0
  203. package/lib/orchestration/worker.mjs +88 -27
  204. package/lib/orchestration-policy.mjs +50 -0
  205. package/lib/parity.mjs +96 -2
  206. package/lib/playwright-demo.mjs +252 -0
  207. package/lib/project-init-shared.mjs +4 -1
  208. package/lib/prompt-composer.js +7 -3
  209. package/lib/prompt-validation-contract.mjs +24 -0
  210. package/lib/provider-capabilities.js +57 -11
  211. package/lib/providers/contract/adapters/confluence/index.mjs +181 -0
  212. package/lib/providers/contract/adapters/git/index.mjs +115 -0
  213. package/lib/providers/contract/adapters/github/index.mjs +166 -0
  214. package/lib/providers/contract/adapters/jira/index.mjs +187 -0
  215. package/lib/providers/contract/adapters/slack/index.mjs +175 -0
  216. package/lib/providers/contract/contract-tests.mjs +57 -0
  217. package/lib/providers/contract/errors.mjs +48 -0
  218. package/lib/providers/contract/interface.mjs +50 -0
  219. package/lib/providers/contract/registry.mjs +102 -0
  220. package/lib/providers/copilot-auth.mjs +297 -0
  221. package/lib/providers/credential-bootstrap.mjs +178 -0
  222. package/lib/providers/credential-catalog.mjs +46 -0
  223. package/lib/providers/credential-sources.mjs +63 -0
  224. package/lib/providers/creds.mjs +5 -2
  225. package/lib/providers/op-run.mjs +59 -0
  226. package/lib/providers/secret-resolver.mjs +159 -0
  227. package/lib/publish-template.mjs +163 -0
  228. package/lib/publish-tooling.mjs +119 -0
  229. package/lib/publish.mjs +305 -0
  230. package/lib/registry/cli.mjs +82 -0
  231. package/lib/registry/consolidation.mjs +147 -0
  232. package/lib/registry/generate-docs.mjs +75 -0
  233. package/lib/registry/skill-verification.mjs +57 -0
  234. package/lib/registry/surface-map.mjs +76 -0
  235. package/lib/registry/validate.mjs +135 -0
  236. package/lib/resources/budget.mjs +82 -0
  237. package/lib/resources/process-budget.mjs +45 -0
  238. package/lib/rules-delivery.mjs +9 -2
  239. package/lib/rules-read.mjs +26 -0
  240. package/lib/runtime-env.mjs +23 -0
  241. package/lib/runtime-pressure.mjs +51 -7
  242. package/lib/schema-infer.mjs +13 -2
  243. package/lib/server/chat-loop.mjs +622 -0
  244. package/lib/server/demo-preview.mjs +63 -0
  245. package/lib/server/index.mjs +228 -7
  246. package/lib/service-manager.mjs +60 -8
  247. package/lib/setup.mjs +70 -7
  248. package/lib/specialists/prompt-schema.mjs +19 -10
  249. package/lib/specialists/roster.mjs +43 -0
  250. package/lib/specialists/scaffold.mjs +56 -0
  251. package/lib/storage/backend.mjs +6 -6
  252. package/lib/storage/hybrid-query.mjs +7 -4
  253. package/lib/storage/state-source.mjs +5 -5
  254. package/lib/storage/sync.mjs +2 -3
  255. package/lib/telemetry/rule-calls.mjs +52 -0
  256. package/lib/template-registry.mjs +2 -2
  257. package/lib/templates/visual-requirements.mjs +27 -51
  258. package/lib/test-corpus-inventory.mjs +313 -0
  259. package/lib/uninstall/uninstall.mjs +19 -7
  260. package/lib/update.mjs +12 -0
  261. package/lib/upgrade.mjs +14 -0
  262. package/lib/wireframe.mjs +20 -14
  263. package/lib/worker/run.mjs +17 -4
  264. package/lib/worker/trace.mjs +11 -3
  265. package/package.json +28 -14
  266. package/personas/construct.md +2 -2
  267. package/platforms/claude/settings.template.json +36 -0
  268. package/rules/common/patterns.md +1 -1
  269. package/rules/common/release-gates.md +4 -3
  270. package/scripts/sync-specialists.mjs +41 -28
  271. package/skills/devops/data-engineering.md +1 -1
  272. package/skills/docs/adr-workflow.md +1 -0
  273. package/skills/docs/backlog-proposal-workflow.md +1 -0
  274. package/skills/docs/codebase-research-workflow.md +40 -0
  275. package/skills/docs/customer-profile-workflow.md +1 -0
  276. package/skills/docs/document-ingest-workflow.md +1 -0
  277. package/skills/docs/evidence-ingest-workflow.md +1 -0
  278. package/skills/docs/init-docs.md +2 -2
  279. package/skills/docs/init-project.md +7 -2
  280. package/skills/docs/prd-workflow.md +24 -1
  281. package/skills/docs/prfaq-workflow.md +1 -0
  282. package/skills/docs/product-intelligence-workflow.md +1 -0
  283. package/skills/docs/product-signal-workflow.md +1 -0
  284. package/skills/docs/research-workflow.md +54 -37
  285. package/skills/docs/runbook-workflow.md +1 -0
  286. package/skills/docs/strategy-workflow.md +1 -0
  287. package/skills/docs/user-research-workflow.md +40 -0
  288. package/skills/operating/orchestration-reference.md +1 -1
  289. package/skills/roles/architect.md +5 -0
  290. package/skills/roles/operator.docs.md +4 -0
  291. package/skills/routing.md +4 -2
  292. package/specialists/artifact-manifest.json +480 -0
  293. package/specialists/artifact-manifest.schema.json +53 -0
  294. package/specialists/audit-enrichments.json +454 -0
  295. package/specialists/contracts.json +37 -25
  296. package/specialists/prompts/_shared/validation-contract.md +26 -0
  297. package/specialists/prompts/cx-accessibility.md +21 -2
  298. package/specialists/prompts/cx-ai-engineer.md +24 -1
  299. package/specialists/prompts/cx-architect.md +4 -1
  300. package/specialists/prompts/cx-business-strategist.md +23 -2
  301. package/specialists/prompts/cx-data-analyst.md +22 -1
  302. package/specialists/prompts/cx-data-engineer.md +23 -2
  303. package/specialists/prompts/cx-debugger.md +21 -2
  304. package/specialists/prompts/cx-designer.md +26 -3
  305. package/specialists/prompts/cx-devil-advocate.md +19 -2
  306. package/specialists/prompts/cx-docs-keeper.md +28 -1
  307. package/specialists/prompts/cx-engineer.md +22 -1
  308. package/specialists/prompts/cx-evaluator.md +18 -1
  309. package/specialists/prompts/cx-explorer.md +21 -2
  310. package/specialists/prompts/cx-legal-compliance.md +21 -2
  311. package/specialists/prompts/cx-operations.md +21 -2
  312. package/specialists/prompts/cx-oracle.md +94 -0
  313. package/specialists/prompts/cx-orchestrator.md +20 -1
  314. package/specialists/prompts/cx-platform-engineer.md +25 -2
  315. package/specialists/prompts/cx-product-manager.md +32 -1
  316. package/specialists/prompts/cx-qa.md +24 -2
  317. package/specialists/prompts/cx-rd-lead.md +23 -2
  318. package/specialists/prompts/cx-release-manager.md +23 -2
  319. package/specialists/prompts/cx-researcher.md +22 -2
  320. package/specialists/prompts/cx-reviewer.md +18 -1
  321. package/specialists/prompts/cx-security.md +22 -2
  322. package/specialists/prompts/cx-sre.md +30 -3
  323. package/specialists/prompts/cx-test-automation.md +7 -1
  324. package/specialists/prompts/cx-trace-reviewer.md +22 -3
  325. package/specialists/prompts/cx-ux-researcher.md +21 -2
  326. package/specialists/registry.json +52 -173
  327. package/specialists/tone-profiles.json +42 -0
  328. package/templates/demos/playwright/demo-recording.config.mjs +47 -0
  329. package/templates/demos/recordings/agentic-platforms-prd.json +19 -0
  330. package/templates/demos/scripts/agentic-platforms-prd.json +44 -0
  331. package/templates/demos/specs/_helpers/scroll-artifact.ts +89 -0
  332. package/templates/demos/tapes/agentic-platforms-prd.tape +49 -0
  333. package/templates/demos/tapes/resource-guard-rails.tape +49 -0
  334. package/templates/demos/vhs/construct-cockpit.json +24 -0
  335. package/templates/distribution/construct-brand.typ +446 -0
  336. package/templates/distribution/construct-decision.typ +38 -0
  337. package/templates/distribution/construct-deck.html +95 -0
  338. package/templates/distribution/construct-pdf.typ +38 -0
  339. package/templates/distribution/construct-prd.typ +38 -0
  340. package/templates/distribution/construct-research.typ +38 -0
  341. package/templates/distribution/construct-web.html +92 -0
  342. package/templates/distribution/fonts/Geist-Bold.ttf +0 -0
  343. package/templates/distribution/fonts/Geist-Medium.ttf +0 -0
  344. package/templates/distribution/fonts/Geist-Regular.ttf +0 -0
  345. package/templates/distribution/fonts/Geist-SemiBold.ttf +0 -0
  346. package/templates/distribution/fonts/GeistMono-Medium.ttf +0 -0
  347. package/templates/distribution/fonts/GeistMono-Regular.ttf +0 -0
  348. package/templates/distribution/fonts/GeistMono-SemiBold.ttf +0 -0
  349. package/templates/distribution/fonts/IBMPlexMono-Regular.otf +0 -0
  350. package/templates/distribution/fonts/JetBrainsMono-Medium.ttf +0 -0
  351. package/templates/distribution/fonts/JetBrainsMono-Regular.ttf +0 -0
  352. package/templates/distribution/fonts/JetBrainsMono-SemiBold.ttf +0 -0
  353. package/templates/distribution/fonts/PlusJakartaSans-Bold.ttf +0 -0
  354. package/templates/distribution/fonts/PlusJakartaSans-Medium.ttf +0 -0
  355. package/templates/distribution/fonts/PlusJakartaSans-Regular.ttf +0 -0
  356. package/templates/distribution/fonts/PlusJakartaSans-SemiBold.ttf +0 -0
  357. package/templates/distribution/fonts/README.md +36 -0
  358. package/templates/distribution/fonts/SpaceGrotesk-Variable.ttf +0 -0
  359. package/templates/distribution/fonts/handwritten/Caveat.ttf +0 -0
  360. package/templates/distribution/fonts/legacy/IBMPlexMono-Regular.otf +0 -0
  361. package/templates/distribution/fonts/legacy/Inter-Medium.otf +0 -0
  362. package/templates/distribution/fonts/legacy/Inter-Regular.otf +0 -0
  363. package/templates/distribution/fonts/legacy/Inter-SemiBold.otf +0 -0
  364. package/templates/distribution/fonts/legacy/InterDisplay-SemiBold.otf +0 -0
  365. package/templates/distribution/fonts/legacy/SourceSerif4-Regular.otf +0 -0
  366. package/templates/distribution/fonts/legacy/SourceSerif4-Semibold.otf +0 -0
  367. package/templates/distribution/icons/activity.svg +15 -0
  368. package/templates/distribution/icons/alert-triangle.svg +17 -0
  369. package/templates/distribution/icons/book-open.svg +16 -0
  370. package/templates/distribution/icons/bot.svg +20 -0
  371. package/templates/distribution/icons/brain.svg +22 -0
  372. package/templates/distribution/icons/circle-check.svg +16 -0
  373. package/templates/distribution/icons/clipboard-check.svg +17 -0
  374. package/templates/distribution/icons/cpu.svg +28 -0
  375. package/templates/distribution/icons/database.svg +17 -0
  376. package/templates/distribution/icons/eye.svg +16 -0
  377. package/templates/distribution/icons/file-text.svg +19 -0
  378. package/templates/distribution/icons/gauge.svg +16 -0
  379. package/templates/distribution/icons/git-branch.svg +17 -0
  380. package/templates/distribution/icons/key.svg +17 -0
  381. package/templates/distribution/icons/layers.svg +17 -0
  382. package/templates/distribution/icons/list-checks.svg +19 -0
  383. package/templates/distribution/icons/lock.svg +16 -0
  384. package/templates/distribution/icons/message-square.svg +15 -0
  385. package/templates/distribution/icons/network.svg +19 -0
  386. package/templates/distribution/icons/route.svg +17 -0
  387. package/templates/distribution/icons/scale.svg +19 -0
  388. package/templates/distribution/icons/search.svg +16 -0
  389. package/templates/distribution/icons/send.svg +16 -0
  390. package/templates/distribution/icons/server.svg +18 -0
  391. package/templates/distribution/icons/shield-check.svg +16 -0
  392. package/templates/distribution/icons/users.svg +18 -0
  393. package/templates/distribution/icons/webhook.svg +17 -0
  394. package/templates/distribution/icons/workflow.svg +17 -0
  395. package/templates/distribution/icons/wrench.svg +15 -0
  396. package/templates/distribution/run.mjs +18 -1
  397. package/templates/docs/adr.md +7 -0
  398. package/templates/docs/construct_guide.md +8 -8
  399. package/templates/docs/customer-profile.md +4 -0
  400. package/templates/docs/one-pager.md +4 -0
  401. package/templates/docs/postmortem.md +31 -0
  402. package/templates/docs/prd-platform.md +19 -0
  403. package/templates/docs/prd.md +15 -0
  404. package/templates/docs/prfaq.md +7 -0
  405. package/templates/docs/qa-strategy.md +103 -0
  406. package/templates/docs/research-brief.md +8 -0
  407. package/templates/docs/rfc-platform.md +12 -0
  408. package/templates/docs/test-plan.md +7 -0
@@ -0,0 +1,181 @@
1
+ /**
2
+ * providers/confluence/index.mjs — Confluence provider.
3
+ *
4
+ * Transport: Confluence REST API v2 (direct fetch, no SDK).
5
+ * Auth: API token via CONFLUENCE_TOKEN + CONFLUENCE_EMAIL + CONFLUENCE_URL,
6
+ * or passed directly in config.
7
+ *
8
+ * Capabilities: read, write, search
9
+ *
10
+ * read refs:
11
+ * "page:<id>" → single page by ID
12
+ * "space:<key>:pages" → pages in a space
13
+ * "space:<key>:pages:<n>" → first n pages in a space
14
+ * "spaces" → list all accessible spaces
15
+ *
16
+ * write items:
17
+ * { type: 'page', spaceId, title, body, parentId? } → create page
18
+ * { type: 'page-update', pageId, title, body, version } → update page
19
+ * { type: 'comment', pageId, body } → add footer comment
20
+ *
21
+ * search: CQL query string (e.g. "space = DEV AND title ~ 'RFC'")
22
+ */
23
+
24
+ import { AuthError, NotFoundError, RateLimitError } from '../../errors.mjs';
25
+
26
+ export default {
27
+ name: 'confluence',
28
+ capabilities: ['read', 'write', 'search'],
29
+
30
+ _baseUrl: null,
31
+ _auth: null,
32
+
33
+ async init(config = {}) {
34
+ const url = config.baseUrl ?? process.env.CONFLUENCE_URL;
35
+ const email = config.email ?? process.env.CONFLUENCE_EMAIL;
36
+ const token = config.token ?? process.env.CONFLUENCE_TOKEN;
37
+
38
+ if (!url || !email || !token) {
39
+ throw new AuthError(
40
+ 'Confluence provider requires CONFLUENCE_URL, CONFLUENCE_EMAIL, and CONFLUENCE_TOKEN',
41
+ { provider: 'confluence' },
42
+ );
43
+ }
44
+
45
+ this._baseUrl = url.replace(/\/$/, '');
46
+ this._auth = `Basic ${Buffer.from(`${email}:${token}`).toString('base64')}`;
47
+
48
+ const res = await this._fetch('/wiki/rest/api/user/current');
49
+ if (!res.ok) {
50
+ throw new AuthError(`Confluence auth failed: ${res.status} ${res.statusText}`, { provider: 'confluence' });
51
+ }
52
+ },
53
+
54
+ async _fetch(path, { method = 'GET', body } = {}) {
55
+ const url = `${this._baseUrl}${path}`;
56
+ const opts = {
57
+ method,
58
+ headers: {
59
+ Authorization: this._auth,
60
+ 'Content-Type': 'application/json',
61
+ Accept: 'application/json',
62
+ },
63
+ };
64
+ if (body) opts.body = JSON.stringify(body);
65
+ const res = await fetch(url, opts);
66
+ if (res.status === 429) {
67
+ const retryAfter = parseInt(res.headers.get('Retry-After') ?? '60', 10);
68
+ throw new RateLimitError('Confluence rate limit hit', { provider: 'confluence', retryAfter });
69
+ }
70
+ return res;
71
+ },
72
+
73
+ async read(ref, _opts = {}) {
74
+ if (ref === 'spaces') {
75
+ const res = await this._fetch('/wiki/api/v2/spaces?limit=50');
76
+ if (!res.ok) throw new Error(`Confluence error: ${res.status}`);
77
+ const data = await res.json();
78
+ return (data.results ?? []).map((s) => ({
79
+ type: 'space',
80
+ id: s.id,
81
+ key: s.key,
82
+ name: s.name,
83
+ homepageId: s.homepageId,
84
+ }));
85
+ }
86
+
87
+ if (ref.startsWith('page:')) {
88
+ const pageId = ref.slice(5);
89
+ const res = await this._fetch(`/wiki/api/v2/pages/${pageId}?body-format=storage`);
90
+ if (res.status === 404) throw new NotFoundError(`Page not found: ${pageId}`, { provider: 'confluence' });
91
+ if (!res.ok) throw new Error(`Confluence error: ${res.status}`);
92
+ return [normalizePage(await res.json())];
93
+ }
94
+
95
+ const spaceMatch = ref.match(/^space:([^:]+):pages(?::(\d+))?$/);
96
+ if (spaceMatch) {
97
+ const spaceKey = spaceMatch[1];
98
+ const limit = parseInt(spaceMatch[2] ?? '25', 10);
99
+ // Look up space id from key first
100
+ const spacesRes = await this._fetch(`/wiki/api/v2/spaces?keys=${spaceKey}&limit=1`);
101
+ const spacesData = await spacesRes.json();
102
+ const space = spacesData.results?.[0];
103
+ if (!space) throw new NotFoundError(`Space not found: ${spaceKey}`, { provider: 'confluence' });
104
+ const res = await this._fetch(`/wiki/api/v2/spaces/${space.id}/pages?limit=${limit}&body-format=storage`);
105
+ if (!res.ok) throw new Error(`Confluence error: ${res.status}`);
106
+ const data = await res.json();
107
+ return (data.results ?? []).map(normalizePage);
108
+ }
109
+
110
+ throw new NotFoundError(`Unknown Confluence read ref: "${ref}"`, { provider: 'confluence' });
111
+ },
112
+
113
+ async write(item) {
114
+ if (item.type === 'page') {
115
+ const body = {
116
+ spaceId: item.spaceId,
117
+ title: item.title,
118
+ parentId: item.parentId ?? undefined,
119
+ body: { representation: 'storage', value: item.body },
120
+ };
121
+ const res = await this._fetch('/wiki/api/v2/pages', { method: 'POST', body });
122
+ if (!res.ok) throw new Error(`Confluence create page failed: ${res.status}`);
123
+ const data = await res.json();
124
+ return { type: 'page-created', id: data.id, title: data.title };
125
+ }
126
+
127
+ if (item.type === 'page-update') {
128
+ const body = {
129
+ id: item.pageId,
130
+ title: item.title,
131
+ version: { number: item.version },
132
+ body: { representation: 'storage', value: item.body },
133
+ };
134
+ const res = await this._fetch(`/wiki/api/v2/pages/${item.pageId}`, { method: 'PUT', body });
135
+ if (!res.ok) throw new Error(`Confluence update page failed: ${res.status}`);
136
+ return { type: 'page-updated', id: item.pageId };
137
+ }
138
+
139
+ if (item.type === 'comment') {
140
+ const body = {
141
+ pageId: item.pageId,
142
+ body: { representation: 'storage', value: item.body },
143
+ };
144
+ const res = await this._fetch('/wiki/api/v2/footer-comments', { method: 'POST', body });
145
+ if (!res.ok) throw new Error(`Confluence create comment failed: ${res.status}`);
146
+ const data = await res.json();
147
+ return { type: 'comment-created', id: data.id };
148
+ }
149
+
150
+ throw new Error(`Unknown Confluence write item type: "${item.type}"`);
151
+ },
152
+
153
+ async search(query, opts = {}) {
154
+ const limit = opts.limit ?? 25;
155
+ const cql = encodeURIComponent(query);
156
+ const res = await this._fetch(`/wiki/rest/api/content/search?cql=${cql}&limit=${limit}`);
157
+ if (!res.ok) throw new Error(`Confluence search failed: ${res.status}`);
158
+ const data = await res.json();
159
+ return (data.results ?? []).map((r) => ({
160
+ type: 'page',
161
+ id: r.id,
162
+ title: r.title,
163
+ spaceKey: r.space?.key,
164
+ url: r._links?.webui ?? null,
165
+ }));
166
+ },
167
+ };
168
+
169
+ function normalizePage(raw) {
170
+ return {
171
+ type: 'page',
172
+ id: raw.id,
173
+ title: raw.title,
174
+ spaceId: raw.spaceId,
175
+ parentId: raw.parentId ?? null,
176
+ version: raw.version?.number ?? null,
177
+ body: raw.body?.storage?.value ?? null,
178
+ createdAt: raw.createdAt ?? null,
179
+ url: raw._links?.webui ?? null,
180
+ };
181
+ }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * providers/git/index.mjs — git repo provider.
3
+ *
4
+ * Transport: git CLI (via child_process). No external deps.
5
+ *
6
+ * Capabilities: read, search
7
+ *
8
+ * read refs:
9
+ * "commits" → recent commits (default 20)
10
+ * "commits:<n>" → last n commits
11
+ * "branches" → all branch names
12
+ * "file:<path>" → file contents at HEAD
13
+ * "status" → working tree status
14
+ * "log:<path>" → commit log for a specific path
15
+ *
16
+ * search: grep across tracked files (git grep)
17
+ */
18
+
19
+ import { spawnSync } from 'node:child_process';
20
+ import path from 'node:path';
21
+ import { AuthError, NotFoundError } from '../../errors.mjs';
22
+
23
+ function git(args, cwd) {
24
+ const result = spawnSync('git', args, { cwd, encoding: 'utf8' });
25
+ if (result.error) throw new Error(`git spawn error: ${result.error.message}`);
26
+ return { stdout: result.stdout ?? '', stderr: result.stderr ?? '', status: result.status };
27
+ }
28
+
29
+ export default {
30
+ name: 'git',
31
+ capabilities: ['read', 'search'],
32
+
33
+ _cwd: null,
34
+
35
+ async init(config = {}) {
36
+ const cwd = config.cwd ?? process.cwd();
37
+ // Verify this is actually a git repo
38
+ const check = git(['rev-parse', '--git-dir'], cwd);
39
+ if (check.status !== 0) {
40
+ throw new AuthError(`Not a git repository: ${cwd}`, { provider: 'git' });
41
+ }
42
+ this._cwd = cwd;
43
+ },
44
+
45
+ async read(ref, _opts = {}) {
46
+ const cwd = this._cwd ?? process.cwd();
47
+
48
+ if (ref === 'branches') {
49
+ const r = git(['branch', '-a', '--format=%(refname:short)'], cwd);
50
+ return r.stdout.trim().split('\n').filter(Boolean).map((name) => ({ type: 'branch', name }));
51
+ }
52
+
53
+ if (ref === 'status') {
54
+ const r = git(['status', '--porcelain=v1'], cwd);
55
+ return r.stdout.trim().split('\n').filter(Boolean).map((line) => ({
56
+ type: 'status-entry',
57
+ code: line.slice(0, 2).trim(),
58
+ path: line.slice(3),
59
+ }));
60
+ }
61
+
62
+ if (ref.startsWith('commits') || ref === 'commits') {
63
+ const n = ref.includes(':') ? parseInt(ref.split(':')[1], 10) : 20;
64
+ const r = git(['log', `--max-count=${n}`, '--pretty=format:%H\t%an\t%ae\t%ai\t%s'], cwd);
65
+ return r.stdout.trim().split('\n').filter(Boolean).map((line) => {
66
+ const [hash, author, email, date, ...subjectParts] = line.split('\t');
67
+ return { type: 'commit', hash, author, email, date, subject: subjectParts.join('\t') };
68
+ });
69
+ }
70
+
71
+ if (ref.startsWith('file:')) {
72
+ const filePath = ref.slice(5);
73
+ const r = git(['show', `HEAD:${filePath}`], cwd);
74
+ if (r.status !== 0) {
75
+ throw new NotFoundError(`File not found in HEAD: ${filePath}`, { provider: 'git' });
76
+ }
77
+ return [{ type: 'file', path: filePath, content: r.stdout }];
78
+ }
79
+
80
+ if (ref.startsWith('log:')) {
81
+ const filePath = ref.slice(4);
82
+ const r = git(['log', '--follow', '--pretty=format:%H\t%an\t%ai\t%s', '--', filePath], cwd);
83
+ return r.stdout.trim().split('\n').filter(Boolean).map((line) => {
84
+ const [hash, author, date, ...subjectParts] = line.split('\t');
85
+ return { type: 'commit', hash, author, date, subject: subjectParts.join('\t'), path: filePath };
86
+ });
87
+ }
88
+
89
+ throw new NotFoundError(`Unknown git read ref: "${ref}"`, { provider: 'git' });
90
+ },
91
+
92
+ async search(query, opts = {}) {
93
+ const cwd = this._cwd ?? process.cwd();
94
+ const args = ['grep', '--line-number', '-I'];
95
+ if (opts.ignoreCase) args.push('-i');
96
+ args.push(query);
97
+ if (opts.paths) args.push('--', ...opts.paths);
98
+ const r = git(args, cwd);
99
+ if (r.status === 1 && r.stdout === '') return []; // no matches
100
+ if (r.status !== 0 && r.status !== 1) {
101
+ throw new Error(`git grep failed: ${r.stderr}`);
102
+ }
103
+ return r.stdout.trim().split('\n').filter(Boolean).map((line) => {
104
+ const colonIdx = line.indexOf(':');
105
+ const rest = line.slice(colonIdx + 1);
106
+ const lineNumIdx = rest.indexOf(':');
107
+ return {
108
+ type: 'grep-match',
109
+ file: line.slice(0, colonIdx),
110
+ lineNumber: parseInt(rest.slice(0, lineNumIdx), 10),
111
+ text: rest.slice(lineNumIdx + 1),
112
+ };
113
+ });
114
+ },
115
+ };
@@ -0,0 +1,166 @@
1
+ /**
2
+ * providers/github/index.mjs — GitHub provider.
3
+ *
4
+ * Transport: gh CLI (must be installed and authenticated).
5
+ *
6
+ * Capabilities: read, write, search, webhook
7
+ *
8
+ * read refs:
9
+ * "prs" → open pull requests
10
+ * "prs:all" → all PRs (open + closed)
11
+ * "pr:<number>" → single PR detail
12
+ * "issues" → open issues
13
+ * "issue:<number>" → single issue detail
14
+ * "releases" → recent releases
15
+ * "repo" → repo metadata
16
+ *
17
+ * write items:
18
+ * { type: 'pr', title, body, head, base } → create PR
19
+ * { type: 'issue', title, body, labels? } → create issue
20
+ * { type: 'comment', issue_number, body } → comment on issue/PR
21
+ * { type: 'pr-merge', number, merge_method? } → merge a PR
22
+ *
23
+ * search: gh search issues/prs/code
24
+ * { scope: 'issues'|'prs'|'code', q: 'query string' }
25
+ *
26
+ * webhook: process GitHub webhook event objects
27
+ */
28
+
29
+ import { spawnSync } from 'node:child_process';
30
+ import { AuthError, NotFoundError } from '../../errors.mjs';
31
+
32
+ function gh(args, { json = true, input } = {}) {
33
+ const fullArgs = json ? [...args, '--json', ...ghJsonFields(args)] : args;
34
+ const result = spawnSync('gh', fullArgs, {
35
+ encoding: 'utf8',
36
+ input,
37
+ env: process.env,
38
+ });
39
+ if (result.error) throw new Error(`gh spawn error: ${result.error.message}`);
40
+ if (result.status !== 0) {
41
+ const msg = result.stderr?.trim() || `gh exited with status ${result.status}`;
42
+ if (msg.includes('authentication') || msg.includes('401')) {
43
+ throw new AuthError(msg, { provider: 'github' });
44
+ }
45
+ throw new Error(`gh error: ${msg}`);
46
+ }
47
+ if (json && result.stdout) {
48
+ try { return JSON.parse(result.stdout); } catch { return result.stdout; }
49
+ }
50
+ return result.stdout;
51
+ }
52
+
53
+ // gh requires --json to list specific fields; default to a safe broad set
54
+ function ghJsonFields(args) {
55
+ const cmd = args[0];
56
+ if (cmd === 'pr') return ['number,title,state,url,body,headRefName,baseRefName,author,createdAt,mergeable,labels'];
57
+ if (cmd === 'issue') return ['number,title,state,url,body,author,createdAt,labels,assignees'];
58
+ if (cmd === 'release') return ['tagName,name,publishedAt,url,body'];
59
+ if (cmd === 'repo') return ['name,description,url,defaultBranchRef,isPrivate,stargazerCount'];
60
+ return [''];
61
+ }
62
+
63
+ export default {
64
+ name: 'github',
65
+ capabilities: ['read', 'write', 'search', 'webhook'],
66
+
67
+ _repo: null,
68
+
69
+ async init(config = {}) {
70
+ this._repo = config.repo ?? null; // optional "owner/repo" override
71
+ // Verify gh is available and authenticated
72
+ const check = spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', env: process.env });
73
+ if (check.status !== 0) {
74
+ throw new AuthError('gh CLI not authenticated. Run: gh auth login', { provider: 'github' });
75
+ }
76
+ },
77
+
78
+ async read(ref, _opts = {}) {
79
+ const repoFlag = this._repo ? ['-R', this._repo] : [];
80
+
81
+ if (ref === 'prs' || ref === 'prs:all') {
82
+ const stateFlag = ref === 'prs:all' ? ['--state', 'all'] : ['--state', 'open'];
83
+ return gh(['pr', 'list', ...repoFlag, ...stateFlag, '--limit', '50']);
84
+ }
85
+
86
+ if (ref.startsWith('pr:')) {
87
+ const num = ref.slice(3);
88
+ return [gh(['pr', 'view', num, ...repoFlag])];
89
+ }
90
+
91
+ if (ref === 'issues') {
92
+ return gh(['issue', 'list', ...repoFlag, '--state', 'open', '--limit', '50']);
93
+ }
94
+
95
+ if (ref.startsWith('issue:')) {
96
+ const num = ref.slice(6);
97
+ return [gh(['issue', 'view', num, ...repoFlag])];
98
+ }
99
+
100
+ if (ref === 'releases') {
101
+ return gh(['release', 'list', ...repoFlag, '--limit', '20']);
102
+ }
103
+
104
+ if (ref === 'repo') {
105
+ return [gh(['repo', 'view', ...repoFlag])];
106
+ }
107
+
108
+ throw new NotFoundError(`Unknown GitHub read ref: "${ref}"`, { provider: 'github' });
109
+ },
110
+
111
+ async write(item) {
112
+ const repoFlag = this._repo ? ['-R', this._repo] : [];
113
+
114
+ if (item.type === 'issue') {
115
+ const args = ['issue', 'create', ...repoFlag, '--title', item.title, '--body', item.body ?? ''];
116
+ if (item.labels?.length) args.push('--label', item.labels.join(','));
117
+ const out = spawnSync('gh', args, { encoding: 'utf8', env: process.env });
118
+ return { type: 'issue-created', url: out.stdout.trim() };
119
+ }
120
+
121
+ if (item.type === 'pr') {
122
+ const args = ['pr', 'create', ...repoFlag,
123
+ '--title', item.title,
124
+ '--body', item.body ?? '',
125
+ '--head', item.head,
126
+ '--base', item.base ?? 'main',
127
+ ];
128
+ if (item.draft) args.push('--draft');
129
+ const out = spawnSync('gh', args, { encoding: 'utf8', env: process.env });
130
+ return { type: 'pr-created', url: out.stdout.trim() };
131
+ }
132
+
133
+ if (item.type === 'comment') {
134
+ const args = ['issue', 'comment', String(item.issue_number), ...repoFlag, '--body', item.body];
135
+ spawnSync('gh', args, { encoding: 'utf8', env: process.env });
136
+ return { type: 'comment-created', issue_number: item.issue_number };
137
+ }
138
+
139
+ if (item.type === 'pr-merge') {
140
+ const method = item.merge_method ?? 'squash';
141
+ spawnSync('gh', ['pr', 'merge', String(item.number), ...repoFlag, `--${method}`], {
142
+ encoding: 'utf8',
143
+ env: process.env,
144
+ });
145
+ return { type: 'pr-merged', number: item.number };
146
+ }
147
+
148
+ throw new Error(`Unknown GitHub write item type: "${item.type}"`);
149
+ },
150
+
151
+ async search(query, opts = {}) {
152
+ const scope = opts.scope ?? 'issues';
153
+ const repoFlag = this._repo ? ['--repo', this._repo] : [];
154
+ const args = ['search', scope, query, ...repoFlag, '--limit', '30', '--json',
155
+ scope === 'code' ? 'path,repository,url,textMatches' : 'number,title,state,url,repository',
156
+ ];
157
+ const results = gh(args, { json: false });
158
+ try { return JSON.parse(results); } catch { return []; }
159
+ },
160
+
161
+ async webhook(event) {
162
+ // Normalize inbound GitHub webhook event to a common shape for core to consume
163
+ const type = event?.action ? `${event.type ?? 'unknown'}.${event.action}` : (event?.type ?? 'unknown');
164
+ return { provider: 'github', type, raw: event };
165
+ },
166
+ };
@@ -0,0 +1,187 @@
1
+ /**
2
+ * providers/jira/index.mjs — Jira provider.
3
+ *
4
+ * Transport: Jira REST API v3 (direct fetch, no SDK).
5
+ * Auth: API token via JIRA_TOKEN + JIRA_EMAIL + JIRA_URL env vars,
6
+ * or passed directly in config.
7
+ *
8
+ * Capabilities: read, write, search, webhook
9
+ *
10
+ * read refs:
11
+ * "issue:<KEY>" → single issue (e.g. "issue:PROJ-123")
12
+ * "project:<KEY>" → project metadata
13
+ * "board:<id>:sprint" → active sprint issues for a board
14
+ * "search:<JQL>" → JQL query (shorthand; prefer search() for full control)
15
+ *
16
+ * write items:
17
+ * { type: 'issue', project, issueType, summary, description?, assignee?, labels? }
18
+ * { type: 'comment', issueKey, body }
19
+ * { type: 'transition', issueKey, transitionId }
20
+ * { type: 'update', issueKey, fields }
21
+ *
22
+ * search: JQL string (e.g. "project = PROJ AND status = 'In Progress'")
23
+ *
24
+ * webhook: process Jira webhook event objects
25
+ */
26
+
27
+ import { AuthError, NotFoundError, RateLimitError } from '../../errors.mjs';
28
+
29
+ export default {
30
+ name: 'jira',
31
+ capabilities: ['read', 'write', 'search', 'webhook'],
32
+
33
+ _baseUrl: null,
34
+ _auth: null,
35
+
36
+ async init(config = {}) {
37
+ const url = config.baseUrl ?? process.env.JIRA_URL;
38
+ const email = config.email ?? process.env.JIRA_EMAIL;
39
+ const token = config.token ?? process.env.JIRA_TOKEN;
40
+
41
+ if (!url || !email || !token) {
42
+ throw new AuthError(
43
+ 'Jira provider requires JIRA_URL, JIRA_EMAIL, and JIRA_TOKEN (or config.baseUrl/email/token)',
44
+ { provider: 'jira' },
45
+ );
46
+ }
47
+
48
+ this._baseUrl = url.replace(/\/$/, '');
49
+ this._auth = `Basic ${Buffer.from(`${email}:${token}`).toString('base64')}`;
50
+
51
+ // Verify credentials
52
+ const res = await this._fetch('/rest/api/3/myself');
53
+ if (!res.ok) {
54
+ throw new AuthError(`Jira auth failed: ${res.status} ${res.statusText}`, { provider: 'jira' });
55
+ }
56
+ },
57
+
58
+ async _fetch(path, { method = 'GET', body } = {}) {
59
+ const url = `${this._baseUrl}${path}`;
60
+ const opts = {
61
+ method,
62
+ headers: {
63
+ Authorization: this._auth,
64
+ 'Content-Type': 'application/json',
65
+ Accept: 'application/json',
66
+ },
67
+ };
68
+ if (body) opts.body = JSON.stringify(body);
69
+ const res = await fetch(url, opts);
70
+ if (res.status === 429) {
71
+ const retryAfter = parseInt(res.headers.get('Retry-After') ?? '60', 10);
72
+ throw new RateLimitError('Jira rate limit hit', { provider: 'jira', retryAfter });
73
+ }
74
+ return res;
75
+ },
76
+
77
+ async read(ref, _opts = {}) {
78
+ if (ref.startsWith('issue:')) {
79
+ const key = ref.slice(6);
80
+ const res = await this._fetch(`/rest/api/3/issue/${key}`);
81
+ if (res.status === 404) throw new NotFoundError(`Issue not found: ${key}`, { provider: 'jira' });
82
+ const data = await res.json();
83
+ return [normalizeIssue(data)];
84
+ }
85
+
86
+ if (ref.startsWith('project:')) {
87
+ const key = ref.slice(8);
88
+ const res = await this._fetch(`/rest/api/3/project/${key}`);
89
+ if (res.status === 404) throw new NotFoundError(`Project not found: ${key}`, { provider: 'jira' });
90
+ return [await res.json()];
91
+ }
92
+
93
+ if (ref.startsWith('search:')) {
94
+ const jql = ref.slice(7);
95
+ return this.search(jql);
96
+ }
97
+
98
+ throw new NotFoundError(`Unknown Jira read ref: "${ref}"`, { provider: 'jira' });
99
+ },
100
+
101
+ async write(item) {
102
+ if (item.type === 'issue') {
103
+ const body = {
104
+ fields: {
105
+ project: { key: item.project },
106
+ issuetype: { name: item.issueType ?? 'Task' },
107
+ summary: item.summary,
108
+ ...(item.description ? { description: adfDoc(item.description) } : {}),
109
+ ...(item.assignee ? { assignee: { accountId: item.assignee } } : {}),
110
+ ...(item.labels ? { labels: item.labels } : {}),
111
+ },
112
+ };
113
+ const res = await this._fetch('/rest/api/3/issue', { method: 'POST', body });
114
+ const data = await res.json();
115
+ return { type: 'issue-created', key: data.key, id: data.id };
116
+ }
117
+
118
+ if (item.type === 'comment') {
119
+ const body = { body: adfDoc(item.body) };
120
+ const res = await this._fetch(`/rest/api/3/issue/${item.issueKey}/comment`, { method: 'POST', body });
121
+ const data = await res.json();
122
+ return { type: 'comment-created', id: data.id, issueKey: item.issueKey };
123
+ }
124
+
125
+ if (item.type === 'transition') {
126
+ await this._fetch(`/rest/api/3/issue/${item.issueKey}/transitions`, {
127
+ method: 'POST',
128
+ body: { transition: { id: String(item.transitionId) } },
129
+ });
130
+ return { type: 'transitioned', issueKey: item.issueKey, transitionId: item.transitionId };
131
+ }
132
+
133
+ if (item.type === 'update') {
134
+ await this._fetch(`/rest/api/3/issue/${item.issueKey}`, {
135
+ method: 'PUT',
136
+ body: { fields: item.fields },
137
+ });
138
+ return { type: 'updated', issueKey: item.issueKey };
139
+ }
140
+
141
+ throw new Error(`Unknown Jira write item type: "${item.type}"`);
142
+ },
143
+
144
+ async search(query, opts = {}) {
145
+ const body = {
146
+ jql: query,
147
+ maxResults: opts.maxResults ?? 50,
148
+ fields: opts.fields ?? ['summary', 'status', 'assignee', 'priority', 'issuetype', 'created', 'updated'],
149
+ };
150
+ const res = await this._fetch('/rest/api/3/search', { method: 'POST', body });
151
+ const data = await res.json();
152
+ return (data.issues ?? []).map(normalizeIssue);
153
+ },
154
+
155
+ async webhook(event) {
156
+ const type = event?.webhookEvent ?? 'unknown';
157
+ return { provider: 'jira', type, raw: event };
158
+ },
159
+ };
160
+
161
+ // ── Helpers ──────────────────────────────────────────────────────────────
162
+
163
+ function normalizeIssue(raw) {
164
+ const f = raw.fields ?? {};
165
+ return {
166
+ type: 'issue',
167
+ key: raw.key,
168
+ id: raw.id,
169
+ summary: f.summary,
170
+ status: f.status?.name,
171
+ assignee: f.assignee?.displayName ?? null,
172
+ priority: f.priority?.name ?? null,
173
+ issueType: f.issuetype?.name,
174
+ created: f.created,
175
+ updated: f.updated,
176
+ url: raw.self?.replace(/\/rest\/api\/.*/, `/browse/${raw.key}`) ?? null,
177
+ };
178
+ }
179
+
180
+ /** Wrap plain text in minimal ADF document for Jira REST API v3 */
181
+ function adfDoc(text) {
182
+ return {
183
+ type: 'doc',
184
+ version: 1,
185
+ content: [{ type: 'paragraph', content: [{ type: 'text', text: String(text) }] }],
186
+ };
187
+ }