From minutes
Queries meeting history as structured data with co-occurrence and cross-entity searches. Builds a JSON entity index for relationship intelligence across all meetings.
How this skill is triggered — by the user, by Claude, or both
Slash command
/minutes:minutes-graphThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Cross-meeting entity graph that lets you query your meeting history as structured data — **people and topics** out of the box, with companies and products as an opt-in deep-extraction path.
Cross-meeting entity graph that lets you query your meeting history as structured data — people and topics out of the box, with companies and products as an opt-in deep-extraction path.
Minutes already exposes minutes people, minutes person, and minutes insights for first-class entity queries. Graph layers on top of those to answer questions the CLI can't:
Defer to the existing CLI when it suffices. Use graph for the queries the CLI can't answer. Anything about companies or products requires opt-in deep extraction (see Phase 1).
Graph has two modes: build (creates the index) and query (uses it).
If the user explicitly says "build" / "rebuild" / "refresh the graph" → Phase 1 (build mode).
Otherwise → Phase 2 (query mode). Query mode auto-builds the index if it doesn't exist, and incrementally refreshes it if new meetings exist since the last build.
Detect company/product queries upfront. If the user's question mentions a specific company name, product, brand, or anything that wouldn't be in standard meeting frontmatter (e.g., "Stripe", "Notion", "the deal with Acme"), tell them upfront — before any building happens — that this needs deep extraction:
"That query is about a company/product, which isn't in standard meeting frontmatter. I'd need to deep-scan transcripts (~ seconds for meetings) to answer. Continue, or rephrase to use topics/people that are already indexed?"
This avoids the worst flow: build → discover the data isn't there → ask user → rebuild.
The index lives at ~/.minutes/graph/index.json. Use the bundled graph_build.py script — do not try to walk meeting files or parse YAML frontmatter in-context. The script is deterministic, fast, atomic, and handles all the edge cases (incremental rebuilds, garbage filtering, name disambiguation, augmentation from minutes people --json).
Always warn before building, even on the auto-trigger path. Users hate commands that hang silently more than they hate one-line confirmations:
"Building entity graph from meetings. The frontmatter-only path is fast (a few seconds for hundreds of meetings). Continue? (Ctrl+C to abort)"
To get the meeting count for the warning, use Glob on the meetings dir from minutes paths.
Run the script:
python3 "${CLAUDE_PLUGIN_ROOT}/skills/minutes-graph/scripts/graph_build.py" --incremental
Defaults:
--meetings-dir defaults to output_dir from minutes paths--output defaults to ~/.minutes/graph/index.json--incremental skips the rebuild entirely if no meeting files have been modified since the last build (the common case after the first build)The script prints a one-line summary JSON to stdout:
{"status": "ok", "meeting_count": 142, "person_count": 12, "topic_count": 38, "output": "/Users/.../index.json"}
Or {"status": "fresh", ...} if --incremental found nothing to rebuild.
What the script does, in order:
date, attendees (display names), people (wikilink slugs), tags, and decisions[].topic — all the entity-relevant fields the real meeting schema actually hasnone/null/~ topic values that show up in some malformed meetingsattendees and people have the same length, zips them positionally to map each slug to its display name (gives mat → Mat S., etc.)minutes people --json and merges its top_topics, open_commitments, score, losing_touch fields into the people entries it already builtunknown-speaker, speaker-3, etc.) out of the final people indexWhat's NOT in the default build: companies and products. Some real meetings do have an entities: block in their frontmatter — the current schema looks like:
entities:
people:
- slug: speaker-0
label: Speaker 0
aliases: [speaker 0]
projects:
- slug: codex-native-call-attribution
label: Codex Native Call Attribution
But the schema is inconsistent across the corpus — many meetings have no entities: block, and when it's present its structure varies (some meetings have people, some add projects, there's no guarantee of companies or products). graph_build.py intentionally uses a narrower set of fields (attendees, people slugs, tags, decisions[].topic) that are more consistently populated across the full meeting corpus, so the default build is predictable.
If you want the entities block data in the graph, modify graph_build.py to also parse it — but be ready to handle variant schemas across meetings. For on-demand company/product queries that go beyond what frontmatter has, use the opt-in deep extraction path below.
Two paths from here:
Default path (the script above): people, topics, dates. Fast, deterministic, runs on every install with zero deps.
Opt-in deep extraction: only if Phase 0 detected a company/product query and the user confirmed, run a one-time LLM pass over each meeting's transcript section to extract company and product names. Cache results in the index keyed by meeting filename. Every subsequent query reuses the cache. Deep extraction is not built into graph_build.py — it's a separate workflow Claude orchestrates by reading meetings, calling out to the LLM, and updating the index JSON manually.
Index structure:
{
"version": 1,
"built_at": "2026-04-08T15:30:00Z",
"meeting_count": 142,
"people": {
"case-wintermute": {
"name": "Case W.",
"aliases": ["Case", "Case W."],
"meetings": ["2026-03-17-q2-pricing.md", "2026-03-22-followup.md"],
"first_mention": "2025-11-04",
"last_mention": "2026-03-22",
"count": 12,
"top_topics": ["pricing", "onboarding", "hiring"],
"open_commitments": 3,
"score": 8.4,
"losing_touch": false,
"co_occurs_with": {
"people": {"sarah-chen": 8, "logan": 3},
"topics": {"pricing": 6, "hiring": 4}
}
}
},
"topics": {
"pricing": {
"meetings": ["2026-03-17-...", "2026-03-22-..."],
"first_mention": "2025-11-04",
"last_mention": "2026-03-22",
"count": 9,
"co_occurs_with": {
"people": {"sarah-chen": 6, "case-wintermute": 4},
"topics": {"hiring": 3, "onboarding": 2}
}
}
}
}
Co-occurrence is computed within each meeting: every pair of entities (people-people, people-topics, topics-topics) that appear in the same meeting increments their respective co_occurs_with counts. After walking all meetings, the index answers "what co-occurs with X most often?" with a single map lookup — no transcript re-reading.
If the user opted into deep extraction (path 2 above), add "companies": {...} and "products": {...} blocks at the same level as people and topics, with the same shape.
Write the index:
chmod 644 ~/.minutes/graph/index.json
The index is metadata — names, dates, counts, slugs. Not transcript text. 644 perms are fine. Users with unusually sensitive entity names can chmod 600 themselves.
Tell the user when it's done:
"Built graph from meetings:
people, topics. Index at
~/.minutes/graph/index.json. Run any cross-meeting query and I'll answer instantly from the index."
Index freshness check:
built_at (any file with mtime > built_at), do an incremental refresh before answering. Incremental refreshes are fast (<1s for typical updates) and don't need a confirmation prompt.Read ~/.minutes/graph/index.json once at the start of the query response. Don't re-read the file for every sub-question — the JSON is one self-contained blob, so a single Read gives you everything you need to answer any number of follow-ups in the same turn.
Common query types and how to answer them:
| User asks | What to do |
|---|---|
| "Every time we talked about pricing" | Find "pricing" in topics. Return all meetings, most recent first, with dates from each meeting's frontmatter. |
| "First time we talked about X" | Look up first_mention for the entity. Return the date and the meeting file. |
| "Trend for X over the last 6 months" | Walk the entity's meetings list, group by month, return a count-by-month ASCII chart. |
| "Who's been mentioned alongside Sarah" | Look up the canonical slug for Sarah (search both aliases arrays), then read co_occurs_with.people. Return sorted by count. |
| "What topics came up when we talked about hiring" | Find "hiring" in topics and read its co_occurs_with.topics map directly — that's exactly the data you want, sorted by count. (Co-occurrence is precomputed during Phase 1, so this is a single map lookup, not a reverse scan.) |
| "Show me everyone who mentioned Stripe" | Stripe is a company, not in default frontmatter. Tell the user this needs deep extraction and ask before running it (see Phase 1 path 2). |
| "Most active people in the last 30 days" | Defer to minutes people — the CLI does this natively and better, with score and losing_touch flags graph would just be re-deriving. |
| "Who am I losing touch with" | Defer to minutes people --json — losing_touch: true is already a first-class field. Don't reinvent it. |
Lookup rules:
aliases array of every person to find a slug match. If multiple slugs match, ask which one.Output format:
Always cite source meetings as filenames so the user can drill in. Use markdown tables for ranked results. For trends, use simple ASCII bar charts:
2025-10: ███ 3
2025-11: ██████ 6
2025-12: ████ 4
2026-01: ████████ 8
2026-02: ███████████ 11 ← peak
2026-03: █████ 5
After answering, suggest the next natural query if and only if it's obviously useful:
/minutes-brief on any of them?"minutes research \"<topic>\" for a deep dive across the meetings?" (research is a CLI command, not a slash skill)Don't always nudge. Only when the next move is genuinely useful — otherwise stay out of the way.
minutes people --json already gives a relationship overview with losing_touch, score, top_topics, open_commitments per person. minutes person <name> gives a single profile. minutes insights --participant <name> (output is JSON by default — do not pass --json) gives structured decisions and commitments. If the user's question fits one of those natively, just call the CLI — graph is for the queries the CLI can't answer (cross-entity, co-occurrence, "show me X across everything"). Never reinvent.attendees, tags, people, decisions[].topic, action_items[].assignee all exist in real meeting frontmatter. There is no entities: block — don't expect one and don't invent one. Companies and products are NOT in frontmatter; they're a separate opt-in deep-extraction path that the user has to consent to before each run.mtime > built_at. Don't re-extract everything every time. Most rebuilds complete in <1s.attendees: [Case W., Mat S.] (display) and people: [[case-wintermute], [mat]] (slugs). Slugs are stable across meetings; display names drift. Use slugs as the primary key, store display names in an aliases array.chmod 600 themselves.minutes paths is the source of truth for the meeting directory. Don't hardcode ~/meetings — read it from minutes paths. Users may sync to Obsidian/Logseq with a custom output directory.minutes people --json natively (the losing_touch: true field is built-in). Run it and surface the result, then let the user know graph wasn't needed. Trust earned.minutes people --json may return entries like "Unknown speaker" or "Speaker_3" or "Matt" (lowercase variant of "Mat") — these come from imperfect speaker diarization. When listing people in graph output, filter out obvious garbage (slugs that look like unknown-speaker, speaker-N, or duplicate variants of the user's own name).npx claudepluginhub silverstein/minutes --plugin minutesUses Glean's meeting_lookup tool to retrieve decisions, action items, attendees, transcripts from past meetings. For queries like 'what was decided' or 'meeting notes'.
Auto-detects next calendar event and produces a one-page meeting brief with relationship history and open commitments in under 30 seconds.
Compiles deep context on a topic by combining semantic search with relationship traversal across 4 retrieval phases. Useful for research spanning multiple entries and connections.