Files
santifer--career-ops/modes/titles.md
T
wehub-resource-sync d083df1fdb
CodeQL Analysis / Analyze (javascript-typescript) (push) Failing after 2s
Web CI / web typecheck + build (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
CodeQL Analysis / Analyze (go) (push) Failing after 16s
chore: import upstream snapshot with attribution
2026-07-13 12:02:43 +08:00

126 lines
6.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Mode: titles — Adjacent Job-Title Suggestions
## Purpose
The scanner only surfaces what `portals.yml` `title_filter.positive` matches —
and that list is written from the titles the user already knows to search for.
The same job ships under many names (Solutions Architect / Forward Deployed
Engineer / Customer Engineer), so the search is silently narrower than the CV
justifies. This mode reads the CV and proposes adjacent titles the user isn't
searching for yet — then, only after explicit confirmation, writes the accepted
keywords into `title_filter.positive` so the very next `scan` casts the wider net.
`patterns` Step 1b makes the same kind of retargeting recommendation
("consider adding archetype X and reweighting `portals.yml`
`title_filter.positive`"), but only after ≥5 progressed applications and only
from interview-session signal. This mode is the day-zero, CV-driven complement.
It is also the inverse of `upskill`: upskill finds skills missing for current
targets; this finds new targets reachable from current skills.
## Inputs
- `cv.md` — the **only** source of evidence for suggestions (required)
- `config/profile.yml``archetypes` (name / level / fit) for what's already targeted
- `modes/_profile.md` — target roles, framing, and any deal-breakers the user has recorded
- `portals.yml` — the current `title_filter.positive` (and `negative`) keywords
- Optional: if `data/applications.md` has ≥5 entries progressed beyond
`Evaluated`, note which suggestions the outcome data supports (e.g. an axis
that is already converting) — cross-reference `patterns` rather than
duplicating its analysis.
## The Three Axes
Classify every suggestion on exactly one axis, and say which:
- **Lateral** — same work, different label. The core recall win: the user
already does this job; the market just posts it under a name they don't
search for.
- **Stretch** — one level up or larger scope than the CV's strongest evidence.
Plausible, but a hiring manager would probe the gap.
- **Pivot** — an adjacent function reachable from existing CV evidence
(e.g. heavy client-facing delivery work → pre-sales engineering).
## Output Contract (per suggestion)
For each suggested title, show exactly:
- **Title** — the market title as actually posted, not an invented hybrid
- **Axis** — Lateral / Stretch / Pivot
- **CV evidence** — 12 lines from `cv.md` **quoted verbatim**. If you cannot
quote it, do not suggest it.
- **Honest gap note** — what a hiring manager would question; "none" is allowed
for Lateral suggestions but must be earned
- **Market-reality note** — how common the title is, where it tends to be
posted, seniority skew, or noise level
Aim for 510 suggestions, Lateral first. Fewer good suggestions beat a padded
list — this system optimizes for quality, not quantity.
## Filters (apply BEFORE showing suggestions)
1. **Dedup against existing coverage.** Mirror the matcher semantics in
`scan.mjs` (`buildTitleFilter` / `compileKeyword`): the scanner lowercases
both sides and keeps a job when any positive keyword is a
case-insensitive substring of the title (23 letter keywords match on word
boundaries instead). So drop any candidate title that an existing positive
keyword already substring-matches — it is already covered, and suggesting
it adds zero new recall.
2. **Deal-breaker filter.** Never suggest titles that violate the
deal-breakers recorded in `modes/_profile.md` (e.g. "no people management"
rules out Engineering Manager; "no on-site" rules out field roles). Titles
matching `title_filter.negative` keywords are also off the table — the user
already excluded them.
3. **Never invent experience.** Every suggestion must be traceable to quoted
`cv.md` lines — the source-of-truth boundary applies to suggestions exactly
as it does to CV content. Keywords get reformulated, never fabricated. If
the evidence isn't in `cv.md`, ask the user; don't stretch a quote to fit.
## Confirm Gate — Writing Accepted Titles (HARD RULE)
When the user accepts one or more suggestions:
1. Derive **keywords, not raw titles**. The filter matches substrings, so the
keyword should be the shortest phrase that still identifies the role family
("Forward Deployed" covers Forward Deployed Engineer/Architect/Lead).
2. Attach a **breadth warning** to any substring-dangerous keyword: because
matching is substring-based, a short or generic keyword floods the scan.
Propose "Solutions Architect", never bare "Architect" — bare "Architect"
would also match Data Architect, Enterprise Architect, Security Architect.
If the user insists on a broad keyword, warn once and comply.
3. Skip keywords that duplicate existing coverage (same dedup rule as above);
preserve the casing style already used in the user's `portals.yml`.
4. Show the **exact YAML diff** against `portals.yml` `title_filter.positive`
before touching anything.
5. **Never write to `portals.yml` without explicit user confirmation.**
"Show me the diff" is not a yes. Silence is not a yes.
6. `portals.yml` (user layer) is **the only file this mode writes by
default**. This mode proposes no negative keywords — precision guards for
noisy keywords are deferred to #1353's seniority-tier helper.
7. **Separately-confirmed exception:** accepted titles can additionally become
`fit: adjacent` archetypes in `config/profile.yml` (an existing schema
field — see `config/profile.example.yml`). Mention that this is possible,
but do it **only if the user asks** — never write archetypes by default.
When the user does ask, that write gets its **own YAML diff and its own
separate confirmation**; never bundle the `portals.yml` and
`config/profile.yml` writes into one confirmation.
## After the Write
- Suggest `/career-ops scan` — the wider filter only pays off on the next scan.
- Suggest `upskill` scoped to a Stretch title the user liked, to see the gap
map between the CV and that next-level target.
## Error Handling
- `cv.md` missing → stop and point at onboarding (`node doctor.mjs --json`).
There is no evidence base to suggest from, and inventing one is forbidden.
- `portals.yml` missing, or `title_filter.positive` empty → offer to create it
from `templates/portals.example.yml` first, then re-run this mode. (An empty
positive list means the scanner matches everything — nothing to broaden.)
- `config/profile.yml` or `modes/_profile.md` missing → **hard stop**: do not
generate suggestions. Point at onboarding (`node doctor.mjs --json`) and
stop, then re-run this mode once both files exist — the same
fix-first-then-re-run behavior as a missing `portals.yml` above.
Deal-breakers live in `modes/_profile.md` — suggestions generated without
them can propose exactly what the user excluded.