Claude
Skills
Sign in
Back

pm-notes

Included with Lifetime
$97 forever

Use this skill when the user shares a meeting transcript or notes file and wants to know which tracked issues the conversation touched, what adjustments the discussion implies (status changes, scope updates, comments, sub-issues, closures, pivots), or wants a markdown report of proposed changes. Trigger phrases "process this meeting", "any updates from this transcript", "match this transcript to issues", "review my meeting notes", "summarize what we decided", "/pm-notes". Also use proactively when the user shares a .srt, .vtt, or transcript-shaped markdown file in a context where the project-manager plugin is configured — even if they don't explicitly ask for adjustment matching, offer to run pm-notes against it. Builds on the existing project-manager workspace cache, dedup.js similarity, and conventions/vocabulary customization layers. After rendering the dry-run report it can optionally apply accepted proposals (Step 8) by routing each through the existing pm-* verbs/skills, with provenance stamped on every write.

Writing & Docs

What this skill does


# PM Notes — Transcript-to-Issue Matching

Turn a meeting transcript or a notes file into a **dry-run markdown report** of proposed tracker adjustments — which tracked issues the conversation touched, what status changes the discussion implies, what scope updates were called out, what comments / sub-issues / closures / pivots are warranted. The report itself **writes nothing**; it is a proposal. The operator then decides what to apply, and **Step 8** carries each accepted proposal into the existing pm-* verbs/skills (with provenance stamped on every write). Nothing reaches the tracker without the operator picking it.

The pipeline is deterministic for the easy cases and asks Claude for help only where the deterministic pass is structurally unable to decide — paraphrased references ("the Polaris work", "what Megan flagged") and judgment-call action items. Everything else runs without LLM involvement, so the report is reproducible across runs.

## When to run this

- The user shares a `.srt`, `.vtt`, transcript-shaped `.md`, or a meeting-notes file and asks anything resembling "what's in here?" or "what should I update?"
- The user says "process this meeting", "any updates from this transcript", "match this transcript to issues", "review my meeting notes", "summarize what we decided", or invokes `/pm-notes`.
- **Proactive trigger.** The user drops a transcript file path into the conversation in a repo where `.claude/pm/project.json` exists, even if they don't explicitly ask. Offer: *"Want me to run pm-notes against that and surface any tracker adjustments it implies?"*

## When NOT to run this

- The user wants a project status briefing → route to `pm-status`.
- The user wants to file a new issue from scratch (not from a transcript) → route to `pm-issues`.
- The user wants to enrich a specific issue with repo context → route to `pm-improve`.
- The user wants to send a handoff brief to a teammate → route to `pm-handoff`.
- The transcript is a live stream / not yet a file — pm-notes is file-input only.
- The conversation is purely social with no tracker-relevant content — running pm-notes will produce an empty report, which is fine, but don't burn cycles on it if you can tell up front.

## How it works — six stages, two Claude-in-the-loop boundaries

1. **Ingest.** Read the file, autodetect format (SRT / VTT / markdown), reassemble into `{ speaker, timestamp, text }` segments.
2. **Pass 2a — deterministic extraction.** Walk the segments with regex + customization cues and emit `issue-ref`, `decision`, `action-item`, `scope-change`, `close-signal`, `pivot-signal` units. Segments with weak signal but enough length are **flagged for Pass 2b**.
3. **Pass 2b — Claude-in-the-loop refinement (boundary 1).** Read the flagged segments + the project's vocabulary / patterns prose, decide whether each is a paraphrased reference or a judgment-call unit, write `pass2.json`, and re-run the CLI.
4. **Match.** Each unit's text is scored against the workspace cache via `dedup.js` (body-aware weighting, containment, bigrams), banded into **confirmed** (≥ `pm_notes.confidence_threshold`) and **near-miss** (grey zone) candidates.
5. **Rerank — Claude-in-the-loop precision pass (boundary 2, Phase B).** The matcher hands its banded candidates back; Claude judges each (unit, candidate) pair (`related` + calibrated `confidence`), promoting real grey-zone links and rejecting keyword coincidences. Confirmed matches feed the self-teaching vocabulary loop. Skippable via `pm_notes.rerank: false`.
6. **Classify + render.** Each surviving matched unit becomes an adjustment proposal (`status-change`, `comment`, `sub-issue`, `close`, `scope-update`, `pivot`), rendered into one markdown report grouped by tracked issue, with `Near-misses` and `Unmatched units` tails.

