@doxbrix/doxloop 0.1.5 → 0.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 (357) hide show
  1. package/CHANGELOG.md +1145 -0
  2. package/README.md +422 -227
  3. package/assets/doxbrix-preview.css +77 -15
  4. package/contracts/agent-events-v1.schema.json +1 -0
  5. package/contracts/coverage-v1.schema.json +59 -0
  6. package/contracts/drift-v1.schema.json +1 -0
  7. package/contracts/evaluation-v1.schema.json +62 -0
  8. package/contracts/quality-codes-v1.json +1 -0
  9. package/contracts/quality-report-v1.schema.json +1 -0
  10. package/contracts/validation-v1.schema.json +1 -0
  11. package/dist/agent-failure.d.ts +36 -0
  12. package/dist/agent-failure.js +98 -0
  13. package/dist/agent-isolation.d.ts +17 -0
  14. package/dist/agent-isolation.js +94 -0
  15. package/dist/agent-log.d.ts +197 -0
  16. package/dist/agent-log.js +1094 -0
  17. package/dist/agent-process.d.ts +63 -0
  18. package/dist/agent-process.js +128 -0
  19. package/dist/agent-reply.d.ts +47 -0
  20. package/dist/agent-reply.js +315 -0
  21. package/dist/agents.d.ts +24 -5
  22. package/dist/agents.js +60 -9
  23. package/dist/api-coverage.d.ts +11 -0
  24. package/dist/api-coverage.js +72 -0
  25. package/dist/application-probe.d.ts +28 -0
  26. package/dist/application-probe.js +93 -0
  27. package/dist/application-url.d.ts +14 -0
  28. package/dist/application-url.js +26 -0
  29. package/dist/args.js +15 -0
  30. package/dist/artifact-deploy.d.ts +9 -0
  31. package/dist/artifact-deploy.js +28 -8
  32. package/dist/assets.d.ts +72 -0
  33. package/dist/assets.js +382 -0
  34. package/dist/auth.d.ts +10 -0
  35. package/dist/auth.js +38 -9
  36. package/dist/author.d.ts +206 -4
  37. package/dist/author.js +1650 -52
  38. package/dist/authoring-batches.d.ts +190 -0
  39. package/dist/authoring-batches.js +489 -0
  40. package/dist/authoring-postpass.d.ts +79 -0
  41. package/dist/authoring-postpass.js +1289 -0
  42. package/dist/authoring-progress.d.ts +95 -0
  43. package/dist/authoring-progress.js +269 -0
  44. package/dist/autosync.d.ts +56 -0
  45. package/dist/autosync.js +442 -0
  46. package/dist/batch-artifacts.d.ts +81 -0
  47. package/dist/batch-artifacts.js +412 -0
  48. package/dist/batch-limits.d.ts +37 -0
  49. package/dist/batch-limits.js +80 -0
  50. package/dist/branding.d.ts +41 -0
  51. package/dist/branding.js +229 -0
  52. package/dist/bundle-upload.d.ts +26 -0
  53. package/dist/bundle-upload.js +99 -0
  54. package/dist/capture-auth.d.ts +95 -0
  55. package/dist/capture-auth.js +204 -0
  56. package/dist/capture.d.ts +48 -0
  57. package/dist/capture.js +20 -1
  58. package/dist/cli.js +839 -32
  59. package/dist/content-links.d.ts +10 -0
  60. package/dist/content-links.js +49 -0
  61. package/dist/contract-validation.d.ts +4 -0
  62. package/dist/contract-validation.js +25 -0
  63. package/dist/coverage-actions.d.ts +8 -0
  64. package/dist/coverage-actions.js +71 -0
  65. package/dist/coverage-resolutions.d.ts +16 -0
  66. package/dist/coverage-resolutions.js +58 -0
  67. package/dist/db.d.ts +37 -0
  68. package/dist/db.js +288 -0
  69. package/dist/demo.d.ts +12 -0
  70. package/dist/demo.js +122 -0
  71. package/dist/deploy-credentials.d.ts +7 -0
  72. package/dist/deploy-credentials.js +76 -0
  73. package/dist/deploy-targets/github-pages.d.ts +3 -0
  74. package/dist/deploy-targets/github-pages.js +100 -0
  75. package/dist/deploy-targets/index.d.ts +8 -0
  76. package/dist/deploy-targets/index.js +78 -0
  77. package/dist/deploy-targets/netlify.d.ts +3 -0
  78. package/dist/deploy-targets/netlify.js +49 -0
  79. package/dist/deploy-targets/types.d.ts +32 -0
  80. package/dist/deploy-targets/types.js +2 -0
  81. package/dist/deploy-targets/vercel.d.ts +3 -0
  82. package/dist/deploy-targets/vercel.js +95 -0
  83. package/dist/deploy.d.ts +9 -0
  84. package/dist/deploy.js +87 -13
  85. package/dist/deterministic-capture.d.ts +90 -0
  86. package/dist/deterministic-capture.js +435 -0
  87. package/dist/direct-edit.d.ts +40 -0
  88. package/dist/direct-edit.js +159 -0
  89. package/dist/docs-crawl.d.ts +86 -0
  90. package/dist/docs-crawl.js +536 -0
  91. package/dist/docs-site.d.ts +49 -0
  92. package/dist/docs-site.js +233 -0
  93. package/dist/doctor.js +8 -0
  94. package/dist/documentation-collections.d.ts +23 -0
  95. package/dist/documentation-collections.js +191 -0
  96. package/dist/documentation-plan.d.ts +253 -0
  97. package/dist/documentation-plan.js +2563 -0
  98. package/dist/doxbrix-build.d.ts +19 -0
  99. package/dist/doxbrix-build.js +167 -0
  100. package/dist/doxbrix-markdown.d.ts +9 -0
  101. package/dist/doxbrix-markdown.js +115 -14
  102. package/dist/drift.d.ts +10 -0
  103. package/dist/drift.js +164 -0
  104. package/dist/evaluation.d.ts +46 -0
  105. package/dist/evaluation.js +113 -0
  106. package/dist/evidence-pack.d.ts +47 -0
  107. package/dist/evidence-pack.js +358 -0
  108. package/dist/evidence.d.ts +25 -0
  109. package/dist/evidence.js +175 -0
  110. package/dist/fs.d.ts +8 -2
  111. package/dist/fs.js +41 -11
  112. package/dist/generator-api.d.ts +89 -0
  113. package/dist/generator-preflight.d.ts +27 -0
  114. package/dist/generator-preflight.js +105 -0
  115. package/dist/generator-runtime.d.ts +7 -0
  116. package/dist/generator-runtime.js +17 -1
  117. package/dist/generators.d.ts +24 -2
  118. package/dist/generators.js +54 -1
  119. package/dist/git-delivery.d.ts +17 -0
  120. package/dist/git-delivery.js +123 -0
  121. package/dist/globs.d.ts +16 -0
  122. package/dist/globs.js +65 -0
  123. package/dist/glossary.d.ts +26 -0
  124. package/dist/glossary.js +179 -0
  125. package/dist/history.d.ts +106 -0
  126. package/dist/history.js +600 -0
  127. package/dist/html-markdown.d.ts +46 -0
  128. package/dist/html-markdown.js +423 -0
  129. package/dist/interactive.js +3 -0
  130. package/dist/job-events.d.ts +74 -0
  131. package/dist/job-events.js +377 -0
  132. package/dist/keep-awake.d.ts +50 -0
  133. package/dist/keep-awake.js +123 -0
  134. package/dist/local-source-snapshot.d.ts +20 -0
  135. package/dist/local-source-snapshot.js +61 -0
  136. package/dist/mintlify-detect.d.ts +3 -0
  137. package/dist/mintlify-detect.js +18 -0
  138. package/dist/mintlify-import.d.ts +75 -0
  139. package/dist/mintlify-import.js +190 -0
  140. package/dist/navigation.d.ts +98 -0
  141. package/dist/navigation.js +310 -0
  142. package/dist/openapi.d.ts +60 -0
  143. package/dist/openapi.js +439 -0
  144. package/dist/page-editor-bridge.d.ts +3 -0
  145. package/dist/page-editor-bridge.js +109 -0
  146. package/dist/page-editor-preview.d.ts +10 -0
  147. package/dist/page-editor-preview.js +55 -0
  148. package/dist/page-extension.d.ts +9 -0
  149. package/dist/page-extension.js +15 -0
  150. package/dist/page-metadata.d.ts +28 -0
  151. package/dist/page-metadata.js +166 -0
  152. package/dist/page-operations.d.ts +34 -0
  153. package/dist/page-operations.js +215 -0
  154. package/dist/page-routes.d.ts +4 -0
  155. package/dist/page-routes.js +61 -0
  156. package/dist/pages.d.ts +20 -0
  157. package/dist/pages.js +184 -0
  158. package/dist/plan-generator.d.ts +3 -0
  159. package/dist/plan-generator.js +21 -0
  160. package/dist/plan-navigation.d.ts +11 -0
  161. package/dist/plan-navigation.js +30 -0
  162. package/dist/planning-captures.d.ts +20 -0
  163. package/dist/planning-captures.js +143 -0
  164. package/dist/planning-research.d.ts +135 -0
  165. package/dist/planning-research.js +472 -0
  166. package/dist/planning-triage.d.ts +23 -0
  167. package/dist/planning-triage.js +131 -0
  168. package/dist/preview.d.ts +24 -0
  169. package/dist/preview.js +280 -29
  170. package/dist/project-detect.d.ts +36 -0
  171. package/dist/project-detect.js +251 -0
  172. package/dist/project-import.d.ts +54 -0
  173. package/dist/project-import.js +157 -0
  174. package/dist/project-lock.d.ts +6 -0
  175. package/dist/project-lock.js +96 -0
  176. package/dist/project-registry.d.ts +25 -0
  177. package/dist/project-registry.js +79 -0
  178. package/dist/project.d.ts +35 -3
  179. package/dist/project.js +388 -38
  180. package/dist/prompts.d.ts +9 -0
  181. package/dist/prompts.js +25 -0
  182. package/dist/proposal-replay.d.ts +32 -0
  183. package/dist/proposal-replay.js +99 -0
  184. package/dist/quality-claims.d.ts +8 -0
  185. package/dist/quality-claims.js +168 -0
  186. package/dist/quality-config.d.ts +5 -0
  187. package/dist/quality-config.js +84 -0
  188. package/dist/quality-contract.d.ts +37 -0
  189. package/dist/quality-contract.js +46 -0
  190. package/dist/quality-examples.d.ts +4 -0
  191. package/dist/quality-examples.js +233 -0
  192. package/dist/quality-gates.d.ts +16 -0
  193. package/dist/quality-gates.js +192 -0
  194. package/dist/quality-links.d.ts +7 -0
  195. package/dist/quality-links.js +149 -0
  196. package/dist/quality-lint.d.ts +6 -0
  197. package/dist/quality-lint.js +124 -0
  198. package/dist/quality-rendered.d.ts +11 -0
  199. package/dist/quality-rendered.js +222 -0
  200. package/dist/quality-schema.d.ts +3 -0
  201. package/dist/quality-schema.js +71 -0
  202. package/dist/release-notes.d.ts +44 -0
  203. package/dist/release-notes.js +183 -0
  204. package/dist/remote-monitor.d.ts +16 -0
  205. package/dist/remote-monitor.js +74 -0
  206. package/dist/remote-source.d.ts +34 -0
  207. package/dist/remote-source.js +426 -0
  208. package/dist/review-diff.d.ts +82 -0
  209. package/dist/review-diff.js +400 -0
  210. package/dist/review-learning.d.ts +11 -0
  211. package/dist/review-learning.js +60 -0
  212. package/dist/review-render.d.ts +38 -0
  213. package/dist/review-render.js +224 -0
  214. package/dist/review-report.d.ts +9 -0
  215. package/dist/review-report.js +89 -0
  216. package/dist/review-ui.d.ts +14 -0
  217. package/dist/review-ui.js +1248 -0
  218. package/dist/schedule.d.ts +78 -0
  219. package/dist/schedule.js +480 -0
  220. package/dist/screen-capture-provider.d.ts +52 -0
  221. package/dist/screen-capture-provider.js +218 -0
  222. package/dist/screenshot-workflow.d.ts +167 -0
  223. package/dist/screenshot-workflow.js +1237 -0
  224. package/dist/settings.d.ts +1 -1
  225. package/dist/settings.js +95 -7
  226. package/dist/site-export.d.ts +18 -0
  227. package/dist/site-export.js +87 -0
  228. package/dist/source-connectors.d.ts +33 -0
  229. package/dist/source-connectors.js +268 -0
  230. package/dist/source-discovery.d.ts +132 -0
  231. package/dist/source-discovery.js +823 -0
  232. package/dist/source-intelligence.d.ts +9 -0
  233. package/dist/source-intelligence.js +306 -0
  234. package/dist/sync-review.d.ts +28 -0
  235. package/dist/sync-review.js +264 -0
  236. package/dist/sync-runs.d.ts +192 -0
  237. package/dist/sync-runs.js +2244 -0
  238. package/dist/sync.d.ts +35 -0
  239. package/dist/sync.js +298 -32
  240. package/dist/text-diff.d.ts +9 -0
  241. package/dist/text-diff.js +59 -0
  242. package/dist/types.d.ts +946 -1
  243. package/dist/ui/assets/doxloop-logo-light-De7Nx7j7.png +0 -0
  244. package/dist/ui/assets/index-BHBYU2aG.css +1 -0
  245. package/dist/ui/assets/index-Cq3RPQiC.js +33 -0
  246. package/dist/ui/index.html +18 -0
  247. package/dist/ui-server.d.ts +83 -0
  248. package/dist/ui-server.js +3532 -0
  249. package/dist/usage-budget.d.ts +28 -0
  250. package/dist/usage-budget.js +90 -0
  251. package/dist/validation.d.ts +25 -1
  252. package/dist/validation.js +312 -26
  253. package/dist/workspace-tools.d.ts +54 -0
  254. package/dist/workspace-tools.js +123 -0
  255. package/docs/agent-compatibility.md +49 -32
  256. package/docs/ci-and-automation.md +105 -66
  257. package/docs/doxbrix-http-api.md +8 -1
  258. package/docs/existing-documentation.md +80 -0
  259. package/docs/generation-performance.md +108 -0
  260. package/docs/generator-authoring.md +68 -5
  261. package/docs/generator-selection.md +50 -13
  262. package/docs/mintlify-import.md +71 -0
  263. package/docs/openapi-security.md +25 -0
  264. package/docs/project-format.md +331 -36
  265. package/docs/release-quality.md +158 -0
  266. package/docs/releasing.md +72 -0
  267. package/docs/review-workflows.md +51 -0
  268. package/docs/security-model.md +102 -39
  269. package/docs/troubleshooting.md +226 -93
  270. package/package.json +46 -15
  271. package/scripts/test-auto-screenshot.mjs +172 -0
  272. package/skills/doxloop-authoring/SKILL.md +242 -361
  273. package/skills/doxloop-authoring/references/existing-documentation.md +94 -0
  274. package/skills/doxloop-authoring/references/navigation-architecture.md +18 -7
  275. package/skills/doxloop-authoring/references/page-depth.md +169 -0
  276. package/skills/doxloop-authoring/references/project-format.md +106 -7
  277. package/skills/doxloop-authoring/references/quality.md +10 -0
  278. package/skills/doxloop-authoring/references/screenshot-manifest.md +113 -0
  279. package/skills/doxloop-authoring/references/screenshots.md +149 -235
  280. package/skills/doxloop-authoring/references/workflows.md +76 -0
  281. package/skills/doxloop-doxbrix/SKILL.md +38 -22
  282. package/skills/doxloop-doxbrix/references/api-endpoints.md +15 -14
  283. package/skills/doxloop-doxbrix/references/components.md +30 -3
  284. package/skills/doxloop-doxbrix/references/manifest.md +3 -2
  285. package/vendor/doxbrix-import/LICENSE +202 -0
  286. package/vendor/doxbrix-import/README.md +17 -0
  287. package/vendor/doxbrix-import/UPSTREAM.json +47 -0
  288. package/vendor/doxbrix-import/dist/docs/frontmatter.d.ts +13 -0
  289. package/vendor/doxbrix-import/dist/docs/frontmatter.js +83 -0
  290. package/vendor/doxbrix-import/dist/docs/import.d.ts +21 -0
  291. package/vendor/doxbrix-import/dist/docs/import.js +147 -0
  292. package/vendor/doxbrix-import/dist/docs/manifest.d.ts +163 -0
  293. package/vendor/doxbrix-import/dist/docs/manifest.js +64 -0
  294. package/vendor/doxbrix-import/dist/docs/project.d.ts +25 -0
  295. package/vendor/doxbrix-import/dist/docs/project.js +77 -0
  296. package/vendor/doxbrix-import/dist/docs/starter.d.ts +4 -0
  297. package/vendor/doxbrix-import/dist/docs/starter.js +11 -0
  298. package/vendor/doxbrix-import/dist/importer.d.ts +227 -0
  299. package/vendor/doxbrix-import/dist/importer.js +1567 -0
  300. package/vendor/doxbrix-import/dist/mintlify-openapi.d.ts +37 -0
  301. package/vendor/doxbrix-import/dist/mintlify-openapi.js +305 -0
  302. package/vendor/doxbrix-import/dist/safe-path.d.ts +9 -0
  303. package/vendor/doxbrix-import/dist/safe-path.js +47 -0
  304. package/dist/agents.d.ts.map +0 -1
  305. package/dist/agents.js.map +0 -1
  306. package/dist/args.d.ts.map +0 -1
  307. package/dist/args.js.map +0 -1
  308. package/dist/artifact-deploy.d.ts.map +0 -1
  309. package/dist/artifact-deploy.js.map +0 -1
  310. package/dist/auth.d.ts.map +0 -1
  311. package/dist/auth.js.map +0 -1
  312. package/dist/author.d.ts.map +0 -1
  313. package/dist/author.js.map +0 -1
  314. package/dist/capture.d.ts.map +0 -1
  315. package/dist/capture.js.map +0 -1
  316. package/dist/cli.d.ts.map +0 -1
  317. package/dist/cli.js.map +0 -1
  318. package/dist/deploy.d.ts.map +0 -1
  319. package/dist/deploy.js.map +0 -1
  320. package/dist/deployment-visibility.d.ts.map +0 -1
  321. package/dist/deployment-visibility.js.map +0 -1
  322. package/dist/doctor.d.ts.map +0 -1
  323. package/dist/doctor.js.map +0 -1
  324. package/dist/doxbrix-markdown.d.ts.map +0 -1
  325. package/dist/doxbrix-markdown.js.map +0 -1
  326. package/dist/errors.d.ts.map +0 -1
  327. package/dist/errors.js.map +0 -1
  328. package/dist/fs.d.ts.map +0 -1
  329. package/dist/fs.js.map +0 -1
  330. package/dist/generator-api.d.ts.map +0 -1
  331. package/dist/generator-api.js.map +0 -1
  332. package/dist/generator-manager.d.ts.map +0 -1
  333. package/dist/generator-manager.js.map +0 -1
  334. package/dist/generator-runtime.d.ts.map +0 -1
  335. package/dist/generator-runtime.js.map +0 -1
  336. package/dist/generators.d.ts.map +0 -1
  337. package/dist/generators.js.map +0 -1
  338. package/dist/interactive.d.ts.map +0 -1
  339. package/dist/interactive.js.map +0 -1
  340. package/dist/preview.d.ts.map +0 -1
  341. package/dist/preview.js.map +0 -1
  342. package/dist/progress.d.ts.map +0 -1
  343. package/dist/progress.js.map +0 -1
  344. package/dist/project.d.ts.map +0 -1
  345. package/dist/project.js.map +0 -1
  346. package/dist/prompts.d.ts.map +0 -1
  347. package/dist/prompts.js.map +0 -1
  348. package/dist/settings.d.ts.map +0 -1
  349. package/dist/settings.js.map +0 -1
  350. package/dist/sync.d.ts.map +0 -1
  351. package/dist/sync.js.map +0 -1
  352. package/dist/types.d.ts.map +0 -1
  353. package/dist/types.js.map +0 -1
  354. package/dist/validation.d.ts.map +0 -1
  355. package/dist/validation.js.map +0 -1
  356. package/dist/version.d.ts.map +0 -1
  357. package/dist/version.js.map +0 -1
