minutes-graph-b383ae — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited minutes-graph-b383ae (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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 (~<estimate> seconds for <N> 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 <N> 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" --incrementalDefaults:
--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 AttributionBut 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:
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.jsonThe 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 <N> meetings: <P> people, <T> 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: █████ 5After 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.~/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).~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.