pipeline-tracker ยท diff

v0.1.0 to v0.2.0

93 added, 24 removed. Audit A to A.

---
name: pipeline-tracker
- version: 0.1.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. Trigger on /pipeline (add, update, report, 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.
+ 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.
- ## Where the file lives
+ ## The profile (this skill never edits it)
- Use the *Pipeline file path* from `${CLAUDE_PLUGIN_ROOT}/profile/profile.md` (section *Files*). If it isn't set, use `~/Documents/job-search/pipeline.md`. If the user says the file is somewhere else (another folder, Google Drive), use that.
+ 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, sync, bare) and every status question that would print pipeline content:**
+ **On every `/pipeline` invocation (add, update, report, week, sync, bare) and every status question that would print pipeline content:**
- 1. **Read the pipeline file from disk, in full, right now.** Not from context, not from an earlier turn. If it paginates, read every page.
+ 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" ยท {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, read again and re-issue the receipt.
+ `๐Ÿ“‚ 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. **Stated vs. inferred.** A fact the other side actually said (a date, a panel, a number) is logged plainly. A fact you inferred gets a `โ€” confirm` tag until the user or an email confirms it.
+ 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. Writing from a stale copy silently overwrites newer data.
+ - **โ›” 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. A header that disagrees with the entries is a bug; fix it in the same edit.
+ - **โ›” 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. This is the default for cold applies.
+ - **`๐Ÿ“‹ 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.
- Persist it in the entry's `Kind:` field. The field drops off once an entry advances past APPLIED. Any APPLIED entry with no `Kind:` counts as `๐Ÿ“‹ cold` in reports.
+ 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}
+ - **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 next action with a due date.** If the user doesn't give one, propose one (default: follow up in 5 business days for APPLIED, 3 for SCREEN/LOOP).
+ - **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.** When an entry reaches **OFFER**, its next action becomes "Evaluate the offer against your criteria before responding," marked ๐ŸŽฏ everywhere it's shown. Compare against the profile's *Search targets* (level, scope, comp, location, dealbreakers) before recommending a response. Offer excitement doesn't get to skip the diligence.
+ - **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.
- 5. Backfill `Kind: ๐Ÿ“‹ cold` on APPLIED entries missing it; flag any that look like warm or real pursuits so the user can re-tag.
+ 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.
+ 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 log, update stage / last touch / next action. Rejection or withdrawal โ†’ move to CLOSED with reason and date. Write the full file.
+ 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`
- Weekly funnel summary:
- 1. **Funnel counts** by stage. Split APPLIED by Kind, e.g. "APPLIED: 20 (5 ๐Ÿค ยท 3 โญ ยท 12 ๐Ÿ“‹)", so cold volume doesn't inflate the read.
+ 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. Is the funnel growing, converting, or leaking, and where?
+ 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 โš ๏ธ. Rendered after the gate, from the file just read.
+ 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.