@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,175 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { DoxloopError } from './errors.js';
4
+ import { pathExists, readJson } from './fs.js';
5
+ import { matchesGlob } from './globs.js';
6
+ export const EVIDENCE_MAP_FILE = join('.doxloop', 'evidence-map.json');
7
+ /**
8
+ * The evidence map records which configured source produced each page, so
9
+ * staleness can be attributed to individual pages without starting an agent.
10
+ * It is committed, so a malformed file fails closed rather than being ignored.
11
+ */
12
+ export async function readEvidenceMap(root) {
13
+ const path = join(root, EVIDENCE_MAP_FILE);
14
+ if (!(await pathExists(path)))
15
+ return undefined;
16
+ const map = normalizeEvidenceConfidence(await readJson(path));
17
+ if (!isEvidenceMap(map)) {
18
+ throw new DoxloopError(`${EVIDENCE_MAP_FILE} has an unsupported format. Delete it and run \`doxloop update\` to rebuild it.`);
19
+ }
20
+ return map;
21
+ }
22
+ /**
23
+ * A page whose evidence the writer found contradicted needs a person to look
24
+ * at it; "contradicted" is the claim-level word for that, and an agent that
25
+ * has just marked a claim contradicted sometimes writes the same word as the
26
+ * page's confidence. That is a wording slip, not a broken file: refusing the
27
+ * whole map for it discarded complete generation runs.
28
+ */
29
+ function normalizeEvidenceConfidence(value) {
30
+ if (!value || typeof value !== 'object' || Array.isArray(value))
31
+ return value;
32
+ const pages = value.pages;
33
+ if (!pages || typeof pages !== 'object' || Array.isArray(pages))
34
+ return value;
35
+ let changed = false;
36
+ const normalized = Object.fromEntries(Object.entries(pages).map(([page, evidence]) => {
37
+ if (!evidence || typeof evidence !== 'object' || Array.isArray(evidence))
38
+ return [page, evidence];
39
+ const confidence = evidence.confidence;
40
+ if (confidence !== 'contradicted')
41
+ return [page, evidence];
42
+ changed = true;
43
+ return [page, { ...evidence, confidence: 'needs-human' }];
44
+ }));
45
+ return changed ? { ...value, pages: normalized } : value;
46
+ }
47
+ export async function writeEvidenceMap(root, map) {
48
+ const pages = Object.fromEntries(Object.entries(map.pages).sort(([left], [right]) => left.localeCompare(right)));
49
+ await writeFile(join(root, EVIDENCE_MAP_FILE), `${JSON.stringify({ schemaVersion: 1, pages }, null, 2)}\n`, 'utf8');
50
+ }
51
+ /**
52
+ * A run that ends by recording the sync baseline has just verified its pages
53
+ * against that baseline. Stamp each verified page with the revision it was
54
+ * checked against, so coverage and drift can tell "verified at the current
55
+ * baseline" from "verified at some point"; without it every generated page
56
+ * counted as unverified. Existing stamps are kept. Returns the pages stamped.
57
+ */
58
+ export async function stampVerifiedRevisions(root, state) {
59
+ const map = await readEvidenceMap(root);
60
+ if (!map)
61
+ return 0;
62
+ let stamped = 0;
63
+ for (const evidence of Object.values(map.pages)) {
64
+ if (evidence.confidence !== 'verified')
65
+ continue;
66
+ let changed = false;
67
+ for (const entry of evidence.sources) {
68
+ const record = state.sources[entry.source];
69
+ if (!record?.commit || evidence.verifiedAt?.[entry.source])
70
+ continue;
71
+ evidence.verifiedAt = { ...evidence.verifiedAt, [entry.source]: record.commit };
72
+ changed = true;
73
+ }
74
+ if (changed)
75
+ stamped += 1;
76
+ }
77
+ if (stamped > 0)
78
+ await writeEvidenceMap(root, map);
79
+ return stamped;
80
+ }
81
+ /**
82
+ * Pages that claim behavior from a changed source path, in page order.
83
+ * An entry naming only a source, with no paths, matches every change in it.
84
+ */
85
+ export function pagesForChange(map, source, changedPaths) {
86
+ const matches = new Map();
87
+ for (const [page, evidence] of Object.entries(map.pages)) {
88
+ const patterns = evidenceIdentifiers(evidence, source);
89
+ if (patterns === undefined)
90
+ continue;
91
+ const matched = patterns.length === 0
92
+ ? [...changedPaths]
93
+ : changedPaths.filter((path) => patterns.some((pattern) => evidenceMatches(path, pattern)));
94
+ if (matched.length > 0)
95
+ matches.set(page, matched);
96
+ }
97
+ return matches;
98
+ }
99
+ /** Every source name the map attributes at least one page to. */
100
+ export function trackedSources(map) {
101
+ const names = new Set();
102
+ for (const evidence of Object.values(map.pages)) {
103
+ for (const entry of evidence.sources)
104
+ names.add(entry.source);
105
+ }
106
+ return names;
107
+ }
108
+ /** Patterns recorded for one source, or undefined when the page ignores it. */
109
+ function evidenceIdentifiers(evidence, source) {
110
+ const entries = evidence.sources.filter((entry) => entry.source === source);
111
+ if (entries.length === 0)
112
+ return undefined;
113
+ return entries.flatMap((entry) => [...(entry.paths ?? []), ...(entry.operations ?? [])]);
114
+ }
115
+ /**
116
+ * A recorded path may be an exact file, a directory, or a glob. A bare
117
+ * directory is treated as everything below it so `src/routes` keeps matching
118
+ * after a new file is added to it.
119
+ */
120
+ function evidenceMatches(path, pattern) {
121
+ return matchesGlob(path, pattern) || matchesGlob(path, `${pattern}/**`);
122
+ }
123
+ function isEvidenceMap(value) {
124
+ if (!value || typeof value !== 'object' || Array.isArray(value))
125
+ return false;
126
+ const candidate = value;
127
+ if (candidate.schemaVersion !== 1)
128
+ return false;
129
+ if (!candidate.pages ||
130
+ typeof candidate.pages !== 'object' ||
131
+ Array.isArray(candidate.pages)) {
132
+ return false;
133
+ }
134
+ return Object.entries(candidate.pages).every(([page, evidence]) => page.trim() !== '' && isPageEvidence(evidence));
135
+ }
136
+ function isPageEvidence(value) {
137
+ if (!value || typeof value !== 'object' || Array.isArray(value))
138
+ return false;
139
+ const evidence = value;
140
+ if (!Array.isArray(evidence.sources) || !evidence.sources.every(isPageEvidenceSource)) {
141
+ return false;
142
+ }
143
+ if (evidence.confidence !== undefined &&
144
+ !['verified', 'inferred', 'needs-human'].includes(evidence.confidence)) {
145
+ return false;
146
+ }
147
+ if (evidence.claims !== undefined && !isTextList(evidence.claims))
148
+ return false;
149
+ if (evidence.claimVerification !== undefined && (!evidence.claimVerification ||
150
+ typeof evidence.claimVerification !== 'object' ||
151
+ Array.isArray(evidence.claimVerification) ||
152
+ !Object.entries(evidence.claimVerification).every(([claim, state]) => claim.trim() !== '' && ['verified', 'inferred', 'contradicted', 'needs-human'].includes(String(state)))))
153
+ return false;
154
+ const revisionMapValid = evidence.verifiedAt === undefined || (typeof evidence.verifiedAt === 'object' &&
155
+ !Array.isArray(evidence.verifiedAt) &&
156
+ Object.entries(evidence.verifiedAt).every(([source, revision]) => source.trim() !== '' && typeof revision === 'string' && revision.trim() !== ''));
157
+ const dateMapValid = evidence.verifiedOn === undefined || (typeof evidence.verifiedOn === 'object' &&
158
+ !Array.isArray(evidence.verifiedOn) &&
159
+ Object.entries(evidence.verifiedOn).every(([source, timestamp]) => source.trim() !== '' && typeof timestamp === 'string' && !Number.isNaN(Date.parse(timestamp))));
160
+ return revisionMapValid && dateMapValid;
161
+ }
162
+ function isPageEvidenceSource(value) {
163
+ if (!value || typeof value !== 'object' || Array.isArray(value))
164
+ return false;
165
+ const entry = value;
166
+ return (typeof entry.source === 'string' &&
167
+ entry.source.trim() !== '' &&
168
+ (entry.paths === undefined || isTextList(entry.paths)) &&
169
+ (entry.operations === undefined || isTextList(entry.operations)));
170
+ }
171
+ function isTextList(value) {
172
+ return (Array.isArray(value) &&
173
+ value.every((item) => typeof item === 'string' && item.trim() !== ''));
174
+ }
175
+ //# sourceMappingURL=evidence.js.map
package/dist/fs.d.ts CHANGED
@@ -1,6 +1,12 @@
1
1
  export declare function assertInside(root: string, candidate: string): string;
