T-PP4I-books-consolidation-p1-content
Status: open/ready · Impact: high · Complexity: large
Book content lives inside one app (learn_web/data/) while the desktop
app that distributes books has none — an untracked symlink is currently
load-bearing — and every consumer hardcodes its content path. Create the
solutions/augmented_learning/ solution’s content/ tier, move both corpora there,
and introduce one path resolver so consumers stop hardcoding. This is
Phase 1 of the books consolidation; T-SRJQ (apps) and T-V57A
(packages) build on it, and its resolver’s walk-up design is what makes
those later moves zero-code-edit.
| Location | Role today |
|---|---|
solutions/augmented/apps/learn_web/data/books/ | Model A corpus (+book.md hierarchy format; 131 tracked files, incl. flat remnants like books/test.json — move as-is, cleanup is backlog) |
solutions/augmented/apps/learn_web/data/books_v2/ | Model B corpus (flat ref-based; 133 tracked files; the actively SERVED one; 5 editor-scratch books ride along — backlog) |
solutions/augmented/apps/learn_web/layers/books/server/utils/books.ts | Hardcodes data/books_v2 joined to process.cwd(); feeds createBookServiceFSv1; 30+ Nitro routes ride the singleton |
solutions/augmented/apps/learn_web/scripts/migrate-v1-to-v2.ts | One-shot A→B converter; hardcodes cwd-relative data/books |
solutions/augmented/apps/learn_web/scripts/migrate-to-hybrid-ids.ts | Same hardcode pattern |
solutions/augmented/packages/ts/hierarchy/tests/v2/integration/learn_web_books.test.ts | describe.skip with a STALE “data was not vendored” comment; its relative path currently resolves — repoint AND attempt un-skip |
solutions/augmented/packages/ts/hierarchy/tests/v2/integration/sqlite_example.test.ts | Same skip + relative reach |
solutions/augmented/apps/augmented-books/src-tauri/src/api.rs#resolve_content_books_path | Multi-candidate search for content/books (manifest-parent / cwd / cwd-parent); finds nothing without the symlink |
solutions/augmented/apps/augmented-books/src-tauri/src/bin/prepare_books.rs | Default input content/books, output assets/catalog |
solutions/augmented/apps/augmented-books/scripts/zip-books.sh | Hardcodes $PROJECT_ROOT/content/books |
solutions/augmented/apps/augmented-books/.gitignore | Does NOT ignore assets/catalog/ (currently untracked-not-ignored) |
lefthook.yml | markdown-fmt pre-commit runs rumdl on staged *.md with exclude solutions/augmented/apps/learn_web/** — the moved corpus at its NEW path would be reflowed unless excluded FIRST |
packages/ts/vault-path/src/index.ts | The resolution-order pattern to mirror (override > env > walk) — do NOT depend on it; wrong anchor markers for books |
docs/planning/decisions/D-0001-project-structure.md | Solutions tier definition; needs the in-place content/ tier amendment |
Proposed
Section titled “Proposed”solutions/augmented_learning/ content/ books/ # Model A corpus (git mv, as-is) books_v2/ # Model B corpus (git mv, as-is) — canonical packages/ts/ books-content/ # NEW @augmented/books-content resolver packageResolver semantics (both languages): explicit override → env
BOOKS_CONTENT_DIR → walk-up from start dirs to the nearest ancestor
containing solutions/augmented_learning/content → error naming the env var. TS
start dirs: opts.from ?? process.cwd(), then the module’s own dir (so
node .output/server/index.mjs from any cwd still lands in-repo). Rust
start dirs: CARGO_MANIFEST_DIR, then current_dir().
@augmented/books-content ships source like vault-path (exports →
./src/index.ts, no build, no deps). API: resolveBooksContentDir(opts?)
and booksCorpusDir("books" | "books_v2", opts?). Rust side is a new
content_path module inside the desktop crate (no shared crate until a
second Rust consumer appears — D-0001 promotion rule).
Approach
Section titled “Approach”- FIRST edit
lefthook.yml: addsolutions/augmented_learning/content/**to the markdown-fmt exclude list (comment: vendored corpus, not the dev rumdl regime). This must be in the working tree before any commit that stages the moved.mdfiles, or the pre-commit hook reflows the corpus. git mv solutions/augmented/apps/learn_web/data/books solutions/augmented_learning/content/booksand…/data/books_v2 → solutions/augmented_learning/content/books_v2. Move as-is (remnants/scratch books are backlog). After committing, assertgit diff origin/main --stat -- 'solutions/augmented_learning/content'shows pure renames (100% similarity).- Create
solutions/augmented_learning/packages/ts/books-content/:package.json(name@augmented/books-content, source-shipped),src/index.ts(API above),src/index.test.ts(override wins; env wins over walk; walk from a nested dir finds the corpus; total miss throws namingBOOKS_CONTENT_DIR), thinmoon.yml(mirrorsolutions/augmented/packages/ts/books/moon.yml, minus build). Root workspaces/moon globs (solutions/*/packages/ts/*,solutions/*/packages/*/*) already cover it — no root edits. - Repoint learn_web: in
layers/books/server/utils/books.tsdelete thebooks_DATA_PATHconstant +getDataPath(), passbooksCorpusDir("books_v2")asdataPath; add"@augmented/books-content": "workspace:*"to learn_web deps. Repoint the two migrate scripts viabooksCorpusDir. - Repoint hierarchy tests to
booksCorpusDir("books")(devDep@augmented/books-content); replace the stale skip comment; attemptdescribe.skip→describe. If the suites fail on corpus drift, keep the skip with the repointed path and an accurate comment — phase stays green either way; record the outcome in the PR. - Desktop app: new
src-tauri/src/content_path.rs(BOOKS_CONTENT_ENVconst +resolve_books_content_dir()), registerpub mod content_path;inlib.rs(the bin reaches it asaugmented_books_lib::content_path). Inapi.rs#resolve_content_books_path, try the resolver first (.join("books")), keep the legacy candidate list as fallback, fold all attempted paths into the error. Inprepare_books.rs, default the input dir to the resolver (explicit argv still wins). Inzip-books.sh, derive fromgit rev-parse --show-toplevelwith aBOOKS_CONTENT_DIRoverride. - Delete the untracked symlink
solutions/augmented/apps/augmented-books/content/books(local-only; note in the PR body so other checkouts do the same). Addassets/catalog/to the app.gitignore. - Docs: rewrite the desktop README’s “content/books is absent” section
(canonical path, resolver order, env var) and its
docs/architecture/project-structure.mdmention. Amend D-0001 in place: solutions gain an optionalcontent/tier — extend the Summary bullet and Decision sentence ({apps/, packages/{ts,rust}, content/}), add acontent/line to the end-state tree, one placement sentence (“corpus data consumed by more than one app of a solution lives atsolutions/<s>/content/, reached via a resolver, never by app-relative hardcode”), and namesolutions/augmented_learningas the second occupant. Thensdlc docs generateand commit any drift. - Regenerate root
bun.lock(bun install); verify withbun install --frozen-lockfile. - Verify (all from repo root unless noted): books-content
bun test- typecheck;
bun run testin books and hierarchy packages; learn_webbun run build, then bootnode solutions/augmented/apps/learn_web/.output/server/index.mjsfrom the REPO ROOT andcurl localhost:3000/api/books/v2(proves walk-up via module dir);cargo check -p augmented_books;cargo run -p augmented_books --bin prepare-books(resolves the new path, writesassets/catalog);zip-books.sh;moon query projects | grep books-content;bun run apps/sdlc/cli/sdlc.ts docs generate --check.
- typecheck;
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
lefthook.yml | modify | markdown-fmt exclude += solutions/augmented_learning/content/** (FIRST, before the move commit) |
solutions/augmented/apps/learn_web/data/books/ | delete | git mv → solutions/augmented_learning/content/books/ |
solutions/augmented/apps/learn_web/data/books_v2/ | delete | git mv → solutions/augmented_learning/content/books_v2/ |
solutions/augmented_learning/content/ | new | Destination of both corpora (pure rename) |
solutions/augmented_learning/packages/ts/books-content/ | new | @augmented/books-content resolver package + tests + moon.yml |
solutions/augmented/apps/learn_web/layers/books/server/utils/books.ts | modify | Drop hardcode; dataPath: booksCorpusDir("books_v2") |
solutions/augmented/apps/learn_web/package.json | modify | dep @augmented/books-content: workspace:* |
solutions/augmented/apps/learn_web/scripts/migrate-v1-to-v2.ts | modify | Repoint via resolver |
solutions/augmented/apps/learn_web/scripts/migrate-to-hybrid-ids.ts | modify | Repoint via resolver |
solutions/augmented/packages/ts/hierarchy/tests/v2/integration/learn_web_books.test.ts | modify | Resolver path; un-skip attempt; fix stale comment |
solutions/augmented/packages/ts/hierarchy/tests/v2/integration/sqlite_example.test.ts | modify | Same |
solutions/augmented/packages/ts/hierarchy/package.json | modify | devDep @augmented/books-content |
solutions/augmented/apps/augmented-books/src-tauri/src/content_path.rs | new | Env + ancestor-walk resolver |
solutions/augmented/apps/augmented-books/src-tauri/src/lib.rs | modify | pub mod content_path; |
solutions/augmented/apps/augmented-books/src-tauri/src/api.rs#resolve_content_books_path | modify | Resolver first, legacy fallback, richer error |
solutions/augmented/apps/augmented-books/src-tauri/src/bin/prepare_books.rs | modify | Default input via resolver |
solutions/augmented/apps/augmented-books/scripts/zip-books.sh | modify | repo-root + BOOKS_CONTENT_DIR override |
solutions/augmented/apps/augmented-books/.gitignore | modify | += assets/catalog/ |
solutions/augmented/apps/augmented-books/README.md | modify | Content section rewrite |
solutions/augmented/apps/augmented-books/docs/architecture/project-structure.md | modify | Path update |
docs/planning/decisions/D-0001-project-structure.md | modify | In-place content/ tier amendment |
docs/index.md | modify | Regenerated (docs generate) |
bun.lock | modify | New workspace member + dep edits |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1: Both corpora live under
solutions/augmented_learning/content/andgit diff origin/main --stat -- 'solutions/augmented_learning/content'shows pure renames (no content-modified.md— the lefthook exclude held). - AC-2:
bun testpasses insolutions/augmented_learning/packages/ts/books-content/covering override / env / walk-up / error cases. - AC-3: learn_web
bun run buildsucceeds and the prod server booted from the REPO ROOT answers/api/books/v2. - AC-4:
cargo run -p augmented_books --bin prepare-booksresolves the corpus at the new path with no symlink present and writesassets/catalog. - AC-5:
bun run testpasses in the hierarchy package; the integration suites are un-skipped, or the PR records why the skip stays. - AC-6: No hardcoded
data/books,data/books_v2, or app-relativecontent/booksstring remains in learn_web, hierarchy tests, or the desktop crate outsidecontent_path.rs’s legacy fallback (grep-proof in PR). - AC-7: D-0001 names the
content/tier andsolutions/augmented_learning;docs generate --checkpasses. - AC-8:
bun install --frozen-lockfilepasses at root.
Out of scope
Section titled “Out of scope”- Moving the apps (
T-SRJQ) or packages (T-V57A). - Corpus cleanup (scratch books, flat remnants) and format convergence — captured as backlog.
- Bundling content into the desktop app’s build (
tauri.conf.jsonships no resources today;BOOKS_CONTENT_DIRis the bundled escape hatch).
Dependencies
Section titled “Dependencies”- none — first phase; PR #1122 (the conflicting rename) was closed as superseded 2026-08-01.
Discovery context
Section titled “Discovery context”- Owner decision 2026-08-01: consolidate the book ecosystem under
solutions/augmented_learning/; reading-log notes in the dormantsksizer/augmentedrepo are explicitly excluded (different kind of thing). Plan file:~/.claude/plans/twinkly-meandering-rabbit.md.