From 9af617f38cc128020ab6e33d7c3a217bdbfb135e Mon Sep 17 00:00:00 2001 From: mpstaton Date: Sat, 9 May 2026 22:23:14 -0500 Subject: [PATCH] feat(source-profile): handle google_books_url for book-type sources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- src/docs/templates/source-profile.md | 12 +++++++++++- src/services/directoryTemplateService.ts | 14 +++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/docs/templates/source-profile.md b/src/docs/templates/source-profile.md index 93690aa..73db226 100644 --- a/src/docs/templates/source-profile.md +++ b/src/docs/templates/source-profile.md @@ -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 diff --git a/src/services/directoryTemplateService.ts b/src/services/directoryTemplateService.ts index d047faf..6affd47 100644 --- a/src/services/directoryTemplateService.ts +++ b/src/services/directoryTemplateService.ts @@ -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 ?"). 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) => { fm['cf_last_run'] = runTimestamp; fm['cf_last_run_model'] = runModelLabel; + if (harvestedGoogleBooksUrl && !fm['google_books_url']) { + fm['google_books_url'] = harvestedGoogleBooksUrl; + } }); if (!quiet) {