@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
@@ -0,0 +1,400 @@
1
+ /**
2
+ * Diff primitives for the local review center.
3
+ *
4
+ * Two kinds of diff are produced here. `unifiedRows` turns the stored proposal
5
+ * hunks back into a reviewable unified diff with surrounding context and
6
+ * word-level emphasis, and `alignBlocks`/`markHtmlWordDiff` line up the two
7
+ * rendered versions of a page so a reader can compare them side by side without
8
+ * reading Markdown at all.
9
+ */
10
+ /** Guard so a pathological file cannot allocate a huge LCS table. */
11
+ const MAX_TABLE_CELLS = 4_000_000;
12
+ /** Longest-common-subsequence diff over pre-computed comparison keys. */
13
+ export function diffKeys(before, after) {
14
+ const spans = [];
15
+ const push = (type, bs, be, as, ae) => {
16
+ if (bs === be && as === ae)
17
+ return;
18
+ const last = spans.at(-1);
19
+ if (last?.type === type) {
20
+ last.beforeEnd = be;
21
+ last.afterEnd = ae;
22
+ return;
23
+ }
24
+ spans.push({ type, beforeStart: bs, beforeEnd: be, afterStart: as, afterEnd: ae });
25
+ };
26
+ // Trimming the shared head and tail keeps the table small for the common case
27
+ // of a one-line edit inside a long file.
28
+ let head = 0;
29
+ while (head < before.length && head < after.length && before[head] === after[head])
30
+ head += 1;
31
+ let tail = 0;
32
+ while (tail < before.length - head &&
33
+ tail < after.length - head &&
34
+ before[before.length - 1 - tail] === after[after.length - 1 - tail]) {
35
+ tail += 1;
36
+ }
37
+ const oldMid = before.slice(head, before.length - tail);
38
+ const newMid = after.slice(head, after.length - tail);
39
+ push('equal', 0, head, 0, head);
40
+ if (oldMid.length === 0 || newMid.length === 0 ||
41
+ (oldMid.length + 1) * (newMid.length + 1) > MAX_TABLE_CELLS) {
42
+ push('delete', head, head + oldMid.length, head, head);
43
+ push('insert', head + oldMid.length, head + oldMid.length, head, head + newMid.length);
44
+ }
45
+ else {
46
+ const table = Array.from({ length: oldMid.length + 1 }, () => new Uint32Array(newMid.length + 1));
47
+ for (let old = oldMid.length - 1; old >= 0; old -= 1) {
48
+ for (let next = newMid.length - 1; next >= 0; next -= 1) {
49
+ table[old][next] = oldMid[old] === newMid[next]
50
+ ? table[old + 1][next + 1] + 1
51
+ : Math.max(table[old + 1][next], table[old][next + 1]);
52
+ }
53
+ }
54
+ let old = 0;
55
+ let next = 0;
56
+ while (old < oldMid.length || next < newMid.length) {
57
+ if (old < oldMid.length && next < newMid.length && oldMid[old] === newMid[next]) {
58
+ push('equal', head + old, head + old + 1, head + next, head + next + 1);
59
+ old += 1;
60
+ next += 1;
61
+ }
62
+ else if (old < oldMid.length &&
63
+ (next >= newMid.length || table[old + 1][next] >= table[old][next + 1])) {
64
+ // Removals are emitted before the additions that replace them so a
65
+ // consumer can pair the two into a single before/after comparison.
66
+ push('delete', head + old, head + old + 1, head + next, head + next);
67
+ old += 1;
68
+ }
69
+ else {
70
+ push('insert', head + old, head + old, head + next, head + next + 1);
71
+ next += 1;
72
+ }
73
+ }
74
+ }
75
+ const beforeTailStart = before.length - tail;
76
+ const afterTailStart = after.length - tail;
77
+ push('equal', beforeTailStart, before.length, afterTailStart, after.length);
78
+ return spans;
79
+ }
80
+ /**
81
+ * Rebuild a reviewable unified diff from the proposal's stored hunks. The hunks
82
+ * only carry the changed lines, so the surrounding context comes from the
83
+ * snapshot of the file taken when the proposal was generated.
84
+ */
85
+ export function unifiedRows(beforeText, hunks, options = {}) {
86
+ const context = options.context ?? 3;
87
+ const lines = textLines(beforeText);
88
+ const rows = [];
89
+ let added = 0;
90
+ let removed = 0;
91
+ let oldCursor = 0;
92
+ let newCursor = 0;
93
+ /**
94
+ * `leading` marks a run that follows a change (keep its first lines) and
95
+ * `trailing` a run that precedes one (keep its last lines). A run with
96
+ * neither — a file without changes — is shown in full.
97
+ */
98
+ const emitContext = (from, to, leading, trailing) => {
99
+ const count = to - from;
100
+ if (count <= 0)
101
+ return;
102
+ const head = leading ? Math.min(context, count) : trailing ? 0 : count;
103
+ const tail = trailing ? Math.min(context, count) : 0;
104
+ if (head + tail >= count) {
105
+ for (let index = from; index < to; index += 1) {
106
+ rows.push({
107
+ type: 'context',
108
+ oldNumber: index + 1,
109
+ newNumber: newCursor + (index - from) + 1,
110
+ html: escapeHtml(lines[index] ?? ''),
111
+ });
112
+ }
113
+ newCursor += count;
114
+ return;
115
+ }
116
+ for (let index = from; index < from + head; index += 1) {
117
+ rows.push({
118
+ type: 'context',
119
+ oldNumber: index + 1,
120
+ newNumber: newCursor + (index - from) + 1,
121
+ html: escapeHtml(lines[index] ?? ''),
122
+ });
123
+ }
124
+ rows.push({ type: 'gap', html: '', hidden: count - head - tail });
125
+ for (let index = to - tail; index < to; index += 1) {
126
+ rows.push({
127
+ type: 'context',
128
+ oldNumber: index + 1,
129
+ newNumber: newCursor + (index - from) + 1,
130
+ html: escapeHtml(lines[index] ?? ''),
131
+ });
132
+ }
133
+ newCursor += count;
134
+ };
135
+ for (const [position, hunk] of hunks.entries()) {
136
+ emitContext(oldCursor, hunk.oldStart, position > 0, true);
137
+ oldCursor = hunk.oldStart;
138
+ newCursor = hunk.newStart;
139
+ const state = hunk.acceptedAt ? 'accepted' : hunk.rejectedAt ? 'rejected' : 'pending';
140
+ const pairs = pairChangedLines(hunk.oldLines, hunk.newLines);
141
+ for (const [index, line] of hunk.oldLines.entries()) {
142
+ const partner = pairs.get(index);
143
+ rows.push({
144
+ type: 'delete',
145
+ oldNumber: oldCursor + index + 1,
146
+ html: partner === undefined ? escapeHtml(line) : wordDiffText(line, partner).before,
147
+ hunkId: hunk.id,
148
+ hunkState: state,
149
+ });
150
+ removed += 1;
151
+ }
152
+ for (const [index, line] of hunk.newLines.entries()) {
153
+ const partner = pairs.has(index) ? hunk.oldLines[index] : undefined;
154
+ rows.push({
155
+ type: 'insert',
156
+ newNumber: newCursor + index + 1,
157
+ html: partner === undefined ? escapeHtml(line) : wordDiffText(partner, line).after,
158
+ hunkId: hunk.id,
159
+ hunkState: state,
160
+ });
161
+ added += 1;
162
+ }
163
+ oldCursor = hunk.oldStart + hunk.oldLines.length;
164
+ newCursor = hunk.newStart + hunk.newLines.length;
165
+ }
166
+ emitContext(oldCursor, lines.length, hunks.length > 0, false);
167
+ return { rows, added, removed };
168
+ }
169
+ /**
170
+ * Pair a removed line with the added line that replaced it, so the two can be
171
+ * shown with only the differing words highlighted. Only confident matches are
172
+ * paired; unrelated lines stay whole-line additions and removals.
173
+ */
174
+ function pairChangedLines(oldLines, newLines) {
175
+ const pairs = new Map();
176
+ const count = Math.min(oldLines.length, newLines.length);
177
+ for (let index = 0; index < count; index += 1) {
178
+ const before = oldLines[index];
179
+ const after = newLines[index];
180
+ if (before !== after && similarity(before, after) >= 0.45)
181
+ pairs.set(index, after);
182
+ }
183
+ return pairs;
184
+ }
185
+ function similarity(before, after) {
186
+ const left = words(before);
187
+ const right = words(after);
188
+ if (left.length === 0 || right.length === 0)
189
+ return 0;
190
+ const shared = new Map();
191
+ for (const word of left)
192
+ shared.set(word, (shared.get(word) ?? 0) + 1);
193
+ let common = 0;
194
+ for (const word of right) {
195
+ const available = shared.get(word) ?? 0;
196
+ if (available > 0) {
197
+ shared.set(word, available - 1);
198
+ common += 1;
199
+ }
200
+ }
201
+ return (2 * common) / (left.length + right.length);
202
+ }
203
+ /** Word-level diff of two plain-text lines, returned as escaped HTML. */
204
+ export function wordDiffText(before, after) {
205
+ const left = tokenizeWords(before);
206
+ const right = tokenizeWords(after);
207
+ const spans = diffKeys(left, right);
208
+ let beforeHtml = '';
209
+ let afterHtml = '';
210
+ for (const span of spans) {
211
+ const removedText = left.slice(span.beforeStart, span.beforeEnd).join('');
212
+ const addedText = right.slice(span.afterStart, span.afterEnd).join('');
213
+ if (span.type === 'equal') {
214
+ beforeHtml += escapeHtml(removedText);
215
+ afterHtml += escapeHtml(addedText);
216
+ continue;
217
+ }
218
+ if (span.type === 'delete' && removedText.trim() !== '') {
219
+ beforeHtml += `<mark class="rv-word rv-word--del">${escapeHtml(removedText)}</mark>`;
220
+ }
221
+ else if (span.type === 'delete') {
222
+ beforeHtml += escapeHtml(removedText);
223
+ }
224
+ if (span.type === 'insert' && addedText.trim() !== '') {
225
+ afterHtml += `<mark class="rv-word rv-word--ins">${escapeHtml(addedText)}</mark>`;
226
+ }
227
+ else if (span.type === 'insert') {
228
+ afterHtml += escapeHtml(addedText);
229
+ }
230
+ }
231
+ return { before: beforeHtml, after: afterHtml };
232
+ }
233
+ function tokenizeWords(value) {
234
+ return value.match(/\s+|[A-Za-z0-9_]+|[^\sA-Za-z0-9_]/g) ?? [];
235
+ }
236
+ function words(value) {
237
+ return value.match(/[A-Za-z0-9_]+/g) ?? [];
238
+ }
239
+ /* ── Rendered HTML diff ───────────────────────────────────────────────── */
240
+ const VOID_ELEMENTS = new Set([
241
+ 'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input',
242
+ 'link', 'meta', 'param', 'source', 'track', 'wbr',
243
+ ]);
244
+ /**
245
+ * Split rendered documentation HTML into its top-level blocks (one paragraph,
246
+ * heading, code block, callout, table … per entry) so two renderings can be
247
+ * lined up against each other.
248
+ */
249
+ export function splitHtmlBlocks(html) {
250
+ const blocks = [];
251
+ const pattern = /<!--[\s\S]*?-->|<\/?([a-zA-Z][\w-]*)((?:"[^"]*"|'[^']*'|[^>"'])*)>/g;
252
+ let depth = 0;
253
+ let start = 0;
254
+ let match;
255
+ while ((match = pattern.exec(html)) !== null) {
256
+ if (match[0].startsWith('<!--'))
257
+ continue;
258
+ const name = match[1].toLowerCase();
259
+ const closing = match[0].startsWith('</');
260
+ const selfClosing = match[2]?.trimEnd().endsWith('/') ?? false;
261
+ if (VOID_ELEMENTS.has(name) || selfClosing) {
262
+ if (depth === 0) {
263
+ blocks.push(html.slice(start, match.index + match[0].length).trim());
264
+ start = match.index + match[0].length;
265
+ }
266
+ continue;
267
+ }
268
+ if (closing) {
269
+ depth -= 1;
270
+ if (depth <= 0) {
271
+ depth = 0;
272
+ blocks.push(html.slice(start, match.index + match[0].length).trim());
273
+ start = match.index + match[0].length;
274
+ }
275
+ continue;
276
+ }
277
+ depth += 1;
278
+ }
279
+ const rest = html.slice(start).trim();
280
+ if (rest !== '')
281
+ blocks.push(rest);
282
+ return blocks.filter((block) => block !== '');
283
+ }
284
+ /** Visible text of an HTML fragment, used as the block comparison key. */
285
+ export function htmlText(html) {
286
+ return decodeEntities(html.replace(/<[^>]*>/g, ' '))
287
+ .replace(/\s+/g, ' ')
288
+ .trim();
289
+ }
290
+ /**
291
+ * Align the blocks of two rendered pages. Adjacent removals and additions are
292
+ * paired into a single "changed" row so the reader sees one before/after
293
+ * comparison instead of two disconnected entries.
294
+ */
295
+ export function alignBlocks(before, after) {
296
+ const spans = diffKeys(before.map(blockKey), after.map(blockKey));
297
+ const rows = [];
298
+ for (let index = 0; index < spans.length; index += 1) {
299
+ const span = spans[index];
300
+ if (span.type === 'equal') {
301
+ for (let offset = 0; offset < span.beforeEnd - span.beforeStart; offset += 1) {
302
+ rows.push({
303
+ status: 'equal',
304
+ before: before[span.beforeStart + offset],
305
+ after: after[span.afterStart + offset],
306
+ });
307
+ }
308
+ continue;
309
+ }
310
+ if (span.type === 'delete') {
311
+ const next = spans[index + 1];
312
+ const removed = before.slice(span.beforeStart, span.beforeEnd);
313
+ if (next?.type === 'insert') {
314
+ const inserted = after.slice(next.afterStart, next.afterEnd);
315
+ const paired = Math.min(removed.length, inserted.length);
316
+ for (let offset = 0; offset < paired; offset += 1) {
317
+ rows.push({ status: 'changed', before: removed[offset], after: inserted[offset] });
318
+ }
319
+ for (const block of removed.slice(paired))
320
+ rows.push({ status: 'removed', before: block });
321
+ for (const block of inserted.slice(paired))
322
+ rows.push({ status: 'added', after: block });
323
+ index += 1;
324
+ continue;
325
+ }
326
+ for (const block of removed)
327
+ rows.push({ status: 'removed', before: block });
328
+ continue;
329
+ }
330
+ for (const block of after.slice(span.afterStart, span.afterEnd)) {
331
+ rows.push({ status: 'added', after: block });
332
+ }
333
+ }
334
+ return rows;
335
+ }
336
+ function blockKey(html) {
337
+ const tag = /<\s*([a-zA-Z][\w-]*)/.exec(html)?.[1]?.toLowerCase() ?? 'text';
338
+ return `${tag}${htmlText(html)}`;
339
+ }
340
+ /**
341
+ * Highlight the words that differ between two rendered blocks while leaving the
342
+ * surrounding markup — links, code spans, table cells — intact.
343
+ */
344
+ export function markHtmlWordDiff(before, after) {
345
+ const left = tokenizeHtml(before);
346
+ const right = tokenizeHtml(after);
347
+ const leftKeys = left.map((token) => (token.tag ? `${token.value}` : token.value));
348
+ const rightKeys = right.map((token) => (token.tag ? `${token.value}` : token.value));
349
+ const spans = diffKeys(leftKeys, rightKeys);
350
+ let beforeHtml = '';
351
+ let afterHtml = '';
352
+ for (const span of spans) {
353
+ for (let index = span.beforeStart; index < span.beforeEnd; index += 1) {
354
+ beforeHtml += wrapToken(left[index], span.type === 'delete' ? 'del' : undefined);
355
+ }
356
+ for (let index = span.afterStart; index < span.afterEnd; index += 1) {
357
+ afterHtml += wrapToken(right[index], span.type === 'insert' ? 'ins' : undefined);
358
+ }
359
+ }
360
+ return { before: beforeHtml, after: afterHtml };
361
+ }
362
+ function tokenizeHtml(html) {
363
+ const tokens = [];
364
+ const pattern = /<[^>]*>|\s+|[^\s<]+/g;
365
+ let match;
366
+ while ((match = pattern.exec(html)) !== null) {
367
+ tokens.push({ value: match[0], tag: match[0].startsWith('<') });
368
+ }
369
+ return tokens;
370
+ }
371
+ function wrapToken(token, mark) {
372
+ if (!mark || token.tag || token.value.trim() === '')
373
+ return token.value;
374
+ return `<mark class="rv-word rv-word--${mark}">${token.value}</mark>`;
375
+ }
376
+ /* ── Shared helpers ───────────────────────────────────────────────────── */
377
+ export function textLines(text) {
378
+ if (text === '')
379
+ return [];
380
+ const normalized = text.replace(/\r\n/g, '\n');
381
+ return normalized.endsWith('\n') ? normalized.slice(0, -1).split('\n') : normalized.split('\n');
382
+ }
383
+ export function escapeHtml(value) {
384
+ return value
385
+ .replaceAll('&', '&amp;')
386
+ .replaceAll('<', '&lt;')
387
+ .replaceAll('>', '&gt;')
388
+ .replaceAll('"', '&quot;')
389
+ .replaceAll("'", '&#039;');
390
+ }
391
+ function decodeEntities(value) {
392
+ return value
393
+ .replaceAll('&lt;', '<')
394
+ .replaceAll('&gt;', '>')
395
+ .replaceAll('&quot;', '"')
396
+ .replaceAll('&#039;', "'")
397
+ .replaceAll('&nbsp;', ' ')
398
+ .replaceAll('&amp;', '&');
399
+ }
400
+ //# sourceMappingURL=review-diff.js.map
@@ -0,0 +1,11 @@
1
+ export declare const REVIEW_PREFERENCES_FILE: string;
2
+ export interface ReviewPreference {
3
+ id: string;
4
+ at: string;
5
+ kind: 'revision' | 'rejection' | 'inline-edit' | 'edit';
6
+ paths: string[];
7
+ instruction: string;
8
+ }
9
+ export declare function recordReviewPreference(root: string, preference: Omit<ReviewPreference, 'id' | 'at'>): Promise<void>;
10
+ export declare function reviewPreferenceGuidance(root: string): Promise<string>;
11
+ //# sourceMappingURL=review-learning.d.ts.map
@@ -0,0 +1,60 @@
1
+ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { ensureGitignoreEntries, pathExists } from './fs.js';
4
+ export const REVIEW_PREFERENCES_FILE = join('.doxloop', 'review-preferences.json');
5
+ const preferenceQueues = new Map();
6
+ export async function recordReviewPreference(root, preference) {
7
+ const previous = preferenceQueues.get(root) ?? Promise.resolve();
8
+ const queued = previous.catch(() => undefined).then(() => persistReviewPreference(root, preference));
9
+ preferenceQueues.set(root, queued);
10
+ try {
11
+ await queued;
12
+ }
13
+ finally {
14
+ if (preferenceQueues.get(root) === queued)
15
+ preferenceQueues.delete(root);
16
+ }
17
+ }
18
+ async function persistReviewPreference(root, preference) {
19
+ const current = await readPreferences(root);
20
+ const entry = {
21
+ ...preference,
22
+ paths: [...new Set(preference.paths.filter((path) => typeof path === 'string').map((path) => path.slice(0, 500)))].slice(0, 100),
23
+ instruction: redactPreference(preference.instruction).trim().slice(0, 2_000),
24
+ id: `feedback-${Date.now().toString(36)}`,
25
+ at: new Date().toISOString(),
26
+ };
27
+ if (!entry.instruction)
28
+ return;
29
+ const entries = [...current, entry].slice(-100);
30
+ await ensureGitignoreEntries(root, [REVIEW_PREFERENCES_FILE]);
31
+ await mkdir(join(root, '.doxloop'), { recursive: true, mode: 0o700 });
32
+ await writeFile(join(root, REVIEW_PREFERENCES_FILE), `${JSON.stringify({ schemaVersion: 1, entries }, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
33
+ }
34
+ export async function reviewPreferenceGuidance(root) {
35
+ const entries = (await readPreferences(root)).slice(-20);
36
+ if (entries.length === 0)
37
+ return 'No prior reviewer preferences have been recorded.';
38
+ return entries.map((entry) => `- ${entry.kind} on ${entry.paths.join(', ') || 'the proposal'}: ${entry.instruction}`).join('\n');
39
+ }
40
+ async function readPreferences(root) {
41
+ const path = join(root, REVIEW_PREFERENCES_FILE);
42
+ if (!(await pathExists(path)))
43
+ return [];
44
+ try {
45
+ const value = JSON.parse(await readFile(path, 'utf8'));
46
+ return value.schemaVersion === 1 && Array.isArray(value.entries) ? value.entries.filter(validPreference) : [];
47
+ }
48
+ catch {
49
+ return [];
50
+ }
51
+ }
52
+ function validPreference(value) {
53
+ return !!value && ['revision', 'rejection', 'inline-edit', 'edit'].includes(value.kind) && Array.isArray(value.paths) && value.paths.every((item) => typeof item === 'string') && typeof value.instruction === 'string' && value.instruction.trim() !== '';
54
+ }
55
+ function redactPreference(value) {
56
+ return value
57
+ .replace(/-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g, '[REDACTED PRIVATE KEY]')
58
+ .replace(/\b(?:gh[pousr]_[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16})\b/g, '[REDACTED CREDENTIAL]');
59
+ }
60
+ //# sourceMappingURL=review-learning.js.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Builds the rendered before/after comparison shown inside the review center.
3
+ *
4
+ * Both versions of a page are rendered with the same engine the published site
5
+ * uses, then lined up block by block so a writer can read the proposal as a
6
+ * finished page instead of as a patch.
7
+ */
8
+ export interface RenderedSide {
9
+ title: string;
10
+ description: string;
11
+ body: string;
12
+ exists: boolean;
13
+ }
14
+ export interface RenderedDiffOptions {
15
+ before: RenderedSide;
16
+ after: RenderedSide;
17
+ }
18
+ export interface RenderedDiff {
19
+ html: string;
20
+ added: number;
21
+ removed: number;
22
+ changed: number;
23
+ }
24
+ export declare function renderedDiff(options: RenderedDiffOptions): RenderedDiff;
25
+ export interface ComparisonDocument {
26
+ diff: RenderedDiff;
27
+ /** Layout and density are owned by the surrounding screen, not by this frame. */
28
+ layout: 'split' | 'unified';
29
+ onlyChanges: boolean;
30
+ theme: 'light' | 'dark';
31
+ notice?: string;
32
+ }
33
+ /**
34
+ * Document for the comparison frame. It carries no controls of its own — the
35
+ * review screen owns every choice — so the whole frame is documentation.
36
+ */
37
+ export declare function renderedDiffDocument(input: ComparisonDocument): string;
38
+ //# sourceMappingURL=review-render.d.ts.map