Files
stack/packages/library
jason.woltje 29ce1e3602 row 231: C2-01b library reader and survey fixes: control and bidi escapes, front matter list links, an unresolved body embed to a non-.md target is an attachment (#1733)
Lands Gosalyn's round 2 candidate, f37a65857 on gosalyn/c2-01b-r2 (base fefd54e13; commits 3647a5a93,
88daaac50, f37a65857), approved by Webby at merge scope (webby-0072, REVIEW-r2 efd6c1f02 on webby/reviews,
#1733 comment 30005, rev 2787). Nine paths, manifest 312c16fc4f65. No 231 path changed between fefd54e13
and this commit's parent. Patch-id 59b98b1b3e00, the same as fefd54e13..f37a65857.

Evidence: Gosalyn's singles on my slot, 61/0 on r2 (gosalyn-0099; run 1 60/1 was a test order defect).
Gate on my slot, 10:05:31-10:07:52Z on 62ddc96cc plus the nine paths: library 45/0, and every scripts/test-*.sh
green (auth 15, conductor 17, config 31, discord 78, extension-package 18, foundation 44, land-work 86,
queue 156 and 28, release 14, task 98, all 0 failed).
Webby's N4 (README:88 "A link to ... an attachment is R4" next to the embed paragraph) is doc-only and
stays as restored (sage-1917).
2026-10-21 05:09:23 -05:00
..

packages/library

Reads a writing repo by a mapping of folders to books, types, canon and playground. This is Case 2 row C2-01 (docs/plans/2026-10-20_case2-rows.md), built from design sections 1.1 to 1.3 and 7.4 in agents/gosalyn/work/case2/DESIGN.md on gosalyn/case2-design. It is pure code with no state. Business file loading (C2-04), sync (C2-03a), the index (C2-05a) and the guard (C2-06) build on it.

Mapping

checkMapping(entry, where, { fail }) checks a kind: "library" project entry and returns a deep-frozen copy. where names the entry in messages. fail(code, message) defaults to throwing a LibraryError. C2-04 passes its own so a business file refusal carries the file name.

{
  "kind": "library",
  "remote": "https://git.example.invalid/her/books.git",
  "branch": "main",
  "vaultPrefix": "books",
  "types": { "Characters": "character", "Places": "place", "Research": "research" },
  "manuscript": ["Chapters"],
  "universes": {
    "harbor": { "name": "Harbor", "books": ["Book One"], "playground": ["Book One/Playground/"] }
  }
}
  • remote is https with no credentials, query or fragment. branch is a plain ref name.
  • vaultPrefix is optional, one folder name.
  • types maps a folder name to character, place, research, tracker or summary. manuscript lists folder names and shares none with types.
  • A universe has name, books and playground. A hybrid adds kind: "hybrid", two or more non-hybrid sources, constraints (each one { "exclude": ... } or { "pin": { "entity", "field", "value" } }) and notes.

Refusals:

Code When
library-playground-outside-book a playground prefix is not strictly inside a book of its own universe
library-mapping-invalid any other shape fault: unknown key, bad remote or branch, a book folder in two universes, a book folder that holds another, a playground prefix that holds another, an unknown hybrid source

Scope

classify(mapping, path) decides scope from the path alone. Only paths ending in lowercase .md are mapped. A path under a playground prefix is playground. Any other path under a book folder is canon. Everything else is unmapped. The type is the first folder after the book, or after the playground prefix: a types name, manuscript, or null.

Reader

readLibrary({ repo, commit, mapping }) reads one commit, never a working tree. repo may be a clone or a bare mirror. commit must be a full 40-hex id that names a commit, else library-commit. Git runs through git() and gitBytes() from packages/project/src/git.mjs, so the same safe config, environment and ceiling directory apply. A failed git call refuses library-git.

It returns { commit, files, problems }, deep-frozen and sorted. Each file has its path, scope, universe, book, type, numbered lines, bodyLine, parsed frontmatter or null, frontmatterRaw and links. Front matter appears_in and relationships wikilinks count as links. A link in a block list carries its list item's line. One in a scalar or flow value carries its key's line.

Problems carry { code, path, line, message }:

Rule Meaning File indexed
R1 not a regular 100644 blob of at most 1 MiB, or not UTF-8, or holds a NUL no
R2 path is unmapped no
R3 front matter not fully parsed, line is where parsing stopped yes, front matter kept raw
R4 link resolves to no file or to more than one yes, link dropped
R5 canon file links into the playground yes
R6 canon file links into another universe, a hybrid's sources excepted yes
R7 two paths differ only in case no, neither

The front matter parser takes scalars, quoted strings, flow lists, block lists and wikilinks. Nested maps, anchors, block scalars and duplicate keys stop it with R3.

resolver(paths, { vaultPrefix }) tries four steps in order, each with the target as written and then with .md added:

  1. root: the path from the repo root.
  2. vault-prefix: the same path with vaultPrefix/ stripped.
  3. relative: relative to the linking file, never above the repo root.
  4. shortest: a target with no / that matches the base name of exactly one indexed file.

It resolves only against indexed files. A link to a .canvas file or an attachment is R4. A link into another repo resolves to nothing. extractLinks skips fenced and inline code.

A ![[...]] embed resolves like any link. A body embed that resolves to nothing and whose target ends in an extension other than .md, in any case, embeds an attachment, and it is never R4. An extension is a dot and up to 8 letters and digits, at least one a letter. Any other unresolved embed is R4, and so is an unresolved front matter value written as an embed (C2-01b N3, sage-1900).

renderItems

renderItems(body, { scopeOf, changed }) turns a reply body into the plain text both channels show. It is pure. Item kinds are fact, finding, correction (with from and to), question and idea. Each cite is a path with commit, line range and quote, or a url with a quote.

1. Correction: Chapter 1 says grey.
   From: "green eyes"
   To: "grey eyes"
   [canon] Book One/Characters/Mara Vell.md:9 @bbbbbbb "green eyes" (changed since)

A body of { "items": [], "notice": ... } gives No cited answer, No answer or The turn failed. Any other shape gives the fixed line This reply could not be shown. and none of the body's text.

Survey

mosaic library survey <repo> [--mapping <file>]

It reads the repo's HEAD commit and prints folder names, file counts by extension, front matter key sets, link-form counts and the count of body embeds whose target has an extension other than .md, which still count as embeds. That count goes by the name alone, so an embed of Dr.Quinn.md written ![[Dr.Quinn]] counts there too. Folder names print as JSON strings, with DEL, C1 and bidi controls, U+061C among them, also as \uXXXX. It never prints file text, front matter values, file names or link targets. A key that is not a short identifier prints as (other key), since it could be her words. With --mapping it adds scope counts, resolver step counts and problem counts by rule.

Exit codes follow packages/project: 0 done, 2 the repo can't be read (library-git, library-commit), 3 a bad mapping, 4 usage. scripts/mosaic sends library survey here. Other library verbs will be bus verbs.

Tests

node --test packages/library/tests/

Fixtures are made-up repos built per test in a temp dir. A marker string stands in for her text, and the survey tests check that it never appears in output.