pipeline: autonomous operation — shortlist stage, auto modes, daily-pipeline orchestrator
- daily-finds: feed-first fetch strategy (fetch: feed + feedUrl), cadence-aware scanning, new score-and-shortlist stage (shortlist: true, up to 12/day, top 1-3 flagged as review candidates), Amazon affiliate lookup for shortlisted physical products, and authority to apply mechanical sources.json maintenance (skip dead sources, demote dormant ones, record working fetch paths) instead of logging recommendations nobody executes. - build-bundle: auto mode (no confirmation gate when unattended) and a 'weekly' date-based catch mode so shortlisted finds never sit invisible for weeks. - cluster-bundles: auto mode. - build-review: hero.ts registration step removed (heroes auto-discovered from hero.<ext>); mandatory fact-check against the product's own site in auto mode; amazonLink carry-through; auto mode publishes and logs instead of pausing twice for approval. - New daily-pipeline skill: capture → bundle → reviews → pick of the day → build → commit → push → digest, with granular per-stage commits. This is the entrypoint for scheduled runs. - Project CLAUDE.md documenting the pipeline and its foot-gun conventions.
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: build-bundle
|
name: build-bundle
|
||||||
description: Draft a 'bundle' (themed editorial roundup of finds) for unique.rzen.dev. Selects find items from src/content/find/, groups them under a theme, and writes a new file at src/content/bundles/. Use when the user runs /build-bundle, asks to "draft today's bundle", "build a roundup", "make a bundle", or "make a catch".
|
description: Draft a 'bundle' (themed editorial roundup of finds) for unique.rzen.dev. Selects find items from src/content/find/, groups them under a theme, and writes a new file at src/content/bundles/. Supports 'auto' (unattended, no confirmation) and 'weekly' (date-based catch of unbundled shortlist finds) modes. Use when the user runs /build-bundle, asks to "draft today's bundle", "build a roundup", "make a bundle", or "make a catch".
|
||||||
disable-model-invocation: true
|
disable-model-invocation: true
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -10,6 +10,12 @@ Author one editorial bundle (collection: `bundles`) that references existing fin
|
|||||||
|
|
||||||
Invoke from inside the `unique.rzen.dev` repo.
|
Invoke from inside the `unique.rzen.dev` repo.
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
- **Interactive (default when a user is present)**: propose the cluster, wait for sign-off (step 2), then write.
|
||||||
|
- **Autonomous (`auto` argument, or when running unattended — e.g. invoked by `/daily-pipeline`, a scheduled run, or any headless session)**: skip the confirmation gate. Apply the same quality bar, write directly, and record the would-have-been proposal in the log so the user can veto after the fact. Prefer the *tighter* theme when torn — an unattended run should err conservative.
|
||||||
|
- **Weekly catch (`weekly` argument)**: a date-based safety valve, not a themed bundle. Collect every `shortlist: true` find from the last 7 days that appears in **no** existing bundle's `items[]`; if fewer than 3, widen to all unbundled finds from the window. Title: "The week's catch — <Month D–D>". Blurb names the week, not a theme. Cap at 8 by shortlist rank. If nothing qualifies, do nothing and report that. Purpose: no shortlisted find stays invisible for weeks awaiting a theme (finds are only reachable through bundles).
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
|
|
||||||
- `src/content/find/<year>/<month>/<slug>/index.mdx` — the available pool of finds (folders are nested by date but slugs are globally unique; recurse with `find src/content/find -name index.mdx` or rely on the `find` content collection's id which is the leaf slug).
|
- `src/content/find/<year>/<month>/<slug>/index.mdx` — the available pool of finds (folders are nested by date but slugs are globally unique; recurse with `find src/content/find -name index.mdx` or rely on the `find` content collection's id which is the leaf slug).
|
||||||
@@ -31,13 +37,13 @@ Invoke from inside the `unique.rzen.dev` repo.
|
|||||||
|
|
||||||
- **If the user provided a theme phrase only:** scan all finds, surface the 8–12 that best fit the theme.
|
- **If the user provided a theme phrase only:** scan all finds, surface the 8–12 that best fit the theme.
|
||||||
|
|
||||||
- **If neither:** scan recent (last ~14 days) finds that are not yet referenced by any existing bundle. From the unbundled pool, propose 1–3 thematic clusters of 5–10 finds each, with a candidate theme phrase per cluster.
|
- **If neither:** scan recent (last ~14 days) finds that are not yet referenced by any existing bundle, weighting `shortlist: true` finds first. From the unbundled pool, propose 1–3 thematic clusters of 5–10 finds each, with a candidate theme phrase per cluster. (In autonomous mode: pick the single strongest cluster and write it.)
|
||||||
|
|
||||||
Re-use is allowed — a find can appear in multiple bundles under different themes. But by default, prefer surfacing **unbundled** finds first; only reach for re-uses when the theme genuinely calls for them.
|
Re-use is allowed — a find can appear in multiple bundles under different themes. But by default, prefer surfacing **unbundled** finds first; only reach for re-uses when the theme genuinely calls for them.
|
||||||
|
|
||||||
### 2. Confirm the cluster
|
### 2. Confirm the cluster
|
||||||
|
|
||||||
Always show the proposed cluster(s) before writing anything. The exact format depends on which path step 1 took.
|
**Autonomous mode skips this step** — apply the bar yourself, write, and put the cluster rationale in the log entry instead. Otherwise, show the proposed cluster(s) before writing anything. The exact format depends on which path step 1 took.
|
||||||
|
|
||||||
**Seed mode** (slugs given) — split the seed from the expansion so the user can see what the inferred theme pulled in:
|
**Seed mode** (slugs given) — split the seed from the expansion so the user can see what the inferred theme pulled in:
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: build-review
|
name: build-review
|
||||||
description: Graduate a find item to a full review for unique.rzen.dev. Reads src/content/find/<year>/<month>/<slug>/index.mdx, expands the body into a full review, writes src/content/reviews/<slug>/index.mdx, and links the two via fromFind / promotedTo. Use when the user runs /build-review <slug>, says "review this find", or "graduate <slug>".
|
description: Graduate a find item to a full review for unique.rzen.dev. Reads src/content/find/<year>/<month>/<slug>/index.mdx, fact-checks against the product's own site, expands the body into a full review, writes src/content/reviews/<slug>/index.mdx, and links the two via fromFind / promotedTo. Supports an 'auto' unattended mode. Use when the user runs /build-review <slug>, says "review this find", or "graduate <slug>".
|
||||||
disable-model-invocation: true
|
disable-model-invocation: true
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -10,6 +10,11 @@ Convert one captured `find` into a full `review`. Both records persist — the f
|
|||||||
|
|
||||||
Invoke from inside the `unique.rzen.dev` repo with a find slug as the argument.
|
Invoke from inside the `unique.rzen.dev` repo with a find slug as the argument.
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
- **Interactive (default when a user is present)**: propose the hero asset and show the drafted body before writing (steps 3 and 4 pause for approval).
|
||||||
|
- **Autonomous (`auto` argument, or when running unattended — invoked by `/daily-pipeline`, a scheduled run, or any headless session)**: no pauses. Download the hero, verify, write, publish. The fact-check step (step 3) is **mandatory** in this mode — it is the substitute for the human eye. Record the editorial decisions (hero choice, category, corrections made) in the log entry so the user can review after the fact.
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
|
|
||||||
- `src/content/find/<year>/<month>/<slug>/index.mdx` — the source find (must exist; abort otherwise). The slug is unique across the whole `find/` tree, so locate it via `find src/content/find -type d -name "<slug>"` if you don't already know the year/month.
|
- `src/content/find/<year>/<month>/<slug>/index.mdx` — the source find (must exist; abort otherwise). The slug is unique across the whole `find/` tree, so locate it via `find src/content/find -type d -name "<slug>"` if you don't already know the year/month.
|
||||||
@@ -21,31 +26,46 @@ Note on hero assets: the find's hero, when present, sits at `src/content/find/<y
|
|||||||
|
|
||||||
### 1. Read the find
|
### 1. Read the find
|
||||||
|
|
||||||
Parse frontmatter. Note: `name`, `subtitle`, `link`, `linkText`, `source`, `topics`, `tags`, plus the body.
|
Parse frontmatter. Note: `name`, `subtitle`, `link`, `linkText`, `amazonLink`, `source`, `topics`, `tags`, plus the body.
|
||||||
|
|
||||||
If `promotedTo` is already set, abort: "This find has already been promoted to <slug>."
|
If `promotedTo` is already set, abort: "This find has already been promoted to <slug>."
|
||||||
|
|
||||||
### 2. Suggest a hero asset
|
### 2. Pick a hero asset
|
||||||
|
|
||||||
Look at the external link and propose a hero asset — a screenshot, demo gif, or product photo from the maker's site.
|
Look at the external link and pick a hero — a screenshot, demo gif, or product photo from the maker's site. The find's own `hero.<ext>` is a valid fallback, but prefer a richer asset from the source when one exists (finds often carry low-res OG cards).
|
||||||
|
|
||||||
- Mac apps: an App Store screenshot or hero image from the developer's site.
|
- Mac apps: an App Store screenshot or hero image from the developer's site.
|
||||||
- Physical products: the maker's product photo.
|
- Physical products: the maker's product photo.
|
||||||
- Travel destinations: a Wikipedia or Wikimedia Commons photo (free-to-use only).
|
- Travel destinations: a Wikipedia or Wikimedia Commons photo (free-to-use only).
|
||||||
|
|
||||||
Tell the user what you'd download and where to place it (`src/content/reviews/<slug>/hero.<ext>` — colocated with the review's `index.mdx`). Pause for approval before downloading.
|
Save it at `src/content/reviews/<slug>/hero.<ext>`. **Name it exactly `hero.<ext>`** — review heroes are auto-discovered by `src/lib/hero.ts` from that filename (images and `hero.mp4` both); no registration step exists anymore. Heavy originals go in `src/content/reviews/<slug>/source/` (never bundled).
|
||||||
|
|
||||||
### 3. Expand the body
|
Interactive mode: state what you'd download and pause for approval first. Autonomous mode: download it.
|
||||||
|
|
||||||
|
If the review legitimately has no usable hero (rare — text-only subjects), note it explicitly in the log entry ("no hero asset — text-only review") so the omission is intentional. The grid tile will render without media.
|
||||||
|
|
||||||
|
### 3. Fact-check the find (mandatory in autonomous mode)
|
||||||
|
|
||||||
|
Finds are 2-3 sentences written from a source's secondhand description — they routinely undersell or misdescribe (a "terminal with a sidebar" turned out to be a full Git-tooling workspace; an "AI agent" was specifically Claude Code). Before expanding:
|
||||||
|
|
||||||
|
- Fetch the product's **own** site / App Store page / repo README — not just the source that surfaced it.
|
||||||
|
- Verify: what it actually is and does, current availability and price model, platform requirements, licensing, and whether the find's claimed virtue is real.
|
||||||
|
- If the find got something wrong or undersold it, the review states the corrected picture (calling out the correction when it's interesting, as the Muxy review did). If the product turns out to fail the site's bar entirely (vaporware, abandoned, misrepresented), **abort and report** instead of publishing — in autonomous mode, log it as a rejected graduation.
|
||||||
|
- If the find has no `amazonLink` and the item is a physical product, check Amazon for an exact match while you're at it (same maker + model only; canonical `/dp/<ASIN>` form). Carry any confident result into the review frontmatter.
|
||||||
|
|
||||||
|
### 4. Expand the body
|
||||||
|
|
||||||
Take the find's 2-3 sentence body and expand it to a full review:
|
Take the find's 2-3 sentence body and expand it to a full review:
|
||||||
|
|
||||||
- Lead with a `>` blockquote summary (1-2 sentences) — this renders as the accent-bordered lead per the existing review style.
|
- Lead with a `>` blockquote summary (1-2 sentences) — this renders as the accent-bordered lead per the existing review style.
|
||||||
- 3-5 paragraphs covering: what it is, the specific problem it solves, the concrete details that make it earn its place, and any honest limitations.
|
- 3-5 paragraphs covering: what it is, the specific problem it solves, the concrete details that make it earn its place, and any honest limitations (surfaced by step 3).
|
||||||
- Reference the hero asset inline if downloaded (``).
|
- Reference the hero asset inline where it helps (relative `./hero.<ext>`; use `astro:assets` `<Image>` for JPG sources — plain Markdown images have rendered small on JPGs).
|
||||||
- Don't pad. If the find body is already complete, the review is just it + a blockquote summary + maybe one paragraph on context.
|
- Don't pad. If the find body is already complete, the review is just it + a blockquote summary + maybe one paragraph on context.
|
||||||
- Match the voice of existing reviews under `src/content/reviews/` — informative, concise, no sales tone.
|
- Match the voice of existing reviews under `src/content/reviews/` — informative, concise, no sales tone.
|
||||||
|
|
||||||
### 4. Write the review
|
Interactive mode: show the proposed body and wait. Autonomous mode: proceed.
|
||||||
|
|
||||||
|
### 5. Write the review
|
||||||
|
|
||||||
File: `src/content/reviews/<slug>/index.mdx`.
|
File: `src/content/reviews/<slug>/index.mdx`.
|
||||||
|
|
||||||
@@ -58,8 +78,9 @@ subtitle: "<from the find — required for reviews>"
|
|||||||
date: YYYY-MM-DD # today (the review date), not the find's date
|
date: YYYY-MM-DD # today (the review date), not the find's date
|
||||||
category: <pick from existing categories or propose a new one>
|
category: <pick from existing categories or propose a new one>
|
||||||
tags: <merge the find's tags with any review-specific additions>
|
tags: <merge the find's tags with any review-specific additions>
|
||||||
link: "<from the find>"
|
link: "<from the find — or a better canonical URL surfaced by step 3>"
|
||||||
linkText: "<from the find, if set>"
|
linkText: "<from the find, if set>"
|
||||||
|
amazonLink: "<from the find or step 3, physical products only — omit otherwise>"
|
||||||
description: "<short meta description>"
|
description: "<short meta description>"
|
||||||
source: "<from the find>"
|
source: "<from the find>"
|
||||||
fromFind: "<find slug>"
|
fromFind: "<find slug>"
|
||||||
@@ -68,38 +89,19 @@ fromFind: "<find slug>"
|
|||||||
|
|
||||||
If the find had no `subtitle`, generate one — it's required on reviews.
|
If the find had no `subtitle`, generate one — it's required on reviews.
|
||||||
|
|
||||||
### 5. Update the find
|
### 6. Update the find
|
||||||
|
|
||||||
Edit `src/content/find/<year>/<month>/<slug>/index.mdx` frontmatter: add `promotedTo: <review-slug>`.
|
Edit `src/content/find/<year>/<month>/<slug>/index.mdx` frontmatter: add `promotedTo: <review-slug>`.
|
||||||
|
|
||||||
(Slug usually matches the find's slug, so `promotedTo` and the find's filename match.)
|
(Slug usually matches the find's slug, so `promotedTo` and the find's filename match.)
|
||||||
|
|
||||||
### 6. Register the hero in `src/lib/hero.ts` (REQUIRED if a hero asset exists)
|
|
||||||
|
|
||||||
This is the step that has bitten us repeatedly. **The home grid and category cards do NOT auto-discover review hero assets** — they only show a hero if the review's slug is a key in the `heroes` record exported by `src/lib/hero.ts`. Skipping this step ships a review whose grid card is empty.
|
|
||||||
|
|
||||||
For a review with a static image hero (`hero.png`, `hero.jpg`, `hero.webp`):
|
|
||||||
|
|
||||||
1. Add an `import` line near the top, alphabetically grouped with the other image imports. The variable name is the slug in camelCase. Example for slug `tablepro`:
|
|
||||||
```ts
|
|
||||||
import tablepro from '../content/reviews/tablepro/hero.png';
|
|
||||||
```
|
|
||||||
2. Add an entry to the `heroes` record, alphabetically by key:
|
|
||||||
```ts
|
|
||||||
tablepro: { kind: 'image', src: tablepro },
|
|
||||||
```
|
|
||||||
|
|
||||||
For a review with a video hero (`.mp4`), put the import in the second (video) import group and the record entry uses `kind: 'video'`. Match the existing pattern.
|
|
||||||
|
|
||||||
If the review legitimately has no hero asset (rare — e.g. text-only reviews like `snewpapers-newspaper-archive`, `sindre-sorhus-older-mac-apps`), explicitly note it in the log entry from step 8 ("no hero asset — text-only review") so the omission is intentional and traceable.
|
|
||||||
|
|
||||||
### 7. Verify
|
### 7. Verify
|
||||||
|
|
||||||
- Run `npm run build`. Confirm:
|
- Run `npm run build`. Confirm:
|
||||||
- `/reviews/<slug>/` exists, shows the source badge and "← First surfaced…" backlink to the find page.
|
- `/reviews/<slug>/` exists, shows the source badge and "← First surfaced…" backlink to the find page.
|
||||||
- `/find/<slug>/` shows the "→ Read the full review" callout pointing to the review.
|
- `/find/<slug>/` shows the "→ Read the full review" callout pointing to the review.
|
||||||
- Any bundle that references the find still renders fine; the find card on the grid remains.
|
- Any bundle that references the find still renders fine; the find card on the grid remains.
|
||||||
- Visit `/grid/` (or `/`) and confirm the new review's tile renders with its hero — not blank. **If the tile is empty, step 6 was skipped.**
|
- The new review's tile on `/grid/` renders with its hero — if it's blank, the hero file isn't named `hero.<ext>` (or is missing).
|
||||||
|
|
||||||
### 8. Update the log
|
### 8. Update the log
|
||||||
|
|
||||||
@@ -109,11 +111,15 @@ Append to `daily-finds.log.md`:
|
|||||||
## YYYY-MM-DD — review: <slug>
|
## YYYY-MM-DD — review: <slug>
|
||||||
|
|
||||||
Graduated from find captured on <find-date>, source: <source>.
|
Graduated from find captured on <find-date>, source: <source>.
|
||||||
|
Hero: <what was used and where it came from>.
|
||||||
|
Category: <category> (<new or existing>).
|
||||||
|
Fact-check: <corrections made, or "find held up">.
|
||||||
|
Amazon: <amazonLink added / carried / n-a>.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- The find's slug and the review's slug should match. Same slug = same item across the pipeline; cross-references stay simple.
|
- The find's slug and the review's slug should match. Same slug = same item across the pipeline; cross-references stay simple.
|
||||||
- Don't move or delete the find. Both records persist. The find is the historical capture; the review is the long-form treatment.
|
- Don't move or delete the find. Both records persist. The find is the historical capture; the review is the long-form treatment.
|
||||||
- Never auto-write a review without showing the user the proposed body first. This is editorial work; the skill's job is to draft, not to publish unilaterally.
|
- Making the new review the homepage pick is a separate one-line edit to `src/data/pick.json` (`{"hero": "<slug>"}`) — the pipeline or user decides that after graduation; it is not part of this skill.
|
||||||
- If multiple finds describe the same underlying thing, the user should consolidate them manually before running this skill.
|
- If multiple finds describe the same underlying thing, consolidate them before running this skill (autonomous mode: pick the better capture, note the duplicate in the log).
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ When choosing which items to repeat: only repeat a find when the second theme ge
|
|||||||
|
|
||||||
### 4. Confirm with the user
|
### 4. Confirm with the user
|
||||||
|
|
||||||
Before writing any files, show a compact plan:
|
**Autonomous mode** (`auto` argument, or running unattended — e.g. via `/daily-pipeline` or a scheduled/headless session): skip the confirmation; hold yourself to the same bar, write the bundles, and record the full plan (clusters, overlaps, uncovered finds) in the log entry so the user can review and veto afterwards. When torn between a bold theme and a safe one, take the safe one.
|
||||||
|
|
||||||
|
Otherwise, before writing any files, show a compact plan:
|
||||||
|
|
||||||
```
|
```
|
||||||
Cluster A — "<theme>" (N items)
|
Cluster A — "<theme>" (N items)
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
---
|
---
|
||||||
name: daily-finds
|
name: daily-finds
|
||||||
description: Capture-only daily sweep for unique.rzen.dev. Scans every source in sources.json, writes one find file per qualifying item to src/content/find/, and nominates any new sources cited in the items it reads (plus optionally via WebSearch). Does NOT pick a winner, write a bundle, or address the user/audience. Picking happens via /build-bundle and /build-review. Use when the user asks for "today's finds", "morning sweep", "capture", or runs /daily-finds.
|
description: Capture-only daily sweep for unique.rzen.dev. Scans every source in sources.json, writes one find file per qualifying item to src/content/find/, marks the day's shortlist, adds Amazon affiliate links where the product is confidently matched, performs mechanical source maintenance, and nominates new sources. Does NOT write bundles or reviews — that's /build-bundle and /build-review (or /daily-pipeline for the whole chain). Use when the user asks for "today's finds", "morning sweep", "capture", or runs /daily-finds.
|
||||||
disable-model-invocation: true
|
disable-model-invocation: true
|
||||||
---
|
---
|
||||||
|
|
||||||
# daily-finds
|
# daily-finds
|
||||||
|
|
||||||
Pure capture: every item that passes the bar becomes a `find` file. No daily cap, no theme detection, no audience-facing post. Whether any of these finds appears in a `bundle` or graduates to a review is a separate editorial decision.
|
Pure capture plus triage: every item that passes the bar becomes a `find` file, and the run ends by ranking the day's captures into a **shortlist** (up to 12) that drives downstream automation. Whether a find appears in a `bundle` or graduates to a review is still a separate step — but the shortlist is the pipeline's selection signal.
|
||||||
|
|
||||||
|
This skill is designed to run unattended. Never pause for confirmation mid-run; make the calls, log them, and surface anything odd in the end-of-run digest.
|
||||||
|
|
||||||
Invoke from inside the `unique.rzen.dev` repo.
|
Invoke from inside the `unique.rzen.dev` repo.
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
|
|
||||||
- `sources.json` at the project root — curated source list with per-source `fetch` strategy (`webfetch` / `curl` / `skip`) and optional `fetchUrl`.
|
- `sources.json` at the project root — curated source list with per-source fetch strategy (`feed` / `webfetch` / `curl` / `skip`), optional `feedUrl` (RSS/Atom) and `fetchUrl`.
|
||||||
- `daily-finds.log.md` — running log of every find slug ever written (used to skip duplicates).
|
- `daily-finds.log.md` — running log of every find slug ever written (used to skip duplicates).
|
||||||
- `src/content/find/<year>/<month>/<slug>/index.mdx` — already-captured finds (every existing slug is a "do not re-suggest" item; slugs are globally unique across years/months even though folders are nested by date).
|
- `src/content/find/<year>/<month>/<slug>/index.mdx` — already-captured finds (every existing slug is a "do not re-suggest" item; slugs are globally unique across years/months even though folders are nested by date).
|
||||||
- `src/content/reviews/<slug>/index.mdx` — already-reviewed items (do not re-suggest).
|
- `src/content/reviews/<slug>/index.mdx` — already-reviewed items (do not re-suggest).
|
||||||
@@ -22,7 +24,7 @@ Invoke from inside the `unique.rzen.dev` repo.
|
|||||||
|
|
||||||
A find qualifies if and only if it is **(a)** delightful, unusual, or distinctive in a way the audience would care about *and* **(b)** usable daily (relax to "memorable for a visitor" for travel). Drop generic mass-market items, ad-copy listicles, speculative / not-yet-shipping products, self-promo, and anything not traceable to a real currently-buyable / currently-visitable thing.
|
A find qualifies if and only if it is **(a)** delightful, unusual, or distinctive in a way the audience would care about *and* **(b)** usable daily (relax to "memorable for a visitor" for travel). Drop generic mass-market items, ad-copy listicles, speculative / not-yet-shipping products, self-promo, and anything not traceable to a real currently-buyable / currently-visitable thing.
|
||||||
|
|
||||||
Crucially: do **not** filter by "is it the best of the day." Capture broadly. Editorial selection happens later.
|
Crucially: do **not** filter by "is it the best of the day." Capture broadly. Selection happens in the shortlist step, and editorial treatment later.
|
||||||
|
|
||||||
## Procedure
|
## Procedure
|
||||||
|
|
||||||
@@ -30,13 +32,16 @@ Crucially: do **not** filter by "is it the best of the day." Capture broadly. Ed
|
|||||||
|
|
||||||
For each source in `sources.json`:
|
For each source in `sources.json`:
|
||||||
|
|
||||||
- **Default (`fetch: "webfetch"` or no field)**: WebFetch against `fetchUrl` if present, else `url`. Extract recently-featured items (name, link, one-line description).
|
- **`fetch: "feed"` (preferred)**: `curl -sL --max-time 25 -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" "<feedUrl>"` and parse the RSS/Atom entries (title, link, summary, date). Consider only entries newer than the source's last productive run (when in doubt, the last ~30 days). Feeds bypass Cloudflare/HTML breakage — when a `webfetch`/`curl` source keeps failing, look for a feed and switch it over (see step 6).
|
||||||
- **`fetch: "curl"`**: shell out to `curl -sL -A "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Safari/605.1.15" --max-time 25 "<fetchUrl|url>"` and parse the HTML body. For Reddit JSON endpoints, pipe through `python3 -c '…'` to extract titles, links, and selftext. Treat HTTP 403 with a non-empty HTML body as a content response (Atlas Obscura quirk) — only treat empty bodies or Cloudflare challenge markers as blocked.
|
- **`fetch: "webfetch"` or no field**: WebFetch against `fetchUrl` if present, else `url`. Extract recently-featured items (name, link, one-line description).
|
||||||
- **`fetch: "skip"`**: log the source as skipped (with `skipReason`) and move on. Do not retry.
|
- **`fetch: "curl"`**: shell out to curl with the Safari UA above against `fetchUrl|url` and parse the HTML (or JSON — e.g. Shopify `products.json` endpoints). Treat HTTP 403 with a non-empty HTML body as a content response — only treat empty bodies or Cloudflare challenge markers as blocked.
|
||||||
|
- **`fetch: "skip"`**: log the source as skipped (with `skipReason`) and move on. Do not retry (re-probing skipped sources is a step-6 quarterly job, not a per-run one).
|
||||||
|
|
||||||
Parallelize WebFetch in batches of ~8 concurrent requests; finish all sources before proceeding.
|
Respect `cadence`: sources marked `weekly` / `monthly` / `as-needed` only need scanning when their cadence window has elapsed since the last productive scan — check the log; skip (and log as `cadence-skipped`) otherwise. This cuts run cost and rate-limit exposure.
|
||||||
|
|
||||||
For each source record: items extracted, or `empty` / `blocked` / `skipped`.
|
Parallelize fetches in batches of ~8 concurrent requests; finish all sources before proceeding.
|
||||||
|
|
||||||
|
For each source record: items extracted, or `empty` / `blocked` / `skipped` / `cadence-skipped`.
|
||||||
|
|
||||||
### 2. Filter
|
### 2. Filter
|
||||||
|
|
||||||
@@ -49,7 +54,7 @@ For each extracted item, drop:
|
|||||||
|
|
||||||
### 3. Write find files
|
### 3. Write find files
|
||||||
|
|
||||||
For every survivor, write one file at `src/content/find/<year>/<month>/<slug>/index.mdx` where `<year>` and `<month>` come from today's date (e.g. `src/content/find/2026/05/<slug>/index.mdx`). Hero image (when present) sits next to it as `src/content/find/<year>/<month>/<slug>/hero.<ext>`. The leaf folder name is the slug; URLs stay flat at `/find/<slug>/` because the content-collection loader uses a `leafSlug` id derivation.
|
For every survivor, write one file at `src/content/find/<year>/<month>/<slug>/index.mdx` where `<year>` and `<month>` come from today's date. Hero image (when present) sits next to it as `src/content/find/<year>/<month>/<slug>/hero.<ext>`. The leaf folder name is the slug; URLs stay flat at `/find/<slug>/` because the content-collection loader uses a `leafSlug` id derivation.
|
||||||
|
|
||||||
- Slug: kebab-case form of the name (lowercase, ASCII, no leading numbers).
|
- Slug: kebab-case form of the name (lowercase, ASCII, no leading numbers).
|
||||||
- Frontmatter: `name`, `subtitle` (optional, one-liner), `date` (today), `link` (external URL — required), `linkText` (optional), `source` (the source name from sources.json — required, exact match), `topics` (copied from the source's `topics`), `tags` (free editorial tags inferred from the item — keep to 2-4), `description` (optional meta override).
|
- Frontmatter: `name`, `subtitle` (optional, one-liner), `date` (today), `link` (external URL — required), `linkText` (optional), `source` (the source name from sources.json — required, exact match), `topics` (copied from the source's `topics`), `tags` (free editorial tags inferred from the item — keep to 2-4), `description` (optional meta override).
|
||||||
@@ -63,60 +68,99 @@ If `link` for a Reddit-discovered item points at a v.redd.it / i.redd.it URL, fe
|
|||||||
For every find, try to retrieve a single representative image from the linked page so the find has visual support if it later graduates to a review (or gets surfaced on a tile).
|
For every find, try to retrieve a single representative image from the linked page so the find has visual support if it later graduates to a review (or gets surfaced on a tile).
|
||||||
|
|
||||||
- **Where to look (in priority order):** `og:image` meta tag → JSON-LD `image` field → main product photo / hero image markup → first article image.
|
- **Where to look (in priority order):** `og:image` meta tag → JSON-LD `image` field → main product photo / hero image markup → first article image.
|
||||||
- **Where to save:** `src/content/find/<year>/<month>/<slug>/hero.<ext>` — colocated next to `index.mdx`. Preserve the original extension (`.jpg`, `.png`, `.webp`, `.gif`). One file per find — `hero` is the canonical name.
|
- **Where to save:** `src/content/find/<year>/<month>/<slug>/hero.<ext>` — colocated next to `index.mdx`. Preserve the original extension (`.jpg`, `.png`, `.webp`, `.gif`, `.avif`). One file per find — `hero` is the canonical name.
|
||||||
- **What to skip:** images with marketing/sales overlay text (Amazon-style "BEST PRESS / NO GRIT" tags), tiny thumbnails (<400px on the long edge), generic site logos, paywalled CDN URLs that 403 to curl. If the only available shots are marketing collages, skip rather than save a noisy image — a missing hero is fine, a bad one isn't.
|
- **What to skip:** images with marketing/sales overlay text (Amazon-style "BEST PRESS / NO GRIT" tags), tiny thumbnails (<400px on the long edge), generic site logos, paywalled CDN URLs that 403 to curl. If the only available shots are marketing collages, skip rather than save a noisy image — a missing hero is fine, a bad one isn't.
|
||||||
- **For Mac/iOS apps:** prefer the App Store hero or developer-site screenshot. For physical products: prefer a clean studio shot or lifestyle photo without overlay copy. For travel destinations: prefer a Wikimedia Commons / Wikipedia image (free-to-use only). For text-only finds (quotes, dad jokes, essays): no media expected — skip.
|
- **For Mac/iOS apps:** prefer the App Store hero or developer-site screenshot. For physical products: prefer a clean studio shot or lifestyle photo without overlay copy. For travel destinations: prefer a Wikimedia Commons / Wikipedia image (free-to-use only). For text-only finds (quotes, dad jokes, essays): no media expected — skip.
|
||||||
- **Fetch strategy:** `curl -sL --max-time 25 -A "Mozilla/5.0 (...Safari...)" "<image-url>" -o <path>`. WebFetch is for HTML, not binaries.
|
- **Fetch strategy:** `curl -sL --max-time 25 -A "Mozilla/5.0 (...Safari...)" "<image-url>" -o <path>`. WebFetch is for HTML, not binaries.
|
||||||
- **Don't register heroes in `src/lib/hero.ts` at this stage.** That's a review-time decision (see `build-review`). The asset just needs to exist on disk so it's there if/when the find graduates.
|
|
||||||
|
|
||||||
Log image-fetch failures (404, 403, marketing-only) as a one-line note in the capture-run section of `daily-finds.log.md` so we can revisit broken media-source patterns later — same way we log fetch issues for source pages.
|
Log image-fetch failures (404, 403, marketing-only) as a one-line note in the capture-run section of `daily-finds.log.md` so we can revisit broken media-source patterns later.
|
||||||
|
|
||||||
### 4. Nominate new sources
|
### 4. Score and shortlist
|
||||||
|
|
||||||
Watch for new sources organically and via search. No cap on nominees per run — if a day's reading surfaces five plausible new sources, capture all five.
|
After all finds are written, rank the day's captures and mark the shortlist — the up-to-a-dozen finds the rest of the pipeline should act on.
|
||||||
|
|
||||||
- **From inline references in source posts.** When a post on a known source credits or links out to another publication as where they originally discovered the thing, capture that publication as a candidate source. Trigger phrases to watch for: *"via X"*, *"first seen on Y"*, *"spotted by Z"*, *"h/t X"*, *"originally posted on Y"*, *"thanks to X for the find"*, or any byline / sidebar link pointing to a different domain that itself looks like a curated feed. Cool Tools, Tools and Toys, Recomendo, Kottke, and Daring Fireball especially tend to credit their finds — read posts carefully enough to catch these credits.
|
- Score each of today's finds on: **delight** (would the audience light up), **specificity of virtue** (does the body name something concrete), **media** (clean hero present), and **variety** (the shortlist should not be twelve variations of one topic — spread across topics when quality allows).
|
||||||
|
- Set `shortlist: true` in the frontmatter of the top finds. Aim for 6–12 on a normal day; fewer on a thin day. **Never pad** — a 4-item shortlist beats a 12-item one with filler.
|
||||||
|
- Record the shortlist in the log, ranked, with a one-line rationale each. Note the top 1–3 as **review candidates** (strong media + strong story) — `/build-review` (or the pipeline) picks from these.
|
||||||
|
|
||||||
|
#### Amazon affiliate lookup
|
||||||
|
|
||||||
|
For every **shortlisted physical product** (tools, gifts, clothing, kitchen, EDC — not apps, not travel, not quotes/jokes/web pages):
|
||||||
|
|
||||||
|
- Check whether the exact product is sold on Amazon: WebSearch `site:amazon.com "<maker> <product name>"`, or curl the Amazon search page.
|
||||||
|
- Only when the hit is unambiguously the **same product** (same maker, same model — not a lookalike, not a marketplace knockoff), add `amazonLink: https://www.amazon.com/dp/<ASIN>` to the find's frontmatter. Use the canonical `/dp/<ASIN>` form with no query parameters — the site's affiliate layer (`src/lib/affiliate.ts`) appends the tracking ID, badge, and disclosure at render time.
|
||||||
|
- When in doubt, omit. A wrong affiliate link is worse than none. The site's focus is interesting items, not monetizable ones — this is a bonus, not a filter.
|
||||||
|
- Non-shortlisted physical finds may get the same treatment when the match is trivially confident; don't burn time on them otherwise.
|
||||||
|
|
||||||
|
### 5. Nominate new sources
|
||||||
|
|
||||||
|
Watch for new sources organically and via search. No cap on nominees per run.
|
||||||
|
|
||||||
|
- **From inline references in source posts.** When a post on a known source credits or links out to another publication as where they originally discovered the thing, capture that publication as a candidate source. Trigger phrases: *"via X"*, *"first seen on Y"*, *"spotted by Z"*, *"h/t X"*, *"originally posted on Y"*, or any byline / sidebar link pointing to a different domain that itself looks like a curated feed. Cool Tools, Recomendo, Kottke, and Daring Fireball especially tend to credit their finds.
|
||||||
- **From general observation.** When a publication is mentioned in a way that suggests it'd fit the brief (curated, taste-driven, focused on delightful or unusual items), nominate it.
|
- **From general observation.** When a publication is mentioned in a way that suggests it'd fit the brief (curated, taste-driven, focused on delightful or unusual items), nominate it.
|
||||||
- **From targeted search.** If the run produced few or no organic candidates, run one `WebSearch` for `"best <topic> newsletter"` or `"<topic> curator blog"`.
|
- **From targeted search.** If the run produced few or no organic candidates, run one `WebSearch` for `"best <topic> newsletter"` or `"<topic> curator blog"`.
|
||||||
|
|
||||||
Append every nominee to `candidate-sources.md` under today's date heading. For each: name, URL, a one-line case for adding, and how it was surfaced — name the originating source post when applicable (e.g. *"via a Cool Tools post about the Spyderco Dragonfly"*), or *"WebSearch"* for search-derived candidates.
|
Append every nominee to `candidate-sources.md` under today's date heading. For each: name, URL, a one-line case for adding, and how it was surfaced.
|
||||||
|
|
||||||
Don't auto-add to `sources.json` — that's a user decision.
|
Adding new sources to `sources.json` remains a **user decision** — never auto-add.
|
||||||
|
|
||||||
### 5. Update the log
|
### 6. Source maintenance (mechanical edits — apply, don't recommend)
|
||||||
|
|
||||||
|
This skill has authority over mechanical `sources.json` hygiene. Past runs accumulated "overdue" recommendations nobody executed; that pattern is over. Apply these directly during the run:
|
||||||
|
|
||||||
|
- **Hard-blocked source** (Cloudflare challenge, empty 403s, dead DNS) for the 3rd consecutive run: before skipping, probe for a feed (`<link rel="alternate">` on the homepage, or common paths `/feed`, `/rss.xml`, `/atom.xml`, `/index.xml`). A working feed rescues the source — set `fetch: "feed"` + `feedUrl`. Otherwise set `fetch: "skip"` with a dated `skipReason`.
|
||||||
|
- **Dormant source** (live but no new content across 3+ runs): demote `cadence` to `as-needed`; if the newest content is over a year old, `fetch: "skip"` with reason "dormant since <date>".
|
||||||
|
- **Better fetch path discovered** (feed URL, Shopify `/collections/new/products.json`, article-level URL pattern that bypasses an index-level block): record it in `fetch`/`feedUrl`/`fetchUrl` immediately.
|
||||||
|
- **Quarterly re-probe**: if any `fetch: "skip"` entry has a `skipReason` dated 90+ days ago, re-test it this run; blocks get lifted. Remove the skip when the source is back.
|
||||||
|
- Update the top-level `updated` field when you touch the file.
|
||||||
|
|
||||||
|
Every mechanical change gets one line in the log's maintenance section. Only *new* sources need the user (step 5).
|
||||||
|
|
||||||
|
Note on Reddit: anonymous JSON/RSS endpoints have been blocked since ~June 2026. If `REDDIT_CLIENT_ID` / `REDDIT_CLIENT_SECRET` are available in the environment (or `.env`), obtain an app-only OAuth token (`https://www.reddit.com/api/v1/access_token`, grant_type=client_credentials) and fetch `https://oauth.reddit.com/r/<sub>/top?t=week` with it. If no credentials exist, log the Reddit sources as blocked-pending-oauth and move on — don't retry anonymous endpoints every run.
|
||||||
|
|
||||||
|
### 7. Update the log
|
||||||
|
|
||||||
Append to `daily-finds.log.md`:
|
Append to `daily-finds.log.md`:
|
||||||
|
|
||||||
```
|
```
|
||||||
## YYYY-MM-DD — capture run
|
## YYYY-MM-DD — capture run
|
||||||
|
|
||||||
Sources scanned: <N> total — <hits> hits, <empty> empty, <blocked> blocked, <skipped> skipped.
|
Sources scanned: <N> total — <hits> hits, <empty> empty, <blocked> blocked, <skipped> skipped, <cadence-skipped> cadence-skipped.
|
||||||
Productive sources: <comma-separated list of sources that produced at least one written find>.
|
Productive sources: <comma-separated list of sources that produced at least one written find>.
|
||||||
|
|
||||||
Captured finds:
|
Captured finds:
|
||||||
- <slug> — <name> — source: <source>
|
- <slug> — <name> — source: <source>
|
||||||
- <slug> — <name> — source: <source>
|
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
|
Shortlist (<N>, ranked):
|
||||||
|
1. <slug> — <one-line rationale> [review candidate]
|
||||||
|
2. <slug> — <one-line rationale>
|
||||||
|
...
|
||||||
|
|
||||||
|
Amazon links added: <N> (<slug>, <slug>, ...) — or "none".
|
||||||
|
|
||||||
|
Source maintenance:
|
||||||
|
- <one line per sources.json change> — or "none".
|
||||||
|
|
||||||
New source nominees: <count> (see candidate-sources.md).
|
New source nominees: <count> (see candidate-sources.md).
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6. Report to the user
|
### 8. Report to the user
|
||||||
|
|
||||||
Short summary in chat:
|
Short digest in chat (or, when running as part of `/daily-pipeline`, feed this into the pipeline digest):
|
||||||
|
|
||||||
```
|
```
|
||||||
Captured <N> finds across <M> productive sources today.
|
Captured <N> finds across <M> productive sources; shortlisted <K>.
|
||||||
Blocked: <K> sources. New source nominees: <count>.
|
Review candidates: <slug>, <slug>.
|
||||||
Run /build-bundle to draft a bundle from the recent pool, or /build-review <slug> to graduate one to a review.
|
Amazon links: <N>. Source maintenance: <N> changes. New nominees: <count>.
|
||||||
|
Blocked: <list or none>.
|
||||||
```
|
```
|
||||||
|
|
||||||
That's it. No theme, no candidates list, no "pick a winner."
|
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- "Why it fits" / find descriptions should name a *specific* virtue. Bad: "great design". Good: "magnetic connector means it never strains the cable when a foot catches it".
|
- "Why it fits" / find descriptions should name a *specific* virtue. Bad: "great design". Good: "magnetic connector means it never strains the cable when a foot catches it".
|
||||||
- Travel destinations relax the daily-use criterion — a place qualifies if a visitor would remember it years later.
|
- Travel destinations relax the daily-use criterion — a place qualifies if a visitor would remember it years later.
|
||||||
- If a source's homepage is too noisy, prefer adding a `fetchUrl` to `sources.json` rather than working around it inside the skill.
|
- If a source's homepage is too noisy, prefer adding a `feedUrl`/`fetchUrl` to `sources.json` rather than working around it inside the skill.
|
||||||
- Never invent slugs that collide with existing finds or reviews. If a name would produce a colliding slug, append a year or distinguishing word.
|
- Never invent slugs that collide with existing finds or reviews. If a name would produce a colliding slug, append a year or distinguishing word.
|
||||||
- Source attribution must be exact — the `source` field in each find file must match a `name` in `sources.json` so the `/sources` page cross-reference works.
|
- Source attribution must be exact — the `source` field in each find file must match a `name` in `sources.json` so the `/sources` page cross-reference works.
|
||||||
|
- For large sweeps, fan out: scanner subagents per source cluster → central dedup/bar/shortlist pass → writer subagents for files + heroes. Keep the shortlist ranking in the main context — it's the editorial judgment call of the run.
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
---
|
||||||
|
name: daily-pipeline
|
||||||
|
description: The full unattended daily run for unique.rzen.dev — capture (daily-finds) → bundle (build-bundle auto) → graduate reviews (build-review auto) → set the pick of the day → build, commit, and push — ending with a digest. Use when the user runs /daily-pipeline, asks to "run the pipeline", "do the daily run", or when a scheduled/headless session needs the whole chain.
|
||||||
|
disable-model-invocation: true
|
||||||
|
---
|
||||||
|
|
||||||
|
# daily-pipeline
|
||||||
|
|
||||||
|
One command, whole day: everything from source sweep to a deployable commit, no confirmation gates. Each stage is an existing skill run in its autonomous mode — this skill is just the conductor plus the commit/digest discipline around it.
|
||||||
|
|
||||||
|
Invoke from inside the `unique.rzen.dev` repo. Requires a clean-ish working tree (uncommitted changes unrelated to the pipeline should be left untouched — stage only what each step produced).
|
||||||
|
|
||||||
|
## The chain
|
||||||
|
|
||||||
|
### 0. Preflight
|
||||||
|
|
||||||
|
- `git status` — note any pre-existing dirty files; never stage or commit them.
|
||||||
|
- Read the tail of `daily-finds.log.md` — when was the last capture run, the last weekly catch, the last cluster run.
|
||||||
|
- If today's capture run already happened (log has a `YYYY-MM-DD — capture run` entry for today), skip to step 2 rather than double-capturing.
|
||||||
|
|
||||||
|
### 1. Capture — run `daily-finds`
|
||||||
|
|
||||||
|
Follow `.claude/skills/daily-finds/SKILL.md` in full (scan → filter → write → shortlist + Amazon lookup → nominate → source maintenance → log). It runs unattended by design.
|
||||||
|
|
||||||
|
**Commit** (matching repo conventions):
|
||||||
|
`content: YYYY-MM-DD capture run — <N> finds across <M> productive sources`
|
||||||
|
Stage: the new find folders, `daily-finds.log.md`, `candidate-sources.md`, and `sources.json` if maintenance touched it.
|
||||||
|
|
||||||
|
### 2. Bundle — run `build-bundle` in auto mode
|
||||||
|
|
||||||
|
- Default: one themed bundle drawn from the unbundled shortlist pool (auto mode picks the strongest cluster itself). If the day's shortlist supports no honest theme, skip — don't force one.
|
||||||
|
- **Weekly catch check**: if no "week's catch" bundle exists for the trailing 7 days and unbundled shortlisted finds are accumulating, also run `build-bundle weekly`.
|
||||||
|
- **Backlog check**: if the unbundled pool has grown past ~25 shortlisted finds, run `cluster-bundles` in auto mode instead of a single bundle this run.
|
||||||
|
|
||||||
|
**Commit**: `site: bundle — <title>` (or `site: bundles — <t1>, <t2>` / `site: cluster run — <N> bundles`).
|
||||||
|
|
||||||
|
### 3. Reviews — run `build-review` in auto mode
|
||||||
|
|
||||||
|
- Take the capture run's **review candidates** (the top of the shortlist, marked in the log). Graduate **1–2 per run** — quality over cadence; zero is fine on a thin day. Prefer candidates with strong media and a real story; the fact-check step is mandatory.
|
||||||
|
- If today's capture was thin but a prior day's log lists ungraduated review candidates, those are fair game.
|
||||||
|
|
||||||
|
**Commit per review or combined**: `site: review — <Name> (graduates from find)` / `site: reviews — <A>, <B> (graduate from finds)`.
|
||||||
|
|
||||||
|
### 4. Pick of the day
|
||||||
|
|
||||||
|
- If reviews were published this run, set the strongest one as the homepage hero: write `src/data/pick.json` → `{"hero": "<review-slug>"}`. "Strongest" = best hero visual + broadest audience appeal; when torn, the one whose virtue is most immediately legible from the tile.
|
||||||
|
- If no review was published, leave the pick as-is (a stale-but-good hero beats churn for its own sake).
|
||||||
|
|
||||||
|
**Commit**: `site: pick of the day — <Name>`.
|
||||||
|
|
||||||
|
### 5. Verify + push
|
||||||
|
|
||||||
|
- `npm run build` must pass (it will also have been run by the sub-skills; this is the final gate). If it fails, fix what the pipeline broke; never push a red build.
|
||||||
|
- `git push origin main`. If the push fails (auth, network), leave the commits local and flag it in the digest.
|
||||||
|
|
||||||
|
### 6. Digest
|
||||||
|
|
||||||
|
End with a compact, human-readable digest — this is the only thing the user should *need* to read:
|
||||||
|
|
||||||
|
```
|
||||||
|
Daily pipeline — YYYY-MM-DD
|
||||||
|
|
||||||
|
Captured: <N> finds (<M> productive sources), shortlisted <K>
|
||||||
|
Bundle: "<title>" (<n> items) [+ week's catch / cluster run if run]
|
||||||
|
Reviews: <Name> (<slug>), <Name> (<slug>) — or none
|
||||||
|
Pick: <Name> — or unchanged (<current>)
|
||||||
|
Amazon: <n> links added
|
||||||
|
Sources: <n> maintenance changes; <n> new nominees awaiting vetting
|
||||||
|
Attention: <anything needing the user: rejected graduations, blocked sources,
|
||||||
|
failed push, nominee backlog> — or "nothing"
|
||||||
|
Pushed: yes/no (<commit range>)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ground rules
|
||||||
|
|
||||||
|
- **Never pause for input.** Every judgment call is yours; make it, log it, surface it in the digest. The user vetoes after the fact — that's the operating model.
|
||||||
|
- **Granular commits** in the repo's established message style (see `git log`), one per stage, so any stage is individually revertable.
|
||||||
|
- **Don't touch** pre-existing uncommitted changes, `src/` code, or `sources.json` beyond what daily-finds' maintenance step allows.
|
||||||
|
- **Degrade gracefully.** A stage that fails or yields nothing doesn't abort the chain — note it in the digest and continue (except a red `npm run build`, which blocks the push).
|
||||||
|
- Candidate-source vetting and adding new sources to `sources.json` stay with the user. If `candidate-sources.md` has 10+ unvetted nominees, nudge in the digest's Attention line.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# unique.rzen.dev
|
||||||
|
|
||||||
|
A daily product-discovery site (Astro, static). The editorial pipeline is skill-driven and designed to run unattended.
|
||||||
|
|
||||||
|
## Pipeline
|
||||||
|
|
||||||
|
- `/daily-pipeline` — the whole daily chain: capture → bundle → reviews → pick of the day → build → commit → push → digest. This is the entrypoint for scheduled/headless runs.
|
||||||
|
- `/daily-finds` — capture-only sweep of `sources.json`; writes `src/content/find/<yyyy>/<mm>/<slug>/`, marks the day's shortlist (`shortlist: true`, up to 12), adds `amazonLink` for confidently-matched physical products, and applies mechanical source maintenance.
|
||||||
|
- `/build-bundle [auto|weekly|<theme>|<slugs>]` — one themed roundup referencing find slugs. `weekly` = date-based catch of unbundled shortlist finds.
|
||||||
|
- `/cluster-bundles [auto]` — batch-cluster the backlog into 4–7 bundles.
|
||||||
|
- `/build-review <slug> [auto]` — graduate a find to a full review (fact-check against the product's own site is mandatory in auto mode).
|
||||||
|
|
||||||
|
Unattended runs never pause for confirmation — they publish, log to `daily-finds.log.md`, and report a digest; the user vetoes after the fact. Only two things stay user-only: vetting `candidate-sources.md` nominees into `sources.json`, and scheduling.
|
||||||
|
|
||||||
|
## Conventions that bite
|
||||||
|
|
||||||
|
- Review heroes are auto-discovered from `src/content/reviews/<slug>/hero.<ext>` (`src/lib/hero.ts` glob) — name the file `hero.*`; no registration.
|
||||||
|
- The homepage "pick of the day" is `src/data/pick.json` (`{"hero": "<review-slug>"}`); falls back to the latest review.
|
||||||
|
- Find slugs are globally unique although folders are date-bucketed; URLs stay flat at `/find/<slug>/`.
|
||||||
|
- Finds are only reachable through bundles — an unbundled find is invisible on the site.
|
||||||
|
- `source` frontmatter must exactly match a `name` in `sources.json`.
|
||||||
|
- Amazon links: store canonical `https://www.amazon.com/dp/<ASIN>` with no query params; `src/lib/affiliate.ts` adds the tracking ID, badge, and disclosure at render time.
|
||||||
|
- Commit style: see `git log` — `content: …` for capture runs, `site: …` for everything user-visible; one commit per pipeline stage.
|
||||||
|
- `npm run build` (astro check + build) must pass before pushing.
|
||||||
Reference in New Issue
Block a user