refactor(pull): extract tested vault-layout module; harden pull; close review findings

Address the Increment-1 code review (3 warnings + suggestions).

- layout: new pure src/layout.ts (buildVaultLayout) — page-tree -> vault paths,
  sibling + full-path collision disambiguation (sanitized ~slugId suffix), parent
  cycle guard; pull.ts is now a thin I/O loop
- layout: resolve orphan/root collisions at the NAME stage so an orphan ancestor
  can't desync its children's folder segments (fixes review Major); covered by test
- pull: per-page try/catch (one bad page no longer aborts the mirror), bounded
  concurrency (6), progress logging, process.exitCode=1 on partial mirror
- security: filename disambiguation suffix now passes through sanitizeTitle
- docs: AGENTS.md -> Increment 1 status/structure/run targets; pull.ts meta-block
  comment; collectRecentSince JSDoc (lexicographic UTC-ISO precondition)
- tests: layout (9), markdown-document round-trip (no comments block, SPEC §3),
  firstDivergence; export firstDivergence. 49 tests green.
This commit is contained in:
vvzvlad
2026-06-16 21:09:40 +03:00
parent 447d2508ae
commit c6edd73324
8 changed files with 511 additions and 87 deletions

View File

@@ -2704,6 +2704,11 @@ export class DocmostClient {
* collecting items strictly newer than sinceIso and stopping at the first item
* with updatedAt <= sinceIso. `fetchPage(cursor)` returns one page; dedup by id
* guards a server that ignores the cursor; hardPageCap bounds the walk.
*
* Precondition: `sinceIso` and each `item.updatedAt` MUST be the SAME UTC
* ISO-8601 format that Docmost emits, because the cutoff comparison is purely
* lexicographic (string `<=`); mixed formats or non-UTC offsets would compare
* incorrectly.
*/
export async function collectRecentSince(
fetchPage: (cursor: string | null) => Promise<{ items: any[]; nextCursor: string | null }>,