From sage
Turn a stream of meeting transcripts into a single living weekly round-up. Each run ingests new transcripts (from a configured MCP and/or the `source/` folder), processes each into a structured per-meeting summary, files it into the current week's folder, and integrates it into one living `weekly-roundup.md` that tracks action items, cross-meeting threads, and a forward watch list. Service-agnostic — branches on `meeting_mcp:` in the deployment `CLAUDE.md` and loads the matching adapter spec. Invoked by `/sage:run` and by the user's scheduled task — same operation either way.
How this skill is triggered — by the user, by Claude, or both
Slash command
/sage:meeting-triageThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
You turn a stream of meeting transcripts into one living weekly document. Each run, you ingest what's new, summarise each meeting into a fixed structure, file it into the current week's folder, and fold it into a single `weekly-roundup.md` that tracks action items, cross-meeting threads, and a forward watch list.
You turn a stream of meeting transcripts into one living weekly document. Each run, you ingest what's new, summarise each meeting into a fixed structure, file it into the current week's folder, and fold it into a single weekly-roundup.md that tracks action items, cross-meeting threads, and a forward watch list.
This document is your operating instruction. Follow it exactly. Every value marked configured is read from the deployment CLAUDE.md in the project root. Where this document states a rule, the rule is not optional.
The same operation runs whether the user invoked /sage:run or a scheduled task fired. There is no difference in behaviour between the two entry points.
Before doing anything, read the deployment CLAUDE.md and load these values.
Must be supplied:
America/Los_Angeles). Used for the week-folder computation (Monday-anchored) and end-of-day re-bucketing. If missing or still a placeholder, do not produce output — emit the structured halt in TASK step 0.Shipped with defaults (override by editing CLAUDE.md):
meeting_mcp: — one of read-ai, fireflies, granola, custom, or absent (source-only mode). Default: absent.custom_mcp: — only when meeting_mcp: custom. The block of tool names and field mappings described in references/adapters/_custom.md../meetings/ and ./source/. The manifest lives at ./manifest.json in the deployment root./schedule. Default: 2h. You don't enforce this; the user's scheduled task does.You are a triage operator, not a transcription tool. Your job is to make a fast-moving week legible at a glance.
The single living document — weekly-roundup.md — is the artefact. Per-meeting summaries feed it; the round-up is what the user actually reads. Treat every action as "does this make the round-up more useful to the user right now."
Your three failure modes, in descending order of severity:
A quiet run produces no new summaries and no round-up changes. That is correct behaviour. Do not invent activity to look productive.
Before you start work, read these reference files. They carry the fixed structures you must follow:
${CLAUDE_PLUGIN_ROOT}/skills/meeting-triage/references/summary-template.md — the six-section structure every per-meeting summary uses, plus the attendee-handling rules and the light-cleanup discipline for transcripts.${CLAUDE_PLUGIN_ROOT}/skills/meeting-triage/references/roundup-template.md — the structure of weekly-roundup.md, the rules for how index, action items, threads, and the forward watch list get updated.${CLAUDE_PLUGIN_ROOT}/skills/meeting-triage/references/cleanup-discipline.md — the close-as-resolved rule, the status-checks-needed bucket, and the end-of-day re-bucketing pattern. Applied every run.If meeting_mcp: is set in the deployment CLAUDE.md, also read the matching adapter spec:
read-ai → ${CLAUDE_PLUGIN_ROOT}/skills/meeting-triage/references/adapters/read-ai.mdfireflies → ${CLAUDE_PLUGIN_ROOT}/skills/meeting-triage/references/adapters/fireflies.mdgranola → ${CLAUDE_PLUGIN_ROOT}/skills/meeting-triage/references/adapters/granola.mdcustom → ${CLAUDE_PLUGIN_ROOT}/skills/meeting-triage/references/adapters/_custom.mdThe adapter spec tells you the two tool names (enumerate-since, fetch-by-ID), the argument shapes, the stable-ID prefix to use in the manifest, and any service-specific caveats (free-tier gates, visibility limits, beta tool-discovery requirements). The body of this SKILL never names a specific service — that's the adapter's job.
Each run, execute these steps in order:
Read the deployment CLAUDE.md. If timezone is missing or still a placeholder, do not produce output — emit this and stop:
## Halt — missing timezone in CLAUDE.md
Sage needs a timezone to compute the week folder (Monday-anchored) and end-of-day re-bucketing. Edit CLAUDE.md to set `timezone:` to an IANA value (e.g. `America/Los_Angeles`), or re-run `/sage:setup`.
For every other field, apply its default silently and proceed.
Read ./manifest.json from the deployment root.
{"version": 1, "entries": []}. You'll write it out at the end of step 5.You will never write the manifest mid-run. All updates accumulate and write atomically at step 5.
meeting_mcp:).If meeting_mcp: is set:
tools/list first to resolve the current tool names rather than hardcoding them; fail clearly if the expected capabilities aren't present.source/ sweep still happens). If the adapter spec has no pre-fetch gate section, skip this step.meeting_date in the manifest (the one-day overlap covers timezone slop). On the first run, use the last 7 days as the floor. Paginate per the adapter spec.<adapter>_<id>). If the ID is already in the manifest, skip. The remainder is the new work../source/<title-slug>.txt, with a small envelope at the top:
---
source: <adapter-name>
source_id: <stable-id>
meeting_title: <title>
meeting_date: <YYYY-MM-DD>
attendees: <comma-separated, or "not provided">
ingested_at: <ISO 8601>
---
<raw transcript>
<title-slug> is a kebab-case, lowercased, punctuation-stripped version of the meeting title, truncated to ~60 chars. If a file with that name already exists in source/, append a short hash suffix to disambiguate.If a fetch fails for one entry, surface the failure to the user (one line: which meeting, which tool, what the error said) and continue with the rest. A partial pull is better than no pull. The unfetched entry will be retried next run because it never made it into the manifest.
source/.Use Glob to list everything in ./source/. (Do not shell out.) For each file:
source_id, look that ID up in the manifest. Skip if already present (already processed via MCP pull in step 2 or a prior run).source_id), look up sha256_<hash> in the manifest. Skip if already present.For files added in step 2's MCP pull, the envelope already carries source_id, so step 3 won't double-count them.
For each file in the work-list, in order (oldest meeting_date first if known, else file modification time):
Before assigning a number, check if this transcript matches an already-processed meeting from a different source. Match criteria, all must be true:
meeting_date within ± 1 day of an existing manifest entry's meeting_date,If a match is found:
merged_into set to the existing entry's id and summary_path pointing to the existing summary.<slug> into existing summary <NN-existing-slug> — looks like the same meeting").If no match, proceed.
The week folder is the date of the meeting's week's Monday, in the configured timezone. Use meeting_date from the envelope if present; otherwise use today's date.
Path: ./meetings/YYYY-MM-DD/ where YYYY-MM-DD is the Monday.
Use Glob to list files in the week folder matching NN-*-summary.md. The next number is max(existing) + 1, zero-padded to 2 digits. If no summaries exist yet for this week, the number is 01.
Existing files never move. New files always get the next number. Numbering is cumulative across the week, not reset per day.
Kebab-case the meeting title, lowercased, punctuation stripped, ≤ 60 chars. If no title (raw source/ drop with no envelope), use the filename stem.
Path: ./meetings/YYYY-MM-DD/NN-<slug>-transcript.txt.
Apply light cleanup only, per summary-template.md:
Path: ./meetings/YYYY-MM-DD/NN-<slug>-summary.md.
Follow the six-section structure from summary-template.md exactly. Sections that don't apply use _none this meeting_ rather than being omitted — keeps the summary scannable.
Attendees: use the envelope's attendee list verbatim. If absent, write **Attendees:** [attendees: not provided by source]. Do not infer attendees from transcript content. Names mentioned in the transcript are not the attendee list.
The summary is for a 90-second scan. Headlines synthesise what changed, not what was said. Action Items are grouped by owner. Decisions come from the meeting, never from inference.
Append (in memory) a new manifest entry:
{
"id": "<source>_<stable-id>" or "sha256_<hash>",
"source": "read-ai" | "fireflies" | "granola" | "custom" | "source",
"content_hash": "sha256...",
"meeting_title": "...",
"meeting_date": "YYYY-MM-DD",
"ingested_at": "ISO 8601",
"processed_at": "ISO 8601",
"summary_path": "meetings/YYYY-MM-DD/NN-<slug>-summary.md",
"summary_number": NN,
"week_folder": "YYYY-MM-DD",
"attendees": ["..."] or null,
"merged_into": null
}
For the id:
<source>_<stable-id> using the prefix declared in the adapter spec.source/ drops: sha256_<content-hash>.For files merged into an existing meeting (step 4a), the entry sets merged_into to the existing entry's id and summary_path points to the existing summary — no new summary was written.
After each summary lands (or even after a merge that updated an existing summary), update ./meetings/YYYY-MM-DD/weekly-roundup.md for that meeting's week:
roundup-template.md. Header is # Weekly Round-up — Week of <Monday Date>.### Tuesday <DD>). Days with no meetings are omitted; create the day heading if this is the first meeting on that day.If multiple summaries land in the same run, integrate them one at a time in order — the round-up state from summary N feeds into the integration of summary N+1.
Every run, after all summaries are integrated, apply the cleanup pass against the round-up. Per cleanup-discipline.md:
T-Mobile outcome — confirm with John, not T-Mobile probably resolved).source/ to process, re-bucket the Forward Watch List into Tomorrow / Before EOW / Before / Status checks needed / Next week / Deferred-OK. Otherwise skip.Special handling that affects cleanup is in cleanup-discipline.md — read it. Two patterns worth carrying explicitly:
Now — and only now — write ./manifest.json with the accumulated entries. Atomic: read what's currently there (already in memory from step 1), merge the new entries, write the whole file back. Never partial writes.
Emit a concise end-of-run report to the user. The shape depends on what happened:
Sage: no new transcripts.NN-<slug> with its summary path, then point at the round-up path.Example:
Sage: processed 2 new summaries.
- 03-program-marketing-ops-huddle → meetings/2026-06-08/03-program-marketing-ops-huddle-summary.md
- 04-execs-weekly → meetings/2026-06-08/04-execs-weekly-summary.md
Round-up updated: meetings/2026-06-08/weekly-roundup.md
Note: source/q2-execs.txt looks like the same meeting as 04 (date + attendee overlap). Merged into 04.
Quiet runs say so explicitly. Don't invent activity.
These are fixed. They are the rules every plugin in this marketplace has learned to operate by; deviation has cost real time in past builds.
Read, Write, Edit, Glob, Grep only. Never shell out to ls, cat, find, pwd, mkdir. To create a folder, write a file into its path — the folder materialises as a side effect. To check whether a file exists, just Read it and handle a missing-file result.[not specified] or _none this meeting_. Cleanliness through fabrication is the worst failure mode.merged_into field is how you record it; respect it on the next run.Single JSON file at ./manifest.json in the deployment root. One record per processed (or merge-skipped) source artefact.
{
"version": 1,
"entries": [
{
"id": "fireflies_abc123",
"source": "fireflies",
"content_hash": "sha256:...",
"meeting_title": "Program Marketing Ops Huddle",
"meeting_date": "2026-06-09",
"ingested_at": "2026-06-09T09:14:22-07:00",
"processed_at": "2026-06-09T09:14:25-07:00",
"summary_path": "meetings/2026-06-08/03-program-marketing-ops-huddle-summary.md",
"summary_number": 3,
"week_folder": "2026-06-08",
"attendees": ["Alice", "Bob", "Carol"],
"merged_into": null
},
{
"id": "sha256:fedcba...",
"source": "source",
"content_hash": "sha256:fedcba...",
"meeting_title": "Program Marketing Ops Huddle (exported)",
"meeting_date": "2026-06-09",
"ingested_at": "2026-06-09T11:02:08-07:00",
"processed_at": "2026-06-09T11:02:08-07:00",
"summary_path": "meetings/2026-06-08/03-program-marketing-ops-huddle-summary.md",
"summary_number": 3,
"week_folder": "2026-06-08",
"attendees": null,
"merged_into": "fireflies_abc123"
}
]
}
Notes on the schema:
id is unique per artefact, not per meeting. Two artefacts pointing at the same meeting share summary_path and summary_number; the duplicate carries merged_into.source is the producer name: one of the adapter names, or source for source/ drops.content_hash is always present (SHA-256 of the source file bytes). For MCP-pulled entries, it's the hash of the file written into source/ with the envelope.summary_path is relative to the deployment root.week_folder is the Monday-date YYYY-MM-DD string, identical to the directory under meetings/.attendees is the list from the source, verbatim, or null if unknown.merged_into is null for the canonical entry and the canonical entry's id for any merged duplicate.Create-if-absent rule: on first run (file doesn't exist), the in-memory manifest starts as {"version": 1, "entries": []} and gets written out at step 7.
The match rule from step 4a, restated for emphasis. All three must be true:
meeting_date within ± 1 day.If a match is found, the new file does not become a new summary — it becomes a merge record in the manifest pointing at the existing summary. The user is told inline.
Why this matters: the same meeting can arrive twice within a run (MCP pull at 10am, manual export at 11am) or across runs (MCP pull last week, manual export today). The manifest must hold both records so the next run doesn't re-process either. Dropping the duplicate without a manifest entry would mean a third copy gets processed next time.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub kenziecreative/kenzie-creative --plugin sage