@doxbrix/doxloop 0.1.4 → 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 +1153 -0
  2. package/README.md +422 -226
  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 +40 -4
  22. package/dist/agents.js +115 -11
  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 +16 -15
  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 +33 -4
  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 +50 -28
  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
package/dist/pages.js ADDED
@@ -0,0 +1,184 @@
1
+ import { documentationCollections, collectionForPath } from './documentation-collections.js';
2
+ import { resolvePageRoute } from './page-routes.js';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { extname, join, relative, resolve, sep } from 'node:path';
5
+ import { DoxloopError } from './errors.js';
6
+ import { EVIDENCE_MAP_FILE, readEvidenceMap } from './evidence.js';
7
+ import { assertInside, pathExists, resolveContainedDirectory } from './fs.js';
8
+ import { listPages as listHistoryPages } from './history.js';
9
+ import { loadGeneratorAdapter } from './generators.js';
10
+ import { loadPages, loadProject, loadSiteConfig, pageExtensions, pageId, readPage, relativePath, siteConfigPath, } from './project.js';
11
+ import { readDoxbrixNavigation } from './validation.js';
12
+ export async function listPages(root) {
13
+ const project = await loadProject(root);
14
+ const contentRoot = await resolveContainedDirectory(root, project.contentDir, 'Documentation content directory', {
15
+ allowRoot: project.generator === 'doxbrix',
16
+ });
17
+ const collections = await documentationCollections(root, project);
18
+ const files = await loadPages(root, project);
19
+ const adapter = project.generator === 'doxbrix' ? undefined : await loadGeneratorAdapter(root, project);
20
+ const navigation = project.generator === 'doxbrix'
21
+ ? readDoxbrixNavigation((await loadSiteConfig(root, project)).spaces)
22
+ : [];
23
+ const navigationById = new Map(navigation.map((entry, index) => [entry.path, { ...entry, index }]));
24
+ const evidence = await readEvidenceMap(root);
25
+ const history = new Map((await listHistoryPages(root)).map((page) => [portable(page.path), page]));
26
+ const summaries = await Promise.all(files.map(async (absolute) => {
27
+ const path = relativePath(root, absolute);
28
+ const id = pageId(contentRoot, absolute);
29
+ const page = adapter?.readPage ? await adapter.readPage(absolute) : await readPage(absolute);
30
+ const nav = navigationById.get(id);
31
+ return {
32
+ path,
33
+ version: collectionForPath(collections, path)?.version ?? 'current',
34
+ locale: collectionForPath(collections, path)?.locale ?? 'default',
35
+ title: page.title || titleFromPath(path),
36
+ ...(page.description ? { description: page.description } : {}),
37
+ ...(nav?.section ? { section: nav.section } : {}),
38
+ route: await resolvePageRoute(root, project, path),
39
+ wordCount: countWords(page.body),
40
+ ...(history.get(path)?.updatedAt ? { updatedAt: history.get(path).updatedAt } : {}),
41
+ evidence: evidenceState(evidence?.pages[path], project.sync.maxVerificationAgeDays),
42
+ inNavigation: Boolean(nav) || project.generator !== 'doxbrix',
43
+ order: nav?.index ?? Number.MAX_SAFE_INTEGER,
44
+ };
45
+ }));
46
+ return summaries
47
+ .sort((left, right) => left.order - right.order || left.title.localeCompare(right.title) || left.path.localeCompare(right.path))
48
+ .map(({ order: _order, ...summary }) => summary);
49
+ }
50
+ /** Map a project-relative page file to the conventional local-preview route. */
51
+ export function pageRoute(project, path) {
52
+ // External adapters do not currently expose route mapping, so all generators
53
+ // use the same content-relative convention until that API grows one.
54
+ const content = portable(project.contentDir).replace(/^\/+|\/+$/g, '');
55
+ let route = portable(path).replace(/^\/+/, '');
56
+ if (content && (route === content || route.startsWith(`${content}/`))) {
57
+ route = route.slice(content.length).replace(/^\/+/, '');
58
+ }
59
+ route = route.replace(/\.[^./]+$/, '');
60
+ route = route.replace(/(^|\/)index$/, '');
61
+ return `/${route}`.replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/';
62
+ }
63
+ export async function resolveEditScope(root, project, paths, allowRelated) {
64
+ const contentRoot = await resolveContainedDirectory(root, project.contentDir, 'Documentation content directory', {
65
+ allowRoot: project.generator === 'doxbrix',
66
+ });
67
+ const pages = new Set((await loadPages(root, project)).map((path) => relativePath(root, path)));
68
+ const extensions = await pageExtensions(root, project);
69
+ const collections = await documentationCollections(root, project);
70
+ const selectedPaths = new Set();
71
+ for (const raw of paths) {
72
+ const path = portable(raw.trim()).replace(/^\.\//, '');
73
+ const absolute = resolve(root, path);
74
+ let insideContent = false;
75
+ try {
76
+ const collection = collectionForPath(collections, path);
77
+ const allowedRoot = collection ? resolve(root, collection.directory) : contentRoot;
78
+ assertInside(allowedRoot, absolute);
79
+ insideContent = Boolean(collection) && (absolute === allowedRoot || relative(allowedRoot, absolute).split(sep)[0] !== '..');
80
+ }
81
+ catch {
82
+ insideContent = false;
83
+ }
84
+ if (!path ||
85
+ path.split('/').some((part) => ['..', '.doxloop', 'node_modules'].includes(part)) ||
86
+ !insideContent ||
87
+ !extensions.has(extname(path).toLowerCase()) ||
88
+ !pages.has(path)) {
89
+ throw new DoxloopError(`"${raw}" is not an existing documentation page inside ${project.contentDir || 'the project root'}.`, 2);
90
+ }
91
+ selectedPaths.add(path);
92
+ }
93
+ if (selectedPaths.size === 0)
94
+ throw new DoxloopError('Select at least one documentation page to edit.', 2);
95
+ const supportingPaths = new Set([EVIDENCE_MAP_FILE]);
96
+ const supportingPrefixes = new Set();
97
+ if (allowRelated) {
98
+ if (project.generator === 'doxbrix') {
99
+ supportingPaths.add(relativePath(root, await siteConfigPath(root, project)));
100
+ }
101
+ else {
102
+ const adapter = await loadGeneratorAdapter(root, project);
103
+ for (const path of adapter.planning?.navigationFiles ?? [])
104
+ supportingPaths.add(portable(path));
105
+ }
106
+ for (const path of selectedPaths) {
107
+ const raw = await readFile(join(root, path), 'utf8');
108
+ for (const reference of assetReferences(raw)) {
109
+ const resolved = await resolveReferencedAsset(root, contentRoot, path, reference, project);
110
+ if (resolved)
111
+ supportingPaths.add(resolved);
112
+ }
113
+ const slug = path.split('/').at(-1).replace(/\.[^.]+$/, '');
114
+ for (const base of ['assets', 'public/assets', 'static/assets', portable(join(project.contentDir, 'assets'))]) {
115
+ const normalized = base.replace(/^\/+|\/+$/g, '');
116
+ if (normalized)
117
+ supportingPrefixes.add(`${normalized}/${slug}`);
118
+ }
119
+ }
120
+ }
121
+ return {
122
+ selectedPaths,
123
+ supportingPaths,
124
+ supportingPrefixes,
125
+ pageExtensions: extensions,
126
+ wholeProposal: false,
127
+ hunkRanges: new Map(),
128
+ };
129
+ }
130
+ function evidenceState(entry, maxAgeDays) {
131
+ if (!entry)
132
+ return 'none';
133
+ if (entry.confidence === 'needs-human' || entry.confidence === 'inferred' || Object.values(entry.claimVerification ?? {}).some((state) => state !== 'verified')) {
134
+ return 'needs-review';
135
+ }
136
+ const verificationDates = Object.values(entry.verifiedOn ?? {}).map(Date.parse).filter(Number.isFinite);
137
+ const hasVerification = Object.keys(entry.claimVerification ?? {}).length > 0 || Object.keys(entry.verifiedAt ?? {}).length > 0;
138
+ if (!hasVerification)
139
+ return 'needs-review';
140
+ if (maxAgeDays && verificationDates.length > 0 && verificationDates.every((date) => Date.now() - date > maxAgeDays * 86_400_000)) {
141
+ return 'needs-review';
142
+ }
143
+ return 'verified';
144
+ }
145
+ function countWords(value) {
146
+ return value.replace(/```[\s\S]*?```/g, ' ').replace(/<[^>]+>/g, ' ').match(/[\p{L}\p{N}][\p{L}\p{N}'’-]*/gu)?.length ?? 0;
147
+ }
148
+ function titleFromPath(path) {
149
+ return path.split('/').at(-1).replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
150
+ }
151
+ function assetReferences(raw) {
152
+ const references = new Set();
153
+ for (const pattern of [/!\[[^\]]*\]\(([^)\s]+)(?:\s+[^)]*)?\)/g, /\b(?:src|href)=["']([^"']+)["']/g]) {
154
+ for (const match of raw.matchAll(pattern)) {
155
+ const reference = match[1]?.trim();
156
+ if (reference && !/^(?:https?:|data:|#)/i.test(reference))
157
+ references.add(reference);
158
+ }
159
+ }
160
+ return [...references];
161
+ }
162
+ async function resolveReferencedAsset(root, contentRoot, pagePath, reference, project) {
163
+ const adapter = project.generator === 'doxbrix' ? undefined : await loadGeneratorAdapter(root, project);
164
+ const candidates = [
165
+ adapter?.resolveLocalAsset?.({ root, contentRoot, pagePath: join(root, pagePath), reference }),
166
+ reference.startsWith('/') ? resolve(root, reference.slice(1)) : resolve(join(root, pagePath, '..'), reference),
167
+ reference.startsWith('/') ? resolve(contentRoot, reference.slice(1)) : undefined,
168
+ ].filter((value) => Boolean(value));
169
+ for (const candidate of candidates) {
170
+ try {
171
+ const safe = assertInside(root, candidate);
172
+ if (await pathExists(safe))
173
+ return relativePath(root, safe);
174
+ }
175
+ catch {
176
+ // External URLs and assets outside the documentation project are not editable support files.
177
+ }
178
+ }
179
+ return undefined;
180
+ }
181
+ function portable(path) {
182
+ return sep === '/' ? path : path.split(sep).join('/');
183
+ }
184
+ //# sourceMappingURL=pages.js.map
@@ -0,0 +1,3 @@
1
+ import type { DocumentationPlanTarget, DoxloopProject } from './types.js';
2
+ export declare function documentationPlanTarget(root: string, project: DoxloopProject): Promise<DocumentationPlanTarget>;
3
+ //# sourceMappingURL=plan-generator.d.ts.map
@@ -0,0 +1,21 @@
1
+ import { loadGeneratorAdapter } from './generators.js';
2
+ export async function documentationPlanTarget(root, project) {
3
+ if (project.generator === 'doxbrix') {
4
+ return {
5
+ generator: 'doxbrix',
6
+ contentDir: project.contentDir,
7
+ contentFormat: 'markdown',
8
+ pageExtensions: ['.md', '.mdx'],
9
+ navigationFiles: [project.contentDir ? `${project.contentDir}/docs.json` : 'docs.json'],
10
+ };
11
+ }
12
+ const adapter = await loadGeneratorAdapter(root, project);
13
+ return {
14
+ generator: project.generator,
15
+ contentDir: project.contentDir,
16
+ contentFormat: adapter.project.contentFormat ?? 'markdown',
17
+ pageExtensions: adapter.project.pageExtensions,
18
+ navigationFiles: adapter.planning?.navigationFiles ?? [],
19
+ };
20
+ }
21
+ //# sourceMappingURL=plan-generator.js.map
@@ -0,0 +1,11 @@
1
+ import type { DocumentationPlanNavigation } from './types.js';
2
+ /**
3
+ * Every navigation section names the space its group lives in, so a plan
4
+ * with several spaces is built with several spaces. A section the planner
5
+ * gave a known space keeps it (matched case-insensitively); otherwise it goes
6
+ * to the space whose name shares a word with the section title ("APIs" →
7
+ * "API & integrations"). Any other section keeps no space, so navigation
8
+ * repair places it by its pages' paths or in the primary space.
9
+ */
10
+ export declare function assignSectionSpaces<T extends DocumentationPlanNavigation['sections'][number]>(top: readonly string[], sections: readonly T[]): T[];
11
+ //# sourceMappingURL=plan-navigation.d.ts.map
@@ -0,0 +1,30 @@
1
+ const SPACE_WORD_STOP = new Set(['and', 'the', 'a', 'an', 'of', 'for', 'to', 'with', 'your', 'in', 'on', 'docs', 'documentation', 'guide', 'guides']);
2
+ function spaceWords(value) {
3
+ return new Set(value.toLowerCase().split(/[^a-z0-9]+/).filter((word) => word && !SPACE_WORD_STOP.has(word)).map((word) => word.replace(/(?:ies)$/, 'y').replace(/s$/, '')));
4
+ }
5
+ /**
6
+ * Every navigation section names the space its group lives in, so a plan
7
+ * with several spaces is built with several spaces. A section the planner
8
+ * gave a known space keeps it (matched case-insensitively); otherwise it goes
9
+ * to the space whose name shares a word with the section title ("APIs" →
10
+ * "API & integrations"). Any other section keeps no space, so navigation
11
+ * repair places it by its pages' paths or in the primary space.
12
+ */
13
+ export function assignSectionSpaces(top, sections) {
14
+ if (top.length === 0)
15
+ return [...sections];
16
+ const byKey = new Map(top.map((name) => [name.trim().toLowerCase(), name]));
17
+ const secondary = top.slice(1).map((name) => ({ name, words: spaceWords(name) }));
18
+ return sections.map((section) => {
19
+ const named = section.space ? byKey.get(section.space.trim().toLowerCase()) : undefined;
20
+ if (named)
21
+ return { ...section, space: named };
22
+ const titleWords = spaceWords(section.title);
23
+ const match = secondary.find((candidate) => [...candidate.words].some((word) => titleWords.has(word)));
24
+ if (match)
25
+ return { ...section, space: match.name };
26
+ const { space: _unknown, ...rest } = section;
27
+ return rest;
28
+ });
29
+ }
30
+ //# sourceMappingURL=plan-navigation.js.map
@@ -0,0 +1,20 @@
1
+ import type { DocumentationPlan } from './types.js';
2
+ export declare function preparePlanningCaptures(root: string, planId: string): Promise<string>;
3
+ export interface PlanningCaptureReuse {
4
+ reused: number;
5
+ /** Capture IDs the plan references that no saved image could be found for. */
6
+ missing: string[];
7
+ }
8
+ /**
9
+ * Reuse only explicitly selected, reviewed images belonging to this plan's
10
+ * research.
11
+ *
12
+ * The browser tool saves an auto-named file into the capture directory but
13
+ * resolves a caller-supplied filename against its client workspace, so an
14
+ * agent that names its images (as the prompt asks) leaves them at the
15
+ * project root. Those are adopted from there and moved into the plan's
16
+ * capture directory, where the plan's evidence stays together.
17
+ */
18
+ export declare function reusePlanningCaptures(root: string, workspace: string, plan: DocumentationPlan, generator: string): Promise<number>;
19
+ export declare function adoptPlanningCaptures(root: string, workspace: string, plan: DocumentationPlan, generator: string): Promise<PlanningCaptureReuse>;
20
+ //# sourceMappingURL=planning-captures.d.ts.map
@@ -0,0 +1,143 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { copyFile, mkdir, readFile, realpath, rename, writeFile } from 'node:fs/promises';
3
+ import { basename, join, relative, resolve } from 'node:path';
4
+ import { PNG } from 'pngjs';
5
+ import { GUIDE_ASSET_ROOTS, SCREENSHOT_MANIFEST_FILE, dominantColorShare } from './screenshot-workflow.js';
6
+ export async function preparePlanningCaptures(root, planId) {
7
+ if (!/^[\w-]+$/.test(planId))
8
+ throw new Error('Invalid capture plan ID');
9
+ const directory = join(root, '.doxloop', 'plans', planId, 'captures');
10
+ await mkdir(directory, { recursive: true });
11
+ return directory;
12
+ }
13
+ /**
14
+ * Reuse only explicitly selected, reviewed images belonging to this plan's
15
+ * research.
16
+ *
17
+ * The browser tool saves an auto-named file into the capture directory but
18
+ * resolves a caller-supplied filename against its client workspace, so an
19
+ * agent that names its images (as the prompt asks) leaves them at the
20
+ * project root. Those are adopted from there and moved into the plan's
21
+ * capture directory, where the plan's evidence stays together.
22
+ */
23
+ export async function reusePlanningCaptures(root, workspace, plan, generator) {
24
+ return (await adoptPlanningCaptures(root, workspace, plan, generator)).reused;
25
+ }
26
+ export async function adoptPlanningCaptures(root, workspace, plan, generator) {
27
+ const directory = await preparePlanningCaptures(root, plan.id);
28
+ const missing = [];
29
+ let brief;
30
+ try {
31
+ brief = JSON.parse(await readFile(join(root, '.doxloop', 'plans', plan.id, 'research', 'application.json'), 'utf8'));
32
+ }
33
+ catch {
34
+ return { reused: 0, missing };
35
+ }
36
+ const captures = new Map();
37
+ const duplicate = new Set();
38
+ for (const screen of Array.isArray(brief.content?.screens) ? brief.content.screens : []) {
39
+ for (const capture of Array.isArray(screen?.captures) ? screen.captures : []) {
40
+ if (!capture || typeof capture.id !== 'string')
41
+ continue;
42
+ if (captures.has(capture.id))
43
+ duplicate.add(capture.id);
44
+ captures.set(capture.id, { ...capture, route: screen.route, labels: screen.visible });
45
+ }
46
+ }
47
+ let manifest;
48
+ try {
49
+ manifest = JSON.parse(await readFile(join(workspace, SCREENSHOT_MANIFEST_FILE), 'utf8'));
50
+ }
51
+ catch {
52
+ return { reused: 0, missing };
53
+ }
54
+ let reused = 0;
55
+ const catalog = [];
56
+ for (const guide of Array.isArray(manifest.guides) ? manifest.guides : []) {
57
+ if (!Array.isArray(guide?.steps))
58
+ continue;
59
+ const page = plan.pages.find((page) => page.id === guide.page || page.path === guide.page);
60
+ for (const [index, step] of guide.steps.entries()) {
61
+ const sequenceIndex = typeof step.sequenceItem === 'number' ? step.sequenceItem - 1 : index;
62
+ const capture = captures.get(page?.visuals?.captureIds?.[sequenceIndex] ?? '');
63
+ if (!capture || duplicate.has(capture.id))
64
+ continue;
65
+ if (step.status === 'verified' && typeof step.file === 'string') {
66
+ try {
67
+ await readFile(join(workspace, step.file));
68
+ continue;
69
+ }
70
+ catch {
71
+ step.status = 'planned';
72
+ }
73
+ }
74
+ if (!capture.file || !capture.alt || !capture.state || !['expectedStateConfirmed', 'privacyReviewed', 'legibilityReviewed', 'meaningful'].every((key) => capture.checks?.[key] === true))
75
+ continue;
76
+ try {
77
+ const source = await locateCapture(directory, root, capture.file);
78
+ if (!source) {
79
+ missing.push(capture.id);
80
+ continue;
81
+ }
82
+ const bytes = await readFile(source);
83
+ if (bytes.length > 20_000_000)
84
+ continue;
85
+ const png = PNG.sync.read(bytes);
86
+ if (png.width < 320 || png.height < 180 || dominantColorShare(png) > 0.995)
87
+ continue;
88
+ const assetRoot = join(plan.target?.contentDir ?? '', GUIDE_ASSET_ROOTS[generator] ?? GUIDE_ASSET_ROOTS.doxbrix);
89
+ const file = join(assetRoot, 'planning', `${plan.id}-${createHash('sha256').update(bytes).digest('hex').slice(0, 16)}.png`).replaceAll('\\', '/');
90
+ await mkdir(join(workspace, assetRoot, 'planning'), { recursive: true });
91
+ await copyFile(source, join(workspace, file));
92
+ Object.assign(step, { file, alt: capture.alt, status: 'verified', target: typeof step.action === 'string' && step.action ? step.action : capture.action, checks: capture.checks, observedState: capture.state, observedLabels: capture.labels });
93
+ catalog.push({ ...capture, file });
94
+ reused += 1;
95
+ }
96
+ catch { /* Missing or corrupt images remain planned for the capture stage. */ }
97
+ }
98
+ }
99
+ await writeFile(join(workspace, SCREENSHOT_MANIFEST_FILE), JSON.stringify(manifest, null, 2));
100
+ await mkdir(join(workspace, '.doxloop', 'cache'), { recursive: true });
101
+ if (catalog.length > 0)
102
+ await writeFile(join(workspace, '.doxloop', 'cache', 'planning-captures.json'), JSON.stringify(catalog, null, 2));
103
+ return { reused, missing: [...new Set(missing)] };
104
+ }
105
+ /**
106
+ * The saved image for a capture: in the plan's capture directory, or by its
107
+ * base name at the project root where the browser tool put it, in which case
108
+ * it is moved into the capture directory first. Nothing outside those two
109
+ * places is ever read.
110
+ */
111
+ async function locateCapture(directory, root, file) {
112
+ const inDirectory = await safeChild(directory, file);
113
+ if (inDirectory)
114
+ return inDirectory;
115
+ const name = basename(file);
116
+ if (!/^[\w.-]+\.png$/i.test(name))
117
+ return undefined;
118
+ const stray = await safeChild(root, name);
119
+ if (!stray)
120
+ return undefined;
121
+ const moved = join(directory, name);
122
+ try {
123
+ await rename(stray, moved);
124
+ return moved;
125
+ }
126
+ catch {
127
+ return stray;
128
+ }
129
+ }
130
+ /** The real path of `file` when it is an existing file directly inside `parent`. */
131
+ async function safeChild(parent, file) {
132
+ try {
133
+ const target = await realpath(resolve(parent, file));
134
+ const inside = relative(await realpath(parent), target);
135
+ if (!inside || inside.startsWith('..') || inside.startsWith('/'))
136
+ return undefined;
137
+ return target;
138
+ }
139
+ catch {
140
+ return undefined;
141
+ }
142
+ }
143
+ //# sourceMappingURL=planning-captures.js.map
@@ -0,0 +1,135 @@
1
+ import type { CaptureAuthMode } from './capture-auth.js';
2
+ import { type DocumentationDiscoveryInventory } from './source-discovery.js';
3
+ import type { AgentName, DocumentationPlan, DoxloopProject } from './types.js';
4
+ /**
5
+ * Planning research: the reading, browsing, and auditing that used to happen
6
+ * inside one long planning session now runs as short sessions, several at a
7
+ * time, each returning a compact brief. A separate synthesis session writes
8
+ * the plan from the briefs. Every brief is checkpointed under the plan, so a
9
+ * failed or retried plan repeats only the work that is missing.
10
+ */
11
+ export declare const DEFAULT_PLANNING_PARALLEL = 2;
12
+ /** Crawled pages one existing-documentation research session reads. */
13
+ export declare const EXISTING_DOCS_SHARD_PAGES = 20;
14
+ export type ResearchKind = 'product' | 'application' | 'existing-docs';
15
+ export interface ResearchShard {
16
+ source: string;
17
+ /** Snapshot directory, relative to the project root as configured. */
18
+ snapshotPath: string;
19
+ pages: Array<{
20
+ file: string;
21
+ title: string;
22
+ words: number;
23
+ }>;
24
+ index: number;
25
+ total: number;
26
+ }
27
+ export interface ResearchTask {
28
+ id: string;
29
+ kind: ResearchKind;
30
+ label: string;
31
+ /** Whether the session needs the capture browser. */
32
+ browser: boolean;
33
+ shard?: ResearchShard;
34
+ /** Existing pages a page-scoped update names; the product audit reads only the surface behind them. */
35
+ focus?: string[];
36
+ }
37
+ export interface ResearchBrief {
38
+ task: string;
39
+ kind: ResearchKind;
40
+ label: string;
41
+ content: unknown;
42
+ }
43
+ export interface ResearchCheckpoint extends ResearchBrief {
44
+ key: string;
45
+ repairs?: string[];
46
+ savedAt: string;
47
+ }
48
+ /** Staged planning is on unless `DOXLOOP_PLANNING_STAGED` turns it off. */
49
+ export declare function stagedPlanningEnabled(env?: NodeJS.ProcessEnv): boolean;
50
+ /** How many research sessions run at once (`DOXLOOP_PLANNING_PARALLEL`, default 3). */
51
+ export declare function planningParallelism(env?: NodeJS.ProcessEnv): number;
52
+ /**
53
+ * Which research sessions this plan needs: one product-surface audit when
54
+ * code or specification sources are configured, one application exploration
55
+ * when screenshots are wanted and an application is configured, and one
56
+ * existing-documentation audit per shard of crawled pages. The plan's
57
+ * research scope (see planning-triage) narrows that: a navigation-only
58
+ * update researches nothing, and a page-scoped update audits only the
59
+ * product surface behind the named pages, exploring the application only
60
+ * when screenshots are required.
61
+ */
62
+ export declare function researchTasks(root: string, project: Pick<DoxloopProject, 'sources' | 'application'>, current: Pick<DocumentationPlan, 'execution' | 'research'>): Promise<ResearchTask[]>;
63
+ export interface ResearchPromptContext {
64
+ captureDirectory?: string;
65
+ project: DoxloopProject;
66
+ current: Pick<DocumentationPlan, 'request' | 'mode' | 'scope' | 'execution'>;
67
+ discovery: DocumentationDiscoveryInventory;
68
+ /** Deterministic source-change summary. */
69
+ changes: string;
70
+ captureAuth: CaptureAuthMode;
71
+ }
72
+ /** Browser calls one application research session may spend; a session without a budget made a hundred. */
73
+ export declare const APPLICATION_BROWSER_CALLS = 30;
74
+ export declare function researchPrompt(task: ResearchTask, context: ResearchPromptContext): string;
75
+ /** The brief a research session returned, or the reason it could not be read. */
76
+ export declare function readBriefOutput(raw: string, agent: AgentName, task: ResearchTask, prompt?: string): {
77
+ content: unknown;
78
+ repairs: string[];
79
+ };
80
+ /** What the synthesis session reads instead of the sources, application, and pages. */
81
+ export declare function formatResearchBriefs(briefs: readonly ResearchBrief[]): string;
82
+ /**
83
+ * Briefs depend on the sources, the capture intent, the agent, and the
84
+ * research focus, not on the plan's wording or identity: a revision reuses
85
+ * them, and so does a later plan on unchanged sources (see
86
+ * `readResearchCheckpoint`). A page-scoped audit is keyed by its pages.
87
+ */
88
+ export declare function researchCheckpointKey(current: Pick<DocumentationPlan, 'execution' | 'research'>, sourceSnapshot: string, focus?: readonly string[] | null): string;
89
+ /**
90
+ * Keys a saved brief may carry to serve this plan: its own key first, then
91
+ * the key of a full product audit on the same sources, which covers
92
+ * anything a page-scoped audit would find.
93
+ */
94
+ export declare function researchCheckpointKeys(current: Pick<DocumentationPlan, 'execution' | 'research'>, sourceSnapshot: string): string[];
95
+ /**
96
+ * The brief saved for this task under one of `keys`: this plan's own first,
97
+ * then the newest other plan's. Sources that have not changed since another
98
+ * plan audited them do not need auditing again, and a second update on the
99
+ * same snapshot used to redo every session. The application brief is the
100
+ * exception: it names captures saved under its own plan, which this plan
101
+ * cannot embed, so it is only ever reused from this plan.
102
+ */
103
+ export declare function readResearchCheckpoint(root: string, planId: string, task: ResearchTask, keys: string | readonly string[]): Promise<(ResearchCheckpoint & {
104
+ planId: string;
105
+ }) | undefined>;
106
+ /** Briefs saved for these tasks under one of the keys; empty unless every task has one. */
107
+ export declare function savedResearchBriefs(root: string, planId: string, tasks: readonly ResearchTask[], keys: string | readonly string[]): Promise<ResearchBrief[]>;
108
+ export declare function writeResearchCheckpoint(root: string, planId: string, checkpoint: Omit<ResearchCheckpoint, 'savedAt'>): Promise<void>;
109
+ export interface RunResearchOptions {
110
+ root: string;
111
+ planId: string;
112
+ /** Key the briefs this run saves carry. */
113
+ key: string;
114
+ /** Keys a saved brief may carry to be reused; defaults to `key` alone. */
115
+ acceptKeys?: readonly string[];
116
+ agent: AgentName;
117
+ concurrency: number;
118
+ context: ResearchPromptContext;
119
+ /** Run one session and return its raw output. */
120
+ runSession: (task: ResearchTask, prompt: string) => Promise<string>;
121
+ /** Called as tasks finish, for stage progress. */
122
+ onProgress?: (done: number, total: number, task: ResearchTask, outcome: 'cached' | 'completed' | 'failed') => void;
123
+ log?: (line: string) => void;
124
+ }
125
+ /**
126
+ * Run every research task that has no valid checkpoint, at most
127
+ * `concurrency` at a time. A session that fails or returns an unreadable
128
+ * brief is tried once more in a fresh session; the pool keeps going so the
129
+ * other briefs are saved, then the first failure is reported.
130
+ */
131
+ export declare function runResearch(tasks: readonly ResearchTask[], options: RunResearchOptions): Promise<{
132
+ briefs: ResearchBrief[];
133
+ repairs: string[];
134
+ }>;
135
+ //# sourceMappingURL=planning-research.d.ts.map