feat(source-profile): handle google_books_url for book-type sources

For Sources/ entries the model identifies as books:

1. Template: if frontmatter already has google_books_url, treat as
   authoritative and use it as the primary "Where it lives" link.
   Otherwise, search Google Books and find the canonical URL.

2. Post-processor in directoryTemplateService: harvest the first
   books.google.com or google.com/books/edition URL from generated
   body and stamp it to frontmatter as google_books_url. Only stamps
   when frontmatter doesn't already carry the field — user-curated
   URLs are never overwritten.

This makes the URL findable in body content immediately after a run,
and self-canonicalizes into frontmatter so subsequent runs skip the
search. Same pattern as cf_last_run / cf_last_run_model: frontmatter
is the canonical home for canonical IDs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
mpstaton 2026-05-09 22:23:14 -05:00
parent c73cab6410
commit 9af617f38c
2 changed files with 24 additions and 2 deletions

View file

@ -54,6 +54,16 @@ # About this template
- Book → emphasize author, publisher, year, thesis, key chapters /
arguments. The "Catalog" section lists chapters or major arguments.
GOOGLE BOOKS RULE: If the frontmatter above contains a
`google_books_url` field, treat that URL as authoritative and use
it as the primary "Where it lives" link. If the frontmatter does
NOT contain `google_books_url`, search Google Books for the book's
title (and author, when ambiguous) and find the canonical URL —
the URL takes the form `https://books.google.com/books?id=XXXX`
or `https://www.google.com/books/edition/.../XXXX`. Include the
canonical URL as a `[Google Books](url)` link in "Where it lives".
A post-processor will harvest the URL into frontmatter so future
runs skip the search.
- Person → emphasize bio, affiliation, body of work. The "Catalog"
section lists their books / papers / talks / signature ideas.
- Influencer / YouTuber / podcaster → emphasize host, channel URL,
@ -111,7 +121,7 @@ # Type and Format
- Journal: publisher, peer-review status, open-access status.
- Report: issuing org, frequency (annual / one-off), pages, where to download.
- Event: cadence, in-person / virtual / hybrid, host org, founded year.
- **Where it lives:** a single `[Homepage](url)`-style markdown link to the source's primary surface. Add a second link to a secondary surface if relevant (e.g., for a podcaster: their show feed AND their newsletter).
- **Where it lives:** a `[Homepage](url)`-style markdown link to the source's primary surface. Add a secondary link if relevant (e.g., for a podcaster: their show feed AND their newsletter). For books specifically: include a `[Google Books](url)` link — use `google_books_url` from frontmatter if present, otherwise search Google Books for the canonical URL (form: `https://books.google.com/books?id=XXXX` or `https://www.google.com/books/edition/...`).
# The People Behind It

View file

@ -570,13 +570,25 @@ export async function applyTemplate(
const finalContent = `${initialContent}${cleanedStreamed}\n${fallbackImagesSection}${sourcesFooter}`;
await app.vault.modify(target, finalContent);
// Harvest a Google Books URL from generated body if the model surfaced
// one — books are the only source type with a universal canonical-URL
// system worth stamping back into frontmatter. First match wins; we
// only stamp when frontmatter doesn't already carry the field so a
// user-curated URL is never overwritten.
const googleBooksRe = /https:\/\/(?:www\.)?(?:books\.google\.com\/books\?id=[\w-]+|google\.com\/books\/edition\/[^\s)]+)/;
const googleBooksMatch = cleanedStreamed.match(googleBooksRe);
const harvestedGoogleBooksUrl = googleBooksMatch?.[0];
// Stamp run metadata in the target's frontmatter so files can be
// queried for staleness ("which Tooling/ entries were last refreshed
// before <date>?"). Uses fileManager.processFrontMatter so other
// frontmatter keys remain byte-identical apart from these two.
// frontmatter keys remain byte-identical apart from these stamps.
await app.fileManager.processFrontMatter(target, (fm: Record<string, unknown>) => {
fm['cf_last_run'] = runTimestamp;
fm['cf_last_run_model'] = runModelLabel;
if (harvestedGoogleBooksUrl && !fm['google_books_url']) {
fm['google_books_url'] = harvestedGoogleBooksUrl;
}
});
if (!quiet) {