pipeline-tracker ยท v0.2.0 ยท 2026-10-08 ยท sha256 dcb31924e94b779c

pipeline-tracker v0.2.0A

Immutable. This exact content never changes and is served at /api/v1/blob/dcb31924e94b779c.

---
name: pipeline-tracker
version: 0.2.0
description: Job-search pipeline CRM. Maintains a single pipeline.md as the source of truth for every company and role: stage, last touch, next action, follow-up date, with an append-only log of every change. Trigger on /pipeline (add, update, report, week, sync, or bare), or whenever the user asks "what's the status of X," "what's in my funnel," "add this to the pipeline," "update [company]," "check my email for interviews/rejections," mentions applying somewhere new, reports interview or recruiter activity that should be logged, or asks what needs follow-up. If a conversation produces a pipeline event (applied, screen booked, rejection, offer), offer to log it even if the user didn't invoke the skill.
---

# Pipeline Tracker

A CRM you'll actually keep: one markdown file, five stages, every entry has a **next action** and a **date**. The point is to make "wait, what's the status of X?" impossible to ask.

## The profile (this skill never edits it)

Facts about the user live in **`~/Documents/job-search/profile.md`**, inside their working folder `~/Documents/job-search/`. It sits outside the plugin on purpose: plugin updates replace the plugin folder, and the profile has to survive them.

- **Read it fresh** before using any fact about the user.
- **Never overwrite, rewrite, reformat, or delete anything in `profile.md`.**
- **If the file doesn't exist:** say so, and offer to create it by copying `${CLAUDE_PLUGIN_ROOT}/profile/profile.TEMPLATE.md` to that path. Do it only after the user agrees, and only if no file is there.
- **If a section this task needs is missing or still `[placeholder]`:** ask for that one section in chat, one at a time, most important first. When the user answers, show the exact text and, only with their OK, append it to the end of `profile.md` as a new top-level section: a `## ` heading with the template's name for it followed by `(added YYYY-MM-DD)`. Append only. If an older placeholder copy of that heading is still in the file, the newest dated copy wins; tell the user they can delete the old one themselves. If they'd rather skip it, carry on without it and say what's missing.

## Where the files live

Everything is in the user's **working folder**: `~/Documents/job-search/`. Below, `<folder>` means that path.

- `<folder>/pipeline.md`: the pipeline. This skill is its only writer.
- `<folder>/pipeline-log.md`: the ships log, one line per write (see *The ships log*).

If the user says the pipeline is somewhere else (another folder, Google Drive), use that and say so once.

## โ›” The read-confirm gate: runs first, every invocation

People often have more than one chat open, and more than one can write to `pipeline.md`. Any copy in this chat's context is stale the moment another chat saves. Rendering from memory puts closed companies back in play and hides follow-ups you owe.

**On every `/pipeline` invocation (add, update, report, week, sync, bare) and every status question that would print pipeline content:**

1. **Read `<folder>/pipeline.md` from disk, in full, right now.** Not from context, not from an earlier turn. If it paginates, read every page. Never treat "not in my context" as "doesn't exist."
2. **Print a one-line read receipt and stop:**
   `๐Ÿ“‚ Read pipeline.md: header "updated YYYY-MM-DD" ยท file mtime {date time} ยท {N} bytes ยท {A} ACTIVE / {C} CLOSED. Is this the latest version?`
3. **Print nothing from the pipeline and write nothing until the user confirms.** "Yes," "go," or equivalent counts. If they say another chat just saved, or the header or mtime looks old to them, read again and re-issue the receipt.
4. Then run the command. Every table, count, and sentence is rendered **from the file just read**. An entry under `## CLOSED` never appears as active. If the conversation and the file disagree, the file wins and you say so.
5. **Same-turn reconciliation.** If a debrief, prep file, or other file written this turn names a follow-up owed (thank-you note, scheduling, materials), the pipeline entry and any "what's owed" summary must carry it too.
6. **Stated vs. inferred.** A fact the other side actually said (a date, a panel, a number) is logged plainly. A fact you or another chat inferred gets a `โ€” confirm` tag until the user or an email confirms it. Never launder an inference into a fact by writing it twice.

If the gate feels slow, it's still faster than acting on a wrong board.

## File rules

- **โ›” Fresh read before any edit.** Always, even if you think you have the file in context. This skill writes, so editing from a stale copy silently overwrites newer data with old data.
- **If the file doesn't exist yet,** create it from the template below.
- **โ›” Bump the header date on every write** to today's date (from the system clock, never a guess). A header that disagrees with the entries below it is a bug; fix it in the same edit.
- **Write the whole file** after every change. The whole file is the database. Re-read the header afterward to confirm the write landed.
- โ›“๏ธ **Append a ships-log line on every write,** in the same operation (see below). A write without a log line is an incomplete write.
- **Never invent or "remember" entries** that aren't in the file or this conversation.

## Stages (fixed vocabulary)