2
- export declare function resolveContainedDirectory(root: string, configuredPath: string, label?: string): Promise<string>;
3
- export declare function listFiles(root: string, extensions: ReadonlySet<string>): Promise<string[]>;
2
+ export declare function resolveContainedDirectory(root: string, configuredPath: string, label?: string, options?: {
3
+ allowRoot?: boolean;
4
+ }): Promise<string>;
5
+ export declare function listFiles(root: string, extensions: ReadonlySet<string>, options?: {
6
+ ignoredDirectories?: ReadonlySet<string>;
7
+ }): Promise<string[]>;
4
8
  export declare function readJson<T>(path: string): Promise<T>;
5
9
  export declare function pathExists(path: string): Promise<boolean>;
10
+ /** Add machine-local artifacts to .gitignore without losing concurrent additions. */
11
+ export declare function ensureGitignoreEntries(root: string, entries: string[]): Promise<void>;
6
12
  //# sourceMappingURL=fs.d.ts.map
package/dist/fs.js CHANGED
@@ -1,5 +1,5 @@
1
- import { lstat, readdir, readFile, realpath } from 'node:fs/promises';
2
- import { extname, isAbsolute, relative, resolve, sep } from 'node:path';
1
+ import { lstat, readdir, readFile, realpath, writeFile } from 'node:fs/promises';
2
+ import { extname, isAbsolute, join, relative, resolve, sep } from 'node:path';
3
3
  import { DoxloopError } from './errors.js';
