diff --git a/.claude/commands/apply.md b/.claude/commands/apply.md index 2a8ffc6..0505d57 100644 --- a/.claude/commands/apply.md +++ b/.claude/commands/apply.md @@ -311,6 +311,33 @@ List the files written: Tell the user: "Both files are ready for your review. Open them to check the final output before compiling." +### Step 6b: Record the Application + +Do this before the optional offer below, and before ending the turn for any other reason. + +1. Read `job_search_tracker.csv`. If it does not exist, create it with the standard header (identical to `/outcome` Step 1.1, so the two commands never diverge): + ``` + date,company,sector,role,role_type,channel,status,contact_person,fit_rating,notes,cv_file,cover_letter_file,source + ``` +2. Match existing rows case-insensitively on company and role. **On no match, or when every match holds a final status, append a new row. On a match that is still open, update it.** When you append alongside a final row, say so — the earlier application to that role keeps its own row and its own outcome. +3. Values for a new row: + + | Column | Value | + |---|---| + | `date` | today | + | `status` | `drafted` | + | `fit_rating` | the overall score from Step 1 as a bare number, 0-100 — never `XX/100` or a verdict word, since `/upskill` does arithmetic on this column | + | `cv_file`, `cover_letter_file` | the two paths listed under "Files Created" above | + | `source` | the posting URL from `$ARGUMENTS`, empty when the posting was pasted as text | + | `channel` | `portal` when the posting came from a job portal, `online` for a company careers page, empty when unknown | + | `sector`, `role_type`, `contact_person` | from the posting when it states them, empty otherwise | + +4. **Updating an open row: never move it backwards.** Refresh `cv_file`, `cover_letter_file`, `fit_rating` and `source`, and append an undated `redrafted` marker to `notes` (undated deliberately — `/outcome` reads the latest *dated* note as the last contact with the employer, and re-drafting a CV is not that). Leave `status` alone, and leave `date` alone unless the status is still `drafted`, in which case it becomes today. +5. Never restructure the CSV, reorder rows, or touch other rows. +6. **Do not modify `job_scraper/seen_jobs.json`.** Dedup runs off the tracker instead: `/rank` builds its exclusion set from company+role there regardless of status. + +Name the tracker row in the "Files Created" report above. + ### Application-Form Fields (Optional Third Artifact) Check whether the posting or the portal it came from asks for free-text fields the CV and cover letter don't cover — a self-introduction paragraph, structured project entries, a character-limited pitch, or a motivation/competency question under a word cap (see `.claude/skills/job-application-assistant/08-application-forms.md`, "When this applies"). If it does, or the user has already mentioned the portal, offer it in the same turn: @@ -320,5 +347,5 @@ Check whether the posting or the portal it came from asks for free-text fields t **Only on yes**, read `08-application-forms.md` and draft the fields per its rules, grounded against the same three-source union as the CV and cover letter. Save per that file's "Output format" section. **On no, or when the posting has no such fields, say nothing further and move on** — this is an optional addition and never changes the default two-document output. ### Next Steps -- **Submitted?** `/outcome ` logs it in the tracker and starts the per-application record that `/setup` later uses to calibrate the fit framework. +- **Submitted?** `/outcome ` moves the `drafted` row to `applied` and starts the per-application record that `/setup` later uses to calibrate the fit framework. - **Interview scheduled?** `/interview` builds a stage-specific prep pack from this posting and the documents you just created. diff --git a/.claude/commands/gmail-sync.md b/.claude/commands/gmail-sync.md index 8ee59e7..26ebbf5 100644 --- a/.claude/commands/gmail-sync.md +++ b/.claude/commands/gmail-sync.md @@ -29,6 +29,8 @@ Confirm the Gmail MCP tools (`mcp__claude_ai_Gmail__*`) are available. If not, t 1. Read `job_search_tracker.csv`. If it does not exist, tell the user there is nothing to sync against yet (suggest `/outcome` or `/apply` first) and stop. Do not create it here - `/gmail-sync` never originates new applications, only updates existing ones. 2. Read `gmail_sync/state.json` (create if missing: `{"last_sync": null, "processed_message_ids": []}`). 3. Build the set of **open applications**: tracker rows whose `status` is not a final value (`hired`, `rejected`, `no response`, `offer declined`, `withdrawn`). For each, derive its archive folder `documents/applications/_/` (lowercase, underscores - same convention as `/outcome`) and check whether `outcome.md` exists there. + + **`drafted` rows stay in this set, and are the reason it is worth searching.** `/apply` writes them but never submits; the user submits by hand and may not think to run `/outcome`. A reply arriving against a row still marked `drafted` is exactly that case, and the row holds the company name the search needs. 4. If `$ARGUMENTS` named a company, filter this set to the matching row(s) (case-insensitive). No match → tell the user and stop, do not guess. --- @@ -66,7 +68,7 @@ For a matched message, classify by content (require the signal phrase in the sub | Signal | Example phrasing | Tracker `status` | `outcome.md` action | |---|---|---|---| -| Application ack | "we've received your application" | *(no change)* | *(no change - not a status signal, just noise)* | +| Application ack | "we've received your application" | `drafted` -> `applied`, otherwise *(no change)* | On a `drafted` row this is the one email that proves the user submitted by hand, and it arrives within a day of them doing so - propose the move with `date` set to the email's date. On any other status it is noise. | | OA / assessment | "online assessment", "coding challenge", "complete your assessment", HackerRank/Codility links | `interview` | Tick nearest matching stage checkbox (or add a Notes line if no checkbox fits - assessments aren't always a listed stage) | | Interview invite/scheduled | "schedule a call", "phone screen", "technical interview", "next round", "onsite", "final round" | `interview` | Tick the matching stage checkbox with the email's date | | Offer extended | "pleased to offer", "extend an offer", "offer letter" | `offer` | Tick "Offer received" checkbox. **Never propose `hired` or `offer_declined` from an email** - accepting or declining is the user's decision, not something to infer. Flag prominently in the Step 6 summary as needing the user's decision, separate from the plain approve/skip table. | @@ -90,6 +92,9 @@ Scanned N threads (M new messages) since . |---|---|---|---|---|---| | 1 | ... | ... | Interview invite | applied -> interview | "Subject line" (2026-07-10) | | 2 | ... | ... | Offer extended | interview -> offer | "Subject line" (2026-07-12) | +| 3 | ... | ... | Application ack | drafted -> applied, date -> 2026-07-02 | "Subject line" (2026-07-02) | + +A row leaving `drafted` shows its date change in the status cell, as row 3 does: that row was never recorded as submitted, so Step 7a is about to replace the drafting date. Say that the date is taken from the email and ask whether the user knows the real submission date - approving the status move should not silently approve a date they can correct. ### Needs Manual Review (conflicting signal - not proposed, use /outcome) - **** - @@ -120,11 +125,13 @@ Approving the whole batch in one reply is expected UX - the requirement is that For every row the user approved: 1. **Tracker (`job_search_tracker.csv`):** update the matched row's `status` column per the Step 5 table, and append to `notes`: ` gmail-sync: ("")`. Never restructure the CSV, reorder rows, or touch unrelated rows - same rule `/outcome` follows. + + **If the matched row was still `drafted`,** also set `date` to the email's date. The employer replying proves the user submitted by hand without running `/outcome`, so the drafting date now in that column is wrong. The email's date is an upper bound on the real submission date, tight for an ack and loose for a rejection weeks later, which is why Step 6 shows it and lets the user supply the actual date instead. 2. **`outcome.md`:** tick the relevant stage checkbox (adding the date in parentheses) or update `Status`/`Date resolved` per the table. Append a dated entry to `## Notes`, never overwrite existing Notes history: ``` YYYY-MM-DD (via /gmail-sync): . Source: "" from , . ``` -3. If no archive folder/`outcome.md` exists yet for a matched application (it was added to the tracker outside `/apply`/`/outcome`), create the folder and a minimal `outcome.md` following the exact format in `documents/README.md`, same as `/outcome` would. +3. If no archive folder/`outcome.md` exists yet for a matched application, create the folder and a minimal `outcome.md` following the exact format in `documents/README.md`, same as `/outcome` would. This is the normal case for a row that was still `drafted`: `/apply` Step 6b writes the tracker row and only `/outcome` Step 3 ever creates the archive, so the folder legitimately does not exist yet. It is also the case for a row added by hand. Rows the user skipped are left untouched - no tracker write, no `outcome.md` write - but their message IDs are still marked processed in Step 8, so the same email isn't re-proposed every run. @@ -140,6 +147,8 @@ Add every message ID processed this run - approved, skipped, unmatched, or filte For open applications with **no** matching activity found this run, check the tracker's `date` column and the most recent dated Notes entry in their `outcome.md`. If the most recent of those is 30+ days old, flag the application as "needs follow-up" in the closing summary below. This is surfaced only - never write anything for staleness. +**Skip `drafted` rows here** - nothing was sent, so no one is late replying. + --- ## Step 10: Present Closing Summary diff --git a/.claude/commands/html-report.md b/.claude/commands/html-report.md index 7fff2e4..78bb4a2 100644 --- a/.claude/commands/html-report.md +++ b/.claude/commands/html-report.md @@ -21,7 +21,8 @@ Read in parallel: 2. **`documents/applications/*/outcome.md`** — for each resolved application, read the outcome file to get the exact interview stages reached (the checkboxes) and any notes. Merge this into the matching tracker row by company+role fuzzy match (lowercase, ignore punctuation). If an archive exists for a row but there is no match, attach it as extra context anyway. -Status normalisation — map tracker values to five canonical buckets before computing stats: +Status normalisation — map tracker values to six canonical buckets before computing stats: +- `drafted` → **Drafted** (documents written by `/apply`, not yet submitted) - `applied` → **Active** (resume submitted, no further signal) - `interview` → **Interview** - `offer` → **Offer** @@ -34,10 +35,12 @@ Status normalisation — map tracker values to five canonical buckets before com From the normalised data compute: +**Drafted rows are excluded from every statistic below** — they were never submitted. Report the Drafted count on its own, and include it only in the status breakdown. + - **Total applications** - **By status bucket:** count per bucket - **By sector:** count per unique sector value -- **By channel:** online vs referral vs other +- **By channel:** portal vs online vs referral vs other - **By year/season:** group by the `date` field (which may be a year like `2025` or a full date) - **Funnel rates:** what % progressed past resume screen (reached Interview or beyond) - **Rejection rate:** Rejected/Closed ÷ Total with a resolved status (exclude Active) @@ -55,10 +58,10 @@ Write a single self-contained HTML file. All CSS is inline in a `