@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,412 @@
1
+ /**
2
+ * Per-batch artifacts for concurrent authoring sessions.
3
+ *
4
+ * Sessions that run at the same time in one workspace must not edit the same
5
+ * JSON files: two agents saving `.doxloop/screenshot-manifest.json` or the
6
+ * evidence map at once lose each other's rows. Each batch therefore gets its
7
+ * own slice of the plan, of the manifest, and of the evidence map under
8
+ * `.doxloop/cache`, and Doxloop merges the slices back under a lock after the
9
+ * session ends. The cache directory is never part of a proposal.
10
+ */
11
+ import { readFileSync } from 'node:fs';
12
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
13
+ import { isAbsolute, join, normalize, relative, resolve } from 'node:path';
14
+ import { PNG } from 'pngjs';
15
+ import { pathExists } from './fs.js';
16
+ import { readEvidenceMap, writeEvidenceMap } from './evidence.js';
17
+ import { seedEvidence } from './authoring-postpass.js';
18
+ import { preferredPageExtension } from './page-extension.js';
19
+ import { SCREENSHOT_MANIFEST_FILE, dominantColorShare } from './screenshot-workflow.js';
20
+ export function batchArtifactPaths(index) {
21
+ return {
22
+ slice: `.doxloop/cache/authoring-batch-${index}.json`,
23
+ manifest: `.doxloop/cache/screenshot-manifest-batch-${index}.json`,
24
+ evidence: `.doxloop/cache/evidence-batch-${index}.json`,
25
+ };
26
+ }
27
+ /** Write the slices a batch session reads and fills in. */
28
+ export async function writeBatchArtifacts(workspace, plan, batch, slice,
29
+ /** Project-relative page files of the batch, for the evidence slice's keys. */
30
+ pageFiles = []) {
31
+ const paths = batchArtifactPaths(batch.index);
32
+ await mkdir(join(workspace, '.doxloop', 'cache'), { recursive: true });
33
+ await writeFile(join(workspace, paths.slice), `${JSON.stringify(slice, null, 1)}\n`, 'utf8');
34
+ const manifest = await readManifest(workspace);
35
+ const ids = new Set(batch.pages.map((page) => page.id));
36
+ const pathsById = new Map(batch.pages.map((page) => [page.path, page.id]));
37
+ const guides = manifest?.guides.filter((guide) => guide && (ids.has(guide.page) || pathsById.has(guide.page))) ?? [];
38
+ let manifestPath;
39
+ if (guides.length > 0) {
40
+ await writeFile(join(workspace, paths.manifest), `${JSON.stringify({ schemaVersion: 1, guides }, null, 2)}\n`, 'utf8');
41
+ manifestPath = paths.manifest;
42
+ }
43
+ let existing;
44
+ try {
45
+ existing = await readEvidenceMap(workspace);
46
+ }
47
+ catch {
48
+ existing = undefined;
49
+ }
50
+ const pages = {};
51
+ for (const file of pageFiles) {
52
+ const entry = existing?.pages[file];
53
+ if (entry)
54
+ pages[file] = entry;
55
+ }
56
+ // A page not written yet gets its entry at the file the plan puts it in,
57
+ // seeded from the plan's citations, so the writer adds claims and paths to
58
+ // an entry instead of reading other batches' files to learn the schema.
59
+ const contentDir = plan.target?.contentDir || '';
60
+ const extension = preferredPageExtension(plan.target);
61
+ for (const page of batch.pages) {
62
+ const expected = join(contentDir, `${page.path}${extension}`).replaceAll('\\', '/');
63
+ if (Object.keys(pages).some((file) => file === expected || file.replace(/\.[^.]+$/, '') === expected.replace(/\.[^.]+$/, '')))
64
+ continue;
65
+ pages[expected] = seedEvidence(page);
66
+ }
67
+ await writeFile(join(workspace, paths.evidence), `${JSON.stringify({ schemaVersion: 1, pages }, null, 2)}\n`, 'utf8');
68
+ return { slice: paths.slice, ...(manifestPath ? { manifest: manifestPath } : {}), evidence: paths.evidence };
69
+ }
70
+ const PAGE_EXTENSIONS_BY_PREFERENCE = ['.mdx', '.md', '.rst', '.ipynb', '.txt'];
71
+ /** The file a page evidence key names, or the sibling with another page extension when only that one exists. */
72
+ async function writtenPageFile(workspace, file) {
73
+ if (await pathExists(join(workspace, file)))
74
+ return file;
75
+ const match = file.match(/^(.*)(\.[A-Za-z0-9]+)$/);
76
+ if (!match)
77
+ return file;
78
+ for (const extension of PAGE_EXTENSIONS_BY_PREFERENCE) {
79
+ if (extension === match[2])
80
+ continue;
81
+ const candidate = `${match[1]}${extension}`;
82
+ if (await pathExists(join(workspace, candidate)))
83
+ return candidate;
84
+ }
85
+ return file;
86
+ }
87
+ /** Fold a finished batch's manifest and evidence slices back into the workspace files. */
88
+ export async function mergeBatchArtifacts(workspace, artifacts) {
89
+ const report = { guides: 0, evidencePages: 0, problems: [] };
90
+ if (artifacts.manifest && (await pathExists(join(workspace, artifacts.manifest)))) {
91
+ try {
92
+ const slice = JSON.parse(await readFile(join(workspace, artifacts.manifest), 'utf8'));
93
+ const main = (await readManifest(workspace)) ?? { schemaVersion: 1, guides: [] };
94
+ if (Array.isArray(slice?.guides)) {
95
+ for (const guide of slice.guides) {
96
+ if (!guide || typeof guide.page !== 'string' || !Array.isArray(guide.steps))
97
+ continue;
98
+ const index = main.guides.findIndex((item) => item?.page === guide.page);
99
+ if (index >= 0)
100
+ main.guides[index] = guide;
101
+ else
102
+ main.guides.push(guide);
103
+ report.guides += 1;
104
+ }
105
+ await writeFile(join(workspace, SCREENSHOT_MANIFEST_FILE), `${JSON.stringify(main, null, 2)}\n`, 'utf8');
106
+ }
107
+ }
108
+ catch (error) {
109
+ report.problems.push(`screenshot manifest slice ${artifacts.manifest} could not be merged: ${error instanceof Error ? error.message : String(error)}`);
110
+ }
111
+ }
112
+ if (await pathExists(join(workspace, artifacts.evidence))) {
113
+ try {
114
+ const slice = JSON.parse(await readFile(join(workspace, artifacts.evidence), 'utf8'));
115
+ const entries = Object.entries(slice?.pages ?? {}).filter(([file, value]) => typeof file === 'string' && isPageEvidence(value));
116
+ if (entries.length > 0) {
117
+ let main;
118
+ try {
119
+ main = await readEvidenceMap(workspace);
120
+ }
121
+ catch (error) {
122
+ report.problems.push(`evidence map could not be read before merging batch evidence: ${error instanceof Error ? error.message : String(error)}`);
123
+ }
124
+ const merged = main ?? { schemaVersion: 1, pages: {} };
125
+ for (const [file, entry] of entries) {
126
+ // The slice is seeded before the page exists, so its key guesses
127
+ // the extension; a page written as .mdx under a .md key left an
128
+ // orphan entry (validation warned on four pages of a real run).
129
+ const actual = await writtenPageFile(workspace, file);
130
+ if (actual !== file)
131
+ delete merged.pages[file];
132
+ merged.pages[actual] = entry;
133
+ report.evidencePages += 1;
134
+ }
135
+ await writeEvidenceMap(workspace, merged);
136
+ }
137
+ }
138
+ catch (error) {
139
+ report.problems.push(`evidence slice ${artifacts.evidence} could not be merged: ${error instanceof Error ? error.message : String(error)}`);
140
+ }
141
+ }
142
+ return report;
143
+ }
144
+ async function readManifest(workspace) {
145
+ const path = join(workspace, SCREENSHOT_MANIFEST_FILE);
146
+ if (!(await pathExists(path)))
147
+ return undefined;
148
+ try {
149
+ const value = JSON.parse(await readFile(path, 'utf8'));
150
+ return value && Array.isArray(value.guides) ? value : undefined;
151
+ }
152
+ catch {
153
+ return undefined;
154
+ }
155
+ }
156
+ function isPageEvidence(value) {
157
+ if (!value || typeof value !== 'object' || Array.isArray(value))
158
+ return false;
159
+ const sources = value.sources;
160
+ return Array.isArray(sources) && sources.every((source) => source && typeof source === 'object' && typeof source.source === 'string');
161
+ }
162
+ /** Run `worker` over `items` with at most `concurrency` in flight, preserving nothing about order. */
163
+ export async function runPool(items, concurrency, worker, shouldStop) {
164
+ const queue = [...items];
165
+ const width = Math.max(1, Math.min(concurrency, queue.length));
166
+ const lanes = Array.from({ length: width }, async () => {
167
+ for (;;) {
168
+ if (shouldStop?.())
169
+ return;
170
+ const item = queue.shift();
171
+ if (item === undefined)
172
+ return;
173
+ await worker(item);
174
+ }
175
+ });
176
+ await Promise.all(lanes);
177
+ }
178
+ /** A promise-chained mutex for the workspace files several sessions share. */
179
+ export function createLock() {
180
+ let tail = Promise.resolve();
181
+ return (task) => {
182
+ const run = tail.then(task, task);
183
+ tail = run.catch(() => undefined);
184
+ return run;
185
+ };
186
+ }
187
+ // ---------------------------------------------------------------------------
188
+ // Capture status
189
+ const STEP_STATUSES = new Set(['planned', 'verified', 'text-only', 'failed']);
190
+ const CHECK_KEYS = ['expectedStateConfirmed', 'privacyReviewed', 'legibilityReviewed', 'meaningful'];
191
+ const MIN_IMAGE = { width: 320, height: 180 };
192
+ const BLANK_SHARE = 0.995;
193
+ /**
194
+ * Decide each step's status from what is on disk, not from the word the agent
195
+ * wrote. Capture sessions have recorded `captured`, `done`, and `ok`; every
196
+ * such row failed the manifest check, and a real run retook thirteen images
197
+ * it already had. A step with a readable, non-blank PNG of usable size at its
198
+ * recorded path is verified; a step that claims a file it does not have goes
199
+ * back to planned so the retake knows about it.
200
+ */
201
+ export function normalizeCaptureSteps(guides, imageAt) {
202
+ const result = { verified: [], reset: [] };
203
+ for (const guide of guides) {
204
+ if (!guide || !Array.isArray(guide.steps))
205
+ continue;
206
+ for (const raw of guide.steps) {
207
+ const step = raw;
208
+ if (!step || typeof step.id !== 'string')
209
+ continue;
210
+ const label = `${guide.page}/${step.id}`;
211
+ const status = typeof step.status === 'string' ? step.status : '';
212
+ if (status === 'text-only' && typeof step.textOnlyReason === 'string' && step.textOnlyReason.trim())
213
+ continue;
214
+ if (status === 'failed')
215
+ continue;
216
+ const file = typeof step.file === 'string' ? step.file.trim() : '';
217
+ if (file) {
218
+ const image = imageAt(file);
219
+ const reason = !image
220
+ ? `recorded image ${file} is missing or unreadable`
221
+ : image.width < MIN_IMAGE.width || image.height < MIN_IMAGE.height
222
+ ? `recorded image ${file} is too small (${image.width}x${image.height})`
223
+ : image.blank ? `recorded image ${file} is blank` : undefined;
224
+ if (!reason) {
225
+ const checks = (step.checks && typeof step.checks === 'object' ? step.checks : {});
226
+ step.status = 'verified';
227
+ step.capture = true;
228
+ step.checks = Object.fromEntries(CHECK_KEYS.map((key) => [key, checks[key] !== false]));
229
+ if (typeof step.alt !== 'string' || !step.alt.trim())
230
+ step.alt = typeof step.expectedState === 'string' ? step.expectedState : file;
231
+ if (typeof step.target !== 'string' || !step.target.trim())
232
+ step.target = typeof step.action === 'string' ? step.action : file;
233
+ delete step.textOnlyReason;
234
+ result.verified.push(label);
235
+ continue;
236
+ }
237
+ delete step.file;
238
+ step.status = 'planned';
239
+ if (typeof step.capture !== 'boolean')
240
+ step.capture = true;
241
+ result.reset.push({ step: label, reason });
242
+ continue;
243
+ }
244
+ if (!STEP_STATUSES.has(status) || status === 'verified') {
245
+ step.status = 'planned';
246
+ if (typeof step.capture !== 'boolean')
247
+ step.capture = true;
248
+ result.reset.push({ step: label, reason: status === 'verified' ? 'marked verified without a saved image' : `status "${status || '(none)'}" is not planned, verified, text-only, or failed` });
249
+ }
250
+ else if (typeof step.capture !== 'boolean') {
251
+ step.capture = true;
252
+ }
253
+ }
254
+ }
255
+ return result;
256
+ }
257
+ /** Normalize the steps of a manifest file (a capture slice or the main manifest) against the images in the workspace. */
258
+ export async function normalizeCaptureManifest(workspace, manifestFile) {
259
+ const path = join(workspace, manifestFile);
260
+ if (!(await pathExists(path)))
261
+ return { verified: [], reset: [] };
262
+ const manifest = JSON.parse(await readFile(path, 'utf8'));
263
+ if (!Array.isArray(manifest?.guides))
264
+ return { verified: [], reset: [] };
265
+ const root = resolve(workspace);
266
+ const result = normalizeCaptureSteps(manifest.guides, (file) => {
267
+ // Agents save by absolute path as instructed; the manifest keeps it project-relative.
268
+ const absolute = isAbsolute(file) ? file : resolve(root, file);
269
+ if (relative(root, absolute).startsWith('..') || normalize(file).split(/[\\/]/).includes('..'))
270
+ return undefined;
271
+ try {
272
+ const png = PNG.sync.read(readFileSync(absolute));
273
+ return { width: png.width, height: png.height, blank: dominantColorShare(png) > BLANK_SHARE };
274
+ }
275
+ catch {
276
+ return undefined;
277
+ }
278
+ });
279
+ for (const guide of manifest.guides) {
280
+ for (const raw of guide.steps) {
281
+ const step = raw;
282
+ if (typeof step?.file === 'string' && isAbsolute(step.file) && !relative(root, step.file).startsWith('..'))
283
+ step.file = relative(root, step.file).replaceAll('\\', '/');
284
+ }
285
+ }
286
+ if (result.verified.length > 0 || result.reset.length > 0)
287
+ await writeFile(path, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
288
+ return result;
289
+ }
290
+ /**
291
+ * Map the absolute paths a session opened to the configured sources they
292
+ * live in. Only files under a source count; a page the agent re-read or a
293
+ * cache file is not evidence.
294
+ */
295
+ export function attributeReadsToSources(paths, sources, workspace) {
296
+ const roots = sources.map((source) => ({ name: source.name, root: resolve(isAbsolute(source.path) ? source.path : join(workspace, source.path)) }));
297
+ const reads = new Map();
298
+ for (const raw of paths) {
299
+ const absolute = resolve(raw);
300
+ for (const source of roots) {
301
+ const inside = relative(source.root, absolute);
302
+ if (!inside || inside.startsWith('..') || isAbsolute(inside))
303
+ continue;
304
+ const set = reads.get(source.name) ?? new Set();
305
+ set.add(inside.replaceAll('\\', '/'));
306
+ reads.set(source.name, set);
307
+ }
308
+ }
309
+ return reads;
310
+ }
311
+ /** Paths named in a tool call: Read/Grep targets and the file arguments of a shell command. */
312
+ export function pathsInToolCall(tool, input) {
313
+ const out = [];
314
+ for (const key of ['file_path', 'path', 'notebook_path'])
315
+ if (typeof input[key] === 'string')
316
+ out.push(input[key]);
317
+ if (tool === 'Bash' && typeof input.command === 'string') {
318
+ for (const match of input.command.matchAll(/(?:^|[\s"'=(])((?:\/|\$\w+\/|\.\.?\/)[^\s"'`;|&<>)]+)/g)) {
319
+ const token = match[1];
320
+ if (token.startsWith('$'))
321
+ continue;
322
+ out.push(token);
323
+ }
324
+ }
325
+ return out;
326
+ }
327
+ /**
328
+ * Doxloop owns the evidence map. An agent-written slice is input, not truth:
329
+ * its claims are kept, its source paths are kept only where the file exists,
330
+ * and the plan's citations plus the files this session actually read are
331
+ * always present. A page whose entry names no path it can be checked
332
+ * against is `inferred`, whatever the agent wrote.
333
+ */
334
+ export async function reconcileEvidenceSlice(workspace, sliceFile, plan, pages, sources, reads) {
335
+ const path = join(workspace, sliceFile);
336
+ let slice = {};
337
+ try {
338
+ slice = JSON.parse(await readFile(path, 'utf8'));
339
+ }
340
+ catch {
341
+ slice = {};
342
+ }
343
+ const written = (slice.pages && typeof slice.pages === 'object' ? slice.pages : {});
344
+ const roots = new Map(sources.map((source) => [source.name, resolve(isAbsolute(source.path) ? source.path : join(workspace, source.path))]));
345
+ const contentDir = plan.target?.contentDir || '';
346
+ const extension = preferredPageExtension(plan.target);
347
+ const result = { pages: 0, droppedPaths: 0 };
348
+ const next = {};
349
+ for (const page of pages) {
350
+ const expected = join(contentDir, `${page.path}${extension}`).replaceAll('\\', '/');
351
+ const stem = expected.replace(/\.[^.]+$/, '');
352
+ const key = Object.keys(written).find((file) => file === expected || file.replace(/\.[^.]+$/, '') === stem) ?? expected;
353
+ const agent = written[key] ?? {};
354
+ const merged = new Map();
355
+ const add = (source, paths) => {
356
+ const set = merged.get(source) ?? new Set();
357
+ for (const item of paths)
358
+ set.add(item);
359
+ merged.set(source, set);
360
+ };
361
+ for (const detail of page.evidenceDetails ?? [])
362
+ if (detail?.source && roots.has(detail.source))
363
+ add(detail.source, detail.path ? [detail.path] : []);
364
+ let agentNamedPath = false;
365
+ for (const entry of Array.isArray(agent.sources) ? agent.sources : []) {
366
+ if (!entry || typeof entry.source !== 'string' || !roots.has(entry.source))
367
+ continue;
368
+ const kept = [];
369
+ for (const candidate of entry.paths ?? []) {
370
+ if (typeof candidate !== 'string')
371
+ continue;
372
+ const absolute = resolve(roots.get(entry.source), candidate);
373
+ if (relative(roots.get(entry.source), absolute).startsWith('..')) {
374
+ result.droppedPaths += 1;
375
+ continue;
376
+ }
377
+ if (/[*?[]/.test(candidate) || await pathExists(absolute))
378
+ kept.push(candidate.replaceAll('\\', '/'));
379
+ else
380
+ result.droppedPaths += 1;
381
+ }
382
+ if (kept.length > 0)
383
+ agentNamedPath = true;
384
+ add(entry.source, kept);
385
+ }
386
+ // Reads are attributed only when neither the plan nor the agent named a
387
+ // path for the page: a batch session reads for several pages at once, and
388
+ // a path on every page makes every change look relevant.
389
+ if ([...merged.values()].every((set) => set.size === 0))
390
+ for (const [source, set] of reads)
391
+ add(source, set);
392
+ const entry = {
393
+ sources: [...merged].map(([source, set]) => (set.size > 0 ? { source, paths: [...set].sort() } : { source })),
394
+ // The agent's confidence stands only when it named a real file it read; a
395
+ // "verified" over no path is the word, not the check.
396
+ confidence: agentNamedPath && (agent.confidence === 'verified' || agent.confidence === 'inferred' || agent.confidence === 'needs-human') ? agent.confidence : 'inferred',
397
+ };
398
+ const claims = Array.isArray(agent.claims) ? agent.claims.filter((claim) => typeof claim === 'string' && claim.trim().length > 0).slice(0, 40) : [];
399
+ if (claims.length > 0)
400
+ entry.claims = claims;
401
+ if (agent.claimVerification && typeof agent.claimVerification === 'object') {
402
+ const verification = Object.fromEntries(Object.entries(agent.claimVerification).filter(([claim, state]) => claims.includes(claim) && ['verified', 'inferred', 'contradicted', 'needs-human'].includes(String(state))));
403
+ if (Object.keys(verification).length > 0)
404
+ entry.claimVerification = verification;
405
+ }
406
+ next[key] = entry;
407
+ result.pages += 1;
408
+ }
409
+ await writeFile(path, `${JSON.stringify({ schemaVersion: 1, pages: next }, null, 2)}\n`, 'utf8');
410
+ return result;
411
+ }
412
+ //# sourceMappingURL=batch-artifacts.js.map
@@ -0,0 +1,37 @@
1
+ import type { DocumentationPlan } from './types.js';
2
+ export interface BatchLimits {
3
+ maxPages: number;
4
+ maxScreenshots: number;
5
+ maxMinutes: number;
6
+ }
7
+ export type BatchScope = 'starter' | 'standard' | 'comprehensive' | 'custom';
8
+ /**
9
+ * Screenshots a batch allows per page it may write. The planner is asked for
10
+ * a capture of every screen-changing step — four to seven per UI guide — and
11
+ * a comprehensive plan is mostly UI guides, so a cap of one image per page
12
+ * left every real plan over its limit at approval.
13
+ */
14
+ export declare const SCREENSHOTS_PER_PAGE = 3;
15
+ export declare const MAX_BATCH_SCREENSHOTS = 300;
16
+ /** `maxPages` at this value means the evidence sets the size: no page cap. */
17
+ export declare const UNLIMITED_PAGES = 500;
18
+ export declare function hasPageLimit(limits: Pick<BatchLimits, 'maxPages'>): boolean;
19
+ /**
20
+ * The batch a new plan gets when the reviewer has not set limits by hand.
21
+ * A starter plan is a small, bounded batch. Standard and comprehensive plans
22
+ * have no page cap: the evidence sets their size, and a cap of forty turned
23
+ * every large product into a forty-page plan with the rest silently dropped.
24
+ * Reviewers can still set a cap on the plan review's batch-limits panel.
25
+ * Existing approved plans retain their approved scope. `maxMinutes` bounds one attempt.
26
+ */
27
+ export declare function defaultBatchLimits(scope: BatchScope, screenshots: boolean, targetPages?: number): BatchLimits;
28
+ export declare function batchLimits(value: unknown, defaults?: BatchLimits): BatchLimits;
29
+ export declare function assertBatchFits(plan: DocumentationPlan): void;
30
+ /**
31
+ * The batch a reviewer actually approved. Approval accepts every active page
32
+ * and every estimated capture in the plan, so a run that stays within the
33
+ * plan's own footprint never exceeds the batch even when the recorded limits
34
+ * are absent or smaller than what was approved.
35
+ */
36
+ export declare function approvedBatchLimits(plan: DocumentationPlan): BatchLimits;
37
+ //# sourceMappingURL=batch-limits.d.ts.map
@@ -0,0 +1,80 @@
1
+ import { DoxloopError } from './errors.js';
2
+ /**
3
+ * Screenshots a batch allows per page it may write. The planner is asked for
4
+ * a capture of every screen-changing step — four to seven per UI guide — and
5
+ * a comprehensive plan is mostly UI guides, so a cap of one image per page
6
+ * left every real plan over its limit at approval.
7
+ */
8
+ export const SCREENSHOTS_PER_PAGE = 3;
9
+ export const MAX_BATCH_SCREENSHOTS = 300;
10
+ /** `maxPages` at this value means the evidence sets the size: no page cap. */
11
+ export const UNLIMITED_PAGES = 500;
12
+ export function hasPageLimit(limits) {
13
+ return limits.maxPages < UNLIMITED_PAGES;
14
+ }
15
+ /**
16
+ * The batch a new plan gets when the reviewer has not set limits by hand.
17
+ * A starter plan is a small, bounded batch. Standard and comprehensive plans
18
+ * have no page cap: the evidence sets their size, and a cap of forty turned
19
+ * every large product into a forty-page plan with the rest silently dropped.
20
+ * Reviewers can still set a cap on the plan review's batch-limits panel.
21
+ * Existing approved plans retain their approved scope. `maxMinutes` bounds one attempt.
22
+ */
23
+ export function defaultBatchLimits(scope, screenshots, targetPages = 0) {
24
+ const base = scope === 'starter'
25
+ ? { maxPages: 5, maxMinutes: 15 }
26
+ : scope === 'comprehensive'
27
+ ? { maxPages: UNLIMITED_PAGES, maxMinutes: 120 }
28
+ : { maxPages: UNLIMITED_PAGES, maxMinutes: 45 };
29
+ const maxPages = Math.min(UNLIMITED_PAGES, Math.max(base.maxPages, targetPages));
30
+ return {
31
+ maxPages,
32
+ maxScreenshots: screenshots ? Math.min(MAX_BATCH_SCREENSHOTS, maxPages * SCREENSHOTS_PER_PAGE) : 0,
33
+ maxMinutes: base.maxMinutes,
34
+ };
35
+ }
36
+ export function batchLimits(value, defaults = { maxPages: 50, maxScreenshots: 20, maxMinutes: 30 }) {
37
+ const input = value && typeof value === 'object' ? value : {};
38
+ const result = { ...defaults };
39
+ for (const [key, min, max] of [['maxPages', 1, 500], ['maxScreenshots', 0, MAX_BATCH_SCREENSHOTS], ['maxMinutes', 1, 120]]) {
40
+ if (input[key] === undefined)
41
+ continue;
42
+ if (typeof input[key] !== 'number' || !Number.isInteger(input[key]) || input[key] < min || input[key] > max)
43
+ throw new DoxloopError(`${key} must be a whole number between ${min} and ${max}.`);
44
+ result[key] = input[key];
45
+ }
46
+ return result;
47
+ }
48
+ function activePages(plan) {
49
+ return plan.pages.filter((page) => page.priority !== 'later' && page.action !== 'preserve');
50
+ }
51
+ function plannedCaptures(plan, active) {
52
+ if (plan.execution.screenshots === 'disabled' || plan.execution.screenshots === false)
53
+ return 0;
54
+ return active.reduce((sum, page) => sum + (page.visuals?.mode !== 'none' ? page.visuals?.estimatedCaptures ?? 0 : 0), 0);
55
+ }
56
+ export function assertBatchFits(plan) {
57
+ const limits = batchLimits(plan.execution.limits);
58
+ const active = activePages(plan);
59
+ const captures = plannedCaptures(plan, active);
60
+ if (active.length > limits.maxPages)
61
+ throw new DoxloopError(`This plan writes ${active.length} pages; the batch maximum is ${limits.maxPages}. Mark pages Later or explicitly raise the limit before approving.`);
62
+ if (captures > limits.maxScreenshots)
63
+ throw new DoxloopError(`This plan requests ${captures} screenshots; the batch maximum is ${limits.maxScreenshots}. Reduce captures or explicitly raise the limit before approving.`);
64
+ }
65
+ /**
66
+ * The batch a reviewer actually approved. Approval accepts every active page
67
+ * and every estimated capture in the plan, so a run that stays within the
68
+ * plan's own footprint never exceeds the batch even when the recorded limits
69
+ * are absent or smaller than what was approved.
70
+ */
71
+ export function approvedBatchLimits(plan) {
72
+ const limits = batchLimits(plan.execution.limits);
73
+ const active = activePages(plan);
74
+ return {
75
+ ...limits,
76
+ maxPages: Math.max(limits.maxPages, active.length),
77
+ maxScreenshots: Math.max(limits.maxScreenshots, plannedCaptures(plan, active)),
78
+ };
79
+ }
80
+ //# sourceMappingURL=batch-limits.js.map
@@ -0,0 +1,41 @@
1
+ import { type AssetEntry } from './assets.js';
2
+ /**
3
+ * The branding panel edits the Doxbrix theme block of `docs.json`: logos,
4
+ * favicon, accent colours for light and dark, page backgrounds, colour mode,
5
+ * and fonts. The preview reads `docs.json` on every request, so a saved
6
+ * change shows up on the next reload without a build. External generators
7
+ * keep their own theme configuration; the panel names the file instead.
8
+ */
9
+ export declare const THEME_COLOR_FIELDS: readonly ["primaryColor", "lightColor", "darkColor", "backgroundColorLight", "backgroundColorDark"];
10
+ export declare const THEME_FONT_FIELDS: readonly ["font", "headingFont", "codeFont"];
11
+ export declare const THEME_ASSET_FIELDS: readonly ["logoLight", "logoDark", "favicon", "faviconLight", "faviconDark", "backgroundImage"];
12
+ export declare const THEME_MODES: readonly ["light", "dark", "system"];
13
+ export declare const THEME_CODE_THEMES: readonly ["auto", "light", "dark"];
14
+ export type ThemeField = typeof THEME_COLOR_FIELDS[number] | typeof THEME_FONT_FIELDS[number] | typeof THEME_ASSET_FIELDS[number] | 'mode' | 'codeTheme' | 'logoHref';
15
+ export interface Branding {
16
+ generator: string;
17
+ editable: boolean;
18
+ reason?: string;
19
+ /** Project-relative theme configuration file. */
20
+ configFile: string;
21
+ fingerprint: string;
22
+ site: {
23
+ name: string;
24
+ description: string;
25
+ };
26
+ theme: Partial<Record<ThemeField, string>>;
27
+ /** Image assets in the project the logo and favicon pickers can choose from. */
28
+ assets: Array<Pick<AssetEntry, 'path' | 'name' | 'publicPath'>>;
29
+ }
30
+ export declare function readBranding(root: string): Promise<Branding>;
31
+ export interface BrandingWrite {
32
+ fingerprint: string;
33
+ site?: {
34
+ name?: string;
35
+ description?: string;
36
+ };
37
+ /** A string sets the field; `null` or an empty string clears it. */
38
+ theme?: Partial<Record<ThemeField, string | null>>;
39
+ }
40
+ export declare function writeBranding(root: string, raw: unknown): Promise<Branding>;
41
+ //# sourceMappingURL=branding.d.ts.map