4
4
  export function assertInside(root, candidate) {
5
5
  const absoluteRoot = resolve(root);
@@ -10,16 +10,19 @@ export function assertInside(root, candidate) {
10
10
  }
11
11
  return absoluteCandidate;
12
12
  }
13
- export async function resolveContainedDirectory(root, configuredPath, label = 'Directory') {
14
- if (configuredPath.trim() === '' ||
13
+ export async function resolveContainedDirectory(root, configuredPath, label = 'Directory', options = {}) {
14
+ const rootContent = configuredPath === '' && options.allowRoot === true;
15
+ if ((!rootContent && configuredPath.trim() === '') ||
15
16
  isAbsolute(configuredPath) ||
16
17
  configuredPath === '.' ||
17
18
  configuredPath === '..') {
18
19
  throw new DoxloopError(`${label} must be a project-relative directory.`);
19
20
  }
20
21
  const absoluteRoot = resolve(root);
21
- const candidate = assertInside(absoluteRoot, resolve(absoluteRoot, configuredPath));
22
- if (candidate === absoluteRoot) {
22
+ const candidate = rootContent
23
+ ? absoluteRoot
24
+ : assertInside(absoluteRoot, resolve(absoluteRoot, configuredPath));
25
+ if (candidate === absoluteRoot && !rootContent) {
23
26
  throw new DoxloopError(`${label} cannot be the project root.`);
24
27
  }
25
28
  const relation = relative(absoluteRoot, candidate);
@@ -36,20 +39,21 @@ export async function resolveContainedDirectory(root, configuredPath, label = 'D
36
39
  assertInside(realRoot, realCandidate);
37
40
  return candidate;
38
41
  }
39
- export async function listFiles(root, extensions) {
42
+ export async function listFiles(root, extensions, options = {}) {
40
43
  const output = [];
41
- await walk(root, root, extensions, output);
44
+ await walk(root, root, extensions, output, options.ignoredDirectories);
42
45
  return output.sort();
43
46
  }
44
- async function walk(root, directory, extensions, output) {
47
+ async function walk(root, directory, extensions, output, ignoredDirectories) {
45
48
  const entries = await readdir(directory, { withFileTypes: true });
46
49
  for (const entry of entries) {
47
50
  const path = assertInside(root, resolve(directory, entry.name));
48
51
  if (entry.isSymbolicLink()) {
49
52
  throw new DoxloopError(`Symbolic links are not allowed in documentation: ${path}`);
50
53
  }
51
- if (entry.isDirectory())
52
- await walk(root, path, extensions, output);
54
+ if (entry.isDirectory() && !ignoredDirectories?.has(entry.name)) {
55
+ await walk(root, path, extensions, output, ignoredDirectories);
56
+ }
53
57
  else if (entry.isFile() && extensions.has(extname(entry.name)))
54
58
  output.push(path);
55
59
  }
@@ -74,4 +78,30 @@ export async function pathExists(path) {
74
78
  throw error;
75
79
  }
76
80
  }
81
+ const gitignoreQueues = new Map();
82
+ /** Add machine-local artifacts to .gitignore without losing concurrent additions. */
83
+ export async function ensureGitignoreEntries(root, entries) {
84
+ const key = resolve(root);
85
+ const previous = gitignoreQueues.get(key) ?? Promise.resolve();
86
+ const queued = previous.catch(() => undefined).then(async () => {
87
+ const path = join(key, '.gitignore');
88
+ const existing = (await pathExists(path)) ? await readFile(path, 'utf8') : '';
89
+ const lines = existing.split(/\r?\n/).filter(Boolean);
90
+ const seen = new Set(lines);
91
+ for (const entry of entries)
92
+ if (!seen.has(entry)) {
93
+ lines.push(entry);
94
+ seen.add(entry);
95
+ }
96
+ await writeFile(path, `${lines.join('\n')}\n`, 'utf8');
97
+ });
98
+ gitignoreQueues.set(key, queued);
99
+ try {
100
+ await queued;
101
+ }
102
+ finally {
103
+ if (gitignoreQueues.get(key) === queued)
104
+ gitignoreQueues.delete(key);
105
+ }
106
+ }
77
107
  //# sourceMappingURL=fs.js.map
@@ -39,6 +39,58 @@ export interface GeneratorPage {
39
39
  description?: string;
40
40
  body: string;
41
41
  }
42
+ /** One page the navigation should reference. `path` is relative to the content root. */
43
+ export interface GeneratorNavigationPage {
44
+ path: string;
45
+ title: string;
46
+ /** Section label; adapters that support grouping nest the page under it. */
47
+ section?: string;
48
+ }
49
+ export interface GeneratorNavigationContext {
50
+ root: string;
51
+ contentRoot: string;
52
+ project: DoxloopProject;
53
+ /** `add` inserts the page, `remove` drops it, `rename` moves `from` to `page`. */
54
+ action: 'add' | 'remove' | 'rename';
55
+ page: GeneratorNavigationPage;
56
+ /** Content-relative path of the page before a rename. */
57
+ from?: string;
58
+ }
59
+ /**
60
+ * A whole navigation as a tree, for the control center's navigation editor.
61
+ * `file` is content-relative and keeps the page extension the generator uses.
62
+ */
63
+ export type GeneratorNavigationTreeNode = {
64
+ type: 'page';
65
+ file: string;
66
+ title?: string;
67
+ } | {
68
+ type: 'group';
69
+ label: string;
70
+ items: GeneratorNavigationTreeNode[];
71
+ };
72
+ export interface GeneratorNavigationTree {
73
+ nodes: GeneratorNavigationTreeNode[];
74
+ }
75
+ export interface GeneratorNavigationTreeContext {
76
+ root: string;
77
+ contentRoot: string;
78
+ project: DoxloopProject;
79
+ }
80
+ export interface GeneratorRenderContext {
81
+ root: string;
82
+ contentRoot: string;
83
+ /** Content-relative path of the page being rendered. */
84
+ path: string;
85
+ /** The page as read from disk, or unsaved content when previewing an edit. */
86
+ page: GeneratorPage;
87
+ }
88
+ export interface GeneratorRenderedPage {
89
+ /** HTML for the page body only; the host supplies the document shell. */
90
+ html: string;
91
+ /** True when native components were left unrendered and the full build would differ. */
92
+ partial?: boolean;
93
+ }
42
94
  export interface GeneratorAdapter {
43
95
  apiVersion: typeof GENERATOR_API_VERSION;
44
96
  id: string;
@@ -49,11 +101,28 @@ export interface GeneratorAdapter {
49
101
  skillName: string;
50
102
  skillDirectory: string;
51
103
  };
104
+ planning?: {
105
+ /** Generator-native files that own primary or sidebar navigation. */
106
+ navigationFiles: string[];
107
+ };
52
108
  project: {
53
109
  defaultContentDir: string;
54
110
  pageExtensions: string[];
55
111
  gitignore: string[];
56
112
  contentFormat?: 'markdown' | 'rst' | 'html';
113
+ /**
114
+ * Project-relative directory uploaded images land in, and the URL prefix
115
+ * pages use to reference them. Absent means `assets` under the content
116
+ * directory, referenced as `/assets/<name>`.
117
+ */
118
+ assets?: {
119
+ directory: string;
120
+ publicPrefix: string;
121
+ };
122
+ };
123
+ /** The generator's own theme configuration, named in the branding panel when Doxloop cannot edit it. */
124
+ theme?: {
125
+ configFile: string;
57
126
  };
58
127
  build: {
59
128
  command: string;
@@ -64,6 +133,26 @@ export interface GeneratorAdapter {
64
133
  validate(context: GeneratorValidationContext): Promise<ValidationIssue[]>;
65
134
  resolveLocalAsset?(context: GeneratorAssetContext): string | undefined;
66
135
  readPage?(path: string): Promise<GeneratorPage>;
136
+ /**
137
+ * Update the generator's own navigation file when a page is created,
138
+ * removed, or renamed. Absent when the generator derives navigation from the
139
+ * file system or from configuration Doxloop cannot rewrite safely.
140
+ */
141
+ writeNavigation?(context: GeneratorNavigationContext): Promise<void>;
142
+ /**
143
+ * Read the whole navigation as a tree so the control center can reorder,
144
+ * group, and relabel it, and write the edited tree back. Absent when the
145
+ * generator's navigation is code or is derived from the file system.
146
+ */
147
+ readNavigationTree?(context: GeneratorNavigationTreeContext): Promise<GeneratorNavigationTree>;
148
+ writeNavigationTree?(context: GeneratorNavigationTreeContext & {
149
+ tree: GeneratorNavigationTree;
150
+ }): Promise<void>;
151
+ /**
152
+ * Render one page to HTML without running the native build. Absent when the
153
+ * host's generic Markdown preview is the best available approximation.
154
+ */
155
+ renderPage?(context: GeneratorRenderContext): Promise<GeneratorRenderedPage>;
67
156
  }
68
157
  export declare function defineGenerator<T extends GeneratorAdapter>(adapter: T): T;
69
158
  //# sourceMappingURL=generator-api.d.ts.map
@@ -0,0 +1,27 @@
1
+ import { type GeneratorTier } from './generators.js';
2
+ import type { GeneratorName } from './types.js';
3
+ export interface GeneratorPreflightCheck {
4
+ status: 'pass' | 'warning' | 'fail';
5
+ label: string;
6
+ detail?: string;
7
+ }
8
+ export interface GeneratorPreflight {
9
+ generator: GeneratorName;
10
+ tier: GeneratorTier;
11
+ /** False when a required tool is missing; warnings do not clear it. */
12
+ ready: boolean;
13
+ checks: GeneratorPreflightCheck[];
14
+ }
15
+ export interface PreflightProbe {
16
+ findExecutable: (name: string) => Promise<string | undefined>;
17
+ /** Runs `command args` and resolves stdout+stderr, or undefined when it cannot run. */
18
+ runVersion: (command: string, args: string[]) => Promise<string | undefined>;
19
+ nodeVersion: string;
20
+ }
21
+ /**
22
+ * Checks that the runtimes a generator's preview and strict build need are
23
+ * present before the wizard creates a project with it. Doxloop's own
24
+ * requirements (Node.js) are checked once regardless of generator.
25
+ */
26
+ export declare function generatorPreflight(generator: GeneratorName, probe?: Partial<PreflightProbe>): Promise<GeneratorPreflight>;
27
+ //# sourceMappingURL=generator-preflight.d.ts.map
@@ -0,0 +1,105 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { findExecutable } from './generator-runtime.js';
3
+ import { GENERATOR_TOOLCHAINS, generatorCatalogEntry, } from './generators.js';
4
+ const DEFAULT_PROBE = {
5
+ findExecutable,
6
+ runVersion: (command, args) => new Promise((resolveOutput) => {
7
+ execFile(command, args, { timeout: 8_000, windowsHide: true }, (error, stdout, stderr) => {
8
+ resolveOutput(error && !stdout && !stderr ? undefined : `${stdout}${stderr}`.trim());
9
+ });
10
+ }),
11
+ nodeVersion: process.versions.node,
12
+ };
13
+ /**
14
+ * Checks that the runtimes a generator's preview and strict build need are
15
+ * present before the wizard creates a project with it. Doxloop's own
16
+ * requirements (Node.js) are checked once regardless of generator.
17
+ */
18
+ export async function generatorPreflight(generator, probe = {}) {
19
+ const entry = generatorCatalogEntry(generator);
20
+ if (!entry)
21
+ throw new Error(`Unknown generator "${generator}".`);
22
+ const tools = { ...DEFAULT_PROBE, ...probe };
23
+ const checks = [];
24
+ if (entry.toolchain.length === 0) {
25
+ checks.push({ status: 'pass', label: 'Built into Doxloop', detail: 'No extra toolchain is needed for preview or build.' });
26
+ }
27
+ for (const id of entry.toolchain) {
28
+ checks.push(...(await toolchainChecks(id, tools)));
29
+ }
30
+ return {
31
+ generator,
32
+ tier: entry.tier,
33
+ ready: checks.every((check) => check.status !== 'fail'),
34
+ checks,
35
+ };
36
+ }
37
+ async function toolchainChecks(id, tools) {
38
+ const requirement = GENERATOR_TOOLCHAINS[id];
39
+ switch (id) {
40
+ case 'node': {
41
+ const [major = 0, minor = 0] = tools.nodeVersion.split('.').map(Number);
42
+ const supported = major > 20 || (major === 20 && minor >= 12);
43
+ const checks = [
44
+ supported
45
+ ? { status: 'pass', label: `Node.js ${tools.nodeVersion}` }
46
+ : { status: 'fail', label: `Node.js ${tools.nodeVersion} is too old`, detail: `${requirement.label} is required for the native build.` },
47
+ ];
48
+ const managers = await Promise.all(['npm', 'pnpm', 'yarn'].map(async (name) => ((await tools.findExecutable(name)) ? name : undefined)));
49
+ const available = managers.filter((name) => Boolean(name));
50
+ checks.push(available.length > 0
51
+ ? { status: 'pass', label: `Package manager: ${available.join(', ')}` }
52
+ : { status: 'fail', label: 'No package manager found', detail: 'Install npm, pnpm, or yarn so the site dependencies can be installed.' });
53
+ return checks;
54
+ }
55
+ case 'python': {
56
+ const python = (await tools.findExecutable('python3')) ?? (await tools.findExecutable('python'));
57
+ if (!python) {
58
+ return [{ status: 'fail', label: 'Python is not installed', detail: `${requirement.label} is required. ${requirement.detail}` }];
59
+ }
60
+ const version = await tools.runVersion(python, ['--version']);
61
+ const match = version?.match(/(\d+)\.(\d+)(?:\.(\d+))?/);
62
+ const [, major = '0', minor = '0'] = match ?? [];
63
+ const supported = Number(major) > 3 || (Number(major) === 3 && Number(minor) >= 9);
64
+ const checks = [
65
+ supported
66
+ ? { status: 'pass', label: `Python ${match?.[0] ?? version ?? 'found'}` }
67
+ : { status: 'fail', label: `Python ${match?.[0] ?? 'version unknown'} is too old`, detail: `${requirement.label} is required for MkDocs and Sphinx.` },
68
+ ];
69
+ const venv = await tools.runVersion(python, ['-c', 'import venv, ensurepip; print("ok")']);
70
+ checks.push(venv?.includes('ok')
71
+ ? { status: 'pass', label: 'venv and pip modules available' }
72
+ : { status: 'warning', label: 'venv or ensurepip module missing', detail: 'Doxloop creates .doxloop/venv on first preview; install the python3-venv package if that fails.' });
73
+ return checks;
74
+ }
75
+ case 'hugo': {
76
+ const hugo = await tools.findExecutable('hugo');
77
+ if (!hugo) {
78
+ return [{ status: 'fail', label: 'Hugo is not installed', detail: 'Install Hugo from https://gohugo.io/installation/ so preview and build can run.' }];
79
+ }
80
+ const version = await tools.runVersion(hugo, ['version']);
81
+ const extended = /extended/i.test(version ?? '');
82
+ return [
83
+ { status: 'pass', label: `Hugo ${version?.match(/v?\d+\.\d+(?:\.\d+)?/)?.[0] ?? 'found'}${extended ? ' (extended)' : ''}` },
84
+ ...(extended ? [] : [{ status: 'warning', label: 'Standard edition', detail: 'Themes that compile Sass need the extended edition.' }]),
85
+ ];
86
+ }
87
+ case 'ruby': {
88
+ const ruby = await tools.findExecutable('ruby');
89
+ const bundle = await tools.findExecutable('bundle');
90
+ const checks = [];
91
+ if (!ruby) {
92
+ checks.push({ status: 'fail', label: 'Ruby is not installed', detail: 'Install Ruby 3.1 or later; Jekyll runs on it.' });
93
+ }
94
+ else {
95
+ const version = await tools.runVersion(ruby, ['--version']);
96
+ checks.push({ status: 'pass', label: `Ruby ${version?.match(/\d+\.\d+(?:\.\d+)?/)?.[0] ?? 'found'}` });
97
+ }
98
+ checks.push(bundle
99
+ ? { status: 'pass', label: 'Bundler available' }
100
+ : { status: 'fail', label: 'Bundler is not installed', detail: 'Run `gem install bundler` so Jekyll gems can be installed under .doxloop/bundle.' });
101
+ return checks;
102
+ }
103
+ }
104
+ }
105
+ //# sourceMappingURL=generator-preflight.js.map
@@ -19,6 +19,13 @@ export declare function ensurePythonDependencies(root: string, binaryName: strin
19
19
  export declare function runCommand(command: string, args: string[], root: string): Promise<number>;
20
20
  export declare function runPreviewProcess(command: string, args: string[], root: string, label: string, environment?: NodeJS.ProcessEnv): Promise<void>;
21
21
  export declare function openBrowser(url: string): Promise<void>;
22
+ /**
23
+ * A validator that cannot tell which pages the native site links reports one
24
+ * warning instead of guessing. The native strict build remains the authority.
25
+ */
26
+ export declare function navigationUnverifiedIssue(label: string, file: string, reason: string): ValidationIssue;
27
+ /** Content-relative, forward-slash page paths for the validation context. */
28
+ export declare function contentRelativePages(contentRoot: string, pages: readonly string[]): string[];
22
29
  export declare function requiredFileIssues(label: string, files: string[], existing: Set<string>): ValidationIssue[];
23
30
  export declare function serveStaticDirectory(options: GeneratorPreviewOptions, directory: string, label: string): Promise<void>;
24
31
  //# sourceMappingURL=generator-runtime.d.ts.map
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
2
2
  import { createReadStream } from 'node:fs';
3
3
  import { access, readFile, stat, writeFile } from 'node:fs/promises';
4
4
  import { createServer } from 'node:http';
5
- import { delimiter, extname, join, normalize, resolve, sep } from 'node:path';
5
+ import { delimiter, extname, join, normalize, relative, resolve, sep } from 'node:path';
6
6
  import { DoxloopError } from './errors.js';
7
7
  export async function pathExists(path) {
8
8
  try {
@@ -184,6 +184,22 @@ export async function openBrowser(url) {
184
184
  });
185
185
  });
186
186
  }
187
+ /**
188
+ * A validator that cannot tell which pages the native site links reports one
189
+ * warning instead of guessing. The native strict build remains the authority.
190
+ */
191
+ export function navigationUnverifiedIssue(label, file, reason) {
192
+ return {
193
+ severity: 'warning',
194
+ code: 'navigation-unverified',
195
+ message: `${label} navigation was not verified: ${reason} Run the native build to confirm every page is reachable.`,
196
+ file,
197
+ };
198
+ }
199
+ /** Content-relative, forward-slash page paths for the validation context. */
200
+ export function contentRelativePages(contentRoot, pages) {
201
+ return pages.map((page) => relative(contentRoot, page).split('\\').join('/'));
202
+ }
187
203
  export function requiredFileIssues(label, files, existing) {
188
204
  return files
189
205
  .filter((file) => !existing.has(file))
@@ -1,5 +1,12 @@
1
1
  import { type GeneratorAdapter } from './generator-api.js';
2
2
  import type { DoxloopProject, GeneratorName } from './types.js';
3
+ /**
4
+ * How much of Doxloop's workflow a generator gets. The tier is a statement
5
+ * about the adapter, not the framework: it says what validation, preview, and
6
+ * authoring guidance Doxloop itself provides for that generator today.
7
+ */
8
+ export type GeneratorTier = 'full' | 'supported' | 'basic';
9
+ export type GeneratorToolchainId = 'node' | 'python' | 'hugo' | 'ruby';
3
10
  export interface GeneratorCatalogEntry {
4
11
  id: GeneratorName;
5
12
  displayName: string;
@@ -7,7 +14,18 @@ export interface GeneratorCatalogEntry {
7
14
  skillName: string;
8
15
  buildCommand?: string;
9
16
  outputDir?: string;
17
+ tier: GeneratorTier;
18
+ /** External runtimes the native build and preview need, beyond Doxloop itself. */
19
+ toolchain: readonly GeneratorToolchainId[];
10
20
  }
21
+ export declare const GENERATOR_TIERS: Record<GeneratorTier, {
22
+ label: string;
23
+ description: string;
24
+ }>;
25
+ export declare const GENERATOR_TOOLCHAINS: Record<GeneratorToolchainId, {
26
+ label: string;
27
+ detail: string;
28
+ }>;
11
29
  export declare const GENERATOR_CATALOG: readonly GeneratorCatalogEntry[];
12
30
  export declare function generatorCatalogEntry(id: string): GeneratorCatalogEntry | undefined;
13
31
  export declare function parseGenerator(value: string | undefined): GeneratorName | undefined;
@@ -15,7 +33,11 @@ export declare function generatorPackageName(generator: GeneratorName, configure
15
33
  export declare function generatorSkillName(generator: GeneratorName): string;
16
34
  export declare function loadGeneratorAdapter(root: string, projectOrGenerator: DoxloopProject | GeneratorName, configuredPackage?: string): Promise<GeneratorAdapter>;
17
35
  export declare function resolveGeneratorPackage(root: string, packageName: string): string | undefined;
18
- export declare function installedGeneratorEntries(root: string): Promise<Array<GeneratorCatalogEntry & {
36
+ export interface InstalledGeneratorEntry extends GeneratorCatalogEntry {
19
37
  installed: boolean;
20
- }>>;
38
+ tierLabel: string;
39
+ tierDescription: string;
40
+ toolchainLabels: string[];
41
+ }
42
+ export declare function installedGeneratorEntries(root: string): Promise<InstalledGeneratorEntry[]>;
21
43
  //# sourceMappingURL=generators.d.ts.map