Movement Leader Harvest Manifest Builder
Produce a correct, ingest-ready harvest-manifest.json for one tenant from whatever files and catalogs already exist. This skill is the manifest factory — it does not sweep the web (see movement-leader-content-harvest) and does not upload to Supabase (see movement-leader-harvest-ingest).
Invocation
/movement-leader-harvest-manifest {leader-slug}
/movement-leader-harvest-manifest danielle-strickland --from-harvest-only
/movement-leader-harvest-manifest rowland-smith --merge-existing --validate
/movement-leader-harvest-manifest hugh-halter --supplement links.csv
Run from movement-leader-websites/ monorepo root unless cwd is inside a leader repo.
CLI (preferred first pass)
pnpm harvest:build-manifest -- --slug {leader-slug}
pnpm harvest:build-manifest -- --slug danielle-strickland --dry-run
pnpm harvest:build-manifest -- --slug rowland-smith --merge-existing
pnpm harvest:build-manifest -- --slug hugh-halter --supplement docs/movement_leader_research/content/link-only.json
pnpm harvest:validate-manifest -- --slug danielle-strickland
pnpm harvest:build-bundle -- --repo ./danielle-strickland --out ./danielle-strickland-bundle.zip
| Flag | Effect |
|---|---|
--slug |
Required leader slug |
--dry-run |
Print summary + validation; do not write |
--merge-existing |
Keep LINK/TRANSCRIBE rows from current manifest not found on disk |
--supplement PATH |
Merge JSON array or CSV of extra rows (LINK media, catalog-only) |
--from-harvest-only |
Only scan docs/harvest/; ignore legacy docs/articles/ |
--force-ids |
Regenerate ids for files missing frontmatter id |
Resolve the tenant
- Normalize user input to kebab-case
{slug}. - Confirm
{MONOREPO_ROOT}/{slug}/exists (or cwd is that repo). - Set paths:
| Variable | Path |
|---|---|
MANIFEST |
{slug}/docs/movement_leader_research/content/harvest-manifest.json |
HARVEST_ROOT |
{slug}/docs/harvest/ |
CONTENT_DIR |
{slug}/docs/movement_leader_research/content/ |
Exclude: _template, _template-full, docs, scripts, shared, reports.
Workflow (execute in order)
Copy this checklist and mark steps as you go:
- [ ] 1. Inventory sources
- [ ] 2. Classify each item (type + disposition)
- [ ] 3. Assign stable ids + harvested_path
- [ ] 4. Merge LINK-only / catalog rows
- [ ] 5. Write manifest + summary
- [ ] 6. Validate (CLI + ingest preview)
Step 1 — Inventory sources
Scan in priority order (do not double-count):
docs/harvest/{type}/*.md— primary; frontmatter is authoritative when presentdocs/harvest/transcripts/*.md— attach to existing media rows; do not duplicate as articles- Legacy folders (if
--merge-existingor no harvest dir):docs/articles/,docs/videos/,docs/podcasts/ - Existing manifest — preserve LINK/TRANSCRIBE/SKIP rows when merging
- Supplement file — CSV/JSON for platform-only media (YouTube, podcast feeds)
Run: pnpm harvest:build-manifest -- --slug {slug} --dry-run and read the printed source counts.
Step 2 — Classify type and disposition
Type comes from (first match wins):
- Frontmatter
type:field - Parent directory under
docs/harvest/(see reference/type-taxonomy.md) - Heuristics on
source_url/ file path (youtube →video, substack →newsletter)
Disposition (see rubric in reference):
| Disposition | When |
|---|---|
| FETCH | Markdown body on disk under docs/harvest/ |
| LINK | Media or article cataloged by URL only (YouTube, Spotify, stable third-party) |
| TRANSCRIBE | A/V primary content; transcript not ready yet |
| SKIP | Wrong person, duplicate, or book (defer to corpus-upload) |
Never put books in the manifest.
Step 3 — Stable ids and paths
Id format: {slug}-{type-short}-{NNN} or reuse frontmatter id / filename stem.
Examples: danielle-strickland-blog-094, jr-woodward-newsletter-003, hugh-halter-video-012.
harvested_path (FETCH only): repo-relative POSIX path:
docs/harvest/{type-folder}/{id}.md
Type folder names (plural): articles, newsletters, videos, podcasts, interviews, academic, talks, audio.
Frontmatter template for new FETCH files — see reference/frontmatter-template.md.
Step 4 — Merge LINK-only rows
Videos and podcast-host episodes often have no markdown file. Add them via:
--merge-existing(keep prior LINK rows), and/or--supplementJSON:
[
{
"id": "hugh-halter-video-001",
"title": "Talk title",
"type": "video",
"disposition": "LINK",
"canonical_url": "https://www.youtube.com/watch?v=abc123",
"host": "YouTube",
"date": "2021-06-01",
"primary": true
}
]
Dedupe by id first, then by normalized canonical_url.
Step 5 — Write manifest
Output schema: reference/manifest-schema.md.
pnpm harvest:build-manifest -- --slug {slug} --merge-existing
Also refresh CONTENT_SOURCES.md stub summary if the tenant already uses it (counts must match non-SKIP items).
Step 6 — Validate
pnpm harvest:validate-manifest -- --slug {slug}
Gate checks:
- Every FETCH row has
harvested_pathpointing to an existing file - Every LINK video/podcast row has
canonical_url - No duplicate
idvalues -
summary.total==items.length - Summary disposition counts match items
- No
type: bookin items -
leaderfield matches slug
ZenWrite admin bundle: run from zenwrite repo:
pnpm harvest:build-bundle -- --repo ../movement-leader-websites/{slug} --out {slug}-bundle.zip
Upload zip in Admin → Harvest import → Preview must show correct targetTable per row.
Type → ingest target (downstream)
Harvest type |
Default disposition | Ingest table |
|---|---|---|
article, newsletter, interview, academic, resource, course |
FETCH | archive_items (or content_items) |
video |
LINK | videos |
podcast-host, podcast |
LINK | podcast_episodes |
podcast-guest |
FETCH/LINK | content_items if body exists |
book-chapter |
FETCH | book_chapters (requires parent book id in host) |
Full mapping: reference/type-taxonomy.md.
Agent execution rules
- Prefer the CLI over hand-editing JSON — it keeps paths and summaries consistent.
- Read frontmatter before inferring — harvested files often already contain full metadata.
- Do not invent URLs or dates — use
unknownfor date when missing. - Migration, not authoring — manifest describes existing content only.
- Persist incrementally — write manifest after each batch when fixing large tenants.
- No app code changes unless adding manifest tooling under
scripts/harvest/.
Session report (print when done)
{slug}and manifest path- Items by type and disposition (FETCH / LINK / TRANSCRIBE / SKIP)
- Validation: pass / fail with first 5 errors
fetched_to_diskcount vs files found on disk- Suggested next step:
pnpm harvest:transcribe→pnpm harvest:ingestor ZenWrite bundle upload
Related skills
| Skill | Role |
|---|---|
movement-leader-content-harvest |
Full 00–09 web sweep → manifest (upstream) |
movement-leader-media-transcribe |
YouTube captions + Whisper → transcripts |
movement-leader-harvest-ingest |
Manifest → Supabase draft rows |
movemental-leader-corpus-upload |
Books / dossier JSONB (parallel track) |
Additional resources
- reference/manifest-schema.md
- reference/type-taxonomy.md
- reference/frontmatter-template.md
- Step 08 prompt:
docs/build/skills/content-gathering/08-write-inventory-and-manifest.md - ZenWrite ingest mapper:
zenwrite/server/services/admin/ingest/harvestMapper.ts