### Speaker attribution (roster-aware)

Each segment carries a `speaker` string. During extraction, pm-notes resolves it to a **team-roster member** via `team-roster.resolveSpeaker` (full name / alias / unique first name), falling back to the manual `pm_notes.speaker_identities` map in `project.json` (the roster wins when both match). The resolved `@handle` rides each discussion unit through classification, and the report attributes proposals with **"— raised by @handle"**. This degrades silently: if the roster isn't synced (`/pm-team sync` hasn't run) and no `speaker_identities` entry matches, attribution is simply omitted — nothing breaks. Run `/pm-team` to populate the roster and `/pm-proficiency` to enrich it. Troubleshooting: if attribution is missing despite a populated `team.md`, confirm `/pm-team sync` ran under this project's slug — pm-notes reads the roster cache keyed by `project.json`'s `slug`, so a `team-sync` invoked with a different `--slug` (e.g. the `org/repo` key) writes a `team.json` pm-notes won't find.

## How to run pm-notes

### Step 1: Confirm the transcript path

The user should give you a path on disk. If they paste the transcript inline, save it first so the CLI has a file to read:

```bash
cat > "$CLAUDE_JOB_DIR/notes-input.md" <<'EOF'
<pasted transcript content>
EOF
```

Pick the extension that matches the content — `.srt` for SubRip, `.vtt` for WebVTT, `.md` otherwise. The ingest stage autodetects from content shape, not extension, but a sane extension keeps the report header readable.

### Step 2: Invoke the deterministic pipeline

```bash
node "$CLAUDE_PLUGIN_ROOT/hooks/bin/pm-notes.js" report \
  --file <transcript-path> \
  [--scope <expr>] \
  [--save <output-path>]
```

Default scope is `team:<linear_team_key>` from `project.json` (filled in by `pm-setup`). Pass `--scope` to override — same grammar as `pm-status` / `pm-report`:

| Form | Meaning |
|---|---|
| `team:ENG` | Issues with team_key == ENG |
| `team:ENG+subteams` | ENG + transitive children |
| `project:CHK` | One project |
| `workspace:nthplus` | Whole workspace |
| `all` | Every known workspace |

The CLI:
- Writes the rendered markdown report to **stdout**.
- Writes status / Pass 2b handoff hints to **stderr**.
- Exits non-zero on hard failures, emitting `{ ok:false, error, code, hint? }` JSON on stderr.

If you see `CACHE_STALE` in the error, run `/pm --refresh` first — the matcher reads the workspace cache and refuses to guess against a missing one.

### Step 3: Handle Pass 2b refinement (if needed)

If stderr contains a line like:

```
pm-notes: 3 segment(s) flagged for Pass 2b refinement
pm-notes: wrote handoff -> /tmp/notes/pass1.json
pm-notes: invoke the pm-notes SKILL to draft Pass 2b units, then re-run with --pass2-file <path>
```

…the deterministic pass found segments that look like they might contain a paraphrased reference or a judgment-call unit but couldn't decide on its own. **This is the load-bearing Claude-in-the-loop step.** Don't skip it — the deterministic units are already in the report, but paraphrased references like *"the Polaris work"* will only be matched if you emit them here.

**3a. Read the handoff.**

```bash
cat "$CLAUDE_JOB_DIR/notes/pass1.json"   # or /tmp/notes/pass1.json if CLAUDE_JOB_DIR unset
```

The shape is:

```json
{
  "flaggedSegments": [
    { "index": 5, "segment": { "speaker": "Megan", "timestamp": "00:04:12", "text": "..." } },
    ...
  ],
  "vocabularyProse": "...free-form context from conventions.md ## Notes vocabulary...",
  "patternsProse": "...free-form context from conventions.md ## Notes patterns...",
  "pass2OutputShape": {
    "additionalUnits": "[ { kind, sourceSegmentIdx, sourceExcerpt, extractedText, refCandidate } ]"
  }
}
```

**3b. Read `vocabularyProse` first.** This is the project's hand-tuned context for paraphrased-ref matching. If it says *"Polaris = operations-adjustment work, see issues tagged finance:polaris"*, then a flagged segment mentioning "Polaris" is a strong `paraphrased-ref` candidate — emit it. If `vocabularyProse` is empty, you're working from general English knowledge; be more conservative.

**3c. For each flagged segment, decide.**

| Questio

Related in Writing & Docs