`SOURCED โ†’ APPLIED โ†’ SCREEN โ†’ LOOP โ†’ OFFER`, plus terminal `CLOSED` with a reason (rejected / withdrew / ghosted / not a fit / etc.).

- **SOURCED:** found and worth considering; nothing sent yet.
- **APPLIED:** application or submission sent; no human response yet.
- **SCREEN:** a first conversation is booked or held (recruiter or first call).
- **LOOP:** hiring-manager, panel, or later rounds.
- **OFFER:** an offer, or a conversation shaping the role and terms.

## Kind field (not all applications are equal)

Warm channels (referrals, inbound recruiters, someone you know) usually convert far better than cold applications. Tag APPLIED entries so the funnel reads honestly:

- **`๐Ÿค warm`**: referral, inbound recruiter, or a real contact at the company.
- **`โญ real pursuit`**: a cold application the user genuinely wants and will chase.
- **`๐Ÿ“‹ cold`**: a cold application with no contact and no particular intent, including any logged mainly to meet a work-search requirement (see the profile's *Search context*). This is the default for cold applies.

Rules:
- Persist it in the entry's `Kind:` field so a fresh session reads it instead of re-guessing. The field drops off once an entry advances past APPLIED.
- **Missing-`Kind:` fallback:** an APPLIED entry with no `Kind:` counts as `๐Ÿ“‹ cold` everywhere, so the split always sums to the APPLIED total.
- Use the profile's *Channels that work for you* and *Warm-channel-only* sections when choosing a default.

## pipeline.md format

```markdown
# Job Search Pipeline โ€” updated YYYY-MM-DD

## WEEKLY METRICS

| Week ending (Sun) | Active | Mid | Offer | Loop | Screen | Applied | New | Closed | Advances |
|---|---|---|---|---|---|---|---|---|---|

### Week notes
- YYYY-MM-DD: {what moved, what died, what the numbers hide}

## ACTIVE

### {Company} โ€” {Role title}
- **Stage:** SCREEN
- **Kind:** {๐Ÿค warm | โญ real pursuit | ๐Ÿ“‹ cold} (APPLIED only)
- **Contact:** {name, title, email/LinkedIn if known}
- **Source:** {inbound recruiter / applied on site / referral from X / networking โ€” see network.md: {Name}}
- **Comp/Location:** {what's known; โ“ if location unconfirmed}
- **Last touch:** YYYY-MM-DD: {what happened}
- **Next action:** {specific act}, **due YYYY-MM-DD**
- **Log:**
  - YYYY-MM-DD: {event}

## CLOSED

### {Company} โ€” {Role} โ€” CLOSED YYYY-MM-DD ({reason})
- one-line post-mortem if useful
```

Rules:
- **Every ACTIVE entry has a `Stage:` line and a next action with a due date.** If the user doesn't give a next action, propose one (default: follow up in 5 business days for APPLIED, 3 for SCREEN/LOOP).
- Sort ACTIVE by stage (furthest along first), then by due date.
- One terse sentence per log line. It's a CRM, not a journal.
- โ“ on Comp/Location means the location question hasn't been answered yet; that's a next action by definition.
- **Stale rule.** An APPLIED entry more than 30 days old with no response gets `**Stale:** yes โณ`, stays ACTIVE (never auto-closed), and its next action becomes "Decide: nudge through network or close." A reply, screen, or rejection clears it.
- **Offer rule (due-diligence gate).** The moment an entry reaches **OFFER**, or a company opens a role-shaping or terms conversation, its next action becomes "Check the offer against your go/no-go gates before responding," marked ๐ŸŽฏ in every readout. Read the profile's *Offer go/no-go gates* and list them. If that section is empty, stop and ask the user to write their gates first; diligence written in the heat of an offer is worse than none. If any gate fails, the recommended next action is renegotiate or walk, logged plainly.

## โ›“๏ธ The ships log: one line on every write

`pipeline.md` has no version history of its own, so without a log the only record of a past state is whatever backup someone happened to leave. The ships log is the fix.

**`<folder>/pipeline-log.md`.** Append-only. One line per write to `pipeline.md`, appended in the same operation as the write, never as a follow-up someone might forget. Create it if it doesn't exist.

1. **Compute the counts with the script, never by hand.**
   `python3 ${CLAUDE_PLUGIN_ROOT}/skills/pipeline-tracker/count-pipeline.py <folder>/pipeline.md "what changed" --log <folder>/pipeline-log.md`
   prints the line and appends it. (Without `--log` it only prints.) Hand counts drift: an entry missing its `Stage:` field silently drops out, and deduping by company name swallows entries.
2. **Append-only, forever.** Never edit, delete, or reorder a line. A wrong line gets a **correction line appended below it.** The log records what was believed at the time.
3. **The note is the value.** Say what moved and what it means, in one sentence. "updated pipeline" is a wasted line.
4. **Heed the `NO-STAGE` warning.** When the script prints one, MID in that line is short by the entries it names. Fix the entry's `Stage:` field, then append a correction line.
5. **If the log has no line for the previous write** (another session skipped it), say so in the new line rather than papering over it. A gap is data.

### Why MID is the headline number

**MID = OFFER + LOOP + SCREEN.** It counts live processes, and it's the figure that tracks with getting an offer. Cold application volume can grow tenfold without moving it. **Any readout that reports application volume without MID beside it is telling the flattering half of the story.**

## `/pipeline week`: the weekly review (writes a metrics row)

Once a week (Sunday night is the default cadence), append one row to `## WEEKLY METRICS` and one line under `### Week notes`.

- **Key the row to the Sunday that ended the week it covers,** never the day it was written. Mark a late or reconstructed row `~`.
- **Fill gaps from the ships log:** for a missed week, use the last log line before that Monday. Mark it `~`.
- The read-confirm gate and the header-date bump apply.
- โ›” **Never overwrite an existing row.** Correct it with a footnote instead.
- **Advances** counts stage changes on entries present at both ends of the week. It undercounts motion (an entry that arrives already mid-funnel, or a big round held without a stage change), so **read the week's log lines before writing the note.**
- **Show the user** both weeks side by side, the deltas, and an honest read: is MID growing, flat, or shrinking, and why? A week with ten new cold applications and a shrinking MID is a worse week than it feels like, and saying so is the job.

## Gmail sync (`/pipeline sync`)

When a Gmail connector is available, reconcile the pipeline against the inbox. Default lookback is **2 weeks** unless the user says otherwise.

1. Search for job-signal mail: screen or interview invites, "next steps," availability requests, calendar invites, and rejections ("unfortunately," "not moving forward," "other candidates," "position has been filled").
2. Classify each real hit and reconcile:
   - **Interview / screen invite** โ†’ advance to SCREEN (recruiter or first call) or LOOP (hiring manager / panel); log the date.
   - **Rejection** โ†’ move to CLOSED (rejected) with the date.
   - **Recruiter reply** โ†’ log it; advance only if it clearly implies a round.
   - **A company not in the pipeline** โ†’ add it at the stage the email implies.
3. **Ignore noise:** application auto-acknowledgements, job-board digests, newsletters, receipts. An auto-ack alone doesn't change a stage.
4. Only assert what the email says. Ambiguous items get logged with `โ€” confirm`, not a stage change. Never attribute an email to a company by inference alone; two processes can share a coordinator or a week.
5. Backfill `Kind: ๐Ÿ“‹ cold` on APPLIED entries missing it; flag any that look warm or like real pursuits so the user can re-tag.
6. **Show the user the findings and the exact changes before overwriting the file.**

## Commands

### `/pipeline add {details}`
Create an entry from what the user gives plus conversation context. Ask only for what's missing (usually the next action). Set `Kind:` for APPLIED entries. Write the full file and log the line.

### `/pipeline update {company} {what happened}`
Fuzzy-match the company, append to the entry's log, update stage / last touch / next action. Rejection or withdrawal โ†’ move to CLOSED with reason and date. Write the full file and log the line.

### `/pipeline report`
Read `## WEEKLY METRICS` and the ships log first, and state week-over-week from them rather than reconstructing.
1. **Funnel counts** by stage, with **MID** first. Split APPLIED by Kind, e.g. "APPLIED: 20 (5 ๐Ÿค ยท 3 โญ ยท 12 ๐Ÿ“‹)". If any APPLIED entries have no `Kind:`, say how many and suggest `/pipeline sync` to backfill.
2. **Stale and overdue:** stale entries, anything with a past-due next action, anything untouched for 7+ days, each with a one-line suggested nudge.
3. **This week's actions:** every next action due in the next 7 days, by date.
4. **Momentum:** one honest paragraph led by MID and its trend. Compare to the profile's *Weekly targets* if set.

### `/pipeline` (bare)
A compact table (Company | Role | Stage | Next action | Due) of ACTIVE entries, sorted by due date, overdue marked โš ๏ธ, OFFER marked ๐ŸŽฏ. Rendered after the gate, from the file just read.

## Related skills

- **pipeline-overview:** the read-only glance at this file. It never writes.
- **network-tracker:** owns `network.md` (people, not roles). When a network contact produces a role, add it here with **Source:** `networking โ€” see network.md: {Name}`.
- **interview-debrief / interview-prep:** write `debrief-*` and `prep-*` files in the working folder; follow-ups they name must also appear in the entry here.

## Voice

Terse. No cheerleading. A rejection gets logged like weather. When something is stale or leaking, say so plainly. The value of this skill is that nothing rots silently.

## Changelog

- **0.2.0**: Added the ships log (`pipeline-log.md` plus `count-pipeline.py`), MID as the headline number, `/pipeline week` with a weekly metrics table, stage definitions, same-turn reconciliation, and offer gates read from the profile. Paths now come from the profile's working folder. The profile lives at `~/Documents/job-search/profile.md`, outside the plugin; this skill never edits it and asks for missing sections one at a time.
- **0.1.0**: Initial community release.