Systematically analyze a large corpus of personal blog posts or writing: inventory → cluster → extract concepts via parallel subagents → discover themes → identify book/course product opportunities.
Analyze a large corpus of your own blog posts, articles, or essays to extract concepts,
discover themes, and identify product opportunities (ebooks, courses). Designed for
corpora of 100-2,000+ pieces with YAML frontmatter.
Use when the user asks to:
Phase 1: INVENTORY → Phase 2: CONCEPT EXTRACTION → Phase 3: QA → Phase 4: THEMES → Phase 5: PRODUCTS
Each phase builds on the previous. Pause for user review between Phase 2 and 3.
① Run scripts/corpus-inventory.py (or write equivalent) to read all posts:
# For each post, extract: title, date, tags, slug from YAML frontmatter
Count totals, date range, posts per year, tag frequency
② Define clusters based on tags + title keyword analysis. Typical clusters for a generalist writer:
③ Assign each post to one or more clusters using tag overlap + title keyword matching.
Posts can belong to multiple clusters (e.g., a post about AI leadership goes in both AI and Leadership).
④ Save cluster inventory as JSON: _meta/cluster-inventory.json
{
"clusters": {
"ai-tech": {
"name": "AI & Technology",
"count": 133,
"posts": [{"file": "...", "title": "...", "date": "...", "tags": [...]}]
}
}
}
⑤ Present the map to the user. Get buy-in on clusters before proceeding.
Clusters over 150 posts should be split further. Use sub-clustering based on tag/title
patterns (e.g., Personal Development → Mindset, Goals/Habits, Life Lessons).
parallel subagents. Create concept pages in the user's wiki.
Use delegate_task(tasks=[...]) to run 3 subagents in parallel per batch. Each subagent
processes one cluster. Smaller clusters complete; larger ones may timeout but produce
partial results — integrate those per the wiki-ingest-qa skill.
See references/subagent-prompt-template.md for the full template. Key elements:
wiki/writing/concepts/Process clusters in batches of 3 (max concurrent subagents):
| Batch | Example Clusters | Est. Time |
|---|---|---|
| 1 | Largest 3 clusters (100-200 posts each) | 10-15 min |
| 2 | Mid-size (70-120 posts) | 8-12 min |
| 3+ | Smaller (14-54 posts) | 3-8 min |
Podcast interview posts are guest content, not the author's original ideas. For this cluster:
podcast-landscape.md overview page capturing patterns across the body of workSubagents processing 100+ posts typically timeout at 600s. This is partial completion,
not failure. Per wiki-ingest-qa:
1. Audit what pages were created on disk
2. Integrate partial results into index.md
3. For significantly incomplete clusters, re-dispatch with explicit "ALREADY CREATED" lists
After all batches complete, run QA per the wiki-ingest-qa skill:
① Count all concept pages — verify against expectations from each cluster
② Check for duplicates — some subagents may create overlapping pages (e.g., "structured-communication" from both Leadership and Mindset clusters). Check by filename slug.
③ Verify frontmatter — spot-check 5-10 pages for required fields
④ Wikilink verification — check that [[wikilinks]] resolve to actual files
⑤ Update index.md — one central pass adding all concept pages alphabetically under ## Concepts. Avoids the subagent collision problem where 3 agents all update index.md independently.
⑥ Update log.md — prepend one consolidated entry listing all pages created per cluster
Use this pattern (subagents are told NOT to touch index.md):
# 1. List all .md files in concepts/ directory
2. Extract title from each file's YAML frontmatter
3. Build alphabetically-sorted wikilink entries: [[concepts/slug]] — Title
4. Insert into index.md under ## Concepts section
5. Update "Total pages: N" header
to regardless of topic. These become wiki/writing/themes/.
A theme spans 5+ posts across at least 2 different clusters. Example: "systems thinking"
appears in tech posts, leadership posts, AND parenting posts.
After all concept pages exist, read the full concept list and look for:
Create one theme page per cross-cutting pattern, linking to all concept pages and posts it touches.
⚠️ Do NOT read Claude's memories, reflections, or pre-existing analysis before this phase. Those are the AI's interpretation. You need the raw signal.
1. Extract human messages from conversations.json (see references/behavioral-signal-extraction.md)
2. Run behavioral signal analysis: punctuation habits, abbreviation patterns, message structure, conversation arcs, topic frequency
3. Identify patterns invisible in AI summaries: how they correct, how they provide context, how they end conversations, what roles they assign
After your independent analysis:
1. Read Claude's memories/ and reflections/
2. Read any extracted/ analysis files
3. Identify gaps: what did the AI miss or smooth over?
4. The delta between your raw analysis and the AI's interpretation is often the most valuable insight
Write extracted/self-portrait-independent.md with:
A theme graduates to a book when there's enough density (15+ posts across 3+ years). Create a book outline in wiki/writing/books/:
books/systematic-leadership/
├── README.md — outline, chapter map, target audience, pitch
├── 01-foundations.md
├── 02-frameworks.md
...
A cluster with 20+ posts and a clear "how-to" angle can become a course. Evaluate:
Flag posts for:
When the user provides a pre-curated list of posts (e.g., _meta/career-files.txt) or asks to extract concepts from a named tag/category, skip Phase 1 entirely and go directly to single-agent concept extraction. See references/focused-subset-extraction.md for the full workflow: audit existing concepts → batch-read posts → cross-reference → create → validate wikilinks.
When extracting from any large corpus (chat exports, blog archives, transcript collections),
create atask.md BEFORE starting extraction. This prevents drift, lost threads, and
scope creep. Joseph explicitly values this for complex multi-step work.
# <Corpus> Extraction — Task Tracker
Started: <date>
Status: In Progress
Scope: <what's in scope, what's not>
Scope
IN SCOPE: ...
OUT OF SCOPE: ...
Drift Guards
1. Read before writing — no insights without source citations
2. Don't summarize what you haven't read
3. Stop at "good enough" — signal, not completeness
4. Cite sources — every output page has sources: in frontmatter
5. Check before creating — grep for duplicates
6. Time-box — stop after N items or M minutes, report progress
Phase 1: <metadata / quick wins>
- [ ] Task 1
- [ ] Task 2
Phase 2: <deep extraction>
Item
Date
Status
Notes
...
...
[ ]
...
Progress Log
Date
Action
Output
Acceptance Criteria
- [ ] Criterion 1
- [ ] Criterion 2
When the user asks to "analyze this corpus" or "what profile emerges from this data," they usually want independent analysis of the raw data — not a rehash of pre-existing summaries that came with the export. Claude exports include extracted/ folders with AI-generated analyses (corpus-analysis.md, memories.md, etc.). These are Claude's interpretation, not ground truth.
1. Identify what's raw vs. derived. Check for extracted/, analysis.md, *-analysis.md files. These are AI outputs, not source data.
2. Analyze raw data independently. Load conversations.json (or equivalent), extract human messages, run behavioral/textual analysis from scratch.
3. Compare with AI's analysis LAST. After your independent analysis, read the AI's pre-existing analysis to find what it missed or smoothed over. The delta between your analysis and theirs is often the most valuable insight.
4. Tell the user which you did. If you relied on pre-digested outputs, say so. Joseph corrected this directly: "Did you rely on Claude memory and profile or did you do this on your own from the corpus."
Techniques that revealed patterns invisible in AI-summarized analysis:
See references/behavioral-signal-extraction.md for code patterns.
When setting up a chat export project (ChatGPT, Claude, or other), use this structure:
chatgpt/ (or claude/)
├── original/ ← Raw export files (DO NOT MODIFY — source of truth)
├── extracted/ ← Processed outputs only (index.csv, index.md, batch results)
├── scripts/ ← Python scripts (NOT in extracted/ — keeps code separate from data)
├── task.md ← Phased plan with review gates
└── readme.md ← Corpus stats, structure docs, how-to re-run
Why scripts/ not alongside data: Joseph prefers clean separation — extracted/ holds outputs only, scripts/ holds code. This scales as more phase scripts are added.
Why readme.md: Durable context for future sessions. Capture: corpus stats (conversations, chars, date range), tier breakdown, tag distribution, directory map, how to re-run scripts. Prevents "what was this?" in later sessions.
Extraction output filenames: Use sanitized conversation titles, not UUID prefixes. AI-Phases-of-Adaptation.md is browsable; 6fbfc9b5.md is not. The manifest JSON should include a sanitized_title field:
sanitized = re.sub(r'[^\w\s-]', '', title)
sanitized = re.sub(r'\s+', '-', sanitized.strip())[:60].rstrip('-') or 'unnamed'
When the title is empty (common in Claude exports — ~22% of conversations), use the summary field as fallback, then 'unnamed'.
conversation_template_id field links each conversation to the Custom GPT it was created in. Map these IDs to meaningful project names via GPT_PROJECT_MAP in the indexer script. 780 conversations had no custom GPT (mapped to 'general'); 19 Custom GPTs had 10+ conversations each. See references/chatgpt-export-format.md for the full mapping.
Claude projects are standalone. Claude exports include projects/<uuid>.json files with project briefs and docs, but conversations in conversations.json have NO project_uuid field — there is no link between them. Projects must be extracted separately; cross-referencing conversations to projects requires content matching, not a foreign key.
Phase 1 (index/inventory) is always detailed. Phases 2+ are outlined at high level.
Each phase gets detailed planning only when reached. Review gate between phases:
user reviews Phase 1 output, adjusts tier assignments, agrees on Phase 2 batch sizes.
This prevents over-planning and ensures Phase 2 design reflects what Phase 1 revealed.
any(t.startswith('routine/') for t in tags)), ensure tags is a list, not a pipe-delimited string. Iterating over a string yields individual characters, so the check silently fails. Always .split('|') first. Caught in production: 0 skip-tier conversations when there should have been 5.execute_code loops. Validate all wikilinks before reporting completion.Path(__file__).parent.parent resolves relative to the script's actual location. A script at chatgpt/scripts/batch_extract.py has parent.parent = chatgpt/, not agentic-chats/. Use parent.parent.parent when the script is 3 levels deep. Always test path resolution with a print statement before running the full script.batch-NN/manifest.json; (2) subagents consume manifests via delegate_task. This keeps data prep deterministic and LLM extraction delegatable. The manifest JSON includes full conversation text plus a sanitized_title field for human-readable output filenames.references/claude-export-format.md for field-level details.head, cat, read_file, or line-based tools. Always use python3 -c "import json; ..." or execute_code with json.load(). Check size with ls -lh first.extracted/, analysis.md, corpus-analysis.md, or similar AI-generated summaries, these are derived outputs — not ground truth. When asked for independent analysis, you must load and analyze the raw data (conversations.json) yourself. Relying on the AI's own analysis produces the AI's interpretation, not yours. Joseph explicitly corrected this: "I told you to do an independent analysis."scripts/corpus-inventory.py — Read all posts, extract frontmatter, tag frequency, date rangescripts/cluster-posts.py — Assign posts to clusters based on tags + title keywordsreferences/subagent-prompt-template.md — Template for concept extraction subagent promptsreferences/focused-subset-extraction.md — Workflow for single-agent extraction from pre-curated post lists (no inventory phase)references/claude-export-format.md — Claude chat export JSON structure, field reference, extraction patterns