@@ -0,0 +1,124 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { loadPages, loadSiteConfig, readPage, relativePath } from './project.js';
4
+ import { QUALITY_CODES } from './quality-contract.js';
5
+ const MISSPELLINGS = {
6
+ accomodate: 'accommodate',
7
+ occured: 'occurred',
8
+ recieve: 'receive',
9
+ seperate: 'separate',
10
+ teh: 'the',
11
+ untill: 'until',
12
+ };
13
+ export async function lintDocumentation(root, project, config) {
14
+ const checks = [];
15
+ const paragraphs = new Map();
16
+ const maximumTitle = config.lint?.maximumTitleLength ?? 72;
17
+ for (const path of await loadPages(root, project)) {
18
+ const file = relativePath(root, path);
19
+ const page = await readPage(path);
20
+ if (page.title.length > maximumTitle)
21
+ checks.push(lint(file, `Title is ${page.title.length} characters; the configured maximum is ${maximumTitle}.`));
22
+ const prose = page.body.replace(/```[\s\S]*?```/g, '').replace(/<[^>]+>/g, ' ');
23
+ for (const [misspelling, replacement] of Object.entries(MISSPELLINGS)) {
24
+ if (new RegExp(`\\b${misspelling}\\b`, 'i').test(prose))
25
+ checks.push(lint(file, `Possible spelling error: use "${replacement}" instead of "${misspelling}".`, true));
26
+ }
27
+ for (const [term, preferred] of Object.entries(project.documentation.terminology)) {
28
+ if (term.toLowerCase() === preferred.toLowerCase() || preferred.length > 60)
29
+ continue;
30
+ if (new RegExp(`\\b${escapeRegExp(term)}\\b`, 'i').test(prose) && !new RegExp(escapeRegExp(preferred), 'i').test(prose))
31
+ checks.push(lint(file, `Terminology guidance prefers "${preferred}" where "${term}" is used.`));
32
+ }
33
+ const sentences = prose.split(/[.!?]+(?:\s|$)/).map((item) => item.trim()).filter((item) => item.split(/\s+/).length >= 4);
34
+ const long = sentences.filter((item) => item.split(/\s+/).length > 35);
35
+ if (long.length > Math.max(1, sentences.length / 4))
36
+ checks.push(lint(file, `${long.length} sentences exceed 35 words; shorten them for easier scanning.`));
37
+ for (const paragraph of prose.split(/\n\s*\n/).map(normalizeParagraph).filter((item) => item.length >= 100)) {
38
+ const files = paragraphs.get(paragraph) ?? [];
39
+ files.push(file);
40
+ paragraphs.set(paragraph, files);
41
+ }
42
+ }
43
+ for (const files of paragraphs.values())
44
+ if (new Set(files).size > 1)
45
+ checks.push(lint([...new Set(files)].join(', '), 'Substantial duplicate prose appears across multiple pages.'));
46
+ if (project.generator === 'doxbrix') {
47
+ const site = await loadSiteConfig(root, project);
48
+ const maximum = config.lint?.maximumNavigationLabelLength ?? 42;
49
+ for (const label of navigationLabels(site.spaces.flatMap((space) => space.nav)))
50
+ if (label.length > maximum)
51
+ checks.push(lint(join(project.contentDir, 'docs.json'), `Navigation label "${label}" is ${label.length} characters; the configured maximum is ${maximum}.`));
52
+ }
53
+ return checks.length > 0 ? checks : [{ code: QUALITY_CODES.lintPassed, category: 'lint', status: 'pass', message: 'Documentation linting found no terminology, duplication, spelling, readability, title, or navigation-label issues.' }];
54
+ }
55
+ export async function fixDocumentation(root, project) {
56
+ const changed = [];
57
+ for (const path of await loadPages(root, project)) {
58
+ const original = await readFile(path, 'utf8');
59
+ let next = normalizeLocalLinks(original.replace(/[ \t]+$/gm, ''));
60
+ next = addFenceLanguages(next);
61
+ next = repairSingleHeadingJumps(next);
62
+ if (!next.endsWith('\n'))
63
+ next += '\n';
64
+ if (next !== original) {
65
+ await writeFile(path, next, 'utf8');
66
+ changed.push(relativePath(root, path));
67
+ }
68
+ }
69
+ return { changed };
70
+ }
71
+ function addFenceLanguages(value) {
72
+ const lines = value.split('\n');
73
+ let insideFence = false;
74
+ for (let index = 0; index < lines.length; index += 1) {
75
+ const fence = /^```(.*)$/.exec(lines[index] ?? '');
76
+ if (!fence)
77
+ continue;
78
+ if (!insideFence && fence[1]?.trim() === '') {
79
+ const sample = lines[index + 1]?.trim() ?? '';
80
+ const language = /^\s*(?:\{|\[)/.test(sample) ? 'json' : /^(?:npm|pnpm|yarn|npx|curl|git|doxloop)\b/.test(sample) ? 'bash' : /^(?:const|let|var|import|export|function)\b/.test(sample) ? 'javascript' : '';
81
+ if (language)
82
+ lines[index] = `\`\`\`${language}`;
83
+ }
84
+ insideFence = !insideFence;
85
+ }
86
+ return lines.join('\n');
87
+ }
88
+ function repairSingleHeadingJumps(value) {
89
+ let previous = 0;
90
+ let insideFence = false;
91
+ return value.split('\n').map((line) => {
92
+ if (/^```/.test(line)) {
93
+ insideFence = !insideFence;
94
+ return line;
95
+ }
96
+ if (insideFence)
97
+ return line;
98
+ const match = /^(#{1,6})(\s+\S.*)$/.exec(line);
99
+ if (!match)
100
+ return line;
101
+ let level = match[1].length;
102
+ if (previous > 0 && level > previous + 1)
103
+ level = previous + 1;
104
+ previous = level;
105
+ return `${'#'.repeat(level)}${match[2]}`;
106
+ }).join('\n');
107
+ }
108
+ function normalizeLocalLinks(value) {
109
+ let insideFence = false;
110
+ return value.split('\n').map((line) => {
111
+ if (/^```/.test(line)) {
112
+ insideFence = !insideFence;
113
+ return line;
114
+ }
115
+ if (insideFence)
116
+ return line;
117
+ return line.replace(/\]\(\.\/([^)]*)\)/g, ']($1)').replace(/\]\(([^)]*\\[^)]*)\)/g, (_all, target) => `](${target.replace(/\\/g, '/')})`);
118
+ }).join('\n');
119
+ }
120
+ function navigationLabels(nodes) { return nodes.flatMap((node) => node.type === 'group' ? [node.label, ...navigationLabels(node.items)] : node.type === 'label' ? [node.text] : node.type === 'page' && node.title ? [node.title] : node.type === 'link' || node.type === 'api' ? [node.title] : []); }
121
+ function lint(file, message, fixable = false) { return { code: QUALITY_CODES.lintIssue, category: 'lint', status: 'warning', message, file, ...(fixable ? { fixable: true } : {}) }; }
122
+ function normalizeParagraph(value) { return value.toLowerCase().replace(/\s+/g, ' ').trim(); }
123
+ function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
124
+ //# sourceMappingURL=quality-lint.js.map
@@ -0,0 +1,11 @@
1
+ import type { DoxloopProject, QualityCheck, QualityConfig } from './types.js';
2
+ export declare function checkRenderedQuality(root: string, project: DoxloopProject, config: QualityConfig, updateVisuals: boolean): Promise<{
3
+ checks: QualityCheck[];
4
+ accessibility?: string;
5
+ visuals?: string;
6
+ }>;
7
+ export declare function compareScreenshots(baselinePath: string, currentPath: string, diffPath: string): Promise<{
8
+ ratio: number;
9
+ diff: string;
10
+ }>;
11
+ //# sourceMappingURL=quality-rendered.d.ts.map
@@ -0,0 +1,222 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { cp, mkdir, readFile, readdir } from 'node:fs/promises';
3
+ import { dirname, extname, join, relative, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import axe from 'axe-core';
6
+ import pixelmatch from 'pixelmatch';
7
+ import { PNG } from 'pngjs';
8
+ import { ensurePlaywright } from './capture.js';
9
+ import { renderMarkdown } from './doxbrix-markdown.js';
10
+ import { pathExists } from './fs.js';
11
+ import { loadGeneratorAdapter } from './generators.js';
12
+ import { loadPages, loadSiteConfig, pageId, readPage } from './project.js';
13
+ import { doxbrixDocument } from './preview.js';
14
+ import { QUALITY_CODES } from './quality-contract.js';
15
+ export async function checkRenderedQuality(root, project, config, updateVisuals) {
16
+ if (!config.rendered?.enabled)
17
+ return { checks: [
18
+ { code: QUALITY_CODES.accessibilityManual, category: 'accessibility', status: 'skipped', message: 'Rendered checks are opt-in. Enable rendered.enabled in .doxloop/quality.json.' },
19
+ { code: QUALITY_CODES.visualSkipped, category: 'visual', status: 'skipped', message: 'Visual regression checks are disabled.' },
20
+ ] };
21
+ const pages = await renderedPages(root, project, config.rendered.routes ?? ['/']);
22
+ if (pages.length === 0)
23
+ return { checks: [{ code: QUALITY_CODES.accessibilityIssue, category: 'accessibility', status: 'fail', message: 'No representative rendered pages were found.' }, { code: QUALITY_CODES.visualSkipped, category: 'visual', status: 'skipped', message: 'No rendered pages are available for visual comparison.' }] };
24
+ const outputRoot = join(root, '.doxloop', 'quality-artifacts');
25
+ const currentRoot = join(outputRoot, 'visuals', project.generator);
26
+ const baselineRoot = join(root, '.doxloop', 'visual-baselines', project.generator);
27
+ await mkdir(currentRoot, { recursive: true });
28
+ const checks = [];
29
+ let playwright;
30
+ try {
31
+ playwright = await ensurePlaywright();
32
+ }
33
+ catch (error) {
34
+ return { checks: [
35
+ { code: QUALITY_CODES.accessibilityManual, category: 'accessibility', status: 'warning', message: 'Rendered accessibility automation is unavailable; manual review is required.', detail: error instanceof Error ? error.message : String(error) },
36
+ { code: QUALITY_CODES.visualSkipped, category: 'visual', status: 'warning', message: 'Visual screenshots could not be captured in this environment.' },
37
+ ] };
38
+ }
39
+ const browser = await playwright.chromium.launch({ headless: true });
40
+ try {
41
+ for (const rendered of pages) {
42
+ for (const theme of config.rendered.themes ?? ['light', 'dark'])
43
+ for (const viewport of config.rendered.viewports ?? []) {
44
+ const context = await browser.newContext({ viewport: { width: viewport.width, height: viewport.height }, colorScheme: theme, reducedMotion: 'reduce' });
45
+ try {
46
+ const page = await context.newPage();
47
+ await page.route('**/*', (route) => route.abort('blockedbyclient'));
48
+ await page.setContent(rendered.html, { waitUntil: 'load' });
49
+ await page.evaluate((selectedTheme) => {
50
+ document.documentElement.dataset.colorTheme = selectedTheme;
51
+ document.querySelectorAll('[data-color-theme]').forEach((element) => { element.dataset.colorTheme = selectedTheme; });
52
+ }, theme);
53
+ const slug = routeSlug(rendered.route);
54
+ const directory = join(currentRoot, theme, slug);
55
+ const screenshot = join(directory, `${viewport.name}.png`);
56
+ await mkdir(directory, { recursive: true });
57
+ await page.screenshot({ path: screenshot, fullPage: true, animations: 'disabled' });
58
+ await page.addScriptTag({ content: axe.source });
59
+ const axeIssues = await page.evaluate(async () => {
60
+ const runner = globalThis.axe;
61
+ return runner ? (await runner.run()).violations.map((violation) => `${violation.id}: ${violation.help}${violation.nodes.length ? ` — ${violation.nodes.slice(0, 3).flatMap((node) => node.target).join(', ')}` : ''}`) : ['Axe could not be loaded into the rendered page.'];
62
+ });
63
+ const issues = [...await page.evaluate(ACCESSIBILITY_SCRIPT), ...await keyboardIssues(page), ...axeIssues];
64
+ for (const issue of issues)
65
+ checks.push({ code: QUALITY_CODES.accessibilityIssue, category: 'accessibility', status: 'fail', message: issue, file: rendered.route });
66
+ if (issues.length === 0)
67
+ checks.push({ code: QUALITY_CODES.accessibilityPassed, category: 'accessibility', status: 'pass', message: `${rendered.route} passed automated landmark, name, heading, image, overflow, and focus checks at ${viewport.name}.`, file: rendered.route });
68
+ const baseline = join(baselineRoot, theme, slug, `${viewport.name}.png`);
69
+ if (updateVisuals) {
70
+ await mkdir(dirname(baseline), { recursive: true });
71
+ await cp(screenshot, baseline);
72
+ checks.push({ code: QUALITY_CODES.visualCreated, category: 'visual', status: 'pass', message: `Updated ${project.generator}/${theme}/${slug}/${viewport.name} visual baseline.`, file: rendered.route });
73
+ }
74
+ else if (!(await pathExists(baseline)))
75
+ checks.push({ code: QUALITY_CODES.visualChanged, category: 'visual', status: 'warning', message: `No approved visual baseline exists for ${rendered.route} at ${viewport.name}.`, file: rendered.route, detail: 'Run doxloop quality --rendered --update-visuals after intentional review.' });
76
+ else {
77
+ const comparison = await compareScreenshots(baseline, screenshot, join(directory, `${viewport.name}.diff.png`));
78
+ const maximum = config.rendered.maximumDiffRatio ?? 0.001;
79
+ if (comparison.ratio <= maximum)
80
+ checks.push({ code: QUALITY_CODES.visualMatched, category: 'visual', status: 'pass', message: `${rendered.route} matches the approved ${theme} ${viewport.name} visual baseline (${(comparison.ratio * 100).toFixed(3)}% changed).`, file: rendered.route });
81
+ else
82
+ checks.push({ code: QUALITY_CODES.visualChanged, category: 'visual', status: 'fail', message: `${rendered.route} differs from the approved ${theme} ${viewport.name} baseline by ${(comparison.ratio * 100).toFixed(3)}%.`, file: rendered.route, detail: `Current: ${relative(root, screenshot)}\nDiff: ${relative(root, comparison.diff)}` });
83
+ }
84
+ }
85
+ finally {
86
+ await context.close();
87
+ }
88
+ }
89
+ }
90
+ }
91
+ finally {
92
+ await browser.close();
93
+ }
94
+ checks.push({ code: QUALITY_CODES.accessibilityManual, category: 'accessibility', status: 'warning', message: 'Automation cannot prove WCAG conformance. Manually review keyboard order, visible focus, contrast, zoom, screen-reader output, and reflow before release.' });
95
+ return { checks, accessibility: relative(root, outputRoot), visuals: relative(root, currentRoot) };
96
+ }
97
+ async function renderedPages(root, project, routes) {
98
+ if (project.generator === 'doxbrix') {
99
+ const [site, files] = await Promise.all([loadSiteConfig(root, project), loadPages(root, project)]);
100
+ const contentRoot = resolve(root, project.contentDir);
101
+ const css = await readFile(resolve(fileURLToPath(new URL('..', import.meta.url)), 'assets', 'doxbrix-preview.css'), 'utf8');
102
+ const byRoute = new Map(files.map((path) => [`/${pageId(contentRoot, path)}`, path]));
103
+ if (files[0])
104
+ byRoute.set('/', files[0]);
105
+ return Promise.all(routes.flatMap((route) => byRoute.get(route) ? [{ route, path: byRoute.get(route) }] : []).map(async ({ route, path }) => {
106
+ const page = await readPage(path);
107
+ return { route, html: doxbrixDocument({ site, title: page.title, ...(page.description ? { description: page.description } : {}), current: pageId(contentRoot, path), rendered: renderMarkdown(page.body) }).replace('<link rel="stylesheet" href="/__doxloop/doxbrix.css">', `<style>${css}</style>`) };
108
+ }));
109
+ }
110
+ const adapter = await loadGeneratorAdapter(root, project);
111
+ const output = resolve(root, adapter.build.outputDir);
112
+ if (!(await pathExists(output)))
113
+ return [];
114
+ const html = await listHtml(output);
115
+ const byRoute = new Map(html.map((path) => [htmlRoute(output, path), path]));
116
+ return Promise.all(routes.flatMap((route) => {
117
+ const normalized = normalizeRoute(route);
118
+ const path = byRoute.get(normalized);
119
+ return path ? [{ route: normalized, path }] : [];
120
+ }).map(async ({ route, path }) => ({ route, html: await inlineLocalAssets(await readFile(path, 'utf8'), path, output) })));
121
+ }
122
+ async function listHtml(root) {
123
+ const output = [];
124
+ for (const entry of await readdir(root, { withFileTypes: true })) {
125
+ const path = join(root, entry.name);
126
+ if (entry.isDirectory())
127
+ output.push(...await listHtml(path));
128
+ else if (extname(path) === '.html')
129
+ output.push(path);
130
+ }
131
+ return output.sort();
132
+ }
133
+ function routeSlug(route) { return route.replace(/^\/+|\/+$/g, '').replace(/[^a-z0-9]+/gi, '-') || 'index'; }
134
+ function normalizeRoute(route) { const value = `/${route.replace(/^\/+|\/+$/g, '')}`; return value === '/' ? value : `${value}/`; }
135
+ function htmlRoute(output, path) {
136
+ const value = relative(output, path).replace(/\\/g, '/').replace(/(?:^|\/)index\.html$/, '').replace(/\.html$/, '/');
137
+ return normalizeRoute(value);
138
+ }
139
+ async function inlineLocalAssets(html, htmlPath, outputRoot) {
140
+ let result = html;
141
+ const styles = [...html.matchAll(/<link\b[^>]*\brel=["']stylesheet["'][^>]*\bhref=["']([^"']+)["'][^>]*>/gi)];
142
+ for (const match of styles) {
143
+ const asset = await localAsset(match[1], htmlPath, outputRoot);
144
+ if (asset)
145
+ result = result.replace(match[0], `<style data-doxloop-inline="${escapeAttribute(match[1])}">${(await readFile(asset, 'utf8')).replace(/<\/style/gi, '<\\/style')}</style>`);
146
+ }
147
+ const scripts = [...result.matchAll(/<script\b([^>]*)\bsrc=["']([^"']+)["']([^>]*)><\/script>/gi)];
148
+ for (const match of scripts) {
149
+ const asset = await localAsset(match[2], htmlPath, outputRoot);
150
+ if (asset)
151
+ result = result.replace(match[0], `<script${match[1] ?? ''}${match[3] ?? ''}>${(await readFile(asset, 'utf8')).replace(/<\/script/gi, '<\\/script')}</script>`);
152
+ }
153
+ return result;
154
+ }
155
+ async function localAsset(reference, htmlPath, outputRoot) {
156
+ if (/^(?:[a-z]+:|\/\/|data:)/i.test(reference))
157
+ return undefined;
158
+ const clean = reference.split(/[?#]/, 1)[0];
159
+ const path = resolve(clean.startsWith('/') ? outputRoot : dirname(htmlPath), clean.replace(/^\/+/, ''));
160
+ const rel = relative(outputRoot, path);
161
+ return rel !== '..' && !rel.startsWith('../') && !rel.startsWith('..\\') && await pathExists(path) ? path : undefined;
162
+ }
163
+ function escapeAttribute(value) { return value.replace(/&/g, '&amp;').replace(/"/g, '&quot;'); }
164
+ async function digest(path) { return createHash('sha256').update(await readFile(path)).digest('hex'); }
165
+ export async function compareScreenshots(baselinePath, currentPath, diffPath) {
166
+ if (await digest(baselinePath) === await digest(currentPath))
167
+ return { ratio: 0, diff: diffPath };
168
+ const [baseline, current] = await Promise.all([readFile(baselinePath).then((value) => PNG.sync.read(value)), readFile(currentPath).then((value) => PNG.sync.read(value))]);
169
+ if (baseline.width !== current.width || baseline.height !== current.height)
170
+ return { ratio: 1, diff: currentPath };
171
+ const diff = new PNG({ width: current.width, height: current.height });
172
+ const changed = pixelmatch(baseline.data, current.data, diff.data, current.width, current.height, { threshold: 0.1, includeAA: false });
173
+ await writePng(diffPath, diff);
174
+ return { ratio: changed / Math.max(current.width * current.height, 1), diff: diffPath };
175
+ }
176
+ async function writePng(path, png) {
177
+ const { writeFile } = await import('node:fs/promises');
178
+ await writeFile(path, PNG.sync.write(png));
179
+ }
180
+ async function keyboardIssues(page) {
181
+ const count = await page.evaluate(() => document.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])').length);
182
+ if (count === 0)
183
+ return [];
184
+ const seen = new Set();
185
+ for (let index = 0; index < Math.min(count + 1, 80); index += 1) {
186
+ await page.keyboard.press('Tab');
187
+ const name = await page.evaluate(() => {
188
+ const active = document.activeElement;
189
+ if (!active || active === document.body)
190
+ return '';
191
+ return active.getAttribute('aria-label') ?? active.getAttribute('href') ?? active.id ?? active.textContent?.trim().slice(0, 80) ?? active.tagName;
192
+ });
193
+ if (name)
194
+ seen.add(name);
195
+ }
196
+ return seen.size < Math.min(count, 2) ? ['Keyboard traversal did not reach the expected interactive controls.'] : [];
197
+ }
198
+ const ACCESSIBILITY_SCRIPT = String.raw `(() => {
199
+ const issues = [];
200
+ if (!document.documentElement.lang) issues.push('The rendered document needs a language declaration.');
201
+ if (!document.querySelector('main, [role="main"]')) issues.push('The rendered document needs a main landmark.');
202
+ if (!document.querySelector('h1')) issues.push('The rendered document needs an h1.');
203
+ document.querySelectorAll('img').forEach((image) => { if (!image.hasAttribute('alt')) issues.push('Every rendered image needs an alt attribute.'); });
204
+ document.querySelectorAll('button, a[href], input, select, textarea').forEach((control) => {
205
+ const name = control.getAttribute('aria-label') || control.getAttribute('title') || control.textContent?.trim() || control.getAttribute('placeholder');
206
+ if (!name) issues.push('Every interactive control needs an accessible name.');
207
+ if (control.getAttribute('tabindex') && Number(control.getAttribute('tabindex')) > 0) issues.push('Positive tabindex values create an unpredictable keyboard order.');
208
+ });
209
+ let previous = 0;
210
+ document.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach((heading) => { const level = Number(heading.tagName.slice(1)); if (previous && level > previous + 1) issues.push('Rendered heading levels must not skip ranks.'); previous = level; });
211
+ if (document.documentElement.scrollWidth > window.innerWidth + 2) issues.push('The rendered page overflows horizontally at this viewport.');
212
+ const focusTarget = document.querySelector('a[href], button, input, select, textarea');
213
+ if (focusTarget) {
214
+ focusTarget.focus();
215
+ const focus = getComputedStyle(focusTarget);
216
+ if (focus.outlineStyle === 'none' && focus.boxShadow === 'none') issues.push('The first keyboard control has no detectable focus indicator.');
217
+ }
218
+ document.documentElement.style.zoom = '2';
219
+ if (document.documentElement.scrollWidth > window.innerWidth * 2 + 2) issues.push('The rendered page does not reflow cleanly at 200% zoom.');
220
+ return Array.from(new Set(issues));
221
+ })()`;
222
+ //# sourceMappingURL=quality-rendered.js.map
@@ -0,0 +1,3 @@
1
+ import type { DoxloopProject, QualityCheck } from './types.js';
2
+ export declare function lintSchemas(root: string, project: DoxloopProject): Promise<QualityCheck[]>;
3
+ //# sourceMappingURL=quality-schema.d.ts.map
@@ -0,0 +1,71 @@
1
+ import { loadOpenApiSource } from './openapi.js';
2
+ import { QUALITY_CODES } from './quality-contract.js';
3
+ const METHODS = ['get', 'put', 'post', 'delete', 'patch', 'options', 'head', 'trace'];
4
+ export async function lintSchemas(root, project) {
5
+ const sources = project.sources.filter((source) => source.kind === 'openapi');
6
+ if (sources.length === 0)
7
+ return [{ code: QUALITY_CODES.schemaPassed, category: 'schemas', status: 'pass', message: 'No connector-native schemas require linting.' }];
8
+ const checks = [];
9
+ for (const source of sources) {
10
+ try {
11
+ const loaded = await loadOpenApiSource(root, source);
12
+ const document = loaded.document;
13
+ const operationIds = new Map();
14
+ const rootSecurity = Array.isArray(document.security) && document.security.length > 0;
15
+ for (const [path, rawItem] of Object.entries(record(document.paths))) {
16
+ const item = record(rawItem);
17
+ for (const method of METHODS) {
18
+ if (!record(item[method]) || Object.keys(record(item[method])).length === 0)
19
+ continue;
20
+ const operation = record(item[method]);
21
+ const location = `${method.toUpperCase()} ${path}`;
22
+ const operationId = text(operation.operationId);
23
+ if (!operationId)
24
+ checks.push(issue(source.path, `${location} should declare operationId.`, 'warning'));
25
+ else if (operationIds.has(operationId))
26
+ checks.push(issue(source.path, `${location} duplicates operationId "${operationId}" from ${operationIds.get(operationId)}.`, 'fail'));
27
+ else
28
+ operationIds.set(operationId, location);
29
+ if (!text(operation.summary) && !text(operation.description))
30
+ checks.push(issue(source.path, `${location} needs a summary or description for reader-facing API documentation.`, 'warning'));
31
+ if (Object.keys(record(operation.responses)).length === 0)
32
+ checks.push(issue(source.path, `${location} must declare at least one response.`, 'fail'));
33
+ if (!rootSecurity && operation.security === undefined)
34
+ checks.push(issue(source.path, `${location} does not declare an explicit security policy.`, 'warning'));
35
+ for (const reference of references(operation)) {
36
+ if (!reference.startsWith('#/'))
37
+ checks.push(issue(source.path, `${location} uses unsupported external schema reference ${reference}.`, 'warning'));
38
+ else if (resolvePointer(document, reference) === undefined)
39
+ checks.push(issue(source.path, `${location} references missing schema ${reference}.`, 'fail'));
40
+ }
41
+ }
42
+ }
43
+ if (!checks.some((check) => check.file === source.path))
44
+ checks.push({ code: QUALITY_CODES.schemaPassed, category: 'schemas', status: 'pass', message: `${source.name} passed OpenAPI structural linting.`, file: source.path });
45
+ }
46
+ catch (error) {
47
+ checks.push(issue(source.path, error instanceof Error ? error.message : String(error), 'fail'));
48
+ }
49
+ }
50
+ return checks;
51
+ }
52
+ function issue(file, message, status) { return { code: QUALITY_CODES.schemaIssue, category: 'schemas', status, message, file }; }
53
+ function record(value) { return value && typeof value === 'object' && !Array.isArray(value) ? value : {}; }
54
+ function text(value) { return typeof value === 'string' && value.trim() ? value.trim() : undefined; }
55
+ function references(value) {
56
+ if (Array.isArray(value))
57
+ return value.flatMap(references);
58
+ if (!value || typeof value !== 'object')
59
+ return [];
60
+ return Object.entries(value).flatMap(([key, item]) => key === '$ref' && typeof item === 'string' ? [item] : references(item));
61
+ }
62
+ function resolvePointer(document, pointer) {
63
+ let value = document;
64
+ for (const segment of pointer.slice(2).split('/').map((item) => item.replace(/~1/g, '/').replace(/~0/g, '~'))) {
65
+ if (!value || typeof value !== 'object' || Array.isArray(value))
66
+ return undefined;
67
+ value = value[segment];
68
+ }
69
+ return value;
70
+ }
71
+ //# sourceMappingURL=quality-schema.js.map
@@ -0,0 +1,44 @@
1
+ import type { DoxloopProject, ReleaseInventory, SourceBinding } from './types.js';
2
+ /**
3
+ * Deterministic inputs for a release-notes plan: the commits and changed files
4
+ * between two Git refs of every Git-backed source, plus the section of the
5
+ * product's own changelog that names the release. The planner and writer get
6
+ * this inventory verbatim, so the page is grounded in the actual commit range
7
+ * rather than in what the agent remembers about the product.
8
+ */
9
+ export declare const MAX_RELEASE_COMMITS = 200;
10
+ export declare const MAX_RELEASE_FILES = 300;
11
+ export interface SourceRefs {
12
+ source: string;
13
+ /** Newest first. */
14
+ tags: string[];
15
+ branches: string[];
16
+ head: string;
17
+ /** The refs a "latest release" request would use, when two tags exist. */
18
+ suggested?: {
19
+ from: string;
20
+ to: string;
21
+ version: string;
22
+ };
23
+ }
24
+ export interface ReleaseTemplateInput {
25
+ version: string;
26
+ from: string;
27
+ to: string;
28
+ /** Restrict the inventory to these sources; empty means every Git-backed directory source. */
29
+ sources?: string[];
30
+ }
31
+ /** Directory sources whose checkout is a Git repository. */
32
+ export declare function gitBackedSources(root: string, project: DoxloopProject): Promise<SourceBinding[]>;
33
+ export declare function listSourceRefs(root: string, sourceName: string): Promise<SourceRefs>;
34
+ export declare function collectReleaseInventory(root: string, project: DoxloopProject, input: ReleaseTemplateInput): Promise<ReleaseInventory>;
35
+ /** The changelog section that names the version, or the top of the file when no section matches. */
36
+ export declare function changelogExcerpt(sourceRoot: string, version: string): Promise<{
37
+ path: string;
38
+ excerpt: string;
39
+ } | undefined>;
40
+ export declare function extractVersionSection(text: string, version: string): string;
41
+ /** The inventory as the planner and writer read it. */
42
+ export declare function formatReleaseInventory(inventory: ReleaseInventory): string;
43
+ export declare function releaseNotesPagePath(version: string): string;
44
+ //# sourceMappingURL=release-notes.d.ts.map