From afdef7eb94bc864395fc63f5e72de9f89f8990ff Mon Sep 17 00:00:00 2001 From: Dennis Thiessen Date: Fri, 11 Sep 2026 19:23:13 +0200 Subject: [PATCH] refactor: shared agent workflows (F01) and one application registry (F02) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checkpoint for the 2026-09-11 project review remediation. Work by a previous agent that ran out of context; committed unchanged as a rollback point before the remaining findings land on top of it. F01 — the two agent directories had drifted into different processes - Six shared workflows under `resume_builder/reference/workflows/`; both `.agents/skills/` and `.claude/skills/` are now thin entrypoints that route to the same file instead of carrying full copies. - The `.claude` copies were the stale side: fixed bullet line variants and character budgets that the newer process had already dropped. Removing them is the point of the change, not collateral. - Parity/link regression test added; corrected the top-level file map, which pointed at a directory that does not exist. F02 — application status was stored in four places and disagreed - New registry `job_scout/state/applications.json` (26 records) with `application_records.py` (schema, validation, transactional write) and `application_tracker.py` (add/update, no quota). - AGENTS.md and CLAUDE.md no longer keep their own status tables; both point at `current_applications.md`, generated from the registry. - Interview-reached is now independent of outcome, so a withdrawal after an interview can record both facts. The old tables are preserved verbatim in `job_scout/state/history/application_status_before_2026-09-11.json`. Verification: `python -m unittest discover -s tests -v` — 18/18 pass. Spot-checked that the detailed SBB and Kdo Cy notes survived the migration into the registry rather than being flattened. Known unresolved, deliberately not invented (see FIX_PROGRESS.md): Aker BP's application date and its Adjacent-despite-79 classification. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Qej5SBvhcbGXHwLqNWiskM --- .agents/skills/critique/SKILL.md | 58 +-- .agents/skills/edit-resume/SKILL.md | 47 +-- .agents/skills/make-cl/SKILL.md | 41 +- .agents/skills/make-resume/SKILL.md | 86 +--- .agents/skills/setup-build-kb/SKILL.md | 78 +--- .agents/skills/setup-extract/SKILL.md | 97 +---- .claude/skills/critique/SKILL.md | 110 +---- .claude/skills/edit-resume/SKILL.md | 207 +--------- .claude/skills/make-cl/SKILL.md | 141 +------ .claude/skills/make-resume/SKILL.md | 240 +---------- .claude/skills/setup-build-kb/SKILL.md | 328 +-------------- .claude/skills/setup-extract/SKILL.md | 169 +------- AGENTS.md | 32 +- CLAUDE.md | 37 +- FIX_PROGRESS.md | 47 +++ README.md | 16 +- job_scout/state/application_cohort.json | 297 ++++++++++---- .../project_review.tex | 383 ++++++++++++++++++ resume_builder/helpers/application_records.py | 140 +++++++ resume_builder/helpers/application_tracker.py | 117 ++++++ resume_builder/helpers/cohort_tracker.py | 82 +--- .../helpers/validate_resume_system.py | 2 +- .../reference/application_strategy.md | 4 +- .../reference/current_applications.md | 41 ++ resume_builder/reference/shared_ops.md | 4 +- .../reference/workflows/critique.md | 59 +++ .../reference/workflows/edit-resume.md | 48 +++ resume_builder/reference/workflows/make-cl.md | 42 ++ .../reference/workflows/make-resume.md | 86 ++++ .../reference/workflows/setup-build-kb.md | 79 ++++ .../reference/workflows/setup-extract.md | 98 +++++ tests/test_application_records.py | 96 +++++ tests/test_resume_system.py | 14 + 33 files changed, 1558 insertions(+), 1768 deletions(-) create mode 100644 FIX_PROGRESS.md create mode 100644 output/Project_Review_2026-09-11/project_review.tex create mode 100644 resume_builder/helpers/application_records.py create mode 100644 resume_builder/helpers/application_tracker.py create mode 100644 resume_builder/reference/current_applications.md create mode 100644 resume_builder/reference/workflows/critique.md create mode 100644 resume_builder/reference/workflows/edit-resume.md create mode 100644 resume_builder/reference/workflows/make-cl.md create mode 100644 resume_builder/reference/workflows/make-resume.md create mode 100644 resume_builder/reference/workflows/setup-build-kb.md create mode 100644 resume_builder/reference/workflows/setup-extract.md create mode 100644 tests/test_application_records.py diff --git a/.agents/skills/critique/SKILL.md b/.agents/skills/critique/SKILL.md index 3f6ff2d..2e15186 100644 --- a/.agents/skills/critique/SKILL.md +++ b/.agents/skills/critique/SKILL.md @@ -3,60 +3,8 @@ name: critique description: Critique an existing resume or cover-letter package against its real job description. Use for application-fit review, truth/provenance audit, recruiter and hiring-manager evaluation, separate Evidence Fit and Document Quality scoring, channel-strength assessment, and post-edit re-critique. --- -# Critique an Application Package +# Shared project workflow -## Startup +Read and follow [the critique workflow](../../../resume_builder/reference/workflows/critique.md) before doing this task. Resolve project paths from the repository root. -1. Read `resume_builder/reference/shared_ops.md`. -2. Read `resume_builder/canonical/claims.json` completely. -3. Read `config.md`, the session file, and the real verbatim JD. -4. Stop if the JD is reconstructed, incomplete or inferred. -5. Read `resume_builder/reference/critique_framework.md` and `resume_builder/support/ai_fingerprint_rules.md`. -6. Read the resume and optional cover letter. Never use either as evidence; verify against canonical claims and experience files. - -## Preflight - -Run: - -```powershell -python resume_builder/helpers/validate_resume_system.py --document -``` - -Add `--document ` when a letter exists. Any failure is Tier 1 and prevents approval. - -Compile the documents and inspect the PDFs. Extract resume text with `pdftotext` and confirm that employer, formal title and dates appear in the expected order. - -## Critique - -Follow `critique_framework.md` exactly: - -1. Hard-gate audit. -2. Evidence Fit score, independent of writing. -3. Document Quality score, independent of role fit. -4. Channel Strength rating. -5. Competitive and reader-sequence assessment. -6. Canonical claim audit. -7. Tiered fixes. -8. Cover-letter decision/critique. -9. Mechanical verification. - -Do not issue a single overall score. Do not label a high-quality document submit-ready when Evidence Fit fails a hard gate. - -## Cover-Letter Handling - -If the session records `Cover Letter Decision: NO`, do not penalize absence. If a letter exists, first assess whether it should exist under `cl_reference.md`. - -## Save and Report - -Save `output//critique_.md`. Update the session with: - -- Evidence Fit score and Core/Adjacent/Stretch classification. -- Hard-gate result. -- Document Quality score. -- Channel Strength. -- Tier 1 fixes. -- Critique status and date. - -Present the fit verdict first, then the two scores and actionable fixes. Wait for explicit user approval before finalization or editing. - -When approved, run the finalization checks from `shared_ops.md`. A package with a hard-gate NO-GO may be archived, but must not be described as recommended for submission. +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.agents/skills/edit-resume/SKILL.md b/.agents/skills/edit-resume/SKILL.md index a59297b..f55be11 100644 --- a/.agents/skills/edit-resume/SKILL.md +++ b/.agents/skills/edit-resume/SKILL.md @@ -3,49 +3,8 @@ name: edit-resume description: Edit an existing resume or cover letter from critique or user feedback. Use to make evidence-safe content, hierarchy, format, or readability changes while preserving canonical claims, revalidating role fit, compiling LaTeX, and recording the edit. --- -# Edit a Resume or Cover Letter +# Shared project workflow -## Startup +Read and follow [the edit-resume workflow](../../../resume_builder/reference/workflows/edit-resume.md) before doing this task. Resolve project paths from the repository root. -1. Read `shared_ops.md` and `resume_builder/canonical/claims.json` completely. -2. Run the system validator. -3. Read the session, real JD, target `.tex`, critique/feedback, selected profile rules and relevant experience files. -4. Check `historical_outputs.json`. A package marked unsafe must be regenerated from canonical sources rather than lightly polished. -5. If the package was already submitted, preserve it as history. Create a clearly named new version only when the user requests a reusable corrected document. - -## Diagnose - -Classify requested changes as: - -- Truth/provenance correction. -- Fit/framing correction. -- Information hierarchy or readability. -- Content selection. -- Mechanical/LaTeX. -- Blocked because evidence is absent. - -Do not treat whitespace, equal bullet lengths or an exact character target as defects. Do not add filler. A critique cannot authorize an unsupported claim. - -Present a concise edit plan and wait for explicit confirmation unless the user already approved specific edits. - -## Execute - -- Source every work claim from canonical IDs and experience files. -- Keep employer/title/date hierarchy conventional. -- Preserve natural bullet lengths and verified ownership scope. -- Remove unsupported skills rather than replacing them with synonyms. -- Update a cover letter only when its session decision is YES or the user explicitly requests it. -- If edits reveal a hard fit gate, update the Evidence Fit classification; writing improvements cannot remove the gate. - -## Verify - -Run the validator on every edited document, compile, inspect the PDF, and verify extracted text order. Compare before/after for: - -- validator failures; -- page count; -- employer/title/date visibility; -- bullet count and relevance; -- duplicated sections; -- readability at normal size. - -Update the session Edit History, Document Quality, status and next action. Mark the critique stale after material edits. Present results and wait for approval. +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.agents/skills/make-cl/SKILL.md b/.agents/skills/make-cl/SKILL.md index bcb48f0..d95fb2d 100644 --- a/.agents/skills/make-cl/SKILL.md +++ b/.agents/skills/make-cl/SKILL.md @@ -3,43 +3,8 @@ name: make-cl description: Decide whether a cover letter adds value and, when justified, generate a concise evidence-first LaTeX letter from an approved session and resume. Use for requested letters, Swiss/DACH motivation letters, meaningful transitions, or employer-specific formats; skip generic optional letters. --- -# Decide and Generate a Cover Letter +# Shared project workflow -## Startup +Read and follow [the make-cl workflow](../../../resume_builder/reference/workflows/make-cl.md) before doing this task. Resolve project paths from the repository root. -1. Read `shared_ops.md`, `resume_builder/canonical/claims.json`, `cl_reference.md`, the session, real JD and finished resume. -2. Run the validator on the resume. -3. Confirm that the session has a completed resume and a recorded Cover Letter Decision. - -## Decision Gate - -If the session says NO, explain the recorded reason and stop unless the user explicitly asks to override it. If no decision exists, apply `cl_reference.md` before writing and record YES/NO. - -Do not generate a letter merely because an upload field exists or because older workflow expected a three-page package. - -## Plan - -State in one sentence what the letter adds beyond the resume. Select at most two evidence themes. Verify any named company/product/programme hook from a current primary source and save its URL in the session. - -If the only possible content repeats resume bullets or paraphrases company news, change the decision to NO and explain why. - -## Generate - -- Use the selected audience profile and employer-requested language. -- International Tech/industry: normally 200--300 words in two or three paragraphs. -- Swiss/DACH: one A4 page, appropriate formality. -- Employer-specific: follow the requested format or portal limit. -- Lead with candidate-role connection. -- Add motivation, decision context or a genuine connection. -- Use only claims already present in both the canonical registry and resume. -- Never introduce a new tool, metric, customer, ownership claim or result. - -## Verify - -Run: - -```powershell -python resume_builder/helpers/validate_resume_system.py --document --document -``` - -Compile and inspect the letter. Confirm it is readable, within the employer's limit, and materially different from the resume. Update the session and present the letter for approval. Point to `/critique` next. +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.agents/skills/make-resume/SKILL.md b/.agents/skills/make-resume/SKILL.md index 1aa0160..03e5bcd 100644 --- a/.agents/skills/make-resume/SKILL.md +++ b/.agents/skills/make-resume/SKILL.md @@ -3,88 +3,8 @@ name: make-resume description: Generate a tailored evidence-first resume from a real job description. Use for new applications to assess hard qualifications and role fit before writing, select International Tech or Swiss/DACH format, plan canonical achievements, generate LaTeX, validate claims, compile, and record the application decision. --- -# Generate a Resume +# Shared project workflow -## Input +Read and follow [the make-resume workflow](../../../resume_builder/reference/workflows/make-resume.md) before doing this task. Resolve project paths from the repository root. -Accept a JD file, pasted JD, or URL. Obtain the real posting text before analysis. If it cannot be retrieved, ask the user to paste it; never reconstruct it. - -## Phase 0 — Canonical and JD Preflight - -1. Read `resume_builder/reference/shared_ops.md`. -2. Read `resume_builder/canonical/claims.json` completely. -3. Read `config.md`, `AGENTS.md`, `application_strategy.md`, `resume_reference.md`, and `critique_framework.md`. -4. Run `python resume_builder/helpers/validate_resume_system.py`. -5. Verify and save the verbatim JD; record source/date/status. -6. Create the output folder and session from `session_file_template.md`. - -## Phase 1 — Fit Gate Before Writing - -Build a requirement table with Required/Preferred and Direct/Adjacent/Gap/Constraint classifications. Cite canonical claim IDs for every Direct or Adjacent match. - -Compute Evidence Fit and identify hard gates using `critique_framework.md`. Then assign: - -- Core. -- Adjacent. -- Stretch. -- No-go. - -Record Channel Strength and a warm-channel action for Core roles. - -Stop and present the fit decision before resume planning when: - -- a hard gate fails; -- the role is Stretch; -- the selected cohort category is already full; -- a practical constraint is unresolved. - -Proceed on a no-go only after the user explicitly overrides the stated reason. An override does not change the fit classification. - -## Phase 2 — Audience and Content Plan - -Select International Tech by default. Use Swiss/DACH or Employer-specific format only when the employer/context supports it. - -Read the matching role bundle, `skills_taxonomy.md`, `achievement_reframing_guide.md`, and only the experience files needed for selected claims. - -Plan: - -- Optional 2--3 line summary. -- Four to six evidence-backed skills lines. -- Swisscom 4--5 bullets. -- Bosch 3--4 bullets. -- Zero or one bullet for each older role. -- Normally 11--14 bullets total, with no page-fill quota. - -For each proposed bullet show canonical ID, evidence type, scope, and why it belongs. Identify any impact metric that still needs user confirmation. - -Apply `cl_reference.md` and propose `Cover Letter Decision: YES/NO` with one reason. - -Present the plan and wait for explicit user confirmation before generation. - -## Phase 3 — Generate - -Copy `resume.cls` and `resume_template.tex` into the output folder. For a Swiss/DACH audience, also read `resume_template_dach.tex` as an audience-policy overlay; it is not a standalone document. Write fresh content from canonical claims and experience records; never copy a historical output. - -Rules: - -- Employer, formal/transparent title, dates and location come first. -- Do not use tailored themes as job titles. -- Mix natural bullet lengths. -- Use only verified metrics and outcomes. -- Preserve allowed verbs and ownership scope. -- Never add a skill to mirror the JD. -- Certifications appear once. - -## Phase 4 — Validate and Inspect - -Run: - -```powershell -python resume_builder/helpers/validate_resume_system.py --document -``` - -Fix every failure. Compile the LaTeX, inspect both pages, and run `pdftotext` to verify parsing and information order. Do not add filler to reduce whitespace. - -Update the session with the as-built content, validation results, page count, cover-letter decision, and next action. - -Present the finished resume and the original Evidence Fit classification. Wait for approval. If the cover-letter decision is YES, point to `/make-cl`; otherwise point directly to `/critique`. +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.agents/skills/setup-build-kb/SKILL.md b/.agents/skills/setup-build-kb/SKILL.md index 884bb0f..948188d 100644 --- a/.agents/skills/setup-build-kb/SKILL.md +++ b/.agents/skills/setup-build-kb/SKILL.md @@ -3,80 +3,8 @@ name: setup-build-kb description: Build or update the resume knowledge base from structured extractions. Use to normalize employment evidence, maintain the canonical claims registry, add achievements with ownership and metric status, update evidence-tiered skills and role bundles, propagate corrections, and validate the full resume system. --- -# Build or Update the Knowledge Base +# Shared project workflow -## Authority Model +Read and follow [the setup-build-kb workflow](../../../resume_builder/reference/workflows/setup-build-kb.md) before doing this task. Resolve project paths from the repository root. -The pipeline is: - -`source documents -> extractions -> canonical claims -> experience records/bundles -> generated applications` - -Generated applications never flow backward into the KB. - -Read `resume_builder/canonical/claims.json`, `config.md`, and all relevant extractions before changing normalized sources. Preserve raw source documents. A verified correction must update canonical claims first and then every dependent normalized file. - -## Canonical Claim Record - -For each employment item or achievement, record: - -- stable ID; -- employer, title, dates and location; -- factual scope; -- individual vs shared ownership; -- allowed verbs; -- forbidden overstatements; -- metric/result and verification status; -- evidence source; -- last verified date. - -When ownership is ambiguous, use contributing/shared scope and ask the user. Do not default to full ownership. - -## Experience Files - -Experience files explain facts and context. They may contain example phrasings, but those examples cannot override canonical scope and should not be optimized to fixed line counts. - -Every achievement section must include canonical ID, source, ownership scope, status, verified result/metric state, relevant skills and role relevance. - -## Skills Taxonomy - -Use only these evidence levels: - -- Production — current. -- Production — historical. -- Hands-on — current. -- Project / proof of concept. -- Certification / coursework. -- Unverified / never used. - -Do not assign Expert/Proficient/Familiar labels. Add a skill to canonical claims before it can appear in a generated document. - -## Role Bundles - -Each bundle must contain: - -1. Defensible positioning. -2. Good-fit roles and hard-gate cautions. -3. Achievement priorities. -4. Safe reframing rules. -5. Evidence-backed skills guidance. -6. Conditional cover-letter guidance. - -Bundles may rank evidence; they may not broaden it. Title-defining gaps must be stated explicitly. - -## Significance Context - -Keep company/industry context separate from candidate impact. Mark external claims for re-verification at application time. Never attach generic scale, economics, customers or market trends to the candidate. - -## Correction Propagation - -After any correction, search canonical claims, config, extractions, experience files, bundles, support files, templates and active drafts. Historical submitted outputs remain unchanged but update `historical_outputs.json` when they become unsafe for reuse. - -## Validation - -Run: - -```powershell -python resume_builder/helpers/validate_resume_system.py -``` - -Then review the diff for dates, titles, ownership, metrics and skill evidence. Present the changed claim records and affected downstream files to the user for confirmation. +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.agents/skills/setup-extract/SKILL.md b/.agents/skills/setup-extract/SKILL.md index d656958..d899824 100644 --- a/.agents/skills/setup-extract/SKILL.md +++ b/.agents/skills/setup-extract/SKILL.md @@ -3,99 +3,8 @@ name: setup-extract description: Extract source-grounded career evidence from papers, employment records, project documents, and notes into structured knowledge-base files without promoting inference to fact. --- -# Source Evidence Extraction +# Shared project workflow -Use this skill when adding new evidence to `knowledge_base/extractions/`. Extraction preserves what a source supports; it does not tailor claims to a vacancy. +Read and follow [the setup-extract workflow](../../../resume_builder/reference/workflows/setup-extract.md) before doing this task. Resolve project paths from the repository root. -## Required inputs - -1. Read `config.md` and the anti-fabrication rules in `AGENTS.md`. -2. Read the complete source material. -3. Check `resume_builder/canonical/claims.json` for existing facts and identifiers. -4. Inspect related extraction files so new evidence does not create a duplicate or contradiction. - -Generated resumes, cover letters, critiques, and session files under `output/` are never evidence sources. - -## Extraction procedure - -### 1. Identify the source - -Record: - -- source filename and type; -- source date, if known; -- whether it is primary evidence, user-authored notes, or a secondary description; -- the person, employer, project, or publication it concerns. - -### 2. Separate evidence from inference - -For every potentially reusable fact, record: - -- the source-supported statement; -- the exact supporting passage or precise location; -- any interpretation separately as `Inference`; -- confidence: `verified`, `user-confirmed`, `needs verification`, or `do not use`. - -Never silently convert an inference into a verified claim. - -### 3. Capture career facts precisely - -When applicable, extract: - -- exact employer, formal title, location, and start/end dates; -- ownership scope and collaborators; -- work performed, method, technology, and domain; -- outcome and beneficiary; -- metrics with units, baseline, period, and attribution; -- publication status, authorship position, and venue; -- current versus historical skill use; -- certification versus practical experience. - -For large-company work, describe only the user's component, domain, pipeline, service, or contribution. Do not infer ownership of organization-wide platforms or transformations. - -### 4. Record claim controls - -For each major claim, include where useful: - -- allowed verbs; -- verbs or framings that would overstate ownership; -- safe resume wording; -- known contradictions requiring resolution; -- whether a metric is verified, qualitative only, or still missing. - -### 5. Write the extraction - -Create or update a focused Markdown file in `knowledge_base/extractions/`. Use a clear structure such as: - -```markdown -# Subject - -## Source -## Verified facts -## Ownership and attribution -## Outcomes and metrics -## Skills and recency -## Safe claim seeds -## Inferences / open questions -## Conflicts with existing KB -``` - -Claim seeds are factual building blocks, not polished resume bullets. Do not add unsupported impact language for relevance. - -## Publication rules - -- Never infer `published`, `accepted`, or `under review` from a manuscript file. -- Preserve the user's author position. -- Institutional funding is not a personal award. -- Follow `config.md` provenance flags. - -## Completion - -Report: - -- files created or updated; -- newly verified facts; -- unresolved contradictions or missing metrics; -- suggested next command: use `setup-build-kb` to promote reviewed evidence into the canonical registry, experience files, bundles, and support files. - -Do not update canonical claims from an unreviewed extraction. +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.claude/skills/critique/SKILL.md b/.claude/skills/critique/SKILL.md index 945382b..0c911ba 100644 --- a/.claude/skills/critique/SKILL.md +++ b/.claude/skills/critique/SKILL.md @@ -1,111 +1,11 @@ --- -description: Re-critique existing resume/CV output files against a JD +name: critique +description: Critique an existing resume or cover-letter package against its real job description. Use for application-fit review, truth/provenance audit, recruiter and hiring-manager evaluation, separate Evidence Fit and Document Quality scoring, channel-strength assessment, and post-edit re-critique. user-invocable: true --- -# /critique +# Shared project workflow -**User input:** `$ARGUMENTS` +Read and follow [the critique workflow](../../../resume_builder/reference/workflows/critique.md) before doing this task. Resolve project paths from the repository root. -Parse `$ARGUMENTS`: -- Session file path (e.g., `output/Acme/session_acme_engineer.md`) → read session file, derive .tex paths from Output Files -- .tex file path(s) + JD source (existing format) → backward compatible -- Session name (e.g., `acme_engineer`) → find session file via derivation - -If no CL .tex provided or found in session file, critique resume/CV alone (Part 7 adjustments noted below). - ---- - -## Safety Rules - -**Accuracy > Relevance > Impact > ATS > Brevity** - -Read `config.md` Provenance Flags. Verify every claim against that table. -**JD integrity:** the critique is only as valid as the JD. If the JD source is a reconstruction/inference/template rather than the real posting (see `shared_ops.md` → JD Integrity), STOP — flag it to the user and offer to scrape the live posting (Playwright recipe) before critiquing. Never score against a fabricated JD. -Check `config.md` KB Corrections Log — do not flag corrected items as errors. -Use the email from `config.md` Personal Info — flag if a different email appears in output. -FIXED sections (from `config.md` FIXED Sections) are template-locked — do not flag for editing. Flag only VARIABLE sections. - ---- - -## User Input During Execution - -If the user provides feedback, corrections, or suggestions at any point: -1. Acknowledge the input immediately -2. If it changes scoring criteria or focus: adjust the critique accordingly -3. Never restart — resume from current position - ---- - -## Startup - -Read `resume_builder/reference/shared_ops.md` — Fresh Session Startup + Session File Derivation. -Read `CLAUDE.md` — check Active Sessions and KB Corrections. -Read `config.md` — load Provenance Flags, FIXED Sections, email. -Find and read the session file for the .tex being critiqued (use derivation protocol from shared_ops.md). - -**Recovery check:** -- If CL not DONE in session file → "CL not yet generated. Run `/make-cl` first." -- If Critique: CURRENT → "Already critiqued (score X/100). Re-run? Waiting for confirmation." -- If Critique: STALE → "Edits made since last critique. Re-critiquing." -- If Critique: PENDING → proceed - ---- - -## Protocol - -1. **Read session file** — specifically note: - - **Company Context** → reviewer persona, "why this company" - - **Framing Strategy** → intentional reframing decisions (flag only execution inconsistencies, not the strategy itself) - - **Cover Letter Plan** → CL structure rationale - - **Critique Context** → reviewer persona, competitive landscape, domain vocabulary - - If session file lacks Company Context or Critique Context: do 1-2 web searches to fill gaps -2. Read `resume_builder/reference/critique_framework.md` -3. Read `resume_builder/support/ai_fingerprint_rules.md` — use Section 6 checklist in Part 7 verification -3. Read the .tex file(s) — derive paths from session file Output Files, or from `$ARGUMENTS` -4. Read the JD (path from `$ARGUMENTS` or session file) -5. Read the relevant bundle (`resume_builder/bundles/bundle_[role_type].md` — from session file) -6. Run char count: - ```bash - python3 resume_builder/helpers/char_count.py -f [resume|cv] [file.tex] - ``` -7. Compile and visually verify: - ```bash - pdflatex -interaction=nonstopmode -output-directory=output/ [file.tex] - ``` - Use the Read tool to view the compiled PDF — check orphans, page fill, header wrapping. - If compile fails: note "COMPILE FAILED — visual checks could not be verified" in Part 8. -8. If a prior critique exists (`output//critique_.md`): read it and note previous score. -8b. **Paper Hook Verification:** If the CL cites named papers, PIs, programs, or publications, web-search to verify title, journal, year, and PI affiliation. Flag factual errors as Tier 1 fixes. - -9. **Run the full critique per critique_framework.md. The output MUST contain ALL 8 sections** (even if the framework file has partially compacted, produce every section): - - 1. **Domain-Specialist Lens** — 7 elements: - (a) Reviewer persona (b) Company context (c) JD vocabulary extraction (d) Domain vocabulary map - (e) Gap ranking (fatal/serious/cosmetic) (f) Methodology transfer test (g) Competitive landscape - 2. **Five-Perspective Read-Through** — ATS, Recruiter (10s), HR (30s), HM (2min), Technical (10min) — each with verdict - 3. **Eight-Dimension Scoring** — weighted table summing to 100 - (ATS 15%, Summary 10%, Skills 10%, Bullets 25%, Publications 10%, Narrative 15%, Visual 5%, Credibility 10%) - 4. **Interview Likelihood** — per-reader probability + ceiling analysis - 5. **Tiered Improvements** — Tier 1 (>=1pt each), Tier 2 (0.3-0.9), Tier 3 (<0.3) - 6. **Interview Bridge Points** — 5-7 resume-to-interview talking points - 7. **Cover Letter Critique** — 6 sub-checks (6A anti-patterns, 6B tailoring, 6C context-specific, 6D ATS, 6E structural, 6F package cohesion) - - **If no CL provided:** Skip 6A-6E. Run 6F as resume standalone assessment — evaluate whether the resume earns an interview without a CL. Note: "Cover letter not provided — package cohesion not assessed." - 8. **Post-Generation Verification** — mechanical + content + structural checklists - -10. Save to `output//critique_.md` -11. **Update session file** — Critique Summary (score, findings, tier 1 fixes), Status → Critique: CURRENT -12. **Update memory pointer** with new score - -Progress: "Reading session file for framing context..." / "Running ATS keyword scan — 16/20 match..." / "Scoring 8 dimensions..." / "Score: 87.0/100" - -### >>>>>> MANDATORY STOP <<<<<< -Present: score table + tier 1 actionable fixes + interview likelihood. -**You MUST wait for the user's explicit text response before continuing.** -If edits needed, tell user to run `/edit-resume`. - -### When user approves / says "looks good" / finalizes: -Verify all expected files exist in `output//`: -- session file, resume/CV .tex + .pdf, CL .tex + .pdf, critique .md -- Compile artifacts (.aux, .log, .out) -Confirm to user: "Package complete in output// — [list files]" +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.claude/skills/edit-resume/SKILL.md b/.claude/skills/edit-resume/SKILL.md index 4b35349..6866828 100644 --- a/.claude/skills/edit-resume/SKILL.md +++ b/.claude/skills/edit-resume/SKILL.md @@ -1,208 +1,11 @@ --- -description: Edit existing resume/CV or cover letter from critique feedback and user suggestions +name: edit-resume +description: Edit an existing resume or cover letter from critique or user feedback. Use to make evidence-safe content, hierarchy, format, or readability changes while preserving canonical claims, revalidating role fit, compiling LaTeX, and recording the edit. user-invocable: true --- -# /edit-resume +# Shared project workflow -**User input:** `$ARGUMENTS` +Read and follow [the edit-resume workflow](../../../resume_builder/reference/workflows/edit-resume.md) before doing this task. Resolve project paths from the repository root. -Parse `$ARGUMENTS`: First argument is the .tex file path (required). A `.md` path is the critique file. Text in quotes is inline instructions. -- `/edit-resume output/Acme/e2e_acme_resume.tex` -- `/edit-resume output/Acme/e2e_acme_resume.tex output/Acme/critique_acme.md` -- `/edit-resume output/Acme/e2e_acme_resume.tex "shorten Position 1 header, fill last page"` - -If only .tex path and no instructions: ask the user what to fix. - ---- - -## Safety Rules (ALWAYS ENFORCED) - -**Accuracy > Relevance > Impact > ATS > Brevity** - -Read `config.md` Provenance Flags before editing any content. Verify every claim against that table. - -- Use the email from `config.md` Personal Info in all outputs -- Source ALL bullet content from `resume_builder/experience/` files. Never fabricate. -- Resume bullets: ALL variable bullets must be 2L (CV: 2L/3L mix OK, check `config.md` Document Preferences) -- Run `python3 resume_builder/helpers/char_count.py` after edits — the tool is authoritative - -### FIXED Sections — Refuse if Asked to Edit -Check `config.md` FIXED Sections for the list of template-locked sections. Say no and explain: these are template-locked across all outputs. - -VARIABLE sections only: Summary, Technical Skills, Research Experience bullets/headers. - ---- - -## User Input During Execution - -If the user provides feedback, corrections, or suggestions at any point: -1. Acknowledge the input immediately -2. If it affects an already-applied edit: go back, fix it, re-run char count gate -3. If it changes the edit plan: update session file, adjust remaining edits -4. If it's a question: answer it, then continue from current step -5. Never restart a phase — resume from current position - ---- - -## Startup - -Read `resume_builder/reference/shared_ops.md` — Fresh Session Startup + Session File Derivation. -Read `CLAUDE.md` — check Active Sessions and KB Corrections. -Read `config.md` — load Provenance Flags, email, FIXED Sections, document preferences. -Find and read the session file (use derivation protocol from shared_ops.md). - -**Recovery check:** -- Read session file, check for existing Edit N Status -- If Edit N Status shows IN_PROGRESS: read .tex, identify which edits are done, resume -- If no edit in progress: proceed to Phase 1 - ---- - -## Phase 1: Load Context - -Read in this order: -1. **Session file** (`output//session_.md`) — note: Framing Strategy, Company Context, Bullet Plan, Edit History -2. `resume_builder/reference/resume_reference.md` — char limits, budgets, fixed sections -3. The .tex file being edited -4. Critique file (if provided in `$ARGUMENTS`) -5. JD file (path from session file's JD Info section) -6. Compile current .tex and record baseline page count -7. Run: `python3 resume_builder/helpers/char_count.py -f [resume|cv] [file.tex]` - -**Record baseline in session file** under `## Edit [N] Baseline` (scan existing Edit History sections; next N = max existing + 1, or 1 if none): - -``` -## Edit [N] Baseline -- Pages: [N] -- Char violations: [list or "none"] -- Orphan violations: [list or "none"] -- White space last page: [N lines] -- Variable bullets: [N] -- Rendered lines: [N] -``` - -Progress: "Reading session file — [company], [role type] bundle..." / "Baseline: 2 pages, 0 char violations, 1 orphan..." - ---- - -## Phase 2: Diagnose & Plan Edits - -Gather change requests from THREE sources: -1. **User instructions** from `$ARGUMENTS` (highest priority) -2. **Critique file** (Tier 1 fixes first, then Tier 2) -3. **Auto-detected issues** from Phase 1 (char violations, orphans, page fill) - -Cross-check against **session file framing strategy** — edits must stay consistent with decisions from `/make-resume`. - -**For each change, classify:** -- **MODIFY:** Change text of existing bullet/summary/skills. Budget unchanged. -- **SWAP:** Replace one bullet with another. Budget unchanged if same variant. -- **ADD:** Insert new bullet. Budget increases by rendered lines. -- **REMOVE:** Drop a bullet. Budget decreases. -- **VARIANT CHANGE:** e.g., 2L → 3L. Budget changes by rendered line delta. -- **FIXED:** Blocked — show in plan with `[FIXED — cannot edit]` and explain why. - -**Budget revalidation (if any change is ADD, REMOVE, SWAP-with-different-variant, or VARIANT CHANGE):** -Recalculate total variable bullets and rendered lines. Compare against budget from resume_reference.md. -If OVER budget: present overflow and ask user which bullet to drop or shorten. -Show: `Budget: [N] bullets ([M] rendered lines) vs target [T]. PASS/FAIL` - -If edit targets **cover letter** (not resume/CV): note this — Phase 4 will use CL-specific gates. Load CL .tex path from session file Output Files section. - -### >>>>>> MANDATORY STOP — DO NOT PROCEED <<<<<< -Present numbered edit plan. Each item shows: what, why, source, classification (MODIFY/ADD/SWAP/FIXED). -**You MUST wait for the user's explicit text response before continuing.** -Proceeding without confirmation may make unwanted edits that break package consistency. - ---- - -## Phase 3: Load Reference Files (only confirmed edits) - -Load ONLY what the confirmed edits need: - -- **All edits:** `resume_builder/support/ai_fingerprint_rules.md` — scan for banned words/patterns before and after edits -- **Bullet expand/rewrite/add:** `resume_builder/experience/` files + matching bundle + `resume_builder/support/achievement_reframing_guide.md` -- **Summary rewrite:** Bundle (S2 summary guide) + `resume_builder/support/skills_taxonomy.md` -- **Cover letter edits:** `resume_builder/support/significance_*.md` + `resume_builder/reference/cl_reference.md` -- **Simple fixes** (orphans, headers, spacing): No extra files needed - ---- - -## Phase 4: Execute Edits - -Apply edits one section at a time. After each edited section: - -1. Run char count gate: - ```bash - python3 resume_builder/helpers/char_count.py -f [resume|cv] output//[file].tex - ``` -2. Fix any OVER violations or orphans before next section -3. If a bullet expansion doesn't render as expected (1L when targeting 2L, or 3L), adjust immediately - -Update session file Edit N Status after each individual edit: -- Edit 1 (orphan fix): DONE -- Edit 2 (Summary rewrite): IN_PROGRESS - -### Resume/CV Verification Gates -| Gate | Check | If FAIL | -|------|-------|---------| -| Char count | No OVER violations | Fix bullet before proceeding | -| Page fill | Resume: <= 3 lines white space. CV: check rendered line target | Expand/trim variable bullets | -| Page count | Match `config.md` Document Preferences | Trim/expand variable content | -| Orphan | 2L bullet last line >= 70% | Pad or trim | -| Title width | Position title + date fits 1 line | Shorten title | -| Compile | Clean pdflatex | Fix LaTeX errors | - -### Cover Letter Verification Gates (if CL was edited) -| Gate | Check | If FAIL | -|------|-------|---------| -| Word count | Industry 250-300, Lab/Academic 350-450 | Trim/expand | -| Page fill | 1pg: well-filled. 2pg: page 2 >= half filled before signature | Adjust | -| Paragraph count | Industry 3, Lab/Academic 4 | Restructure | -| Anti-patterns | No generic opener, no defensive framing, no credential dump | Rewrite | -| Package cohesion | CL claims traceable to resume bullets, no contradictions | Fix | - -After all edits, compile: -```bash -pdflatex -interaction=nonstopmode -output-directory=output/ output//[file].tex -``` -Use the Read tool to view the compiled PDF — check page count, white space, orphans, header wrapping. - -Progress: "Editing Position 1 bullet 6 — was 184 chars, now 197..." / "Compiling... 2 pages, page fill OK" - ---- - -## Phase 5: Update Session File & Present - -1. **Append Edit History** (use the N from Phase 1 baseline): - ``` - ### Edit [N] ([date]): [short description] - - Changes: [what changed] - - Source: critique item # / user request / auto-detected - - Verification: gates passed - ``` - -2. **Compare against baseline:** - - | Metric | Before | After | Delta | - |--------|--------|-------|-------| - | Page count | [N] | [N] | [+/-] | - | Char violations | [N] | [N] | [+/-] | - | Orphans | [N] | [N] | [+/-] | - | White space | [N] | [N] | [+/-] | - - Flag any metric that worsened. - -3. **Update Status** — mark critique as STALE if edits made after last critique. Update Next. - -4. **Update memory pointer** if status changed. - -5. **Present:** Changes summary + delta table + compiled PDF. - -### >>>>>> MANDATORY STOP <<<<<< -Show results. Wait for user approval or further edits. -**You MUST wait for the user's explicit text response before continuing.** - -### When user approves / says "looks good" / finalizes: -Run file organization from `resume_builder/reference/shared_ops.md` — Finalization check. +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.claude/skills/make-cl/SKILL.md b/.claude/skills/make-cl/SKILL.md index 0b7701b..a5c3e99 100644 --- a/.claude/skills/make-cl/SKILL.md +++ b/.claude/skills/make-cl/SKILL.md @@ -1,142 +1,11 @@ --- -description: Generate a tailored cover letter from an existing session file and finished resume/CV +name: make-cl +description: Decide whether a cover letter adds value and, when justified, generate a concise evidence-first LaTeX letter from an approved session and resume. Use for requested letters, Swiss/DACH motivation letters, meaningful transitions, or employer-specific formats; skip generic optional letters. user-invocable: true --- -# /make-cl +# Shared project workflow -**User input:** `$ARGUMENTS` +Read and follow [the make-cl workflow](../../../resume_builder/reference/workflows/make-cl.md) before doing this task. Resolve project paths from the repository root. -Parse `$ARGUMENTS`: -- Session file path (e.g., `output/Acme/session_acme_engineer.md`) → read that session file -- Session name (e.g., `acme_engineer`) → find session file via shared_ops.md derivation -- Empty → check `CLAUDE.md` Active Sessions for latest - ---- - -## Safety Rules (ALWAYS ENFORCED) - -**Accuracy > Relevance > Impact > ATS > Brevity** - -Read `config.md` Provenance Flags before generating any content. Verify every claim against that table. - -- Use the email from `config.md` Personal Info in all outputs -- CL deepens what resume presents — never introduces new claims not traceable to resume bullets -- Source field context from `resume_builder/support/significance_*.md` files - ---- - -## User Input During Execution - -If the user provides feedback, corrections, or suggestions at any point: -1. Acknowledge the input immediately -2. If it affects already-written content: fix it, re-verify word count and anti-patterns -3. If it changes the framing: note the change in session file Framing Strategy -4. Never restart — resume from current position - ---- - -## Startup - -Read `resume_builder/reference/shared_ops.md` for session startup and file derivation. - -Then: -1. Read `CLAUDE.md` — check Active Sessions and KB Corrections -2. Read `config.md` — load Provenance Flags, email, role types -3. Find and read the session file -4. **Recovery check:** - - If CL Status is DONE → "CL already generated. Run `/critique` next." Show next command. Stop. - - If CL Status is IN_PROGRESS → check if CL .tex exists, offer to resume or regenerate - - If Resume Status is not DONE → "Resume not yet generated. Run `/make-resume` first." Stop. - - If CL Status is PENDING → proceed to Phase 1 - ---- - -## Phase 1: Load Context - -Read in this order: -1. **Session file** — specifically: Company Context, Cover Letter Plan, Framing Strategy, ATS Keywords -2. **Finished resume/CV .tex** — path from session file Output Files. Read to understand what CL must complement. -3. `resume_builder/reference/cl_reference.md` — CL format rules, paragraph templates, anti-patterns -4. `resume_builder/support/ai_fingerprint_rules.md` — Banned words, structural rules (CLs are most vulnerable) -5. The matching bundle from session file role type → `resume_builder/bundles/bundle_[role_type].md` — Section 5 (Cover Letter) -5. All significance files from `resume_builder/support/significance_*.md` - -Update session file Status: `Cover Letter: IN_PROGRESS` - -Progress: "Loading CL context — [company], [role type] bundle, [institution type]..." - ---- - -## Phase 2: Generate Cover Letter - -Read `resume_builder/templates/coverletter_template.tex`. - -**Detect institution type** from session file Cover Letter Plan: -- Industry → 3 paragraphs, 250-300 words -- National Lab → 4 paragraphs, 350-450 words -- Academic → 4 paragraphs, 350-450 words (postdoc) or 450-650 words (faculty) - -**Generate CL following cl_reference.md paragraph structure:** -- Use significance files for field-context depth (NOT resume bullet text) -- Use session file CL hooks and "why them" angle -- Ensure every major claim is traceable to a resume/CV bullet -- Open with a specific reference to their work — no generic openers -- Weave credentials into body paragraphs, not closing - -Save to `output//e2e__cover_letter.tex` - -Progress: "Writing [institution type] cover letter — [N] paragraphs, targeting [N] words..." - -### CL Hook Verification Gate (MANDATORY before presenting to user) - -Web-search every hook used in the CL: -- Academic: PI name + cited paper/research area -- National Lab: named program, thrust area, or group publication -- Industry: product, technology, or company news referenced - -Present evidence as: -> **Claim:** [what the CL says] → **Evidence:** [what the search found] → **Source:** [URL] - -Flag any unverified item: **"UNVERIFIED — please confirm"** - -Do NOT present the CL draft to the user until all hooks are verified or flagged. - ---- - -## Phase 3: Compile & Verify - -```bash -pdflatex -interaction=nonstopmode -output-directory=output/ output//e2e__cover_letter.tex -``` - -Use Read tool to view compiled PDF. Verify: - -| Gate | Check | If FAIL | -|------|-------|---------| -| Word count | Industry 250-300, Lab/Academic 350-450 | Trim/expand | -| Page count | Resume package: 1 page. CV package: 1-2 pages | Adjust content | -| Page fill | 1pg: well-filled. 2pg: page 2 >= half filled before signature | Adjust | -| Anti-patterns | No generic opener, no defensive framing, no credential dump | Rewrite | -| Package cohesion | CL claims traceable to resume bullets, no contradictions | Fix | -| Compile | Clean pdflatex | Fix LaTeX errors | - -Update session file: -- Add CL to Output Files -- Status: `Cover Letter: DONE` -- Add Next Critique command - -Progress: "Compiled — 1 page, 278 words. Package cohesion verified." - -### >>>>>> MANDATORY STOP — DO NOT PROCEED <<<<<< -Present: CL summary (word count, page count, key hooks used). -**You MUST wait for the user's explicit text response before continuing.** - -If user requests changes: apply them, re-compile, re-verify. Update session file. -If user approves: update Status, present next command. - -**Do NOT trigger file organization** — that happens after `/critique` approval. - -"Cover letter done. Next steps: -1. /clear -2. [exact /critique command with session file path]" +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.claude/skills/make-resume/SKILL.md b/.claude/skills/make-resume/SKILL.md index ff89c4f..fea67d7 100644 --- a/.claude/skills/make-resume/SKILL.md +++ b/.claude/skills/make-resume/SKILL.md @@ -1,241 +1,11 @@ --- -description: Generate a tailored resume/CV from a JD +name: make-resume +description: Generate a tailored evidence-first resume from a real job description. Use for new applications to assess hard qualifications and role fit before writing, select International Tech or Swiss/DACH format, plan canonical achievements, generate LaTeX, validate claims, compile, and record the application decision. user-invocable: true --- -# /make-resume +# Shared project workflow -**User input:** `$ARGUMENTS` +Read and follow [the make-resume workflow](../../../resume_builder/reference/workflows/make-resume.md) before doing this task. Resolve project paths from the repository root. -Parse `$ARGUMENTS`: -- File path (e.g., `JDs/*.txt`) → read that file for the JD -- A URL → this is NOT a JD, it's a pointer. Fetch the real posting text first (see JD Integrity below), save it verbatim to `JDs/_.txt`, then proceed. -- Text after the path starting with "Focus:"/"Emphasize:"/"Downplay:" → focus directive -- "Quick:" prefix → Quick Mode (see below) -- Empty → ask the user for the JD -- Inline JD text (no file path) → save to `JDs/temp_.txt`, proceed normally - -**JD Integrity (read `shared_ops.md` → "JD Integrity"):** The JD must be the real posting, verbatim. NEVER invent, reconstruct, paraphrase, or infer JD content. `WebFetch` is JS-blind on careers boards — use the job_scout Playwright recipe in `shared_ops.md` to scrape JS-gated postings. If you cannot get the real text, STOP and ask the user to paste it. Do not proceed on a guessed JD. - ---- - -## Safety Rules (ALWAYS ENFORCED) - -**Accuracy > Relevance > Impact > ATS > Brevity** - -Read `config.md` Provenance Flags before generating any content. Verify every claim against that table. - -- **JD is ground truth — use the real posting verbatim. NEVER reconstruct, invent, or infer a JD** (see `shared_ops.md` → JD Integrity). If you can't get the real text, STOP and ask the user to paste it. -- Use the email from `config.md` Personal Info in all outputs -- Resume bullets: ALL variable bullets are 2L (CV: 2L/3L mix OK, check `config.md` Document Preferences) -- Source ALL bullet content from `resume_builder/experience/` files. Never fabricate. -- Run `python3 resume_builder/helpers/char_count.py` after each section — the tool is authoritative - ---- - -## User Input During Execution - -If the user provides feedback, corrections, or suggestions at any point: -1. Acknowledge the input immediately -2. If it affects an already-written section: go back, fix it, re-run char count gate -3. If it changes the bullet plan: update session file Bullet Plan -4. If it's a question: answer it, then continue from current step -5. Never restart a phase — resume from current position - ---- - -## Startup - -Read `resume_builder/reference/shared_ops.md` for session startup, file derivation, and organization protocols. - -Then: -1. Read `CLAUDE.md` — check Active Sessions and KB Corrections -2. Read `config.md` — load Provenance Flags, email, document preferences, role types -3. If session file exists for this JD: - - Read session file, check Status - - Phase 0: DONE, Phase 1: PENDING → resume at Phase 1 - - Phase 1: DONE → resume at Budget Gate - - Phase 2: IN_PROGRESS → read .tex, check what sections exist, resume from checkpoint - - Phase 2: DONE → "Resume already done. Run /make-cl next." Show next command. Stop. -4. If no session file: proceed to Phase 0 - ---- - -## Quick Mode - -Trigger: `$ARGUMENTS` starts with "Quick:" - -Defaults: -- Select all HIGH priority achievements from bundle's Priority Matrix as 2L -- Fill remaining budget with MEDIUM priority in Priority Matrix order -- Default format: 2-page resume (unless JD clearly requires CV) -- Skip Phase 0 STOP and Phase 1 STOP -- Keep Budget Gate (auto-pass if within target) and end-of-resume STOP -- Run all phases with progress commentary instead of interactive stops - ---- - -## Phase 0: Research & Session Setup - -**Read these files:** -1. The JD — the **real posting text**, verbatim. If `$ARGUMENTS` was a URL or the posting is on a JS-gated board, scrape it with the Playwright recipe in `shared_ops.md` (JD Integrity) BEFORE this step and save to `JDs/`/`output//JD_.txt`. If you cannot obtain the real text, STOP and ask the user to paste it — do NOT reconstruct or infer it. -2. `resume_builder/reference/resume_reference.md` — Budget Card, Section Specs, Char Limits, Page Budgets -3. `config.md` — Role-Type Decision Tree to identify the matching bundle - -**Web Search (MANDATORY — 2-3 searches).** Load WebSearch via ToolSearch first. -1. `[Company] research & development [key JD domain]` — products, recent projects -2. `[Company] [specific technology from JD]` — concrete hooks for cover letter -3. `[Company] careers [role type] culture` OR recent news — hiring context - -If web search returns no results: use JD text + training knowledge. Flag: "Web search returned limited results — CL hooks may be generic." - -**Produce all of these (reference `resume_builder/reference/session_file_template.md` for format):** -- **JD Analysis** — classify every requirement as Direct / Bridge (with confidence) / Gap. Extract ATS keywords by category. -- **Company Context** — mission, role purpose, culture signals, "why them" angle (from web research) -- **Framing Strategy** — lead narrative, reframing map, emphasize/downplay, CL hooks, user focus directives -- **Critique Context** — reviewer persona, competitive landscape, domain vocabulary -- **Cover Letter Plan** — institution type, paragraph structure, hooks, jargon level - -**Create output folder:** -Derive folder name from JD filename: `JDs/JD_Acme.txt` → `output/Acme/` -```bash -mkdir -p output// -``` -Write session file to `output//session_.md` (NOT flat `output/`). -All subsequent output files go in this folder. - -**Verify completeness:** Re-read the session file. Confirm these 8 sections are non-empty: JD Info, Requirements table, ATS Keywords, Gap Assessment, Company Context, Framing Strategy, Critique Context, Cover Letter Plan. Fill any missing section before presenting. - -**Write memory pointer** to `CLAUDE.md` Active Sessions. - -**Update session file Status:** `Phase 0: DONE` - -Progress: "Searching for [company] + [domain]..." / "JD analysis: X/Y requirements direct match, Z bridges, W gaps" - -### >>>>>> MANDATORY STOP — DO NOT PROCEED <<<<<< -Present: research summary, role type + bundle, format, framing strategy. -Ask user to confirm: (1) role type + bundle, (2) format, (3) framing strategy. -**You MUST wait for the user's explicit text response before continuing.** -Proceeding without confirmation misaligns the entire resume and requires full regeneration. - ---- - -## Phase 1: Plan Bullets - -**Re-read `output//session_.md`** — specifically Framing Strategy and ATS Keywords. - -**Read:** -1. The matching bundle from `config.md` Role Types → `resume_builder/bundles/bundle_[role_type].md` — Section 1 (Priority Matrix) - - For hybrid JDs: read both bundles. Use primary for Priority Matrix, secondary for Reframing Map on 1-2 bridging bullets. -2. All experience files from `resume_builder/experience/` -3. `resume_builder/support/achievement_reframing_guide.md` -4. `resume_builder/support/skills_taxonomy.md` -5. `resume_builder/support/pub_metadata.md` - -**Present one table per position:** - -**[Position Name] (Budget: N-M bullets, ~X-Y rendered lines)** - -| | ID | Achievement | Variant | Lines | JD Match | -|---|---|-------------|---------|-------|----------| -| * | P1-1 | [short description] | 2L | 2 | Direct | -| * | P1-5 | [short description] | 2L | 2 | Direct | -| o | P1-3 | [short description] | 2L | 2 | Bridge | -| x | P1-7 | [short description] | -- | -- | Weak | - -**Legend:** `*` = recommended (HIGH on Priority Matrix + Direct JD match) | `o` = available (MEDIUM priority or Bridge match) | `x` = not recommended (LOW priority or Gap) - -**After all positions, show:** -- Recommended set total vs budget (from Quick Budget Card in resume_reference.md) -- Remaining budget slots and what could fill them -- Forced exclusions per provenance flags -- Focus directive impact (what changed vs Priority Matrix defaults) -- CV: confirm first bullet of first experience is 2L (page 1 rule) - -**Update session file** — write Bullet Plan tables. Status: `Phase 1: DONE (N bullets confirmed)` - -Progress: "Reading experience files for bullet candidates..." / "Recommending N bullets per position" - -### >>>>>> MANDATORY STOP — DO NOT PROCEED <<<<<< -Present bullet plan. Wait for user to confirm/modify selections. -**You MUST wait for the user's explicit text response before continuing.** -If you proceed without confirmation, you will generate bullets the user didn't approve. -**Update session file with confirmed plan before continuing.** - ---- - -## Budget Gate (AFTER user confirms bullet plan, BEFORE Phase 2) - -**Re-read session file Bullet Plan section** to verify confirmed counts. - -- Check budget targets from `resume_builder/reference/resume_reference.md` Budget Card. -- Show: `Budget: [N] bullets vs target [T]. PASS/FAIL` -- **FAIL = do not proceed. Reconcile with user first.** - ---- - -## Phase 2: Generate - -**Re-read to restore context after compaction:** -1. `output//session_.md` (framing + confirmed bullet plan) -2. `resume_builder/reference/critical_rules.md` — Character Limits, Bold Width Penalty, Orphan rules -3. `resume_builder/support/ai_fingerprint_rules.md` — Banned words, structural rules, post-gen checklist - -**Read template:** `resume_builder/templates/resume_template.tex` or `cv_template.tex` + `.cls` -FIXED sections (from `config.md` FIXED Sections) are template-locked — only generate VARIABLE sections (Summary, Skills, Experience bullets/headers). - -**Read section specs:** `resume_builder/reference/resume_reference.md` — Section-by-Section Specs for your format - -**Generate section by section** (follow Section-by-Section Specs): -1. Summary → check against session framing strategy - - Update Status → `Phase 2: Summary DONE` -2. Technical Skills - - Update Status → `Phase 2: Skills DONE` -3. Each position's bullets → **CHAR COUNT GATE after each position** - - Position titles: bold theme + date must fit ONE line (see resume_reference.md). If wrapping, shorten title. - - After each position: Update Status → `Phase 2: [Position] DONE` -4. **PAGE FILL GATE after all experience** - -Save .tex to `output//e2e__resume.tex` or `_cv.tex` - -**Update session file** — add Output Files. - -Progress: "Writing Position 1 bullets (6 of 7)..." / "Bullet 4 is SHORT at 184 chars — padding" / "Compiling resume... 2 pages OK" - -### CHAR COUNT GATE (per position) -```bash -python3 resume_builder/helpers/char_count.py -f [resume|cv] output//[file].tex -``` -No OVER violations. Last line of 2L bullets >= 70% fill. **Fix before next position.** - -### PAGE FILL GATE -Resume: <= 3 lines white space on last page. CV: check rendered line target from resume_reference.md. **If FAIL: add/trim variable bullets.** - -### COMPILE GATE -```bash -pdflatex -interaction=nonstopmode -output-directory=output/ output//e2e__resume.tex -``` -Verify page counts match `config.md` Document Preferences. Use the Read tool to view compiled PDF — check orphans, header wrapping, page fill. **If FAIL: fix variable content, recompile.** - -Run the Post-Generation Verification checklist from `resume_builder/reference/resume_reference.md` before proceeding. - -Update Status → `Phase 2: Compile DONE` - ---- - -## End of /make-resume - -Update session file Status: -- `Resume: DONE` -- `Cover Letter: PENDING` -- `Critique: PENDING` -- `Next: /make-cl output//session_.md` -- `Next Critique: /critique output//session_.md` - -### >>>>>> MANDATORY STOP <<<<<< -Present: resume compilation summary (pages, char count results, any violations fixed). -**You MUST wait for the user's explicit text response before continuing.** - -"Resume compiled and verified. Next steps: -1. /clear -2. [exact /make-cl command with session file path]" +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.claude/skills/setup-build-kb/SKILL.md b/.claude/skills/setup-build-kb/SKILL.md index 638165d..d2e97bf 100644 --- a/.claude/skills/setup-build-kb/SKILL.md +++ b/.claude/skills/setup-build-kb/SKILL.md @@ -1,329 +1,11 @@ --- -description: Synthesize completed extractions into the knowledge base files needed for resume generation +name: setup-build-kb +description: Build or update the resume knowledge base from structured extractions. Use to normalize employment evidence, maintain the canonical claims registry, add achievements with ownership and metric status, update evidence-tiered skills and role bundles, propagate corrections, and validate the full resume system. user-invocable: true --- -# /setup-build-kb +# Shared project workflow -**User input:** `$ARGUMENTS` +Read and follow [the setup-build-kb workflow](../../../resume_builder/reference/workflows/setup-build-kb.md) before doing this task. Resolve project paths from the repository root. -Parse `$ARGUMENTS`: -- Empty → full build (all phases) -- Phase number (e.g., `3`) → resume from that phase -- "experience" / "bundles" / "skills" / "pubs" / "reframing" / "significance" → run only that component -- "status" → show what's built and what's missing - ---- - -## Startup - -1. Read `CLAUDE.md` — check KB Corrections Log -2. Read `config.md` — load: - - Personal Info (positions, institutions, dates) - - Role Types table (defines which bundles to create) - - Provenance Flags (propagate to all KB files) - - Document Preferences (bullet variant defaults) -3. Read `knowledge_base/extractions/_INVENTORY.md` — verify extractions exist -4. Scan `resume_builder/` to see what's already built - -**Pre-flight check:** -- If `_INVENTORY.md` is empty or has no entries: "No extractions found. Run `/setup-extract` first." Stop. -- If fewer than 2 extractions: warn "Only [N] extraction(s) found. KB quality improves with more papers. Continue anyway?" - -Progress: "Found [N] extractions across [M] positions. Config has [K] role types defined." - ---- - -## Phase 1: Build Experience Files - -**Goal:** Create one experience file per position, containing all achievements organized for resume generation. - -**Read:** All extraction files listed in `_INVENTORY.md` - -**For each position** (from `config.md` or inferred from extraction metadata): - -1. Group extractions by the position they belong to (based on dates, institution, or user clarification) -2. Ask the user to confirm grouping if ambiguous: "I've grouped these papers under [Position]. Correct?" - -**Experience file format** (`resume_builder/experience/experience_.md`): - -```markdown -# Experience: [Position Title] — [Institution] -## [Date Range] - -### Cross-Position Section -[Brief narrative connecting this position's work to the user's broader trajectory] -[CL framing content — how this position fits the career arc] - ---- - -### Achievement [ID]: [Short Title] -**Source:** [extraction filename] -**Paper:** [citation or "internal"/"unpublished"] -**User's role:** [first author / contributing / sole developer] -**Status:** [published / under review / draft / internal] - -**Context:** [1-2 sentences — what problem, why it matters] - -**Bullet variants:** -- **2L:** [Full 2-line bullet text — STAR format, ~180-210 rendered characters] -- **3L:** [Full 3-line bullet text — for CV use, ~270-310 rendered characters] -- **1L:** [Condensed 1-line version — ~90-110 rendered characters, for tight budgets] - -**Key skills:** [comma-separated list of skills this achievement demonstrates] -**ATS keywords:** [domain-specific terms an ATS might scan for] -**Reframing notes:** [how to emphasize different aspects for different role types] - ---- -[Repeat for each achievement] -``` - -### >>>>>> MANDATORY STOP — DO NOT PROCEED <<<<<< -Present the experience file(s) — show achievement count per position, total bullet variants. -Ask user to review: "Are the groupings correct? Any achievements missing or misattributed?" -**You MUST wait for the user's explicit text response before continuing.** - ---- - -## Phase 2: Build Skills Taxonomy - -**Goal:** Create a categorized inventory of all technical skills from extractions. - -**Read:** All extraction files (Methods & Tools sections) + experience files from Phase 1 - -**Build** `resume_builder/support/skills_taxonomy.md`: - -```markdown -# Skills Taxonomy - -## Summary Stats -- Total unique skills: [N] -- Publications: [N] ([breakdown by status]) -- Top methods: [ranked list] - -## Categories - -### [Category 1: e.g., Computational Methods] -| Skill | Proficiency | Evidence | Resume Weight | -|-------|-----------|----------|---------------| -| [skill] | [expert/proficient/familiar] | [paper IDs] | [HIGH/MED/LOW] | - -### [Category 2: e.g., Programming & Software] -[same table format] - -### [Category 3: e.g., Machine Learning] -[same table format] - -[Continue for all categories — typically 4-7 categories] -``` - -**Proficiency levels:** -- **Expert:** Multiple first-author papers, developed custom tools -- **Proficient:** Used extensively in published work, comfortable teaching -- **Familiar:** Used in one project, or contributed to someone else's implementation - -Progress: "Built taxonomy — [N] skills across [M] categories" - ---- - -## Phase 3: Build Publication Metadata - -**Goal:** Structured pub data for resume/CV generation. - -**Build** `resume_builder/support/pub_metadata.md`: - -```markdown -# Publication Metadata - -## Summary -- Total publications: [N] -- First-author: [N] | Co-first: [N] | Contributing: [N] -- Published: [N] | Under review: [N] | In preparation: [N] - -## Publication List - -### First-Author / Co-First -| # | Citation (et al. format) | Journal | Year | Status | Key Topic | -|---|-------------------------|---------|------|--------|-----------| -| 1 | [Author et al., Journal, Year] | [journal] | [year] | [status] | [topic] | - -### Contributing Author -[same table format] - -### Under Review / In Preparation -[same table format, with provenance notes] -``` - -Progress: "Pub metadata — [N] first-author, [M] contributing, [K] under review" - ---- - -## Phase 4: Build Achievement Reframing Guide - -**Goal:** Per-achievement significance lines + framing directives for each role type. - -**Read:** Experience files + `config.md` Role Types - -**Build** `resume_builder/support/achievement_reframing_guide.md`: - -```markdown -# Achievement Reframing Guide - -## How to Use -For each achievement, `Significance:` provides a one-line framing cue. -The role-type table shows how to emphasize/de-emphasize for each target audience. - ---- - -### [Achievement ID]: [Title] -**Significance:** [One sentence — why this matters broadly] - -| Role Type | Emphasis | Lead Verb | Framing Angle | -|-----------|----------|-----------|---------------| -| [role 1] | HIGH | Developed | [emphasize X aspect] | -| [role 2] | MEDIUM | Applied | [bridge to Y domain] | -| [role 3] | LOW | -- | [omit or condense] | - -**Overclaiming warning:** [if applicable — e.g., "Do not claim sole credit for experimental results"] -**First-pass checklist:** [ ] Verb matches author role [ ] Numbers from paper [ ] Status matches provenance - ---- -[Repeat for each achievement] -``` - -### >>>>>> MANDATORY STOP — DO NOT PROCEED <<<<<< -Present the reframing guide summary — show which achievements are HIGH for which role types. -Ask user: "Does this priority mapping look right for your target roles?" -**You MUST wait for the user's explicit text response before continuing.** - ---- - -## Phase 5: Build Bundles - -**Goal:** One bundle per role type from `config.md`, with 5 sections each. - -**Read:** Experience files + Skills Taxonomy + Reframing Guide + `config.md` Role Types - -**For each role type**, create `resume_builder/bundles/bundle_.md`: - -```markdown -# Bundle: [Role Type Name] - -> Target employers: [from config.md] -> Tier: [from config.md] - ---- - -## S1: Role Profile & Priority Matrix - -**Positioning:** [1-2 sentences — how to position the user for this role type] - -### Priority Matrix -| Priority | Achievement IDs | Rationale | -|----------|----------------|-----------| -| HIGH | [IDs] | [why these lead for this role type] | -| MEDIUM | [IDs] | [supporting evidence, bridge topics] | -| LOW | [IDs] | [omit unless budget allows or JD specifically asks] | - ---- - -## S2: Summary Guide - -**Headline pattern:** [Role-appropriate headline template] -**Building blocks:** [3-5 phrases that should appear in summaries for this role type] -**Avoid:** [terms/framings that don't fit this audience] - ---- - -## S3: Achievement Reframing Map - -[For each HIGH/MEDIUM achievement: which angle to use, which metrics to lead with] - -| ID | Default Framing | This Role's Framing | Key Metric | -|----|----------------|--------------------|-----------| -| [ID] | [generic] | [role-specific angle] | [number to highlight] | - ---- - -## S4: Skills Guide - -**Bold tools (resume):** [3-5 tools to bold in Technical Skills for this role type] -**Must-include skills:** [skills that MUST appear for ATS match] -**Nice-to-have:** [skills to include if budget allows] -**Omit:** [skills irrelevant to this audience] - ---- - -## S5: Cover Letter Guide - -**Institution type:** [Industry / National Lab / Academic] -**Opening hook pattern:** [template for first paragraph opener] -**Key narrative thread:** [what story to tell across paragraphs] -**"Why them" angle:** [what to research about target employer] -**Avoid:** [CL anti-patterns for this role type] -``` - -Progress: "Building bundle for [role type] — [N] HIGH priority achievements, [M] bold tools" - ---- - -## Phase 6: Build Significance Research Files - -**Goal:** Field context for cover letters — NOT for resume bullets. - -**For each position**, create `resume_builder/support/significance_.md`: - -```markdown -# Significance Research: [Position] - -> Use in cover letters and summaries — NOT in resume bullet text. -> These provide field context that demonstrates the user understands the landscape. - ---- - -### [Achievement ID]: Field Context -**The problem:** [What challenge does this address? Industry/scientific context] -**Competing approaches:** [What else exists? What are the limitations?] -**Why this matters:** [Market size, DOE/funding priorities, industry need] -**Differentiation:** [What makes the user's approach unique or better?] - ---- -[Repeat for each major achievement worth cover-letter depth] - -### Field Overview: [Broad Topic] -[2-3 paragraphs of field context that multiple achievements contribute to] -[Useful for cover letter opening hooks and "why this matters" framing] -``` - -Progress: "Significance research — [N] achievements with field context, [M] field overviews" - ---- - -## Final: Status Report - -After all phases complete (or after the requested subset), present: - -### KB Build Status -| Component | File | Status | Items | -|-----------|------|--------|-------| -| Experience files | `experience/*.md` | [DONE/MISSING] | [N achievements] | -| Skills taxonomy | `support/skills_taxonomy.md` | [DONE/MISSING] | [N skills] | -| Pub metadata | `support/pub_metadata.md` | [DONE/MISSING] | [N pubs] | -| Reframing guide | `support/achievement_reframing_guide.md` | [DONE/MISSING] | [N entries] | -| Bundles | `bundles/bundle_*.md` | [DONE/MISSING] | [N bundles] | -| Significance | `support/significance_*.md` | [DONE/MISSING] | [N files] | - -### Ready for Generation? -- [ ] At least 1 experience file with 5+ achievements -- [ ] Skills taxonomy with 20+ skills -- [ ] At least 1 bundle matching a target role type -- [ ] Pub metadata complete -- [ ] Reframing guide covers all achievements -- [ ] Significance files for cover letter depth - -If all checked: "Knowledge base is ready. Save a JD to `JDs/` and run `/make-resume JDs/.txt`" -If gaps: "[List what's missing and which phase to re-run]" - -### >>>>>> MANDATORY STOP <<<<<< -Present status report. Wait for user confirmation. -**You MUST wait for the user's explicit text response before continuing.** +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/.claude/skills/setup-extract/SKILL.md b/.claude/skills/setup-extract/SKILL.md index 41d8295..f84dca5 100644 --- a/.claude/skills/setup-extract/SKILL.md +++ b/.claude/skills/setup-extract/SKILL.md @@ -1,170 +1,11 @@ --- -description: Extract structured information from research papers, PDFs, or code into knowledge base extractions +name: setup-extract +description: Extract source-grounded career evidence from papers, employment records, project documents, and notes into structured knowledge-base files without promoting inference to fact. user-invocable: true --- -# /setup-extract +# Shared project workflow -**User input:** `$ARGUMENTS` +Read and follow [the setup-extract workflow](../../../resume_builder/reference/workflows/setup-extract.md) before doing this task. Resolve project paths from the repository root. -Parse `$ARGUMENTS`: -- File path to a paper (e.g., `papers/Smith2024_catalyst.pdf`, `papers/project_report.tex`) → read that file -- Multiple paths separated by spaces → batch mode (process each sequentially) -- Empty → ask the user for the paper path or paste content - ---- - -## Startup - -1. Read `CLAUDE.md` — check KB Corrections Log for known issues -2. Read `config.md` — load Personal Info (to identify user's author position), Provenance Flags -3. Read `knowledge_base/extractions/_INVENTORY.md` — see what's already extracted, avoid duplicates - -If the paper is already in the inventory: -- Show the existing extraction path -- Ask: "This paper is already extracted. Re-extract (overwrite) or skip?" -- Wait for user response before proceeding - ---- - -## Phase 1: Read & Understand the Paper - -Read the paper using the appropriate method: -- **PDF files:** Use the Read tool (supports PDF reading) -- **.tex source:** Read directly — often has more detail than the compiled PDF -- **If both exist:** Prefer .tex for content extraction, use PDF for figures/tables - -**While reading, collect:** -1. Full title, all authors, year, journal/venue, DOI (if available) -2. The user's position in the author list (first, co-first, second, middle, last, corresponding) -3. Publication status (check `config.md` Provenance Flags first, then infer: published / under review / draft / internal) -4. All computational methods, experimental techniques, software, and frameworks mentioned -5. Quantitative results — speedups, accuracies, efficiencies, improvements over baselines -6. Novelty claims — "first-ever", "new framework", "novel approach", etc. -7. Collaboration indicators — other groups, institutions, shared resources -8. Funding acknowledgments - -Progress: "Reading paper... [title] by [first author] et al., [year]" - ---- - -## Phase 2: Clarify User's Role - -If the user's contribution is not obvious from the paper (common for multi-author work), ask: - -**Questions to ask (skip any that are already clear from the paper):** -1. "What was your specific contribution? (e.g., all computational work, specific analysis, code development)" -2. "Did you develop any tools, methods, or code used in this paper?" -3. "Were there other groups or institutions involved? What was your group's role?" -4. "Any quantitative results you can personally claim? (e.g., 'I ran all the simulations')" -5. "Is there anything in this paper that should NOT appear on your resume? (e.g., collaborator's experimental data)" - -### >>>>>> MANDATORY STOP — DO NOT PROCEED <<<<<< -Present your understanding of the paper and ask the clarifying questions above. -**You MUST wait for the user's explicit text response before continuing.** - ---- - -## Phase 3: Write Extraction - -Create the extraction file at `knowledge_base/extractions/.md` - -**Naming convention:** `<2-3_word_descriptor>.md` -- Examples: `Smith2024_protein_stability.md`, `Chen2023_binding_affinity.md` -- If the user is first author: use their last name -- Normalize to lowercase with underscores - -**Extraction format:** - -```markdown -# [Full Paper Title] - -## Metadata -- **Authors:** [author list — highlight user's name with bold] -- **Year:** [year] -- **Journal:** [journal/venue or "unpublished"/"internal"/"under review at X"] -- **DOI:** [DOI or "N/A"] -- **User's role:** [first author / co-first / contributing / corresponding] -- **Status:** [published | under review | draft | internal] - -## Methods & Tools -- **Computational methods:** [e.g., MD, ML, FEA, CFD, etc. — be specific about methods, force fields, etc.] -- **Software/frameworks:** [e.g., GROMACS, PyTorch, ABAQUS, custom code, etc.] -- **Hardware/HPC:** [if mentioned — clusters, GPU resources, etc.] -- **Key techniques:** [specific methodological details that map to resume skills] - -## Key Results -[Number each result. Include quantitative metrics wherever possible.] -1. [Result with numbers — e.g., "Achieved 5,000x speedup over brute-force screening"] -2. [Result — e.g., "Screened 8,500 variants, identified 7 top candidates"] -3. [...] - -## Novelty Claims -[What's genuinely new — be precise, avoid overclaiming] -- [e.g., "First application of framework X to system Y"] -- [e.g., "New method combining A and B — no prior work exists"] - -## Collaboration & Scope -- **Other groups:** [institutions, PIs involved] -- **User's specific contribution:** [from Phase 2 clarification] -- **Shared vs. sole work:** [what the user did alone vs. with others] - -## Provenance Notes -- **Publication status:** [matches config.md if listed there] -- **Safe to claim:** [what the user can put on a resume without hedging] -- **Needs hedging:** [claims that require "contributed to" or "supported" framing] -- **Do NOT claim:** [results from collaborators, claims that would be overclaiming] - -## Resume Bullet Seeds -[3-5 draft bullets in STAR format. These are seeds, not final text.] -[Use full-ownership verbs only for sole-contributor work. Hedge for shared work.] -1. [Action verb] + [what was done] + [quantitative result/impact] -2. [Action verb] + [method/tool developed] + [what it enabled] -3. [Action verb] + [scope — e.g., "across N systems"] + [outcome] -4. [Optional: collaboration-framed bullet] -5. [Optional: tool/infrastructure bullet] -``` - -Save the file. Show the user the complete extraction. - -Progress: "Writing extraction for [short title]... [N] results identified, [M] bullet seeds drafted" - ---- - -## Phase 4: Update Inventory - -Read and update `knowledge_base/extractions/_INVENTORY.md`. - -Add a row to the inventory table: - -``` -| [filename] | [short title] | [user's role] | [status] | [primary methods] | [date extracted] | -``` - -Present the updated inventory entry to the user. - ---- - -## Phase 5: Next Steps - -After extraction is complete, present: - -1. **Extraction summary:** [N] methods, [M] quantitative results, [K] bullet seeds -2. **Provenance flags:** Any items that need special handling -3. **Suggested next action:** - - If more papers to extract: "Run `/setup-extract [next paper path]`" - - If all papers done: "Run `/setup-build-kb` to synthesize extractions into experience files and bundles" - -### >>>>>> MANDATORY STOP <<<<<< -Present extraction summary. Wait for user feedback or next paper. -**You MUST wait for the user's explicit text response before continuing.** - ---- - -## Batch Mode - -If `$ARGUMENTS` contains multiple file paths: -1. Process each paper through Phases 1-4 sequentially -2. Ask Phase 2 clarifying questions for ALL papers at once (grouped) before writing any extractions -3. After all extractions: present combined inventory update and summary -4. Single STOP at the end (not per paper) +This shared file is the only workflow implementation for both agents. Preserve existing user authorization and resume from the application's recorded state. diff --git a/AGENTS.md b/AGENTS.md index fb82efb..dafdd6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,7 @@ ## File Map ``` -.Codex/skills/ +.agents/skills/ ├── setup-extract/SKILL.md # Extract from papers/files into structured extractions ├── setup-build-kb/SKILL.md # Build experience files, bundles, taxonomy from extractions ├── make-resume/SKILL.md # Phase 0-2: JD research → bullet plan → resume/CV generation @@ -154,34 +154,14 @@ Character counts are diagnostic only; they must never drive page filling or equa ## Active Sessions -_Update this section when starting/finishing a JD._ - -| Session | Status | Next Command | -|---------|--------|-------------| -| NVIDIA - **Senior DevOps Engineer**, **Zürich** (req JR2024256) | **SUBMITTED 2026-09-05.** Evidence Fit **64/100**, fit class **Stretch**, hard gate **FAIL x2**, channel **Weak** (7th consecutive cold submit; this posting published no named contact). 2pp resume 13 bullets + 1pp cover letter 279 words; both validators PASS 0 warnings, 0 boxes, 0 Tier 1 findings open. Measured Document Quality **82/100 pre-edit**; both Tier 1 findings were then closed, so it is better than 82 but **not re-measured - never quote a post-edit estimate as a score**. Submitted on an explicit user override. ⚠ **Hybrid vs 5-day onsite was never resolved before sending** - now a live screening topic, not pre-cleared. Thun→Zürich is ~1h50 each way. Comp is *not* a risk (levels.fyi Greater Zürich IC3 median ≈ CHF 204k). Honest gaps named in the letter: on-prem networking (0 canonical) and that employer-side Linux/Ansible ended Dec 2022, with only the personal `PP-3` server carrying currency. | Await response. **Raise the working model at first contact** - a 5-day-onsite seat would be a late no. | -| SBB - **Data Engineer (m/w/d)**, Asset Management Infrastrukturanlagen, **Bern** (Job ID 103755) | ⭐ **INTERVIEW INVITED 2026-08-28 - 9. September 2026, 08:30, 45 Min, MS Teams. The only live interview in the log.** Stage **2 of 4** per the posting: *"Virtuelles Kennenlernen mit HR und Fuehrungskraft"*, so **HR together with Andri Wienandts**, not a pure HR screen. Brief: `output/SBB_DataEngineer_AssetMgmt/interview_brief_sbb_2026-09-09.md`. **First conversion of the evidence-first cohort** (6 applications, 2 rejections) and it came from a **COLD submit** - the warm contact printed on the posting was never called. The resume never contained **Snowflake, dbt or Power BI**, the literals an ATS screen would key on, and it still got through: a human read it and the honest-substitution framing held. One data point, do not over-read it. Submitted 2026-08-25, Core, Evidence Fit **79/100**, hard gate PASS, Document Quality **92/100**, no cover letter (SBB waives it). Best practical fit in the log: Bern, German-language, no relocation, EU + B permit. **Four unresolved items, now live call topics:** Anforderungsniveau **K** band (capped GAV, not public, likely below the 180k bar); the seat reads **lateral or below** current Staff + Component Owner scope (the declined-BKW shape); **RAMSI** Java/Spring Boot/Angular is a full responsibility line on a historical/absent stack; Kidz Care scales on gross household income. Gaps to answer honestly: Snowflake, dbt, Argo, Helm, Power BI, Spring Boot, Angular - survivable only because the JD says "z. B.". Thesis stays a **methods prototype**: no operational data, no accuracy figures, PSO surveyed only. | **Decide before 9.9.:** what he does if K lands at 130-150k, if architecture is set elsewhere, or if RAMSI is heavily Angular. Any one can be a no - better decided in advance than improvised on camera. | -| RUAG C5I - **AI Engineer C5I**, **Thun** (application ID 18027) | **SUBMITTED 2026-08-27** via jobs.ruag.ch (portal only). Evidence Fit **74/100**, fit class **Stretch**, hard gate **FAIL** (R1/R2, the title-defining responsibilities, are Adjacent not Direct), Document Quality **93/100** after a critique-and-fix round, channel **Weak/cold**. German CV 2 pages/13 bullets + motivation letter 1 page/295 words; validators PASS, 0 boxes, visual QA clean. (Cohort slot language here is historical: the **7/2/1 caps were dropped 2026-09-05** - see `application_strategy.md`.) Positioning is his canonical title Staff Data, Analytics & AI Engineer; `PP-3` carries current Linux/hardening evidence and `SW-5` ties DevSecOps to C5I's stated model. The letter names the AI-platform gap in one sentence. Honest gaps: no AI/LLM platform, no compute/GPU cluster, no RAG/retrieval, ML/AI frameworks certification-context only. **Compensation, PSP/project eligibility and level were never resolved and are now live screening topics.** Marco Heinzen's published direct line was never used. **If rejected, do not drop RUAG** - **Senior DevOps Engineer C5I** is shortlisted and fits better (still live on the board 2026-09-04). The **Data Lakehouse / Senior Data Platform Engineer** req was a title-match prior the JD does not support: reposted 2026-09-04 as **Senior Data Engineer (MSSQL)**, requiring on-prem MSSQL/Talend/SAP S/4HANA - 0 canonical each - and **ruled out**. | Await response; optional calls to Marco Heinzen (scope/level) and Frank Haugwitz (comp/PSP). | -| Schweizer Armee - **Kommando Cyber (Kdo Cy)**, DevOps Engineer III (Data Platform), **Zimmerwald** (Ref JRQ$540-19848) | **SUBMITTED 2026-08-27.** Evidence Fit **79/Core**, hard gate **PASS**, Document Quality **92/100**, channel **Weak**. German 2-page CV plus 1-page motivation letter; validators PASS, 0 boxes, visual QA clean. BW-1 surfaces the six-year Bundeswehr officer career. User explicitly accepted the critique's remaining BS-1 wording issue (*„ohne manuellen Eingriff“*) as good enough and submitted unchanged. Cohort evidence-first-2026-01 is now 5/10, Core 3/7. | Await response; optional post-submit call to Marcel Matthey-Doret about Lohnklasse, Engineer-III level and development path. | -| Google - Software Engineer III, Business Home, Zurich (req 93922217108087494) | **CLOSED - NOT PROCEEDING 2026-07-28** (submitted 2026-07-27; Core, Evidence Fit 89/100, Document Quality 94/100; no interview). Rapid early-screen disposition; known risks were Mid-level versus current Staff scope, data/platform versus product-SWE positioning, preferred algorithms/accessibility gaps, and a cold channel. | Done - cohort rejection recorded; do not treat this outcome alone as a document-quality failure | -| Microsoft — Principal Forward Deployed Engineer, SWE (German Speaking), Zürich (req 200043897) | **SUBMITTED 2026-07-27** (84.2/100 Pass 2; finalized 2-page resume + 1-page cover letter). Strong native-German, Staff progression, production ownership and enterprise-data-readiness case; honest gaps remain in end-to-end LLM delivery, Azure AI and strategic-account FDE experience. | Done — await response | -| BIS Basel — Senior Data & Analytics Engineer, AI (jr100429, 3-yr term) | **SENT 2026-07-10** (80.5/100; deadline 2026-07-24; hybrid Basel, English-working intl org). Critique flags LLM-depth probe (integration/config vs. ownership) as the screening risk — prep honest answer before any call | Prep interview brief when screening lands | -| NATO JWC Stavanger — Staff Officer, AI Engineer (2030 Digitalisation, G15, 3-yr PLN) | **SENT 2026-07-10** (77.5/100; deadline 2026-08-09; final interviews 2nd half Oct 2026). Borderline screen: Azure-cert "or equivalent", LLM depth, no current clearance; German national + officer service are the legibility assets. Tax-free NOK 93,933/mo + allowances | Await screening; no action until contact | -| Microsoft — Senior SWE, Industry Solutions Engineering (ISE), Zürich (req 200040836) | **SENT 2026-07-03** (85.8/100 Pass 2; 2pp resume + 1pp CL; verbatim Eightfold JD; IC4 base CHF 146.2–245.9k; applied ~7 days after posting). Same-day build-to-critique-to-submit. Decisions.json logged as applied | Done — await response | -| Kraken (Payward) — SRE, AI Agents (remote, CH-eligible) | **CLOSED — REJECTED 2026-06-17** (applied 2026-06-15 ~87.2/100, no interview). Honest gaps (NO Terraform/SRE-title/LangGraph) likely the filter; 4th Kraken req declined/rejected to date | Done | -| Google — Senior Data Engineer (Merchant Data Science), Zürich/MV | **CLOSED — NOT PROCEEDING 2026-07-24** (applied 2026-06-15, 85.5/100; passed Google Hiring Assessment 2026-06-20, no interview). Assessment pass remains recorded separately. The official 90-day wait applies only to reapplying for the same job; different Google roles remain eligible, subject to 3 applications per rolling 30 days. | Done — target a different strong-fit Google req | -| Snowflake — Sr SWE, Enterprise (Observe by Snowflake), Zürich | **SENT 2026-06-06** (~86/100; 2pp resume + 1pp CL; real Ashby JD; comp CHF 176–253k base; NO C++ gate). Tier 1+2 applied; Vizrt low-latency skipped per user. Best-fit role in the 2026-06 search | Done — await response | -| Isovalent (Cisco) Sr Data Engineer, Observability | **CLOSED — role pulled** (live Cisco scrape 2026-06-02: not on board; Recruitee link dead). Package finalized ~86/100, SHELVED for reuse | Done — retarget PDFs to next live data-eng req (QuantCo/Grafana/Confluent) | -| Google Zürich Sr SWE Infrastructure (Data Pipeline) | **CLOSED — DROPPED + DELETED 2026-06-02** (poor fit). Live JD = Core infra/systems SWE with **C++ as a MINIMUM qual**, off-thesis vs `user_positioning`. Output folder deleted (was built on a fabricated JD). | Done — do not reattempt this req | -| Kraken AI Infrastructure | **CLOSED — REJECTED** (applied, no interview) | Done | -| Infineon Doctoral | **CLOSED — withdrew** (got interview invite, declined: no relocation to Germany) | Done | -| Infineon AI Engineer | **CLOSED — not applied** (no relocation to Germany) | Done | -| Apple Data Engineer (ISE, Zurich) | **CLOSED — REJECTED** (no interview) | Done | -| Google FDE GenAI (Zurich) | PAUSED — GenAI evidence gap too large; redirecting to data-eng/MLOps roles | Likely abandon | -| Equinor AI Architect (Norway) | **SENT** (~80/100) | Done — await response | -| QuantCo Cloud Engineer (Europe/Zürich) | **CLOSED — REJECTED** (applied 2026-06-01 ~82/100, no interview; rejection 2026-06-15) | Done | +Read [current applications](resume_builder/reference/current_applications.md), generated from `job_scout/state/applications.json`. Update the registry through the tracker, then regenerate views. Do not maintain another status table here. Historical notes are preserved under `job_scout/state/history/`. --- ## KB Corrections Log _See `config.md` for user-specific corrections. Add verified errors here as you find them._ + +## Maintenance resume point + +For the authorized project-review fixes, read [FIX_PROGRESS.md](FIX_PROGRESS.md) before continuing. Both agent skill directories route to the shared workflows in `resume_builder/reference/workflows/`. diff --git a/CLAUDE.md b/CLAUDE.md index 22d4bff..45349b8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,7 +7,7 @@ ## File Map ``` -.claude/skills/ +.agents/skills/ ├── setup-extract/SKILL.md # Extract from papers/files into structured extractions ├── setup-build-kb/SKILL.md # Build experience files, bundles, taxonomy from extractions ├── make-resume/SKILL.md # Phase 0-2: JD research → bullet plan → resume/CV generation @@ -153,36 +153,7 @@ Character counts are diagnostic only; they must never drive page filling or equa ## Active Sessions -_Update this section when starting/finishing a JD._ - -| Session | Status | Next Command | -|---------|--------|-------------| -| NVIDIA - **Senior DevOps Engineer**, **Zürich** (req JR2024256) | **SUBMITTED 2026-09-05.** Recorded in the application record (now 7 entries) and in the scout decision log. ⚠ **Hybrid vs 5-day onsite was never resolved before sending**, so the working model is now a **live screening topic**, not a pre-cleared one - the same pattern as RUAG's comp/PSP. Thun→Zürich is ~1h50 each way; a 5-day-onsite answer is the main disqualifier to watch for. Comp is *not* a risk (levels.fyi Greater Zürich IC3 median ≈ CHF 204k, IC4 ≈ CHF 379k). Channel **Weak - the 7th consecutive cold submit**, though this posting published no named contact, so cold was the only option available. Submission PDFs `Dennis_Thiessen_Resume.pdf` and `Dennis_Thiessen_Cover_Letter.pdf` exist and are MD5-verified against source; letter recompiled so its date reads 5 Sep 2026. ⚠ **The letter carries ` oday` and the submission copies are copies, not build targets - if it is sent on a later day, recompile the letter and refresh both copies first** (`config.md` Output Rules). Submitted despite the hard-gate failures on an explicit user override, the same shape as RUAG AI Engineer C5I. Resume 2pp/13 bullets + 1pp cover letter (279 body words), both validators PASS 0 warnings, 0 boxes, visually inspected. Measured **Document Quality 82/100** at critique; **both Tier 1 findings were fixed 2026-09-05** - `Ubuntu` removed from the skills line (0 canonical; `PP-3` is Debian only, and the Debian→Ubuntu transfer argument belongs in an interview answer, not a skills line) and `security hardening` now marked *(personal server)* per the explicit `claims.json` instruction. Four honest ATS terms were added from existing evidence (configuration management, service reliability, container orchestration, observability), lifting JD term coverage **50%→61%**; no bullet was touched, so the budget stayed at 13 and cadence could not regress. **The critique is now STALE BY DESIGN - the document is better than 82 but that number was not re-measured, so never quote a post-edit estimate as a score.** The headline reorder was deliberately NOT applied: it is a positioning call for the user and `\headline` sits in the FIXED header block. Cover-letter hook is first-party verified (NVIDIA's own live Zürich posting JR2022878); **a hook was DROPPED at verification** - the Phase 0 plan's "Alps / Swiss AI Initiative with ETH Zürich and EPFL" is **not** supported by the first-party NVIDIA newsroom release, which mentions neither EPFL nor any Swiss AI Initiative; that came from secondary coverage. The letter names **both** honest gaps out loud - on-prem networking, and that employer-side Linux/Ansible ended Dec 2022 with only a personal server carrying currency. **⚠ BLOCKING: the working model is still unresolved and no document fix changes that.** Session `output/NVIDIA_SrDevOps_Zurich/session_nvidia_sr_devops.md`; JD scraped live via Playwright and saved verbatim (posted ~2026-09-01, very fresh). Evidence Fit **64/100**, fit class **Stretch**, **hard gate FAIL on two counts**: (1) required line *"Strong expertise in on-premises networking and compute infrastructure"* is a **Gap** - networking is 0 canonical and is not substitutable from data-platform work; (2) **hybrid vs onsite is UNRESOLVED** and the JD is silent - Thun→Zürich is ~1h50 each way. Required tally **3 Direct / 3 Adjacent / 1 Gap**. Recorded as an override rather than a passing gate, the same shape as RUAG AI Engineer C5I. (At the time this also had no free cohort slot; **that constraint no longer exists** - caps dropped 2026-09-05.) **Correction to the same day's scout triage: it called two required lines Gaps; only Q5b networking is one. Q2 (10+ yrs DevOps) is Adjacent and defensible** - a continuous CI/CD/automation thread runs Generali 2015 → today across five employers; the honest weakness is that those roles were titled data/test/research engineer, not DevOps. **The finding that changes the calculus: comp.** levels.fyi Greater Zürich puts NVIDIA IC3 median ≈ CHF 204k and IC4 ≈ CHF 379k, the highest-paying SWE employer in the city - the first role in the log where comp is a reason to *accept* a long commute rather than decline. Caveat: self-reported and equity-heavy after an extraordinary NVDA run; treat base as the reliable part. Strongest match is **R3, CI/CD pipelines for R&D engineering teams** - literally his career shape (`FC-1` Jenkins at a Fraunhofer institute, `BS-6` Ansible/Jenkins for fab analytics teams, `SW-3` Kubernetes + GitLab today). Zürich is NVIDIA's HPC/AI infrastructure-software R&D hub - first-party verified from NVIDIA's own live Zürich posting JR2022878 (*"building the next generation of scalable AI infrastructure ... distributed training, real-time inference, and communication efficiency across large systems"*), which is the hook the letter uses. **The Alps / Swiss AI Initiative framing was checked and dropped** - see above. **Honest gaps that travel with it:** on-prem networking (0), HPC (0), bare metal/switches/QEMU-KVM (0), RHEL/CentOS (0 - Debian→Ubuntu transfers, Red Hat does not), and **Ansible + Linux-at-work both ended Dec 2022** with currency carried only by `PP-3`, a personal server whose canonical entry forbids using it as physical-infrastructure evidence. No metric is quotable anywhere. | **Await response. Raise the working model at first contact** - it went in unresolved and a 5-day-onsite seat would be a late no. Note: `claims.json` has no shell-scripting entry, so **Bash is omitted** from Skills although the JD names it and ten years of Debian admin plus Ansible work plainly involve it - same shape as the Ansible/Linux gaps found 2026-08-27. Confirm with the user and record it. Groovy stays out regardless. | -| RUAG C5I - **AI Engineer C5I**, **Thun** (application ID 18027) | **SUBMITTED 2026-08-27** via jobs.ruag.ch (portal only; email and post refused). Evidence Fit **74/100**, fit class **Stretch**, hard gate **FAIL** (R1 and R2, the two title-defining responsibilities, are Adjacent not Direct), Document Quality **93/100** after a full critique-and-fix round (85 pre-edit), truth and provenance 24/25, **0 Tier 1 findings at Round 2**, channel **Weak/cold**. Submitted: German CV **2 pages, 13 bullets** + German motivation letter **1 page, 295 words**; both validators PASS, 0 boxes, text-order and visual QA clean, submission PDFs MD5-verified before sending. **Cohort evidence-first-2026-01 is now 6/10 and the sole Stretch slot is CONSUMED (Core 3/7, Adjacent 2/2, Stretch 1/1)** - any further application must clear Core or Adjacent on its own merits. Positioning is his canonical title **Staff Data, Analytics & AI Engineer**, carried by `BS-1` production ML-inference integration, `BS-6` Linux/Ansible, `SW-3`/`SW-7`/`SW-2` current Kubernetes and data-product work, `PP-3` current self-hosted Linux and hardening, `SW-5` DevSecOps tied to C5I's stated operating model, and the canonical `BW-1` officer career. The letter **names the AI-platform gap in one sentence** and pivots to the operations/platform side. **Honest gaps that travel with it:** no AI/LLM platform built or operated, no compute/GPU cluster, no RAG/retrieval implementation, no formal LLM evaluation; ML/AI-framework depth is certification-context only. **Compensation, PSP/project eligibility and role level were NEVER resolved before sending, so all three are now live screening topics rather than pre-cleared ones.** ⚠ **Marco Heinzen's published direct line (+41 79 568 14 96) was never used** - this went in as a pure cold submit, the sixth consecutive one. **If rejected, that is NOT a signal to drop RUAG:** five other RUAG C5I reqs sit in the scout log, and **Senior DevOps Engineer C5I** is already shortlisted and is a **materially better fit than this one**, sitting on the lane where his evidence is Direct rather than Adjacent (still live and verified on the board 2026-09-04). The **Data Lakehouse / Senior Data Platform Engineer** req was a title-match prior that the JD does not support — it was reposted 2026-09-04 as **Senior Data Engineer (MSSQL)**, requires on-prem MSSQL / Talend / SAP S/4HANA (0 canonical each), and is **ruled out**. **Scout run 2026-09-07: the RUAG C5I backlog is now SETTLED — undecided RUAG rows went 69 → 3** (`ICT Architect`, `Software Engineer C5I`, `Cyber Security Consultant` remain open; `ICT Architect C5I` stays `paused`). Do not re-triage that board from scratch; read `decisions.json`, where every rule-out note states whether it came from a JD read or from the title/family pattern. Three JD reads that cycle all ruled out: **Test Manager C5I** (the closest call — it escapes the networking gap entirely and BW-1 scores, but required *"fundierte Erfahrung als Test Engineer"* rests on `VIZRT`, **ten months, ended 2018-05**, so it is thin and stale, plus a lane exit and a third C5I req while 18027 is pending), **System Engineer C5I** (VMware + Firewall + Netzwerk, all 0), and **ICT Solution Architect Business Applications** (required *"mehrjährige Erfahrung als Solution Architect"* — a title `feedback_swisscom_datamesh_ownership` forbids — and the duties are *"unter Anleitung"*, a supporting seat below authoring). | Await response. Optional but still worthwhile: call **Marco Heinzen** about technical scope and level, and **Frank Haugwitz** about compensation and PSP/project eligibility. **NEW CONTACT 2026-09-07 — `Lars Kauz`, Head of ICT Infrastructure C5I, +41 79 293 93 82, lars.kauz@ruag.ch**, published on the System Engineer C5I posting. Channel is the rationed variable since the 09-05 cohort change, so a published, reachable name matters — **but he heads ICT Infrastructure, the function whose req was ruled out for VMware/VDI/Windows content, and that is NOT demonstrably the Kubernetes platform group Senior DevOps C5I sits in.** He is a warm entry into C5I, not a verified channel for rank 1; Haugwitz stays the recorded gate. Do not react to a single outcome with positioning changes (`application_strategy.md` §38/§74). | -| Schweizer Armee - **Kommando Cyber (Kdo Cy)**, DevOps Engineer III (Data Platform), **Zimmerwald** (Ref JRQ$540-19848) | **CLOSED - REJECTED 2026-09-10** (submitted 2026-08-27, **14 days to disposition**, no interview - the fastest in the log after Google Business Home). Form letter from **Besarta Mehmeti**; the text is boilerplate (*„Profil und unsere Kriterien nicht in allen Punkten übereinstimmen"*) and carries **zero diagnostic content** - it is not a signal about the package and is **no reason to re-audit it**. Same disposition class as Microsoft ISE, Google Business Home and Aker BP: **a cold-channel no-interview rejection does not implicate document quality on its own.** ⚠ **WORTH WATCHING, NOT CONCLUDING:** this application and **SBB** are identical on every recorded dimension - Core, Evidence Fit **79**, hard gate PASS, Document Quality **92**, channel Weak - and they went opposite ways: SBB converted to an interview, this died in 14 days. **That shows the fit score did not discriminate; it does not show what did.** n=1 on each side - do not react with positioning changes (`application_strategy.md` §38/§74). **Channel:** Marcel Matthey-Doret's published line (+41 58 46 78409) was never called - the **fourth** published, reachable contact left unused after Heinzen (RUAG), Wienandts (SBB) and Marthinsen (Aker BP). Consistent with the 2026-09-05 channel policy but **not evidence for it**: the only interview in the log came from a *cold* submit whose resume was missing all three ATS literals. **Kdo Cy as an organisation is NOT closed** - 11 Kdo Cy roles were open on the portal at submission time and only this req is decided. Evidence Fit **79/Core**, hard gate **PASS**, Document Quality **92/100**, channel **Weak**. Submitted package: German 2-page CV with **15 bullets** and 1-page motivation letter with **300 body words**; validators PASS, 0 boxes, text order and visual QA clean. Canonical **BW-1** records the six-year Bundeswehr officer career and anchors the letter's military/public-service motivation. User explicitly accepted the critique's remaining BS-1 wording issue (*„ohne manuellen Eingriff“*) as good enough and submitted unchanged. Honest gaps remain physical datacenter work, GPGPU, ClickHouse and named Pull-GitOps practice. Cohort evidence-first-2026-01 is now **5/10, Core 3/7** *(pre-dates the 2026-09-05 cap drop; counts are a record, not a budget)*. | Done - rejection recorded in `application_cohort.json`, `decisions.json` and the session file. Optional and low-cost: a one-line feedback request to **Besarta Mehmeti**, which also builds a contact at an org with other open reqs. | -| Citadel Securities — Platform Engineer, Research Platform Engineering, **Zürich** (also NY/Miami) | **SUBMITTED 2026-08-26 — open, awaiting response.** Recorded as `adjacent` in `application_cohort.json` (outcome `applied`) and as `applied` in `decisions.json`. *Slot language from the capped-cohort era has been removed here: the caps were dropped 2026-09-05 and counts are a record, not a budget.* ⚠️ **KEY FINDING — the application form's preferred-work-location selector offered NO Zurich**, despite the JD saying "Miami, Zurich or New York" and the site's own Zurich filter returning this req. Careers site is operated by **Citadel Enterprise Americas LLC**; the apply flow is likely US-entity, or the Zurich seat is closed while the posting stands. **This partly supersedes the working-model risk** — if no Zurich seat is reachable through that form, hybrid-vs-onsite is moot and the application may sit against a US req, a hard NO on relocation. **Rule for any future Citadel application: check the form's location selector BEFORE building a package** — the posting's stated locations and the site filter both proved unreliable. User applied with low expectation; cold channel, evergreen standing req. 2pp resume (13 bullets) + 1pp CL (270 words), both validator **PASS, 0 warnings**; JD term coverage 51%→68% after Tier 1 fixes. Critique **STALE by design** (86/100 pre-edit; user chose to submit as-is over re-critiquing; est. ~91 post-edit, never quote as a score). **CORRECTED 2026-09-10:** this row previously said *"Cohort entry and decision-log entry deliberately NOT written — no confirmed send date"* while its own status line read SUBMITTED. **Both entries exist and both are dated 2026-08-26** — the sentence was pre-submission text that survived the send. **The working-model question is still open and travels with the application:** Evidence Fit **71/100 (Adjacent, mid-band; revised down from 74)**, qualifications gate PASS but **a practical constraint is UNRESOLVED**, which `critique_framework.md` treats as a NO-GO trigger. **The JD is silent on hybrid vs onsite (0 grep matches), and Ken Griffin is on record that Citadel returned to the office 5 days a week and calls it his most important leadership decision.** If that holds for Zurich, it means ~2–2.5h daily commuting from Bern or relocating — breaking the Bern-based / 2–3 day hybrid bar. Only contrary evidence is a stale 2022 anonymous Fishbowl post about Operations. **Must be asked, not researched** — same class as the SBB K band. Practical constraints scored 5/5 in Phase 0 on an unexamined assumption; corrected to 2/5. **Zero Gaps among the 7 minimum quals**, and all five preferred technologies (Kafka, Kubernetes, Spark, Airflow, distributed DBs) are Direct — **the strongest raw stack alignment in the log**. Title-defining function **splits**: R2 (ingestion/transformation/storage/lifecycle of large datasets) is Direct and describes SW-1/SW-7 exactly; **R1 (design and build the research platform itself) is Adjacent** — the Aker BP authoring-vs-building shape again, and unwritable-around under Scope Discipline. **Classification caveat:** `application_strategy.md`'s "a defining responsibility is only Adjacent → Stretch" would strictly class this Stretch; recorded Adjacent per the Aker BP precedent (the caps this once turned on no longer exist). Gaps: platform authoring at firm scale, no professional quant/finance (now partly mitigated by new **PP-1/PP-2**), SDKs (0 evidence), low-latency/HFT (0 evidence — thesis throughput figure is explicitly barred by `thesis_limits`). **Go is NOT a gap** — Q3 is disjunctive and Python satisfies it. **$175–350k in the posting is a US/NY pay-transparency disclosure, NOT the Zurich number — must be asked.** Channel **Weak/cold** — it was the 5th consecutive cold application, in an office visibly staffed from Google and ETH (platform research head Costas Bekas; Nicolai Meinshausen leads principal research). Cover letter **was written and submitted** (1pp, 270 words); the PP-1 domain story carries it. | **Await response** (submitted 2026-08-26). No document work pending. **Two questions travel with this application and must both be asked at first contact, before any interview effort:** (1) **is the application even against the Zürich seat?** — the apply form offered no Zürich, so it may sit against a US req, which is a hard NO on relocation; (2) **hybrid or 5-day onsite?** — unresolved at submission, and 5-day onsite is a likely NO-GO on the Bern constraint. Same shape as the NVIDIA working-model issue: it went out unresolved, so it is now a live screening topic rather than a pre-cleared one. Also unasked: the Zürich compensation figure — the posting's $175–350k is a US/NY pay-transparency disclosure. | -| SBB - **Data Engineer (m/w/d)**, Asset Management Infrastrukturanlagen, **Bern** (Job ID 103755) | **CLOSED - APPLICATION WITHDRAWN BY THE CANDIDATE 2026-09-11.** Interview held 2026-09-09 (HR + Scrum Master, **not** the People Leader the brief expected); SBB confirmed there was no room on the Einstufung, and he withdrew. **This is the cohort's only interview and it ended as a NO from his side, not theirs.** 💰 **HARD COMP FIGURES - supersedes the earlier +10% estimate and is the reference point for the whole Bern/Thun tier:** **Anforderungsniveau K tops out at ~CHF 145k.** He sits at **~CHF 135k inside a Swisscom band that runs to ~CHF 160k**. The decisive argument was **headroom, not the absolute number** - he would have entered K at its ceiling merely to break even, with nothing above. ⚠ **The package was a net DOWNGRADE on four counts:** 41h vs 40h week · 25 days holiday + Brückentage vs 28 + 5 from Zeitautonomie · 3 office days vs 2 · the band ceiling. ⚠ **Qualitative red flag worth carrying forward:** he spoke only to HR and the Scrum Master and **perceived difficulties in the team** - that is not repairable by a salary offer. 🎯 **TARGETING RULE — SBB ONLY: Anforderungsniveau K is too low; at SBB aim at NIVEAU L and/or KADER.** ⚠ **Deliberately NOT generalised** (user, 2026-09-11): *Anforderungsniveau* is **SBB's own** salary system (published at ) and does not map onto other employers — **jobs.admin.ch/Schweizer Armee uses Lohnklassen**, RUAG has its own scheme, PostFinance publishes per-posting bands. ~145k is an **SBB** ceiling, not a tier anchor. ⚠ **Thun and surroundings (RUAG, Kdo Cy) are explicitly OUT of this reasoning:** near-zero commute plus benefits that a salary band does not show — he named **childcare-cost coverage at the Schweizer Armee/Kdo Cy as potentially far more generous** (unverified — ask, do not assume). **Never decline a Thun-area role on band alone before pricing the benefits.** What *does* carry across employers: judge by **headroom in the band**, and price the **total package** (working time, holiday, office days), not the entry salary. Original record: interview was stage 2 of 4, **INTERVIEW HELD 2026-09-09.** SBB asked him to come back on whether the role interests him; **SBB's own feedback is still outstanding.** User's summary: *„ok"*, but **ein paar Red Flags**, and in his read the Rahmenbedingungen do not compensate for them - **leaning negative, explicitly undecided as of 2026-09-10, and he asked to leave it open.** ⚠ **The specific red flags were NOT captured while fresh** - anyone reopening this must ask rather than rely on memory. 💰 **First real read on Anforderungsniveau K:** his post-interview estimate is that roughly **+10% on the current ~140k** looks achievable (very roughly ~155k). **This is HIS ESTIMATE of what is negotiable, not a band SBB is recorded as quoting** - whether the K range was named on the call is unrecorded. Well below the 180k bar, above current pay. Recorded in `comp_worksheet_sbb.md` as the **first calibration point for the entire Bern/Thun local tier** (RUAG, Swissgrid, BKW, BFH), which was previously pure guesswork - treat as an estimate, never quote as a figure. **INTERVIEW INVITED 2026-08-28 - 9. September 2026, 08:30, 45 Min, MS Teams.** Per the posting's own process this is **stage 2 of 4**, *"Virtuelles Kennenlernen mit HR und Fuehrungskraft"* - **HR together with Andri Wienandts**, not a pure HR screen. Brief written to `output/SBB_DataEngineer_AssetMgmt/interview_brief_sbb_2026-09-09.md`. **This is the FIRST interview of the evidence-first cohort** (6 applications, 2 rejections, 1 conversion) and it converted from a **COLD submit** - the channel plan called for phoning Wienandts first and that never happened. **The resume never contained Snowflake, dbt or Power BI**, the three literals an ATS keyword screen would have keyed on; the session had recorded that risk as "accepted, not solvable". It did not filter him out, so a human read the dossier and the honest-substitution framing survived first contact. **One data point, do not over-read it** - but it is evidence against assuming honest omissions are fatal at the screen. Submitted 2026-08-25: 2pp English resume, 13 bullets, no cover letter (SBB waives it), Evidence Fit **79/100 Core**, hard gate **PASS**, Document Quality **92/100**, channel **Weak/cold**. Best practical fit in the log - Bern-based, German-language, no relocation, EU citizen + B permit. **Four things unresolved before submitting and now live call topics:** (1) **Anforderungsniveau K** is a capped GAV band, figures not public, very likely below the 180k bar; (2) the seat reads **lateral or below** current Staff + Component Owner scope, the same shape as the declined BKW, and the JD's only architecture language is a personal skill not an ownership mandate; (3) **RAMSI** Java/Spring Boot/Angular is a whole responsibility line on a stack that is historical or absent - ask its share, do not oversell; (4) Kidz Care scales on gross household income, so far below 90% at this band. Named gaps to answer honestly: Snowflake, dbt, Argo Workflows, Helm, Power BI, Spring Boot, Angular - all non-canonical, all survivable only because the JD prefixes the stack with "z. B.". Thesis stays a **methods prototype**: no real operational data, no accuracy figures, PSO surveyed only, throughput figure barred. | Done - withdrawn and recorded in `application_cohort.json`, `decisions.json`, `comp_worksheet_sbb.md` and memory. **Carry forward the headroom + total-package test, NOT the K→L mapping** — that one is SBB-specific and was explicitly narrowed by the user on 2026-09-11. | -| AWS (AWS EMEA SARL, Switzerland Branch) — Senior Forward Deployed Engineer, Zürich (job 10504263) | **CLOSED — NO-GO 2026-08-18** at the Phase 0 gate. Evidence Fit **69/100** (Adjacent). Minimum-qual gate passed (all 4 Basic Quals Direct) but the *title-defining* capability — customer embedding — is a Gap that `claims.json` globally forbids claiming, and R2 multi-agent/retrieval is a second Gap. User declined: **69 is below the fit bar worth spending a package on.** Consistent with revealed behaviour — every application actually submitted scored **77.5–89**; nothing below 75 has ever been sent. **No cohort slot consumed (Adjacent stays 1/2).** Phase 0 analysis retained in the session file for reuse if a better-fitting AWS req appears. | Done — no package built | -| Google — Forward Deployed Engineer III, Google Cloud GTM (French, German), Zürich (req 78350205438567110) | **CLOSED — NOT PROCEEDING 2026-08-18.** Phase 0 was done (Evidence Fit 67/100, hard gate PASS) but user declined to proceed after **two Google rejections** (Merchant Data Science, Business Home): the bar is now that a Google req must fit *very* well to be worth a third attempt, and this one was Adjacent with an agent-orchestration Gap. **Frees the Adjacent cohort slot (back to 1/2).** Phase 1 never started. | Done — do not reopen without a materially stronger Google req | -| SDU Center for Energy Informatics, Odense DK — PhD, Theme 2 Predictive Maintenance & Asset Management of Smart Energy Networks (job 4159) | **CLOSED — NOT PROCEEDING 2026-08-18.** Enquiry email to Prof. Bo Nørregaard Jørgensen (sent 2026-08-02) went unanswered for 16 days; user dismissed the idea. Genuine thesis fit (vibration condition monitoring, RBR+ANN hybrid) but the blockers were never the documents — 2 letters of recommendation 13 years out of academia, and DKK 37,075/mo ≈ CHF 56k against a 180k bar. Deadline was 2026-08-20. | Done — no further action | -| Aker BP ASA — Data Product Architect, AI-ready Data Products (FINN 469067315) | **CLOSED — REJECTED 2026-08-27** (submitted 2026-07-30, ~28 days to disposition, no interview; the session file and cohort record record the submission as 2026-07-29 — a one-day inconsistency, never reconciled). Adjacent, **Evidence Fit 79/100**, Document Quality 93/100, hard gate PASS, **Channel Weak/cold** — the optional follow-up email to Per Olav Marthinsen was left unsent, so this went in as a pure cold submit. Stavanger/Oslo/Trondheim, English working language, no Norwegian and no clearance required, EEA work rights. 2pp resume + 1pp CL, both validator PASS, no Tier 1 truth findings — **a cold-channel no-interview rejection does not implicate document quality on its own** (same disposition class as Microsoft ISE and Google Business Home). Honest practitioner framing: builds governed data products *inside* Swisscom's Data Mesh; role *authored* an enterprise framework — real level stretch, never fabricated authorship. **Atlassian Compass closed the catalogue-tool gap** (74→79). Standing gaps if a similar req appears: framework authorship, no energy domain, AWS vs their Microsoft/Cognite stack, **zero verified metrics**. **Cohort slot stays consumed** (evidence-first-2026-01, Adjacent 1/2 — a rejection does not free a slot; cohort remains 4/10). **The Norway lane stays OPEN and selective** — NATO JWC Stavanger is still live, and Equinor/Telenor and other strong Norwegian employers remain in scope per the user's standing instruction. Never frame this lane as closed. | Done — cohort rejection recorded; do not react to this single outcome with positioning changes (`application_strategy.md` §38/§74) | -| Google - Software Engineer III, Business Home, Zurich (req 93922217108087494) | **CLOSED - NOT PROCEEDING 2026-07-28** (submitted 2026-07-27; Core, Evidence Fit 89/100, Document Quality 94/100; no interview). Rapid early-screen disposition; known risks were Mid-level versus current Staff scope, data/platform versus product-SWE positioning, preferred algorithms/accessibility gaps, and a cold channel. | Done - cohort rejection recorded; do not treat this outcome alone as a document-quality failure | -| Microsoft — Principal Forward Deployed Engineer, SWE (German Speaking), Zürich (req 200043897) | **SUBMITTED 2026-07-27** (84.2/100 Pass 2; finalized 2-page resume + 1-page cover letter). Strong native-German, Staff progression, production ownership and enterprise-data-readiness case; honest gaps remain in end-to-end LLM delivery, Azure AI and strategic-account FDE experience. | Done — await response | -| BIS Basel — Senior Data & Analytics Engineer, AI (jr100429, 3-yr term) | **SENT 2026-07-10** (80.5/100; deadline 2026-07-24; hybrid Basel, English-working intl org). Critique flags LLM-depth probe (integration/config vs. ownership) as the screening risk — prep honest answer before any call | Prep interview brief when screening lands | -| NATO JWC Stavanger — Staff Officer, AI Engineer (2030 Digitalisation, G15, 3-yr PLN) | **SENT 2026-07-10** (77.5/100; deadline 2026-08-09; final interviews 2nd half Oct 2026). Borderline screen: Azure-cert "or equivalent", LLM depth, no current clearance; German national + officer service are the legibility assets. Tax-free NOK 93,933/mo + allowances | Await screening; no action until contact | -| Microsoft — Senior SWE, Industry Solutions Engineering (ISE), Zürich (req 200040836) | **CLOSED — REJECTED 2026-08-11** (submitted 2026-07-03, 85.8/100 Pass 2; 2pp resume + 1pp CL; verbatim Eightfold JD; IC4 base CHF 146.2–245.9k; applied ~7 days after posting). No interview, ~39 days to disposition. Cold-channel application; does not implicate document quality on its own. The separate Microsoft **Principal FDE** req 200043897 is a different pipeline and remains open | Done — cohort rejection recorded | -| Kraken (Payward) — SRE, AI Agents (remote, CH-eligible) | **CLOSED — REJECTED 2026-06-17** (applied 2026-06-15 ~87.2/100, no interview). Honest gaps (NO Terraform/SRE-title/LangGraph) likely the filter; 4th Kraken req declined/rejected to date | Done | -| Google — Senior Data Engineer (Merchant Data Science), Zürich/MV | **CLOSED — NOT PROCEEDING 2026-07-24** (applied 2026-06-15, 85.5/100; passed Google Hiring Assessment 2026-06-20, no interview). Assessment pass remains recorded separately. The official 90-day wait applies only to reapplying for the same job; different Google roles remain eligible, subject to 3 applications per rolling 30 days. | Done — target a different strong-fit Google req | -| Snowflake — Sr SWE, Enterprise (Observe by Snowflake), Zürich | **CLOSED — NO RESPONSE 2026-08-25** (sent 2026-06-06, ~86/100; 2pp resume + 1pp CL; real Ashby JD; comp CHF 176–253k base; NO C++ gate). Closed by user decision at **80 days silent** — past the longest real disposition in the log (Equinor, 79 days). **No rejection was ever received: this is a presumed-dead call, not a rejection — do not count it in rejection statistics or treat it as a document-quality signal.** Cold channel. Pre-dates the evidence-first-2026-01 cohort (started 2026-07-27), so **no cohort slot freed**. Highest-scoring package in the log at ~86 and it drew no reply at all — the strongest single argument in the log for the channel problem over the document problem. **Unblocks the Snowflake Observe Metrics Platform req** (`db4f0492`), paused 2026-07-28 solely to avoid stacking a second cold application on the same org while this one was open | Done — if Snowflake is still wanted, the Metrics Platform req is now open to pursue, but route it through a recruiter rather than another cold submit | -| Isovalent (Cisco) Sr Data Engineer, Observability | **CLOSED — role pulled** (live Cisco scrape 2026-06-02: not on board; Recruitee link dead). Package finalized ~86/100, SHELVED for reuse | Done — retarget PDFs to next live data-eng req (QuantCo/Grafana/Confluent) | -| Google Zürich Sr SWE Infrastructure (Data Pipeline) | **CLOSED — DROPPED + DELETED 2026-06-02** (poor fit). Live JD = Core infra/systems SWE with **C++ as a MINIMUM qual**, off-thesis vs `user_positioning`. Output folder deleted (was built on a fabricated JD). | Done — do not reattempt this req | -| Kraken AI Infrastructure | **CLOSED — REJECTED** (applied, no interview) | Done | -| Infineon Doctoral | **CLOSED — withdrew** (got interview invite, declined: no relocation to Germany) | Done | -| Infineon AI Engineer | **CLOSED — not applied** (no relocation to Germany) | Done | -| Apple Data Engineer (ISE, Zurich) | **CLOSED — REJECTED** (no interview) | Done | -| Google FDE GenAI (Zurich) | PAUSED — GenAI evidence gap too large; redirecting to data-eng/MLOps roles | Likely abandon | -| Equinor — Manager / AI Architect, Agentic systems (Stavanger/Oslo/Rotvoll/Sandsli; req JR106747) | **CLOSED — REJECTED 2026-08-14** (submitted 2026-05-27, ~80/100; posting deadline 05.06.2026). No interview, **79 days** to disposition — the longest turnaround recorded. Cold channel. This was a **Manager** req, not a pure IC architect role, so level/scope stretch compounds the standing Norway per-role gates (norsk working-language, no NO clearance — **not** comp: the 180k bar is CH-only and does not apply to Norway). **Pre-dates the evidence-first-2026-01 cohort (started 2026-07-27) — does not free a cohort slot.** **The Norway lane stays OPEN but selective** — Equinor, Telenor and NATO JWC remain in the scout; the user has explicitly said he still wants to reach out for very good Norwegian roles, Equinor included, after this rejection. Never frame this lane as closed. | Done — cohort-external rejection recorded | -| QuantCo Cloud Engineer (Europe/Zürich) | **CLOSED — REJECTED** (applied 2026-06-01 ~82/100, no interview; rejection 2026-06-15) | Done | +Read [current applications](resume_builder/reference/current_applications.md), generated from `job_scout/state/applications.json`. Update the registry through the tracker, then regenerate views. Do not maintain another status table here. Historical notes are preserved under `job_scout/state/history/`. --- @@ -216,3 +187,7 @@ _See `config.md` for user-specific corrections. Add verified errors here as you | 2026-08-02 | **Bosch data types named.** Fab sensor/process data: defect-management records, wafer inspection images, PCM electrical parameters (user-confirmed). Previously the KB named no concrete sensor-data types for Bosch. | `claims.json` BS-2 | | 2026-07-27 | **Bullet density.** Fixed 1L/2L/3L and character-band rules made bullets uniform and encouraged page filling. | Replaced globally with natural-length, evidence-led bullets; character counts are diagnostic only. | | 2026-08-25 | **Cadence monoculture across every generated package.** Audit of all 18 packages in `output/` (368 bullets) found **45% used the same "X, Y and Z" triple**; the SBB package hit **85% — 11 of 13 bullets, every Swisscom and Bosch line** — plus two adjacent bullets both opening "Build and…". Not a truth defect: every bullet was accurate and the corpus is clean on AI vocabulary (0 cliché hits in 35 documents) and prose em-dashes (0.08/bullet). It is a *rhythm* tell — uniform three-beat cadence reads as machine-written. PDF metadata is clean (MiKTeX pdfTeX, empty Author/Title, no AI tokens in 69 PDFs, no generator-term leakage). | New `resume_reference.md` §7a + verification step 7; `critical_rules.md` rule 7a; `critique_framework.md` mechanics row now scores cadence (max 1 pt, style only) | + +## Maintenance resume point + +For the authorized project-review fixes, read [FIX_PROGRESS.md](FIX_PROGRESS.md) before continuing. Both agent skill directories route to the shared workflows in `resume_builder/reference/workflows/`. diff --git a/FIX_PROGRESS.md b/FIX_PROGRESS.md new file mode 100644 index 0000000..083d81a --- /dev/null +++ b/FIX_PROGRESS.md @@ -0,0 +1,47 @@ +# Review remediation — 2026-09-11 + +User authorized all recommendations in `output/Project_Review_2026-09-11/project_review.tex`. +Work sequentially; update this file after each verified fix and before stopping. +Do not rewrite submitted application archives or invent missing evidence. + +## Resume point + +F01 and F02 complete. Next: F03, route tracker mutations through the authoritative registry, remove quotas and validate events. +Read this file and inspect `git diff` before resuming; do not repeat completed work. + +## Checklist + +| Finding | Status | Scope / verification | +|---|---|---| +| F01 | DONE | Six shared workflows; both agent entrypoints route identically; parity/link regression test; 10 tests pass | +| F02 | DONE | 26 registry records, preserved original tables, common current view, interview events independent of outcome | +| F03 | TODO | Remove quota, validate and safely update application records | +| F04 | TODO | Rule hierarchy, deliverables and approval semantics | +| F05 | TODO | Structured document evidence and honest mechanical/semantic results | +| F06 | TODO | LaTeX normalization, schema errors and advisory style diagnostics | +| F07 | TODO | Propagate evidence corrections into active writing sources | +| F08 | TODO | Normalize source/context/ownership metadata without inventing evidence | +| F09 | TODO | Safe professional templates; clearly separate academic legacy | +| F10 | TODO | Preserve original grades; remove universal GPA conversion claim | +| F11 | TODO | Dedupe by requisition, not title | +| F12 | TODO | Structured country/remote eligibility | +| F13 | TODO | Shared decision identity resolver | +| F14 | TODO | Search relevance distinct from fit; German terminology | +| F15 | TODO | Typed dates and retained contact/compensation metadata | +| F16 | TODO | Run-scoped reports, transactional persistence and scan coverage | +| F17 | TODO | Reproducible new builds and safe historical archive classification | +| F18 | TODO | Evidence earlier on page one; rendered skills budget | +| F19 | TODO | Education layout, phrasing guidance, reproducible word counts | + +## Verification log + +- Review baseline: 9 existing tests passed; 43/45 LaTeX builds succeeded. See the report for limitations. +- F01: moved current workflows into `resume_builder/reference/workflows/`; preserved skill frontmatter and Claude invocation support. Corrected the top-level file map and added this resume pointer. `python -m unittest discover -s tests -v`: 10/10 pass. The workflow validator now checks the shared critique implementation. +- F01 limitation: external `quick_validate.py` could not start because PyYAML is absent; repository tests validate frontmatter and both routing links. +- F02: imported 26 applications from the newer CLAUDE table and seven structured cohort records. Original tables/cohort preserved in `job_scout/state/history/application_status_before_2026-09-11.json`. `application_records.py --check` detects stale projections. Aker BP's July 29/30 date discrepancy and original Adjacent override remain explicitly unresolved. No date or rubric explanation invented. + +## Completion rules + +DONE requires implementation plus relevant checks. Record partial work and exact next action. +Missing factual evidence must remain explicitly unresolved, never silently promoted to verified. +Historical packages are preserved; fixes apply to active sources and future generation unless specifically noted. diff --git a/README.md b/README.md index 9479a26..cf7147b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ source documents - Target Staff/Senior Data Engineering first. - Use ML Platform/MLOps, data platform, analytics, and semiconductor profiles only where direct evidence covers the actual requirements. - Treat direct LLM engineering, formal model evaluation, Terraform-heavy SRE, and similar missing required experience as gates rather than keyword opportunities. -- Build application cohorts with roughly 70% core, 20% adjacent, and 10% deliberate stretch roles. +- Record fit and outcomes without application quotas; a fit classification is a preparation signal. - Add a warm-channel action for core applications. The default document is a two-page International Tech resume for US-tech/FAANG-style roles in Europe. A Swiss/DACH policy overlay is available for employers that expect local conventions. Cover letters are conditional, not automatic. @@ -66,12 +66,24 @@ Validate a generated document: python resume_builder/helpers/validate_resume_system.py --document output/Role/resume.tex ``` -Track the active 7/2/1 cohort: +Track the uncapped application record (use `summary --all` for older applications too): ```powershell python resume_builder/helpers/cohort_tracker.py summary ``` +`job_scout/state/applications.json` owns current status and event history. The old cohort JSON and +`resume_builder/reference/current_applications.md` are generated views. Update status without erasing interview history: + +```powershell +python resume_builder/helpers/cohort_tracker.py update --id --date YYYY-MM-DD --event interview_held --note "Interview completed" +python resume_builder/helpers/cohort_tracker.py update --id --date YYYY-MM-DD --outcome withdrawn_by_candidate --note "Reason" +python resume_builder/helpers/application_records.py --check +``` + +Use `application_records.py --render` to recover stale generated views. A classification that differs +from its numeric rubric needs `--fit-override-reason`; it is never silently relabelled. + Character counts are available only as readability diagnostics. There are no fixed character bands, equal-length bullet rules, or page-fill quotas. ## Output and prerequisites diff --git a/job_scout/state/application_cohort.json b/job_scout/state/application_cohort.json index e52319a..fd07b1b 100644 --- a/job_scout/state/application_cohort.json +++ b/job_scout/state/application_cohort.json @@ -12,66 +12,64 @@ "adjacent": "Evidence Fit 60-74, no required Gap, normally at most one serious preferred gap", "stretch": "Evidence Fit below 60 or a defining responsibility is only Adjacent" }, + "policy_change_2026_09_05": "Slot caps (7 core / 2 adjacent / 1 stretch) DROPPED by user decision. target_mix is retained only as a record of the original design and is no longer read by cohort_tracker.py or enforced anywhere. This file continues as a durable application log. Channel is now the rationed variable: no application goes out cold when a named contact is published and reachable. See resume_builder/reference/application_strategy.md.", + "generated_from": "job_scout/state/applications.json", "applications": [ { - "company": "Google", - "role": "Software Engineer III, Business Home", - "application_date": "2026-07-27", - "fit_class": "core", - "evidence_fit": 89.0, - "hard_gate": "pass", + "company": "NVIDIA", + "role": "Senior DevOps Engineer, Zurich (JR2024256)", + "application_date": "2026-09-05", + "fit_class": "stretch", + "evidence_fit": 64.0, + "hard_gate": "fail", "channel": "weak", - "outcome": "rejected_no_interview" + "outcome": "applied", + "id": "app-9068f462d9b9", + "label": "NVIDIA - Senior DevOps Engineer, Zürich (req JR2024256)", + "next_action": "**Await response. Raise the working model at first contact** - it went in unresolved and a 5-day-onsite seat would be a late no. Note: `claims.json` has no shell-scripting entry, so **Bash is omitted** from Skills although the JD names it and ten years of Debian admin plus Ansible work plainly involve it - same shape as the Ansible/Linux gaps found 2026-08-27. Confirm with the user and record it. Groovy stays out regardless.", + "current_summary": "SUBMITTED 2026-09-05.", + "events": [ + { + "type": "legacy_status_import", + "recorded_at": "2026-09-11", + "source": "CLAUDE.md / application_cohort.json", + "details": "**SUBMITTED 2026-09-05.** Recorded in the application record (now 7 entries) and in the scout decision log. ⚠ **Hybrid vs 5-day onsite was never resolved before sending**, so the working model is now a **live screening topic**, not a pre-cleared one - the same pattern as RUAG's comp/PSP. Thun→Zürich is ~1h50 each way; a 5-day-onsite answer is the main disqualifier to watch for. Comp is *not* a risk (levels.fyi Greater Zürich IC3 median ≈ CHF 204k, IC4 ≈ CHF 379k). Channel **Weak - the 7th consecutive cold submit**, though this posting published no named contact, so cold was the only option available. Submission PDFs `Dennis_Thiessen_Resume.pdf` and `Dennis_Thiessen_Cover_Letter.pdf` exist and are MD5-verified against source; letter recompiled so its date reads 5 Sep 2026. ⚠ **The letter carries `\today` and the submission copies are copies, not build targets - if it is sent on a later day, recompile the letter and refresh both copies first** (`config.md` Output Rules). Submitted despite the hard-gate failures on an explicit user override, the same shape as RUAG AI Engineer C5I. Resume 2pp/13 bullets + 1pp cover letter (279 body words), both validators PASS 0 warnings, 0 boxes, visually inspected. Measured **Document Quality 82/100** at critique; **both Tier 1 findings were fixed 2026-09-05** - `Ubuntu` removed from the skills line (0 canonical; `PP-3` is Debian only, and the Debian→Ubuntu transfer argument belongs in an interview answer, not a skills line) and `security hardening` now marked *(personal server)* per the explicit `claims.json` instruction. Four honest ATS terms were added from existing evidence (configuration management, service reliability, container orchestration, observability), lifting JD term coverage **50%→61%**; no bullet was touched, so the budget stayed at 13 and cadence could not regress. **The critique is now STALE BY DESIGN - the document is better than 82 but that number was not re-measured, so never quote a post-edit estimate as a score.** The headline reorder was deliberately NOT applied: it is a positioning call for the user and `\\headline` sits in the FIXED header block. Cover-letter hook is first-party verified (NVIDIA's own live Zürich posting JR2022878); **a hook was DROPPED at verification** - the Phase 0 plan's \"Alps / Swiss AI Initiative with ETH Zürich and EPFL\" is **not** supported by the first-party NVIDIA newsroom release, which mentions neither EPFL nor any Swiss AI Initiative; that came from secondary coverage. The letter names **both** honest gaps out loud - on-prem networking, and that employer-side Linux/Ansible ended Dec 2022 with only a personal server carrying currency. **⚠ BLOCKING: the working model is still unresolved and no document fix changes that.** Session `output/NVIDIA_SrDevOps_Zurich/session_nvidia_sr_devops.md`; JD scraped live via Playwright and saved verbatim (posted ~2026-09-01, very fresh). Evidence Fit **64/100**, fit class **Stretch**, **hard gate FAIL on two counts**: (1) required line *\"Strong expertise in on-premises networking and compute infrastructure\"* is a **Gap** - networking is 0 canonical and is not substitutable from data-platform work; (2) **hybrid vs onsite is UNRESOLVED** and the JD is silent - Thun→Zürich is ~1h50 each way. Required tally **3 Direct / 3 Adjacent / 1 Gap**. Recorded as an override rather than a passing gate, the same shape as RUAG AI Engineer C5I. (At the time this also had no free cohort slot; **that constraint no longer exists** - caps dropped 2026-09-05.) **Correction to the same day's scout triage: it called two required lines Gaps; only Q5b networking is one. Q2 (10+ yrs DevOps) is Adjacent and defensible** - a continuous CI/CD/automation thread runs Generali 2015 → today across five employers; the honest weakness is that those roles were titled data/test/research engineer, not DevOps. **The finding that changes the calculus: comp.** levels.fyi Greater Zürich puts NVIDIA IC3 median ≈ CHF 204k and IC4 ≈ CHF 379k, the highest-paying SWE employer in the city - the first role in the log where comp is a reason to *accept* a long commute rather than decline. Caveat: self-reported and equity-heavy after an extraordinary NVDA run; treat base as the reliable part. Strongest match is **R3, CI/CD pipelines for R&D engineering teams** - literally his career shape (`FC-1` Jenkins at a Fraunhofer institute, `BS-6` Ansible/Jenkins for fab analytics teams, `SW-3` Kubernetes + GitLab today). Zürich is NVIDIA's HPC/AI infrastructure-software R&D hub - first-party verified from NVIDIA's own live Zürich posting JR2022878 (*\"building the next generation of scalable AI infrastructure ... distributed training, real-time inference, and communication efficiency across large systems\"*), which is the hook the letter uses. **The Alps / Swiss AI Initiative framing was checked and dropped** - see above. **Honest gaps that travel with it:** on-prem networking (0), HPC (0), bare metal/switches/QEMU-KVM (0), RHEL/CentOS (0 - Debian→Ubuntu transfers, Red Hat does not), and **Ansible + Linux-at-work both ended Dec 2022** with currency carried only by `PP-3`, a personal server whose canonical entry forbids using it as physical-infrastructure evidence. No metric is quotable anywhere.", + "legacy_next_action": "**Await response. Raise the working model at first contact** - it went in unresolved and a 5-day-onsite seat would be a late no. Note: `claims.json` has no shell-scripting entry, so **Bash is omitted** from Skills although the JD names it and ten years of Debian admin plus Ansible work plainly involve it - same shape as the Ansible/Linux gaps found 2026-08-27. Confirm with the user and record it. Groovy stays out regardless." + }, + { + "type": "applied", + "date": "2026-09-05" + } + ], + "cohort_id": "evidence-first-2026-01" }, { - "company": "Aker BP ASA", - "role": "Data Product Architect - AI-ready Data Products", - "application_date": "2026-07-29", - "fit_class": "adjacent", - "evidence_fit": 79.0, - "hard_gate": "pass", + "company": "RUAG MRO Holding AG (Business Area C5I)", + "role": "AI Engineer C5I (w/m/d), Thun", + "application_date": "2026-08-27", + "fit_class": "stretch", + "evidence_fit": 74.0, + "hard_gate": "fail", "channel": "weak", - "outcome": "rejected_no_interview", - "submitted": "2026-07-29", - "rejection_date": "2026-08-27" - }, - { - "company": "SBB", - "role": "Data Engineer (m/w/d), Asset Management Infrastrukturanlagen, Bern", - "job_id": "103755", - "application_date": "2026-08-25", - "fit_class": "core", - "evidence_fit": 79.0, - "hard_gate": "pass", - "channel": "weak", - "outcome": "withdrawn_by_candidate", - "submitted": "2026-08-25", - "open_risk": "Anforderungsniveau K is a capped GAV band, figures not public; seat may read lateral or below current Staff + Component Owner scope.", - "interview": { - "stage": "Stage 2 of 4 - Virtuelles Kennenlernen mit HR und Fuehrungskraft", - "invited": "2026-08-28", - "scheduled": "2026-09-09 08:30", - "duration_min": 45, - "format": "Microsoft Teams", - "held": "2026-09-09", - "result": "Held 2026-09-09 (HR + Scrum Master). Candidate WITHDREW 2026-09-11 after SBB confirmed there was no room to move on the Einstufung.", - "user_read": "User reported 'ein paar Red Flags' and judged that the Rahmenbedingungen do not compensate for them. The specific red flags were NOT captured while fresh - if this is reopened, ask before relying on memory. Leaning negative but explicitly undecided as of 2026-09-10; he asked to leave it open pending SBB's feedback." - }, - "note": "FIRST interview invitation in the evidence-first cohort. Converted from a COLD submit - the published warm contact (Andri Wienandts, +41 79 364 62 53) was never called. The resume never contained Snowflake, dbt or Power BI, the three literals an ATS keyword screen would have keyed on, so a human read the dossier.", - "comp_datapoint": "HARD FIGURES, from the interview - supersedes the earlier +10% estimate. Anforderungsniveau K TOPS OUT AT ~CHF 145k. His own position: ~CHF 135k today inside a Swisscom band that runs to ~CHF 160k. So the decisive point was NOT the absolute number but the HEADROOM: he would have had to enter K at its ceiling just to break even, with nothing left above. First hard calibration of a Swiss public-sector band and the reference point for the whole Bern/Thun tier (RUAG, Swissgrid, BKW, BFH, PostFinance).", - "withdrawn": "2026-09-11", - "why_withdrawn": "Total package was a net DOWNGRADE on four counts: 41h vs 40h week; 25 days holiday + Brueckentage vs 28 + 5 from Zeitautonomie; 3 office days vs 2; and the band ceiling above. Plus a qualitative red flag: he spoke only to HR and the Scrum Master and perceived DIFFICULTIES IN THE TEAM. That one is not fixable by a salary offer - note it if SBB or a similar Bern-tier seat comes back.", - "lesson": "SBB-SPECIFIC targeting rule (user 2026-09-11): at SBB, Anforderungsniveau K is too low - aim at Niveau L and/or Kader. NARROWED the same day at the user's instruction: Anforderungsniveau is SBB's OWN system (https://company.sbb.ch/de/jobs-karriere/arbeitgeberin/lohn.html) and does NOT generalise - jobs.admin.ch uses Lohnklassen, RUAG its own scheme, PostFinance per-posting bands. ~145k is an SBB ceiling, not a tier anchor. THUN-AREA EMPLOYERS (RUAG, Kdo Cy) ARE EXPLICITLY OUT of this reasoning: near-zero commute plus benefits a band does not show - he named childcare-cost coverage at the Schweizer Armee/Kdo Cy as potentially far more generous (UNVERIFIED - ask). Never decline a Thun-area role on band alone. What DOES generalise: judge by headroom in the band, and price the total package (working time, holiday, office days), not the entry number." - }, - { - "company": "Citadel Securities", - "role": "Platform Engineer, Research Platform Engineering (Zurich/NY/Miami)", - "application_date": "2026-08-26", - "fit_class": "adjacent", - "evidence_fit": 71.0, - "hard_gate": "pass", - "channel": "weak", - "outcome": "applied" + "outcome": "applied", + "id": "app-ca0990d47bd0", + "label": "RUAG C5I - AI Engineer C5I, Thun (application ID 18027)", + "next_action": "Await response. Optional but still worthwhile: call **Marco Heinzen** about technical scope and level, and **Frank Haugwitz** about compensation and PSP/project eligibility. **NEW CONTACT 2026-09-07 — `Lars Kauz`, Head of ICT Infrastructure C5I, +41 79 293 93 82, lars.kauz@ruag.ch**, published on the System Engineer C5I posting. Channel is the rationed variable since the 09-05 cohort change, so a published, reachable name matters — **but he heads ICT Infrastructure, the function whose req was ruled out for VMware/VDI/Windows content, and that is NOT demonstrably the Kubernetes platform group Senior DevOps C5I sits in.** He is a warm entry into C5I, not a verified channel for rank 1; Haugwitz stays the recorded gate. Do not react to a single outcome with positioning changes (`application_strategy.md` §38/§74).", + "current_summary": "SUBMITTED 2026-08-27", + "events": [ + { + "type": "legacy_status_import", + "recorded_at": "2026-09-11", + "source": "CLAUDE.md / application_cohort.json", + "details": "**SUBMITTED 2026-08-27** via jobs.ruag.ch (portal only; email and post refused). Evidence Fit **74/100**, fit class **Stretch**, hard gate **FAIL** (R1 and R2, the two title-defining responsibilities, are Adjacent not Direct), Document Quality **93/100** after a full critique-and-fix round (85 pre-edit), truth and provenance 24/25, **0 Tier 1 findings at Round 2**, channel **Weak/cold**. Submitted: German CV **2 pages, 13 bullets** + German motivation letter **1 page, 295 words**; both validators PASS, 0 boxes, text-order and visual QA clean, submission PDFs MD5-verified before sending. **Cohort evidence-first-2026-01 is now 6/10 and the sole Stretch slot is CONSUMED (Core 3/7, Adjacent 2/2, Stretch 1/1)** - any further application must clear Core or Adjacent on its own merits. Positioning is his canonical title **Staff Data, Analytics & AI Engineer**, carried by `BS-1` production ML-inference integration, `BS-6` Linux/Ansible, `SW-3`/`SW-7`/`SW-2` current Kubernetes and data-product work, `PP-3` current self-hosted Linux and hardening, `SW-5` DevSecOps tied to C5I's stated operating model, and the canonical `BW-1` officer career. The letter **names the AI-platform gap in one sentence** and pivots to the operations/platform side. **Honest gaps that travel with it:** no AI/LLM platform built or operated, no compute/GPU cluster, no RAG/retrieval implementation, no formal LLM evaluation; ML/AI-framework depth is certification-context only. **Compensation, PSP/project eligibility and role level were NEVER resolved before sending, so all three are now live screening topics rather than pre-cleared ones.** ⚠ **Marco Heinzen's published direct line (+41 79 568 14 96) was never used** - this went in as a pure cold submit, the sixth consecutive one. **If rejected, that is NOT a signal to drop RUAG:** five other RUAG C5I reqs sit in the scout log, and **Senior DevOps Engineer C5I** is already shortlisted and is a **materially better fit than this one**, sitting on the lane where his evidence is Direct rather than Adjacent (still live and verified on the board 2026-09-04). The **Data Lakehouse / Senior Data Platform Engineer** req was a title-match prior that the JD does not support — it was reposted 2026-09-04 as **Senior Data Engineer (MSSQL)**, requires on-prem MSSQL / Talend / SAP S/4HANA (0 canonical each), and is **ruled out**. **Scout run 2026-09-07: the RUAG C5I backlog is now SETTLED — undecided RUAG rows went 69 → 3** (`ICT Architect`, `Software Engineer C5I`, `Cyber Security Consultant` remain open; `ICT Architect C5I` stays `paused`). Do not re-triage that board from scratch; read `decisions.json`, where every rule-out note states whether it came from a JD read or from the title/family pattern. Three JD reads that cycle all ruled out: **Test Manager C5I** (the closest call — it escapes the networking gap entirely and BW-1 scores, but required *\"fundierte Erfahrung als Test Engineer\"* rests on `VIZRT`, **ten months, ended 2018-05**, so it is thin and stale, plus a lane exit and a third C5I req while 18027 is pending), **System Engineer C5I** (VMware + Firewall + Netzwerk, all 0), and **ICT Solution Architect Business Applications** (required *\"mehrjährige Erfahrung als Solution Architect\"* — a title `feedback_swisscom_datamesh_ownership` forbids — and the duties are *\"unter Anleitung\"*, a supporting seat below authoring).", + "legacy_next_action": "Await response. Optional but still worthwhile: call **Marco Heinzen** about technical scope and level, and **Frank Haugwitz** about compensation and PSP/project eligibility. **NEW CONTACT 2026-09-07 — `Lars Kauz`, Head of ICT Infrastructure C5I, +41 79 293 93 82, lars.kauz@ruag.ch**, published on the System Engineer C5I posting. Channel is the rationed variable since the 09-05 cohort change, so a published, reachable name matters — **but he heads ICT Infrastructure, the function whose req was ruled out for VMware/VDI/Windows content, and that is NOT demonstrably the Kubernetes platform group Senior DevOps C5I sits in.** He is a warm entry into C5I, not a verified channel for rank 1; Haugwitz stays the recorded gate. Do not react to a single outcome with positioning changes (`application_strategy.md` §38/§74)." + }, + { + "type": "applied", + "date": "2026-08-27" + } + ], + "cohort_id": "evidence-first-2026-01" }, { "company": "Schweizer Armee - Kommando Cyber", @@ -85,28 +83,185 @@ "submitted": "2026-08-27", "rejection_date": "2026-09-10", "days_to_disposition": 14, - "note": "Form-letter rejection from Besarta Mehmeti, no interview, 14 days to disposition - the fastest in the log after Google Business Home. The letter is boilerplate ('Profil und unsere Kriterien nicht in allen Punkten uebereinstimmen') and carries ZERO diagnostic content, so it is not a signal about the package and is no reason to re-audit it. Same disposition class as Microsoft ISE, Google Business Home and Aker BP: a cold-channel no-interview rejection does not implicate document quality on its own. WORTH WATCHING, NOT CONCLUDING: this row and the SBB row are identical on every recorded dimension - core, Evidence Fit 79, hard gate pass, Document Quality 92, channel weak - and they went opposite ways (SBB converted to an interview, this died in 14 days). That shows the fit score did not discriminate; it does NOT show what did. n=1 on each side. Do not react with positioning changes (application_strategy.md 38/74). Channel note: Marcel Matthey-Doret (+41 58 46 78409) was published and never called - the fourth published, reachable contact left unused after Heinzen (RUAG), Wienandts (SBB) and Marthinsen (Aker BP). Consistent with the 2026-09-05 channel policy, but NOT evidence for it: the single interview in this log came from a cold submit." + "note": "Form-letter rejection from Besarta Mehmeti, no interview, 14 days to disposition - the fastest in the log after Google Business Home. The letter is boilerplate ('Profil und unsere Kriterien nicht in allen Punkten uebereinstimmen') and carries ZERO diagnostic content, so it is not a signal about the package and is no reason to re-audit it. Same disposition class as Microsoft ISE, Google Business Home and Aker BP: a cold-channel no-interview rejection does not implicate document quality on its own. WORTH WATCHING, NOT CONCLUDING: this row and the SBB row are identical on every recorded dimension - core, Evidence Fit 79, hard gate pass, Document Quality 92, channel weak - and they went opposite ways (SBB converted to an interview, this died in 14 days). That shows the fit score did not discriminate; it does NOT show what did. n=1 on each side. Do not react with positioning changes (application_strategy.md 38/74). Channel note: Marcel Matthey-Doret (+41 58 46 78409) was published and never called - the fourth published, reachable contact left unused after Heinzen (RUAG), Wienandts (SBB) and Marthinsen (Aker BP). Consistent with the 2026-09-05 channel policy, but NOT evidence for it: the single interview in this log came from a cold submit.", + "id": "app-e7389ad9d46f", + "label": "Schweizer Armee - Kommando Cyber (Kdo Cy), DevOps Engineer III (Data Platform), Zimmerwald (Ref JRQ$540-19848)", + "next_action": "Closed; no pending application action. Read recorded history before reopening.", + "current_summary": "CLOSED - REJECTED 2026-09-10", + "events": [ + { + "type": "legacy_status_import", + "recorded_at": "2026-09-11", + "source": "CLAUDE.md / application_cohort.json", + "details": "**CLOSED - REJECTED 2026-09-10** (submitted 2026-08-27, **14 days to disposition**, no interview - the fastest in the log after Google Business Home). Form letter from **Besarta Mehmeti**; the text is boilerplate (*„Profil und unsere Kriterien nicht in allen Punkten übereinstimmen\"*) and carries **zero diagnostic content** - it is not a signal about the package and is **no reason to re-audit it**. Same disposition class as Microsoft ISE, Google Business Home and Aker BP: **a cold-channel no-interview rejection does not implicate document quality on its own.** ⚠ **WORTH WATCHING, NOT CONCLUDING:** this application and **SBB** are identical on every recorded dimension - Core, Evidence Fit **79**, hard gate PASS, Document Quality **92**, channel Weak - and they went opposite ways: SBB converted to an interview, this died in 14 days. **That shows the fit score did not discriminate; it does not show what did.** n=1 on each side - do not react with positioning changes (`application_strategy.md` §38/§74). **Channel:** Marcel Matthey-Doret's published line (+41 58 46 78409) was never called - the **fourth** published, reachable contact left unused after Heinzen (RUAG), Wienandts (SBB) and Marthinsen (Aker BP). Consistent with the 2026-09-05 channel policy but **not evidence for it**: the only interview in the log came from a *cold* submit whose resume was missing all three ATS literals. **Kdo Cy as an organisation is NOT closed** - 11 Kdo Cy roles were open on the portal at submission time and only this req is decided. Evidence Fit **79/Core**, hard gate **PASS**, Document Quality **92/100**, channel **Weak**. Submitted package: German 2-page CV with **15 bullets** and 1-page motivation letter with **300 body words**; validators PASS, 0 boxes, text order and visual QA clean. Canonical **BW-1** records the six-year Bundeswehr officer career and anchors the letter's military/public-service motivation. User explicitly accepted the critique's remaining BS-1 wording issue (*„ohne manuellen Eingriff“*) as good enough and submitted unchanged. Honest gaps remain physical datacenter work, GPGPU, ClickHouse and named Pull-GitOps practice. Cohort evidence-first-2026-01 is now **5/10, Core 3/7** *(pre-dates the 2026-09-05 cap drop; counts are a record, not a budget)*.", + "legacy_next_action": "Done - rejection recorded in `application_cohort.json`, `decisions.json` and the session file. Optional and low-cost: a one-line feedback request to **Besarta Mehmeti**, which also builds a contact at an org with other open reqs." + }, + { + "type": "applied", + "date": "2026-08-27" + }, + { + "type": "rejected_no_interview", + "date": "2026-09-10" + } + ], + "cohort_id": "evidence-first-2026-01" }, { - "company": "RUAG MRO Holding AG (Business Area C5I)", - "role": "AI Engineer C5I (w/m/d), Thun", - "application_date": "2026-08-27", - "fit_class": "stretch", - "evidence_fit": 74.0, - "hard_gate": "fail", + "company": "Citadel Securities", + "role": "Platform Engineer, Research Platform Engineering (Zurich/NY/Miami)", + "application_date": "2026-08-26", + "fit_class": "adjacent", + "evidence_fit": 71.0, + "hard_gate": "pass", "channel": "weak", - "outcome": "applied" + "outcome": "applied", + "id": "app-ef2bec6fe379", + "label": "Citadel Securities — Platform Engineer, Research Platform Engineering, Zürich (also NY/Miami)", + "next_action": "**Await response** (submitted 2026-08-26). No document work pending. **Two questions travel with this application and must both be asked at first contact, before any interview effort:** (1) **is the application even against the Zürich seat?** — the apply form offered no Zürich, so it may sit against a US req, which is a hard NO on relocation; (2) **hybrid or 5-day onsite?** — unresolved at submission, and 5-day onsite is a likely NO-GO on the Bern constraint. Same shape as the NVIDIA working-model issue: it went out unresolved, so it is now a live screening topic rather than a pre-cleared one. Also unasked: the Zürich compensation figure — the posting's $175–350k is a US/NY pay-transparency disclosure.", + "current_summary": "SUBMITTED 2026-08-26 — open, awaiting response.", + "events": [ + { + "type": "legacy_status_import", + "recorded_at": "2026-09-11", + "source": "CLAUDE.md / application_cohort.json", + "details": "**SUBMITTED 2026-08-26 — open, awaiting response.** Recorded as `adjacent` in `application_cohort.json` (outcome `applied`) and as `applied` in `decisions.json`. *Slot language from the capped-cohort era has been removed here: the caps were dropped 2026-09-05 and counts are a record, not a budget.* ⚠️ **KEY FINDING — the application form's preferred-work-location selector offered NO Zurich**, despite the JD saying \"Miami, Zurich or New York\" and the site's own Zurich filter returning this req. Careers site is operated by **Citadel Enterprise Americas LLC**; the apply flow is likely US-entity, or the Zurich seat is closed while the posting stands. **This partly supersedes the working-model risk** — if no Zurich seat is reachable through that form, hybrid-vs-onsite is moot and the application may sit against a US req, a hard NO on relocation. **Rule for any future Citadel application: check the form's location selector BEFORE building a package** — the posting's stated locations and the site filter both proved unreliable. User applied with low expectation; cold channel, evergreen standing req. 2pp resume (13 bullets) + 1pp CL (270 words), both validator **PASS, 0 warnings**; JD term coverage 51%→68% after Tier 1 fixes. Critique **STALE by design** (86/100 pre-edit; user chose to submit as-is over re-critiquing; est. ~91 post-edit, never quote as a score). **CORRECTED 2026-09-10:** this row previously said *\"Cohort entry and decision-log entry deliberately NOT written — no confirmed send date\"* while its own status line read SUBMITTED. **Both entries exist and both are dated 2026-08-26** — the sentence was pre-submission text that survived the send. **The working-model question is still open and travels with the application:** Evidence Fit **71/100 (Adjacent, mid-band; revised down from 74)**, qualifications gate PASS but **a practical constraint is UNRESOLVED**, which `critique_framework.md` treats as a NO-GO trigger. **The JD is silent on hybrid vs onsite (0 grep matches), and Ken Griffin is on record that Citadel returned to the office 5 days a week and calls it his most important leadership decision.** If that holds for Zurich, it means ~2–2.5h daily commuting from Bern or relocating — breaking the Bern-based / 2–3 day hybrid bar. Only contrary evidence is a stale 2022 anonymous Fishbowl post about Operations. **Must be asked, not researched** — same class as the SBB K band. Practical constraints scored 5/5 in Phase 0 on an unexamined assumption; corrected to 2/5. **Zero Gaps among the 7 minimum quals**, and all five preferred technologies (Kafka, Kubernetes, Spark, Airflow, distributed DBs) are Direct — **the strongest raw stack alignment in the log**. Title-defining function **splits**: R2 (ingestion/transformation/storage/lifecycle of large datasets) is Direct and describes SW-1/SW-7 exactly; **R1 (design and build the research platform itself) is Adjacent** — the Aker BP authoring-vs-building shape again, and unwritable-around under Scope Discipline. **Classification caveat:** `application_strategy.md`'s \"a defining responsibility is only Adjacent → Stretch\" would strictly class this Stretch; recorded Adjacent per the Aker BP precedent (the caps this once turned on no longer exist). Gaps: platform authoring at firm scale, no professional quant/finance (now partly mitigated by new **PP-1/PP-2**), SDKs (0 evidence), low-latency/HFT (0 evidence — thesis throughput figure is explicitly barred by `thesis_limits`). **Go is NOT a gap** — Q3 is disjunctive and Python satisfies it. **$175–350k in the posting is a US/NY pay-transparency disclosure, NOT the Zurich number — must be asked.** Channel **Weak/cold** — it was the 5th consecutive cold application, in an office visibly staffed from Google and ETH (platform research head Costas Bekas; Nicolai Meinshausen leads principal research). Cover letter **was written and submitted** (1pp, 270 words); the PP-1 domain story carries it.", + "legacy_next_action": "**Await response** (submitted 2026-08-26). No document work pending. **Two questions travel with this application and must both be asked at first contact, before any interview effort:** (1) **is the application even against the Zürich seat?** — the apply form offered no Zürich, so it may sit against a US req, which is a hard NO on relocation; (2) **hybrid or 5-day onsite?** — unresolved at submission, and 5-day onsite is a likely NO-GO on the Bern constraint. Same shape as the NVIDIA working-model issue: it went out unresolved, so it is now a live screening topic rather than a pre-cleared one. Also unasked: the Zürich compensation figure — the posting's $175–350k is a US/NY pay-transparency disclosure." + }, + { + "type": "applied", + "date": "2026-08-26" + } + ], + "cohort_id": "evidence-first-2026-01" }, { - "company": "NVIDIA", - "role": "Senior DevOps Engineer, Zurich (JR2024256)", - "application_date": "2026-09-05", - "fit_class": "stretch", - "evidence_fit": 64.0, - "hard_gate": "fail", + "company": "SBB", + "role": "Data Engineer (m/w/d), Asset Management Infrastrukturanlagen, Bern", + "job_id": "103755", + "application_date": "2026-08-25", + "fit_class": "core", + "evidence_fit": 79.0, + "hard_gate": "pass", "channel": "weak", - "outcome": "applied" + "outcome": "withdrawn_by_candidate", + "submitted": "2026-08-25", + "interview": { + "stage": "Stage 2 of 4 - Virtuelles Kennenlernen mit HR und Fuehrungskraft", + "invited": "2026-08-28", + "scheduled": "2026-09-09 08:30", + "duration_min": 45, + "format": "Microsoft Teams", + "held": "2026-09-09", + "result": "Held 2026-09-09 (HR + Scrum Master). Candidate WITHDREW 2026-09-11 after SBB confirmed there was no room to move on the Einstufung.", + "user_read": "User reported 'ein paar Red Flags' and judged that the Rahmenbedingungen do not compensate for them. The specific red flags were NOT captured while fresh - if this is reopened, ask before relying on memory. Leaning negative but explicitly undecided as of 2026-09-10; he asked to leave it open pending SBB's feedback." + }, + "withdrawn": "2026-09-11", + "why_withdrawn": "Total package was a net DOWNGRADE on four counts: 41h vs 40h week; 25 days holiday + Brueckentage vs 28 + 5 from Zeitautonomie; 3 office days vs 2; and the band ceiling above. Plus a qualitative red flag: he spoke only to HR and the Scrum Master and perceived DIFFICULTIES IN THE TEAM. That one is not fixable by a salary offer - note it if SBB or a similar Bern-tier seat comes back.", + "lesson": "SBB-SPECIFIC targeting rule (user 2026-09-11): at SBB, Anforderungsniveau K is too low - aim at Niveau L and/or Kader. NARROWED the same day at the user's instruction: Anforderungsniveau is SBB's OWN system (https://company.sbb.ch/de/jobs-karriere/arbeitgeberin/lohn.html) and does NOT generalise - jobs.admin.ch uses Lohnklassen, RUAG its own scheme, PostFinance per-posting bands. ~145k is an SBB ceiling, not a tier anchor. THUN-AREA EMPLOYERS (RUAG, Kdo Cy) ARE EXPLICITLY OUT of this reasoning: near-zero commute plus benefits a band does not show - he named childcare-cost coverage at the Schweizer Armee/Kdo Cy as potentially far more generous (UNVERIFIED - ask). Never decline a Thun-area role on band alone. What DOES generalise: judge by headroom in the band, and price the total package (working time, holiday, office days), not the entry number.", + "id": "app-799e2880a22e", + "label": "SBB - Data Engineer (m/w/d), Asset Management Infrastrukturanlagen, Bern (Job ID 103755)", + "next_action": "Closed. For future SBB roles assess Niveau L/Kader; do not generalize SBB bands to other employers.", + "current_summary": "Withdrew after interview and confirmation that SBB could not improve the classification. Niveau K ceiling and total package unsuitable for this application.", + "events": [ + { + "type": "legacy_status_import", + "recorded_at": "2026-09-11", + "source": "CLAUDE.md / application_cohort.json", + "details": "**CLOSED - APPLICATION WITHDRAWN BY THE CANDIDATE 2026-09-11.** Interview held 2026-09-09 (HR + Scrum Master, **not** the People Leader the brief expected); SBB confirmed there was no room on the Einstufung, and he withdrew. **This is the cohort's only interview and it ended as a NO from his side, not theirs.** 💰 **HARD COMP FIGURES - supersedes the earlier +10% estimate and is the reference point for the whole Bern/Thun tier:** **Anforderungsniveau K tops out at ~CHF 145k.** He sits at **~CHF 135k inside a Swisscom band that runs to ~CHF 160k**. The decisive argument was **headroom, not the absolute number** - he would have entered K at its ceiling merely to break even, with nothing above. ⚠ **The package was a net DOWNGRADE on four counts:** 41h vs 40h week · 25 days holiday + Brückentage vs 28 + 5 from Zeitautonomie · 3 office days vs 2 · the band ceiling. ⚠ **Qualitative red flag worth carrying forward:** he spoke only to HR and the Scrum Master and **perceived difficulties in the team** - that is not repairable by a salary offer. 🎯 **TARGETING RULE — SBB ONLY: Anforderungsniveau K is too low; at SBB aim at NIVEAU L and/or KADER.** ⚠ **Deliberately NOT generalised** (user, 2026-09-11): *Anforderungsniveau* is **SBB's own** salary system (published at ) and does not map onto other employers — **jobs.admin.ch/Schweizer Armee uses Lohnklassen**, RUAG has its own scheme, PostFinance publishes per-posting bands. ~145k is an **SBB** ceiling, not a tier anchor. ⚠ **Thun and surroundings (RUAG, Kdo Cy) are explicitly OUT of this reasoning:** near-zero commute plus benefits that a salary band does not show — he named **childcare-cost coverage at the Schweizer Armee/Kdo Cy as potentially far more generous** (unverified — ask, do not assume). **Never decline a Thun-area role on band alone before pricing the benefits.** What *does* carry across employers: judge by **headroom in the band**, and price the **total package** (working time, holiday, office days), not the entry salary. Original record: interview was stage 2 of 4, **INTERVIEW HELD 2026-09-09.** SBB asked him to come back on whether the role interests him; **SBB's own feedback is still outstanding.** User's summary: *„ok\"*, but **ein paar Red Flags**, and in his read the Rahmenbedingungen do not compensate for them - **leaning negative, explicitly undecided as of 2026-09-10, and he asked to leave it open.** ⚠ **The specific red flags were NOT captured while fresh** - anyone reopening this must ask rather than rely on memory. 💰 **First real read on Anforderungsniveau K:** his post-interview estimate is that roughly **+10% on the current ~140k** looks achievable (very roughly ~155k). **This is HIS ESTIMATE of what is negotiable, not a band SBB is recorded as quoting** - whether the K range was named on the call is unrecorded. Well below the 180k bar, above current pay. Recorded in `comp_worksheet_sbb.md` as the **first calibration point for the entire Bern/Thun local tier** (RUAG, Swissgrid, BKW, BFH), which was previously pure guesswork - treat as an estimate, never quote as a figure. **INTERVIEW INVITED 2026-08-28 - 9. September 2026, 08:30, 45 Min, MS Teams.** Per the posting's own process this is **stage 2 of 4**, *\"Virtuelles Kennenlernen mit HR und Fuehrungskraft\"* - **HR together with Andri Wienandts**, not a pure HR screen. Brief written to `output/SBB_DataEngineer_AssetMgmt/interview_brief_sbb_2026-09-09.md`. **This is the FIRST interview of the evidence-first cohort** (6 applications, 2 rejections, 1 conversion) and it converted from a **COLD submit** - the channel plan called for phoning Wienandts first and that never happened. **The resume never contained Snowflake, dbt or Power BI**, the three literals an ATS keyword screen would have keyed on; the session had recorded that risk as \"accepted, not solvable\". It did not filter him out, so a human read the dossier and the honest-substitution framing survived first contact. **One data point, do not over-read it** - but it is evidence against assuming honest omissions are fatal at the screen. Submitted 2026-08-25: 2pp English resume, 13 bullets, no cover letter (SBB waives it), Evidence Fit **79/100 Core**, hard gate **PASS**, Document Quality **92/100**, channel **Weak/cold**. Best practical fit in the log - Bern-based, German-language, no relocation, EU citizen + B permit. **Four things unresolved before submitting and now live call topics:** (1) **Anforderungsniveau K** is a capped GAV band, figures not public, very likely below the 180k bar; (2) the seat reads **lateral or below** current Staff + Component Owner scope, the same shape as the declined BKW, and the JD's only architecture language is a personal skill not an ownership mandate; (3) **RAMSI** Java/Spring Boot/Angular is a whole responsibility line on a stack that is historical or absent - ask its share, do not oversell; (4) Kidz Care scales on gross household income, so far below 90% at this band. Named gaps to answer honestly: Snowflake, dbt, Argo Workflows, Helm, Power BI, Spring Boot, Angular - all non-canonical, all survivable only because the JD prefixes the stack with \"z. B.\". Thesis stays a **methods prototype**: no real operational data, no accuracy figures, PSO surveyed only, throughput figure barred.", + "legacy_next_action": "Done - withdrawn and recorded in `application_cohort.json`, `decisions.json`, `comp_worksheet_sbb.md` and memory. **Carry forward the headroom + total-package test, NOT the K→L mapping** — that one is SBB-specific and was explicitly narrowed by the user on 2026-09-11.", + "legacy_open_risk": "Anforderungsniveau K is a capped GAV band, figures not public; seat may read lateral or below current Staff + Component Owner scope.", + "legacy_note": "FIRST interview invitation in the evidence-first cohort. Converted from a COLD submit - the published warm contact (Andri Wienandts, +41 79 364 62 53) was never called. The resume never contained Snowflake, dbt or Power BI, the three literals an ATS keyword screen would have keyed on, so a human read the dossier.", + "legacy_comp_datapoint": "HARD FIGURES, from the interview - supersedes the earlier +10% estimate. Anforderungsniveau K TOPS OUT AT ~CHF 145k. His own position: ~CHF 135k today inside a Swisscom band that runs to ~CHF 160k. So the decisive point was NOT the absolute number but the HEADROOM: he would have had to enter K at its ceiling just to break even, with nothing left above. First hard calibration of a Swiss public-sector band and the reference point for the whole Bern/Thun tier (RUAG, Swissgrid, BKW, BFH, PostFinance)." + }, + { + "type": "applied", + "date": "2026-08-25" + }, + { + "type": "interview_invited", + "date": "2026-08-28" + }, + { + "type": "interview_held", + "date": "2026-09-09" + }, + { + "type": "withdrawn_by_candidate", + "date": "2026-09-11" + } + ], + "cohort_id": "evidence-first-2026-01" + }, + { + "company": "Aker BP ASA", + "role": "Data Product Architect - AI-ready Data Products", + "application_date": "2026-07-29", + "fit_class": "adjacent", + "evidence_fit": 79.0, + "hard_gate": "pass", + "channel": "weak", + "outcome": "rejected_no_interview", + "submitted": "2026-07-29", + "rejection_date": "2026-08-27", + "id": "app-80e038df976c", + "label": "Aker BP ASA — Data Product Architect, AI-ready Data Products (FINN 469067315)", + "next_action": "Closed; no pending application action. Read recorded history before reopening.", + "current_summary": "CLOSED — REJECTED 2026-08-27", + "events": [ + { + "type": "legacy_status_import", + "recorded_at": "2026-09-11", + "source": "CLAUDE.md / application_cohort.json", + "details": "**CLOSED — REJECTED 2026-08-27** (submitted 2026-07-30, ~28 days to disposition, no interview; the session file and cohort record record the submission as 2026-07-29 — a one-day inconsistency, never reconciled). Adjacent, **Evidence Fit 79/100**, Document Quality 93/100, hard gate PASS, **Channel Weak/cold** — the optional follow-up email to Per Olav Marthinsen was left unsent, so this went in as a pure cold submit. Stavanger/Oslo/Trondheim, English working language, no Norwegian and no clearance required, EEA work rights. 2pp resume + 1pp CL, both validator PASS, no Tier 1 truth findings — **a cold-channel no-interview rejection does not implicate document quality on its own** (same disposition class as Microsoft ISE and Google Business Home). Honest practitioner framing: builds governed data products *inside* Swisscom's Data Mesh; role *authored* an enterprise framework — real level stretch, never fabricated authorship. **Atlassian Compass closed the catalogue-tool gap** (74→79). Standing gaps if a similar req appears: framework authorship, no energy domain, AWS vs their Microsoft/Cognite stack, **zero verified metrics**. **Cohort slot stays consumed** (evidence-first-2026-01, Adjacent 1/2 — a rejection does not free a slot; cohort remains 4/10). **The Norway lane stays OPEN and selective** — NATO JWC Stavanger is still live, and Equinor/Telenor and other strong Norwegian employers remain in scope per the user's standing instruction. Never frame this lane as closed.", + "legacy_next_action": "Done — cohort rejection recorded; do not react to this single outcome with positioning changes (`application_strategy.md` §38/§74)" + }, + { + "type": "applied", + "date": "2026-07-29" + }, + { + "type": "rejected_no_interview", + "date": "2026-08-27" + } + ], + "cohort_id": "evidence-first-2026-01", + "unresolved": [ + "Application date conflicts: cohort says 2026-07-29; CLAUDE says 2026-07-30. Preserve cohort date pending source confirmation." + ], + "fit_override_reason": "Legacy Adjacent classification retained; numeric score alone would suggest Core. Original rubric/override needs confirmation." + }, + { + "company": "Google", + "role": "Software Engineer III, Business Home", + "application_date": "2026-07-27", + "fit_class": "core", + "evidence_fit": 89.0, + "hard_gate": "pass", + "channel": "weak", + "outcome": "rejected_no_interview", + "id": "app-767097db424a", + "label": "Google - Software Engineer III, Business Home, Zurich (req 93922217108087494)", + "next_action": "Closed; no pending application action. Read recorded history before reopening.", + "current_summary": "CLOSED - NOT PROCEEDING 2026-07-28", + "events": [ + { + "type": "legacy_status_import", + "recorded_at": "2026-09-11", + "source": "CLAUDE.md / application_cohort.json", + "details": "**CLOSED - NOT PROCEEDING 2026-07-28** (submitted 2026-07-27; Core, Evidence Fit 89/100, Document Quality 94/100; no interview). Rapid early-screen disposition; known risks were Mid-level versus current Staff scope, data/platform versus product-SWE positioning, preferred algorithms/accessibility gaps, and a cold channel.", + "legacy_next_action": "Done - cohort rejection recorded; do not treat this outcome alone as a document-quality failure" + }, + { + "type": "applied", + "date": "2026-07-27" + }, + { + "type": "rejected_no_interview", + "date": "2026-07-28" + } + ], + "cohort_id": "evidence-first-2026-01" } - ], - "policy_change_2026_09_05": "Slot caps (7 core / 2 adjacent / 1 stretch) DROPPED by user decision. target_mix is retained only as a record of the original design and is no longer read by cohort_tracker.py or enforced anywhere. This file continues as a durable application log. Channel is now the rationed variable: no application goes out cold when a named contact is published and reachable. See resume_builder/reference/application_strategy.md." + ] } diff --git a/output/Project_Review_2026-09-11/project_review.tex b/output/Project_Review_2026-09-11/project_review.tex new file mode 100644 index 0000000..e260947 --- /dev/null +++ b/output/Project_Review_2026-09-11/project_review.tex @@ -0,0 +1,383 @@ +\documentclass[11pt,a4paper]{article} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage[ngerman]{babel} +\usepackage{lmodern,microtype} +\usepackage[margin=23mm]{geometry} +\usepackage{enumitem,booktabs,tabularx,longtable,array} +\usepackage[hidelinks]{hyperref} +\usepackage{xurl} +\usepackage[version=4]{mhchem} +\setlength{\parindent}{0pt} +\setlength{\parskip}{5pt} +\setlist{nosep,leftmargin=1.4em} +\emergencystretch=2em +\widowpenalty=10000 +\clubpenalty=10000 +\newcommand{\file}[1]{{\small\nolinkurl{#1}}} +\newcommand{\finding}[2]{\subsection*{#1 --- #2}} +\newcommand{\source}[2]{\footnote{#1. Quelle #2 im Quellenverzeichnis; abgerufen am 11.09.2026.}} +\begin{document} +\begin{center}{\LARGE Projektreview und Markttauglichkeit der Bewerbungsunterlagen}\end{center} + +\section{Gesamturteil und Prüfgrundlage} + +Die aktuellen Bewerbungsunterlagen haben eine solide Basis für den Schweizer und europäischen Tech-Arbeitsmarkt: gut lesbare, einspaltige A4-Dokumente, nachvollziehbare Berufschronologie, konkrete technische Arbeit und deutlich bessere Abgrenzung eigener Verantwortung als in älteren Paketen. Ein grundlegender Designwechsel ist nicht erforderlich. Die grössten Verbesserungsmöglichkeiten liegen in der Konsistenz des Generierungssystems, der Verlässlichkeit seiner Prüfungen und der Gewichtung der ersten Lebenslaufseite. + +\textbf{Das System ist derzeit weniger zuverlässig, als seine PASS-Meldungen und detaillierten Bewertungszahlen nahelegen.} Zwei Agenten können unterschiedliche Anweisungen erhalten. Bereits aufgehobene Regeln wirken weiter im Code. Der Validator akzeptiert nachweislich Aussagen, die den zentralen Evidenzregeln widersprechen. Im Job-Scout können passende Stellen verschwinden, bevor eine inhaltliche Bewertung stattfindet. + +Die Recherche stützt das bestehende Grundformat, aber keinen Anspruch auf ein wissenschaftlich nachgewiesenes Erfolgsoptimum. Für die Schweiz, Dänemark und Norwegen existieren unterschiedliche Bewerbungskonventionen; Arbeitgebervorgaben können diese wiederum übersteuern. Besonders die generelle Behandlung von Anschreiben und die starke Orientierung an einem US-Tech-Kurzformat sollten länderspezifischer werden. + +\textbf{Stand: 11. September 2026.} Geprüft wurden Projektanweisungen, Konfiguration, Skills beider Agentenverzeichnisse, Referenzen, kanonische Claims, aktive Wissensbasis und Rollen-Bundles, die fünf projektspezifischen Python-Dateien einschliesslich Tests sowie generierte Dokumente und Tracking-Dateien. Historische Rohquellen wurden zur Einordnung herangezogen; eine erneute Extraktion sämtlicher Originaldokumente war nicht Teil dieses Reviews. + +\begin{tabularx}{\linewidth}{@{}p{.34\linewidth}X@{}} +\toprule Prüfung & Ergebnis und Aussagegrenze\\\midrule +Vorhandene Tests & 9 Tests bestanden mit \file{python -m unittest discover -s tests -v}. Das deckt die neu gefundenen Fehler nicht ab.\\ +Systemvalidator & Standardlauf PASS, 0 Warnungen. Zusätzliche Gegenproben zeigen erhebliche Lücken.\\ +LaTeX-Build & 43 vorhandene Output-Quellen plus zwei professionelle Templates: 43 von 45 erfolgreich, zwei Fehler. Je zwei Durchläufe mit pdfLaTeX, ohne Shell-Escape, Build-Produkte im temporären Verzeichnis.\\ +Visuelle Stichprobe & SBB, NVIDIA, RUAG, Aker BP: je beide CV-Seiten; zusätzlich NVIDIA- und RUAG-Anschreiben. Insgesamt zehn Seiten geprüft.\\ +Scout & 37 konfigurierte Unternehmen, 18 Adaptertypen. Statische Prüfung und gezielte Mock-Gegenproben; kein vollständiger Live-Funktionstest aller Portale.\\ +Änderungsumfang & Keine Bewerbungen, Claims, Produktionsskripte oder eingereichten Archive geändert. Bericht und temporäre Prüfarbeitsdateien erstellt.\\\bottomrule +\end{tabularx} + +\textbf{Prioritäten:} P1 bezeichnet Risiken für Wahrheitsgehalt, Stellenauswahl oder verbindliche Entscheidungen. P2 umfasst Reproduzierbarkeit, Wartbarkeit und wirksame Dokumentverbesserungen. P3 bezeichnet kleinere Bereinigungen. Die Einstufung ist eine Review-Priorisierung, kein berechneter Risikoscore. + +\section{Widersprüche in Regeln, Zustand und Workflow} + +\finding{F01 / P1}{Die Agenten verwenden unterschiedliche Bewerbungsprozesse} + +\textbf{Fundstellen:} \file{.agents/skills/} und \file{.claude/skills/}, besonders jeweils \file{make-resume/SKILL.md} und \file{critique/SKILL.md}. + +Alle sechs korrespondierenden Skills unterscheiden sich inhaltlich. Die Claude-Fassung von \file{make-resume} verlangt weiterhin feste Bullet-Zeilenvarianten und Zeichenbudgets; die neuere Fassung setzt auf natürliche Länge, Claims und Evidenz. Auch die Behandlung eines fehlenden Anschreibens, geschützter Abschnitte und der Bewertung unterscheidet sich. Damit hängt das Ergebnis vom verwendeten Agenten ab, obwohl beide dasselbe Projekt bearbeiten. + +\textbf{Folge:} Veraltete Regeln können wieder eingeführt werden, ohne dass ein Codefehler nötig ist. Besonders problematisch sind alte Schreibanweisungen, die neuere Genauigkeitskontrollen umgehen oder Relevanz zugunsten eines Seitenfüllziels verschieben. + +\textbf{Empfehlung:} Ein gemeinsamer, versionierter Prozess mit kleinen agentenspezifischen Einstiegspunkten. Die Einstiegspunkte sollen dieselben Referenzen laden, nicht vollständige Kopien enthalten. Ein Konsistenztest sollte diese Referenzen und die gültige Prozessversion prüfen. Die Dateikarte in \file{AGENTS.md} verweist ausserdem auf \file{.Codex/skills/}, während die tatsächlich verwendeten Skills anderswo liegen. + +\finding{F02 / P1}{Bewerbungsstatus ist mehrfach und widersprüchlich gespeichert} + +\textbf{Fundstellen:} aktive Sessions in \file{AGENTS.md}, \file{CLAUDE.md}, Kohortenstatus und Scout-Entscheidungen. + +SBB steht in AGENTS noch als bevorstehendes Interview am 9. September; die aktuelleren Einträge dokumentieren das Gespräch und den Rückzug am 11. September. Kdo Cy steht dort noch als eingereicht, obwohl der neuere Tracker eine Absage am 10. September enthält. Auch Microsoft ISE und Equinor haben in CLAUDE bereits Absagen, während AGENTS auf Antwort wartet. + +Das ist operativ relevant: Ein neuer Agent könnte eine Vorbereitung für ein abgeschlossenes Gespräch anbieten, falsche Conversion-Zahlen nennen oder eine Stelle erneut priorisieren. Lange Session-Zellen mischen zudem aktuelle Erkenntnisse, frühere Schätzungen und inzwischen überholte nächste Schritte. Die SBB-Gehaltskenntnis darf beispielsweise nicht auf andere Arbeitgeber in Bern oder Thun verallgemeinert werden. + +\textbf{Empfehlung:} Eine strukturierte Bewerbung pro Requisition, mit aktuellem Zustand und separatem Ereignisverlauf. AGENTS- und CLAUDE-Übersichten daraus erzeugen. Ein Rückzug nach einem Interview muss zugleich \emph{aktueller Status: zurückgezogen} und \emph{Interview erreicht: ja} ausdrücken können. Historische Notizen erhalten, aber aus der aktuellen Handlungsanweisung entfernen. + +\finding{F03 / P1}{Die aufgehobene Quote blockiert weiterhin Bewerbungen} + +\textbf{Fundstelle:} \file{resume_builder/helpers/cohort_tracker.py:59}, insbesondere Zeile 61; ausserdem README und ältere Skill-Anweisungen. + +Seit dem 5. September gelten keine Bewerbungsquoten mehr. Der Tracker verweigert dennoch die elfte Bewerbung mit \emph{cohort already contains ten applications}. Diese Sperre wurde mit einem synthetischen Datensatz reproduziert. Auch Hinweise auf volle Fit-Kategorien sind noch vorhanden. + +\textbf{Empfehlung:} Die operative Obergrenze entfernen; historische Kohortenbezeichnungen dürfen bestehen bleiben. Zugleich eindeutige Bewerbungs-IDs, gültige Datumswerte, Score-Bereiche und idempotente Updates prüfen. Der aktuelle Schreibweg schützt weder ausreichend vor Duplikaten noch vor widersprüchlichen Nachträgen. Eine atomare Speicherung allein löst dabei keine konkurrierenden Updates. + +\textbf{Weitere Bewertungsinkonsistenz:} Aker BP wird trotz 79 Punkten und bestandenem Hard Gate als Adjacent geführt, obwohl die numerische Einteilung Core nahelegt. Das kann eine sachlich gewollte Entscheidung sein. Es braucht dann einen expliziten Override-Grund und eine Rubrikversion, keine stillschweigende Umklassifizierung. + +\finding{F04 / P2}{Regelhierarchie und Auslieferungsauftrag sind nicht eindeutig} + +AGENTS verlangt ausschliesslich TEX-Ausgaben und lokale Kompilierung durch Dennis, fordert an anderer Stelle aber selbst Kompilierung und Sichtprüfung; Konfiguration und Skills beschreiben PDF-Auslieferungen. Alte und neue Referenzen widersprechen sich zudem bei Capgemini, der Bedeutung von Zeichenbudgets und verpflichtenden Bildungsdetails. + +\textbf{Empfehlung:} Drei Ebenen unterscheiden: unveränderliche Evidenzregeln, aktuelle persönliche Präferenzen und markt-/arbeitgeberspezifische Darstellung. Technische QA und auszuliefernde Dateitypen separat festlegen. Dieser Review liefert entsprechend der TEX-Vorgabe den Bericht als TEX; die eigene Kompilierung dient ausschliesslich der QA. + +\section{Evidenzprüfung und Wissensbasis} + +\finding{F05 / P1}{PASS ist keine belastbare Wahrheitsprüfung} + +\textbf{Fundstelle:} \file{resume_builder/helpers/validate_resume_system.py:121}, Funktion \file{scan_document}. + +Der Dokumentcheck sucht im Wesentlichen globale verbotene Textmuster, bestimmte Skill-Namen und wenige Titelmuster. Er prüft nicht systematisch die erlaubten Verben und Verbote jedes einzelnen Claims, Eigentumsumfang, Zertifikatskontext, Arbeitgeberdaten, Identität, Zahlen oder die Zuordnung zwischen Bullet und Claim. + +Folgende absichtlich falschen beziehungsweise unzulässigen Gegenproben bestanden ohne Fehler: +\begin{itemize} +\item vollständiger Besitz einer Data-Mesh-Plattform und des gesamten ML-Lebenszyklus; +\item Capgemini trotz ausdrücklicher persönlicher Ausschlussregel; +\item produktives Training mit PyTorch und TensorFlow trotz begrenzter kanonischer Evidenz; +\item produktive Arbeit mit nicht entsprechend belegten Skills wie Groovy und Snowflake; +\item falsche E-Mail, falscher Beginn der Staff-Rolle und eine erfundene Kostensenkung; +\item ein stehengebliebener \file{[GENERATE: Bullet]}-Platzhalter. +\end{itemize} + +Das sind Tests des Validators, keine Behauptung, alle diese Fehler stünden in aktuellen Lebensläufen. Sie zeigen, dass sein grünes Ergebnis den zentralen Qualitätsanspruch nicht absichert. + +\textbf{Empfehlung:} Jedes Dokument bekommt ein strukturiertes Inhaltsmanifest mit Claim-IDs, Skill-Evidenz und beabsichtigter Formulierung. Deterministisch prüfbar sind Identität, Daten, bekannte Metriken, Pflichtkontext, ausgeschlossene Inhalte und Referenzintegrität. Die semantische Frage, ob ein Satz unzulässige Gesamtverantwortung suggeriert, braucht weiterhin eine gesonderte inhaltliche Prüfung. Beide Resultate getrennt ausgeben; keinen automatischen Wahrheitsnachweis versprechen. + +\finding{F06 / P2}{Der Textscan erzeugt Fehlalarme und sprachabhängige Lücken} + +Ein Kommentar wie \file{\% Never claim Terraform experience} wird als unzulässiger Skill gewertet. Dasselbe gilt für die ehrliche Aussage, keine Terraform-Erfahrung zu haben. Umgekehrt umgeht ein durch LaTeX-Auszeichnung aufgeteilter Skillname den Stringvergleich. In einer SBB-Quelldatei wird bereits davor gewarnt, verbotene Namen auch nur in Kommentaren zu erwähnen: Der Workflow arbeitet um den Prüfer herum. + +Die Bullet-Analyse verarbeitet nur bestimmte einzeilige Quellmuster. Die Dreierlisten-Heuristik in Zeile 140 erkennt englisches \emph{and}, nicht das entsprechende deutsche \emph{und}. Gegenproben zeigen diesen Unterschied; andere Warnungen wie wiederholte Verben funktionieren teilweise auch deutsch. Daraus folgt kein allgemeines Urteil, der gesamte Prüfer könne kein Deutsch. + +\textbf{Empfehlung:} Kommentare und sichtbaren Text unterscheiden, LaTeX normalisieren und die relevanten Strukturen robust extrahieren. Verneinungen benötigen Kontext oder manuelle Freigabe. Stilheuristiken als unverbindliche Hinweise ausgeben. Für eine Grenze von ungefähr 50 Prozent Dreierlisten als Qualitäts- oder KI-Erkennungsregel wurde keine belastbare externe Grundlage gefunden. Belegte Verben und natürliche Sprache sind wichtiger als künstliche Variation. + +Die Schema-Prüfung hat zusätzliche Lücken: Eine fehlende Identität führt nach der Fehlermeldung zu einem \file{KeyError}; ein unmögliches Datum wie \file{2035-99} und ein nicht existierender Quellenverweis wurden akzeptiert. Hier sind kleine negative Regressionstests besonders wirksam. + +\finding{F07 / P1}{Korrekturen fehlen in aktiven Schreibquellen} + +\begin{longtable}{@{}p{.30\linewidth}p{.64\linewidth}@{}} +\toprule Fundstelle & Widerspruch und notwendige Behandlung\\\midrule +\endhead +\file{bundles/bundle_ml_ai_engineer.md:74,81} & PyTorch wird unter aufzunehmenden Skills genannt; weiter unten steht korrekt, dass der Zertifikatskontext nötig ist. Die generische Liste kann genau diesen Kontext verlieren.\\ +\file{experience/experience_swisscom.md:166--189} & Retrieval-Begriffe stehen als Skills/Keywords und Evaluation als Schreibempfehlung, obwohl Retrieval-Implementierung und breitere LLM-Evaluationsverantwortung nicht belegt sind. Datenbereitschaft und konfigurierte Agenten enger beschreiben.\\ +\file{bundles/bundle_data_platform.md:69} & Aus kontinuierlichem Fabrikbetrieb wird eine Zero-Downtime-Deployment-Anforderung abgeleitet. Das ist ohne zusätzlichen Beleg eine stärkere Behauptung.\\ +\file{experience/experience_bosch.md}, BS-1 & Vollständiges Ersetzen manueller Waferinspektion und unbeaufsichtigter Betrieb reichen weiter als die kanonisch belegte Bildklassifikation und qualitative Reduktion manueller Arbeit. Varianten zurück auf den bestätigten Umfang führen.\\ +\file{reference/resume_reference.md:62} und Capgemini-Erfahrungsdatei & Bedingtes Aufnehmen von Capgemini widerspricht der persönlichen Ausschlussregel. Rohhistorie erhalten, aktive Auswahlregeln korrigieren.\\ +\file{experience/experience_bosch.md} und Plattform-Bundle & Die neuere BS-6-Evidenz zu Linux/Ansible ist nicht vollständig in den normalen Auswahlpfaden angekommen. Das System verliert dadurch echte Stärken.\\ +\file{support/skills_taxonomy.md} & Veralteter Hinweis auf unbekanntes IBM-Abschlussjahr trotz vorhandener verifizierter Datierung; unterschiedliche Einordnung von PyTorch. Auch Linux/Bash fehlen in der erwarteten systematischen Abbildung.\\ +\bottomrule +\end{longtable} + +Alle verkürzten Pfade in dieser Tabelle liegen unter \file{resume_builder/}. Die kritische Beobachtung ist die Richtung des Fehlers: Es entstehen sowohl Übertreibungen als auch unnötige Unterbewertungen. Das NVIDIA-Anschreiben spricht beispielsweise davon, dass die Produktionslinie für ein Deployment nicht gestoppt werden konnte; RUAG verwendet eine ähnliche Stillstandsaussage. Diese Aussagen sind aus dem bisher kanonisch dokumentierten 24/7-Kontext allein nicht ableitbar. Sie sollten bestätigt oder enger formuliert werden, nicht vorschnell als nachweislich falsch gelten. + +Weitere Widersprüche betreffen die Priorität des Plattform-Bundles und die Platzierung des Security-Champion-Themas. Solche Prioritäten sollten aus einer gemeinsamen Rollenmatrix kommen. Nach einer Claim-Korrektur ist ein gezielter Abhängigkeitslauf über alle aktiven Varianten sinnvoll; unveränderliche Originalquellen und bereits versandte Dokumente dürfen dabei nicht stillschweigend umgeschrieben werden. + +\finding{F08 / P2}{Das Evidenzmodell ist noch nicht durchgehend maschinenprüfbar} + +Von 26 kanonischen Claims besitzt nur einer die jeweils geprüften strukturierten Felder \file{source}, \file{last_verified} und \file{ownership}. Andere Claims enthalten durchaus Evidenz und Einschränkungen in Freitext; die Zählung bedeutet ausdrücklich nicht, dass 25 Claims unbelegt seien. Sie erklärt aber, weshalb automatische Referenz- und Aktualitätsprüfungen derzeit schwer möglich sind. + +Auch Skills, die innerhalb bestätigter Tätigkeiten vorkommen, sind nicht vollständig als eigenständige Skill-Einträge normalisiert. Das kollidiert mit der Regel, jeder ausgegebene Skill müsse vorher kanonisch registriert sein. Ein sinnvoller nächster Schritt ist ein kleines Schema für Quellenanker, Gültigkeitszeitraum, Kontext, Eigentumsumfang und erlaubte Ableitungen. Die Claim-ID verbindet die Daten; zusätzliche parallele Freitextregister würden den Pflegeaufwand weiter erhöhen. + +\finding{F09 / P1}{Vorlagen enthalten bereits riskante Aussagen} + +\textbf{Fundstelle:} \file{resume_builder/templates/resume_template.tex:36}. Die fest eingetragene Aussage \emph{12+ years of production software experience} folgt nicht aus der dargestellten Chronologie. Mai 2015 bis September 2026 sind rund elf Jahre und vier Monate verstrichene Zeit; daraus darf ohnehin nicht ungeprüft eine lückenlose Netto-Berufserfahrung berechnet werden. + +\textbf{Empfehlung:} Die Jahreszahl entfernen oder aus bestätigten, nicht doppelt gezählten Zeiträumen ableiten. Auch eine automatisch gesetzte Thesis-Note und unveränderliche Bildungsdetails sollten keine allgemeine Industrievorlage steuern. Die ältere akademische \file{cv_template.tex} enthält weiterhin mehrseitige Zielvorgaben, feste Zeilenbudgets und unaufgelöste Platzhalter-/Asset-Erwartungen. Sie sollte als akademischer Legacy-Pfad klar abgegrenzt oder aktualisiert werden. Ein europäischer beruflicher CV ist nicht automatisch ein akademischer Lang-CV. + +\finding{F10 / P2}{Die US-GPA-Umrechnung ist kein universeller Karrierefakt} + +\textbf{Fundstelle:} \file{resume_builder/canonical/claims.json}, Bildungsangaben mit \emph{US = 5 -- G}. Die daraus resultierenden GPA-Werte setzen ein bestimmtes Zielnotensystem voraus. Die angeführte modifizierte bayerische Formel wird beispielsweise von der TUM für die Umrechnung ausländischer Noten in das deutsche System erläutert; auch dort ist der Rechner nur eine Orientierung. Daraus folgt keine allgemein akzeptierte inverse US-Umrechnung.\source{Technische Universität München: Bayerische Formel}{Q16} + +\textbf{Empfehlung:} Bestätigte deutsche Originalnoten mit Skalenkontext verwenden. Nur wenn ein konkreter Empfänger eine Umrechnung verlangt, dessen Methode dokumentieren. Keine pauschale US-GPA als gleichrangigen kanonischen Abschlussfakt führen. Für die primären europäischen Zielmärkte bringt diese Umrechnung ohnehin wenig. + +\section{Job-Scout: Fehler mit Einfluss auf die Stellenauswahl} + +\finding{F11 / P1}{Titel-Deduplizierung entfernt verschiedene Requisitionen} + +\textbf{Fundstelle:} \file{job_scout/scout.py:2168}, innerhalb \file{_process_company}. Pro Unternehmen wird auf dem normalisierten Titel vor dem ersten senkrechten Strich dedupliziert. Zwei verschiedene Requisitionen namens Senior Data Engineer in Bern und Zürich ergaben in der Gegenprobe nur einen Treffer. Auch unterschiedliche Teams mit demselben Titel sind betroffen. + +Der Wunsch, mehrfach veröffentlichte Remote-Standorte zusammenzuführen, ist sinnvoll. Der Titel beweist aber keine Identität. \textbf{Lösung:} Arbeitgeber plus stabile Requisitions-ID als Primärschlüssel; mehrere Standorte nur bei derselben Requisition zusammenführen. Titelähnlichkeit darf einen Hinweis auf mögliche Duplikate erzeugen, keine irreversible Entfernung. + +\finding{F12 / P1}{Ortsfilter verlieren Schweizer Stellen und erkennen falsche Remote-Eignung} + +\textbf{Fundstelle:} \file{job_scout/scout.py:1728}, \file{location_eligibility}, sowie Adapter-Normalisierung. + +Die isolierte Filterfunktion akzeptiert Bern, lehnt aber unter anderem Thun, Zimmerwald, Baar und Olten ohne Länderzusatz ab. Bernau in Deutschland wird wegen des Teilstrings Bern akzeptiert. Texte mit Europe können als Remote eingeordnet werden, obwohl London/Onsite oder eine Beschränkung auf Deutschland danebensteht. Aarhus und Odense werden ebenfalls nicht wie Kopenhagen erkannt. + +Nicht jeder Adapter ist gleich betroffen: Manche ergänzen das Land. Ein konkreter Mock des SmartRecruiters-Pfads zeigt jedoch, dass \file{city=Baar, country=CH} auf den String Baar reduziert und danach verworfen wird. Damit ist das Problem mehr als eine abstrakte Schwäche des Ortsvokabulars. + +\textbf{Lösung:} Länderkennung, Ort, Arbeitsmodell, erlaubte Beschäftigungsländer und Umzugspflicht als getrennte Felder erhalten. Unbekanntes Arbeitsmodell bleibt unbekannt. Europa-Remote allein belegt keine Anstellungsmöglichkeit in der Schweiz. Norwegen ist derzeit nur über ausgewählte Unternehmens-Overrides abgedeckt; die gewollten Ausnahmen sollten als explizite Suchpräferenz gepflegt werden. + +\finding{F13 / P2}{Entscheidungs-IDs und Wiedererkennung sind nicht einheitlich} + +\textbf{Fundstelle:} \file{job_scout/scout.py:1873}, \file{_decision_key}, sowie RUAG-Fallback und Report-Index. + +Der generische Fallback verliert Query-Parameter: Zwei URLs \file{/vacancy?job=1} und \file{/vacancy?job=2} ergeben denselben Schlüssel. Bei gemeinsam genutzten ATS-Hosts fehlt teilweise der Arbeitgeber im Namensraum; eine Kollision wäre möglich, falls IDs nicht global eindeutig sind. Das ist ein strukturelles Risiko, kein Nachweis einer bereits eingetretenen SmartRecruiters-Kollision. + +Für RUAG erkennt die interaktive Entscheidungssuche in der Gegenprobe einen bekannten Slug mit neuer UUID wieder; der Report-Lookup findet die alte Entscheidung nicht. \textbf{Lösung:} Ein gemeinsamer Identitätsresolver für Schreiben und Lesen. Neue Requisitionen mit ähnlichem Slug als verwandt markieren; nicht automatisch alte Ablehnungen übertragen, da eine Neuausschreibung sachlich anders sein kann. + +\finding{F14 / P1}{Der Scout nennt Keyword-Interesse fälschlich Fit} + +\textbf{Fundstelle:} \file{job_scout/scout.py:1790}, \file{score_job}, und Report-Kategorien. + +Im Titeltest erreicht Senior Data Engineer drei Punkte, Senior AI Engineer mit Generative AI, LLM und RAG dagegen zwölf. Senior Dateningenieur erreicht nur einen Punkt. Das entspricht weder automatisch Dennis' stärkster Evidenz noch einer belastbaren Eignungsbewertung. Bei vorgefilterten Boards wird teilweise nur der Titel gewertet; harte Anforderungen im JD können so unberücksichtigt bleiben. + +\textbf{Lösung:} Den Wert ehrlich als Suchrelevanz kennzeichnen. Anschliessend separate Signale für Evidenz, zwingende Anforderungen, Sprache, Standort, Niveau und praktische Attraktivität berechnen beziehungsweise als unbekannt markieren. Deutsche Berufsbezeichnungen ergänzen. Die verbindliche Fit-Prüfung bleibt am vollständigen JD; ein höheres Keyword-Ergebnis darf keine bevorzugte Bewerbung suggerieren. + +\finding{F15 / P2}{Datums- und Kontaktinformationen verlieren ihre Bedeutung} + +\textbf{Fundstellen:} Taleo-Parser ab \file{job_scout/scout.py:1110}, Datumsparser und Ergebnisobjekt in \file{_process_company}. + +Der Taleo-Parser schreibt in der Gegenprobe eine Bewerbungsfrist vom 30. September in \file{posted}. Dadurch kann eine Stelle fälschlich als besonders frisch erscheinen. Andere Adapter liefern relative Daten oder Aktualisierungszeitpunkte, die nicht einheitlich mit Veröffentlichungsdaten vergleichbar sind. + +Der Prospective-Pfad liest nützliche Angaben wie Einheit, Lohnklasse und Ansprechpartner in die Beschreibung ein. Der finale Ergebnisdatensatz verwirft die Beschreibung und führt diese Angaben nicht strukturiert fort. Damit gehen genau die Informationen verloren, die für Niveau, Gehalt und den Kontaktansatz wichtig sind. + +\textbf{Lösung:} \file{published_at}, \file{updated_at}, \file{deadline} und \file{first_seen_at} trennen, inklusive Rohwert und Herkunft. Ansprechpartner, Vergütungshinweis und Organisationseinheit als eigene Felder führen; JD-Inhalt oder einen stabilen lokalen Verweis mitsamt Abrufdatum erhalten. + +\finding{F16 / P2}{Report- und Zustandswrites können Informationen verlieren} + +\textbf{Fundstellen:} \file{job_scout/scout.py:2202--2340}, Speicherroutinen und breite Exception-Behandlung in Adaptern. + +Alle Läufe desselben Tages schreiben auf denselben Markdown-/JSON-Namen. Ein eingeschränkter Lauf mit \file{--only} oder \file{--new-only} kann damit den vollständigen Tagesreport überschreiben. Gesehen-Markierungen werden gespeichert, bevor der Report erfolgreich geschrieben ist. Bei einem Schreibfehler kann ein Wiederholungslauf mit New-only deshalb gerade die noch nicht ausgelieferten Treffer ausblenden. + +Ein fester temporärer Dateiname bei atomaren Writes verhindert keine konkurrierenden Schreibkonflikte. Breite \file{except Exception: continue}-Blöcke können einzelne kaputte Karten stillschweigend auslassen; Pagination-Limits und vollständige Abdeckung sind nicht überall erkennbar. + +\textbf{Lösung:} Eindeutige Run-ID mit Suchumfang, unveränderliche Reports und erst nach erfolgreicher Report-Erstellung committed Seen-State. Konkurrenzschutz oder bewusst nur ein schreibender Prozess. Pro Adapter Mengen, Abbruchgrund und Vollständigkeitsstatus ausgeben. Die rund 2.400 Zeilen Scout-Code nach Adapter, Normalisierung, Matching und Persistenz aufzuteilen wäre anschliessend sinnvoll; ein grosses neues Framework ist dafür unnötig. + +\section{Builds, historische Pakete und sichtbarer Output} + +\finding{F17 / P2}{Nicht alle Pakete sind aus den vorhandenen Quellen reproduzierbar} + +\begin{itemize} +\item \file{output/NATO_AI_ENGINEER/e2e_nato_ai_engineer_resume.tex}: Build scheitert wegen fehlender \file{resume.cls}. Ein vorhandenes PDF verdeckt die unvollständige Quelldistribution. +\item \file{output/Infineon_AI_Engineer/e2e_infineon_ai_engineer_cover_letter.tex}: Im geprüften pdfLaTeX-Setup Fehler bei der Schriftexpansion, weil eine verwendete Schrift nicht skalierbar ist. +\item Microsoft-ISE-Resume: Overfull-Box von rund 25,3 pt; Isovalent-Resume: kleine Overfull-Box von rund 0,88 pt. Die vier aktuellen Stichproben bauten ohne solche Boxen. +\end{itemize} + +Historische Klassenkopien sind nicht automatisch schädliche Duplikate: Sie können den damaligen Build einfrieren. Sie sollten über eine Version oder einen Hash nachvollziehbar sein. Beim NATO-Paket einfach die neueste Klasse hineinzukopieren wäre keine sichere Reparatur, weil alte Makros abweichen können. + +Mehrere PDF-Kopien unterscheiden sich binär. Bei Kdo Cy ist der extrahierte Text dennoch identisch; das ist kein belegter Inhaltsfehler. Beim NATO-Resume unterscheiden sich dagegen die Skills und die enthaltenen Stationen zwischen E2E- und Abgabe-PDF. Es fehlt ein eindeutig maschinenlesbarer Verweis, welche Quelle die tatsächlich eingereichte Fassung erzeugt hat. Keine dieser historischen Fassungen sollte stillschweigend ersetzt werden. + +\textbf{Empfehlung:} Pro Abgabe ein Manifest mit Requisition, Dokumenttyp, Quellen-/Klassenhash, festem Datum, finalem Dateinamen und Status. \file{\today} in historischen Anschreiben macht den erneuten Build datumsabhängig und sollte bei neuen Paketen durch das explizite Bewerbungsdatum ersetzt werden. Das Wiederverwendungsregister klassifiziert 14 Pakete, während 21 Bewerbungsordner vorhanden sind; unbekannte Pakete benötigen einen klaren Revalidierungsdefault. + +\finding{F18 / P2}{Die erste Seite zeigt zu spät die stärksten Belege} + +Die vier visuell geprüften Lebensläufe sind gut lesbar, technisch ruhig und ohne Überlappungen. Das A4-Format, die Schriftgrösse und die klare Chronologie funktionieren. Die Textausleitung zeigte in diesen PDFs keine Ersatzzeichen und eine nachvollziehbare Reihenfolge. Das ist ein nützlicher technischer Check, aber keine Zertifizierung für jedes ATS. + +\begin{tabularx}{\linewidth}{@{}lrrX@{}} +\toprule Paket & Seiten & Beginn Erfahrung & Beobachtung\\\midrule +SBB & 2 & 50,6\% & Viel Platz für Profil/Skills; lange Bildungszeile.\\ +NVIDIA & 2 & 55,4\% & Bosch/Linux-Evidenz erst auf Seite zwei.\\ +RUAG & 2 & 50,3\% & Bildungsblock bereits besser aufgeteilt.\\ +Aker BP & 2 & 50,5\% & Starker Skills-Vorlauf vor Berufserfahrung.\\\bottomrule +\end{tabularx} + +Die Prozentwerte bezeichnen die vertikale Position der Überschrift zur Berufserfahrung relativ zur Seitenhöhe, nicht ein wissenschaftliches Layoutziel. Sie machen das gemeinsame Problem sichtbar: Der erste konkrete Berufseintrag beginnt erst etwa auf halber Seite. Fünf oder sechs Skills-Kategorien entsprechen durch Umbrüche wesentlich mehr gedruckten Zeilen. Eine Regel für vier bis sechs Quellmakros kontrolliert daher nicht die tatsächliche Dichte. + +\textbf{Empfehlung:} Skills auf die entscheidenden belegten Anforderungen reduzieren und Wiederholungen zu den Experience-Bullets entfernen. Direkt nach dem kurzen Profil entweder die Erfahrung oder einen sehr kompakten Kompetenzblock zeigen. Die Belegreihenfolge innerhalb einer Station nach der Zielrolle priorisieren; die Arbeitgeberchronologie beibehalten. Kein fixes Pixelziel und kein erzwungenes Auffüllen der zweiten Seite. + +\finding{F19 / P2}{Einige Formulierungen und Details schwächen die Wirkung} + +Die langen Bildungszeilen mit Abschluss, Institution und rechtsbündigem Datum erzeugen bei SBB, NVIDIA und Aker BP auffällige Wortabstände beziehungsweise Umbrüche. Die RUAG-Variante mit getrennten Angaben ist ein brauchbares Vorbild. Abschlussnote und Thesis sollten bei erfahrenen Industrieprofilen nur bleiben, wenn sie konkrete Relevanz tragen. + +Bei NVIDIA nimmt die Erklärung fehlender Erfahrung relativ viel Raum im Anschreiben ein. Eine klar benannte, entscheidende Lücke mit anschliessender belastbarer Anschlussfähigkeit wäre meist wirksamer als ein eigener längerer Defizitblock. Ehrlichkeit bleibt verbindlich; sie erfordert keine vorgezogene Selbstabsage. Bei RUAG könnten die echte Verbindung zu Thun und der Offiziershintergrund früher erscheinen. Deutsche Bullets sollten eine konsistente Perspektive erhalten; eine Mischung aus drittpersonalen Verbformen und anderen Stilen wirkt unnötig distanziert. + +Ein enger, evidenztreuer Formulierungsrahmen wäre beispielsweise: \emph{Governed data products within Swisscom's company-wide Data Mesh} statt eines ganzen selbst gebauten Data Mesh. Bei Bosch sollte die konkret bestätigte Bildklassifikation beziehungsweise Integration beschrieben werden, statt die gesamte Waferinspektion als ersetzt darzustellen. Diese Beispiele sind Richtungen für eine spätere Bearbeitung, keine neu validierten Ersatzbullets. + +Die Metadaten zur Wortzahl sind nicht überall reproduzierbar: NVIDIA nennt 279, während eine einfache Whitespace-Zählung des extrahierten Briefkörpers etwa 300 Wörter ergibt; RUAG liegt mit rund 299 nahe an den dokumentierten 295. Unterschiedliche Zählregeln können kleine Abweichungen erklären. Der relevante Befund ist fehlende Zählkonvention, nicht ein Verstoss gegen eine universelle optimale Wortzahl. Beide Briefe passen auf eine gut lesbare Seite. + +\section{Vertiefte Recherche: Was passt zu den Zielmärkten?} + +\subsection{Evidenzstärke und sinnvolle Grenzen} + +Für die Frage nach dem grössten Bewerbungserfolg sind drei Quellenarten zu unterscheiden: Arbeitgebervorgaben zeigen, was ein konkreter Prozess akzeptiert oder verlangt. Nationale Arbeitsmarktstellen und Berufsverbände beschreiben verbreitete Konventionen. Experimente können Wirkungen untersuchen, gelten aber nur eingeschränkt ausserhalb ihrer untersuchten Population. + +Die recherchierten Quellen liefern keine belastbare Rangfolge, mit der eine bestimmte Schrift, eine exakt zweizeilige Zusammenfassung oder ein fester Bullet-Rhythmus Dennis' Interviewwahrscheinlichkeit maximiert. Solche Regeln können gute redaktionelle Hilfen sein; als wissenschaftliches Erfolgsversprechen wären sie überzogen. Der Bericht empfiehlt deshalb einen gut begründeten Standard mit wenigen situativen Varianten. + +\subsection{Schweiz: Das bestehende Grundformat beibehalten} + +SECO/arbeit.swiss empfiehlt einen klaren, umgekehrt chronologischen Lebenslauf und nennt bis zu drei Seiten. Ein Foto ist dort eine mögliche Ergänzung. Das unterstützt einen zweiseitigen CV für das vorliegende Erfahrungsprofil; es begründet weder eine Pflicht zur dritten Seite noch einen starren Einseiter.\source{SECO: Bewerbungstipps}{Q1} + +Für Schweizer Industrie-, Infrastruktur- und öffentliche Arbeitgeber sollten konkrete Verantwortung, Betriebsumfeld, Zusammenarbeit und verständliche Rollenbezeichnungen schnell erkennbar sein. Die tatsächlichen Component-/Application-Owner-Titel sind dabei wertvoll, müssen aber durch den eigenen Umfang erklärt werden. Arbeitsort, wahrheitsgemässe Sprachangaben und vorhandener Aufenthaltsstatus können praktische Einordnung erleichtern. Diese Gewichtung ist eine aus Quellen und Profil abgeleitete Empfehlung, keine allgemeine rechtliche Dokumentpflicht. + +\textbf{Die Arbeitgeberregel hat Vorrang.} SBB erklärt das Motivationsschreiben ausdrücklich für freiwillig und bevorzugt PDFs. Der Lebenslauf soll separat und mit möglichst wenig Grafikelementen hochgeladen werden; weitere Unterlagen können gebündelt werden. Das SBB-Paket ohne Anschreiben war deshalb prozessgerecht.\source{SBB: Bewerbungstipps}{Q2} + +Wo ein Anschreiben sinnvoll ist, empfiehlt die UZH eine Seite mit konkretem Bezug zur Stelle und nachvollziehbarer Motivation, statt den Lebenslauf zu wiederholen. Für Dennis kann das bei einem Branchenwechsel, einem besonderen Bezug zum Arbeitgeber oder einer erklärungsbedürftigen Anschlussfähigkeit echten Nutzen haben.\source{Universität Zürich: Anschreiben}{Q3} + +Bei persönlichen Daten widersprechen sich selbst seriöse Schweizer Ratgeber. Der ETH-Welcome-Center-Leitfaden führt traditionelle Angaben zu Geburtsdatum und Familie auf, bezeichnet das Foto aber als optional; arbeit.swiss formuliert weniger eng. Das sind Konventionsempfehlungen, kein Beweis, dass mehr private Angaben bessere Chancen erzeugen. Die bewusste Präferenz gegen Geburtsdatum, Familienstand und Kinder sollte bestehen bleiben. Ein Foto ist ebenfalls kein notwendiger Standard.\source{ETH Zürich: How to apply for a job}{Q4} + +Für international rekrutierende Tech-Unternehmen in der Schweiz bleibt das International-Tech-Profil passend: belegte technische Beiträge, klare Ergebnisse, wenig administrative Zusatzinformation. Die Sprache sollte sich nach Ausschreibung, Team und ausdrücklicher Vorgabe richten. Das englische SBB-Dokument war eine bewusste Entscheidung und wird deshalb nicht allein wegen des deutschen JDs als Fehler bewertet. + +\subsection{Dänemark: Kurzprofil und Motivation systematisch mitdenken} + +Workindenmark empfiehlt ein zugeschnittenes persönliches Profil von etwa vier bis acht Zeilen mit den wichtigsten Kompetenzen. Für internationale Bewerbende kann die Motivation für Dänemark relevant sein. Eine globale Zwei-bis-drei-Zeilen-Regel sollte deshalb kein hartes Hindernis sein.\source{Workindenmark: A Good Personal Profile Counts, aktualisiert 25.03.2026}{Q5} + +Die gleiche staatliche Plattform rät dazu, unbekannte Arbeitgeber und die tatsächliche Funktion verständlich einzuordnen und Verantwortung, Zusammenarbeit sowie Ergebnisse zu beschreiben. Das passt gut zu Dennis' Laufbahn: Schweizer Unternehmensnamen und interne Owner-Titel erklären sich im Ausland nicht von selbst. Ein knapper Kontextsatz ist oft wertvoller als zusätzliche Technologiebegriffe.\source{Workindenmark: Work Experience, aktualisiert 12.03.2026}{Q6} + +Das Anschreiben wird dort als häufig ebenso wichtig wie der CV beschrieben: höchstens eine Seite, individuell, mit Motivation und Beispielen fachlicher und sozialer Kompetenz. Für eine dänische Bewerbung sollte deshalb der Standard lauten: den erwarteten Bewerbungsumfang prüfen und ein spezifisches Schreiben ernsthaft einplanen. Die bestehende globale Tendenz zum Weglassen eines optionalen Briefs ist hier zu grob. Eine ausdrückliche Arbeitgebervorgabe gegen Anschreiben übersteuert diesen Standard.\source{Workindenmark: Your Cover Letter, aktualisiert 12.03.2026}{Q7} + +Der Ingenieurverband IDA empfiehlt in seinem aktuellen Ratgeber gewöhnlich ein bis zwei Seiten und ein klares, angepasstes Kompetenzprofil. Das stützt zwei Seiten als brauchbaren Ausgangspunkt für diese technische Laufbahn.\source{IDA: How to make a good CV}{Q8} + +Workindenmark beschreibt persönliche Informationen und Interessen als üblich. Daraus folgt keine Notwendigkeit, die persönliche Datenschutzpräferenz aufzugeben. Falls zutreffend, können eine konkrete Dänemark-Motivation, reale Sprachlernbemühungen oder relevante Interessen die persönliche Anschlussfähigkeit zeigen, ohne Familienangaben einzuführen. Relocation-Bereitschaft darf nur genannt werden, wenn sie tatsächlich besteht.\source{Workindenmark: Personal Details in Your CV, aktualisiert 12.03.2026}{Q9} + +\subsection{Norwegen: Fachliches Profil und spezifische Bewerbung} + +NAV empfiehlt eine übersichtliche Berufschronologie, genaue Tätigkeits- und Zeitangaben und verständliche Sprachkenntnisse. Die aktuelle Darstellung ist damit grundsätzlich vereinbar. Referenzen können auf Anfrage verfügbar sein; Namen sollten nicht ohne vorherige Abstimmung veröffentlicht werden.\source{NAV/Arbeidsplassen: Slik skriver du en god CV}{Q10} + +Für technische Profile ist Tekna besonders einschlägig. Der Verband empfiehlt ein zugeschnittenes Kompetenzprofil von ungefähr vier bis sechs Zeilen, konkrete Verantwortung und Ergebnisse, umgekehrte Chronologie und im Regelfall maximal zwei Seiten. Foto ist eine persönliche Entscheidung; weniger relevante ältere Erfahrung darf zusammengefasst werden. Das spricht für eine kleine Norwegen-Variante des bestehenden CVs, nicht für ein völlig neues Design.\source{Tekna: How to write a good CV}{Q11} + +NAV beschreibt das Bewerbungsschreiben als kurze, spezifische Begründung mit passenden Beispielen, vorzugsweise innerhalb einer Seite. Auch hier ist ein pauschaler internationaler Skip-Default zu wenig differenziert. Ein Anruf ist sinnvoll, wenn echte offene Fragen bestehen; Fragen, die schon in der Anzeige beantwortet sind, rechtfertigen ihn nicht.\source{NAV/Arbeidsplassen: Tips til jobbsøknaden}{Q12} + +Für Aker BP, Equinor oder NATO sollten tatsächliche Motivation, Arbeitssprache und Standortmodell jeweils separat geprüft werden. Eine internationale Organisation in Norwegen kann ein eigenes Bewerbungsformat haben. Norwegische Marktgewohnheiten ersetzen dann nicht deren Portalvorgaben. Ungeklärte Umzugsbereitschaft ist eine offene praktische Frage, kein automatisch vorhandener Vorteil. + +\subsection{Empfohlene Profilmatrix} + +\begin{tabularx}{\linewidth}{@{}>{\raggedright\arraybackslash}p{.22\linewidth}>{\raggedright\arraybackslash}X>{\raggedright\arraybackslash}X@{}} +\toprule Kontext & Standard & Bewusste Abweichung\\\midrule +Schweiz, internationaler Tech-Arbeitgeber & Zwei Seiten, belegte technische Ergebnisse, kompakte Skills, Englisch sofern passend. & Brief nur bei Anforderung oder konkretem Mehrwert.\\ +Schweiz, lokale Industrie und Infrastruktur & Zwei Seiten, verständliche Verantwortung, Sprachen und praktische Einordnung. & Dossier und Brief nach Arbeitgeber; dritte Seite nur bei relevantem Bedarf.\\ +Dänemark & Zwei Seiten, stärkeres persönliches Fachprofil, Arbeitgeberkontext und Zusammenarbeit. & Individuellen Brief und Landesmotivation prüfen; Arbeitgeberausnahmen beachten.\\ +Norwegen & Zwei Seiten, klare Schlüsselqualifikationen, Aufgaben und belegte Ergebnisse. & Kurzes spezifisches Schreiben; internationaler Arbeitgeber kann eigene Regeln haben.\\ +Akademische Bewerbung & Separater akademischer CV-Prozess. & Publikationen und Lehre nach Anforderung, keine Industrie-Seitenregel erzwingen.\\\bottomrule +\end{tabularx} + +\subsection{ATS: Textstruktur und Portalprüfung sind wichtiger als Mythen} + +Greenhouse nennt komplexe Tabellen, Spalten, Grafiken und Informationen in Kopf-/Fusszeilen als mögliche Ursachen unvollständiger Verarbeitung. Der Anbieter nennt ausserdem eine Grenze von 2,5 MB für das Parsing. Ein Parsingfehler bedeutet laut Dokumentation nicht automatisch Ablehnung: Das Dokument kann angehängt bleiben und Daten müssen manuell korrigiert werden.\source{Greenhouse: Unsuccessful resume parse, aktualisiert 02.03.2026}{Q13} + +Die aktuellen einspaltigen, textbasierten PDFs sind daher ein vernünftiger Standard. Eine LaTeX-Kontaktzeile im normalen Dokumentkörper ist dabei nicht mit einer Word-Kopfzeile gleichzusetzen. Die vorhandene Schrift ist kein nachgewiesenes ATS-Problem. Ein Wechsel zu einer anderen Schrift allein verspricht keinen belegten Vorteil. + +Microsoft verlangt ausdrücklich, die aus dem CV übernommenen Profilinformationen zu prüfen und gegebenenfalls zu korrigieren. Eine kurze Kontrolle der Portal-Felder vor der Abgabe ist deshalb konkreter und verlässlicher als ein selbst vergebener ATS-Score.\source{Microsoft Careers: Hiring FAQs}{Q14} + +\textbf{Empfohlene QA:} sichtbaren PDF-Text kopieren, Reihenfolge und Sonderzeichen prüfen, Kontaktinformationen und Datumszuordnung kontrollieren, Links testen und im tatsächlichen Portal die extrahierten Felder ansehen. DOCX nur dann zusätzlich erzeugen, wenn ein Arbeitgeber es verlangt oder das konkrete Portal mit dem PDF Probleme hat. Keywords müssen aus belegten Tätigkeiten stammen; unbekannte Technologien werden durch Aufnahme in eine Skills-Liste nicht zu Erfahrung. + +\section{Was tatsächlich mehr Erfolg verspricht} + +\subsection{Formulierungsqualität hilft; feste Stilrezepte sind nicht belegt} + +Eine grosse randomisierte Studie von Wiles, Munyikwa und Horton untersuchte algorithmische Schreibunterstützung bei knapp 500.000 Neueintritten auf einem Online-Arbeitsmarkt. Die unterstützte Gruppe wurde relativ rund acht Prozent häufiger eingestellt; die Arbeitgeberzufriedenheit sank nicht. Das ist relevante Evidenz dafür, dass verständlichere Darstellung die Erkennung vorhandener Fähigkeiten verbessern kann. Es ist keine Prognose von acht Prozentpunkten mehr Erfolg für einen Schweizer Staff Engineer und kein Test eines bestimmten CV-Layouts.\source{Wiles, Munyikwa und Horton: Algorithmic Writing Assistance on Jobseekers' Resumes Increases Hires; NBER 30886, später Management Science 2025}{Q15} + +Für die Formulierung folgt daraus als redaktionelle Empfehlung: klare Handlung, eigenes Objekt beziehungsweise eigener Umfang, technischer oder organisatorischer Kontext und ein bestätigtes Ergebnis. Nicht jeder Bullet braucht jede Komponente oder eine Zahl. Ein glaubwürdiges qualitatives Ergebnis ist besser als eine erfundene Prozentzahl. Zusammenarbeit und begrenzte Verantwortung können gerade im Grossunternehmen Kompetenz zeigen. + +Bei einem Staff-Profil sollten vorhandene Belege für Entscheidungen, Standards, Abstimmung über Schnittstellen und dauerhafte Verantwortung sichtbarer werden. Der aktuelle Technologieumfang allein erklärt den Staff-Mehrwert nicht immer. Fehlende Nachweise hierfür sollten gezielt erhoben werden; sie dürfen nicht aus dem Titel hergeleitet werden. + +\subsection{Kontaktstrategie: sinnvoll, aber keine Erfolgsgarantie} + +Randomisierte LinkedIn-Experimente zu schwächeren beruflichen Verbindungen liefern Evidenz, dass Netzwerkstruktur berufliche Mobilität beeinflussen kann. Sie testen jedoch Empfehlungen neuer Verbindungen, nicht die Behauptung, ein Anruf bei jeder veröffentlichten Kontaktperson sei zwingend besser als eine direkte Bewerbung.\source{MIT-Bericht zur Science-Studie von Rajkumar et al., veröffentlicht 15.09.2022}{Q17} + +Die neue projektinterne Kontaktpriorität ist als Arbeitsstrategie nachvollziehbar: Offene Fragen zu Lohnklasse, Scope, Präsenz und Team können früh geklärt werden. Ein kurzer Kaltanruf ist aber noch keine Empfehlung und macht den Kanal nicht automatisch stark. Als Verbesserungsvorschlag sollten Kontaktversuch, tatsächliches Gespräch, hilfreiche Information und echte Empfehlung getrennt erfasst werden. Vergebliche Kontaktversuche sollten eine passende Bewerbung kurz vor Fristende nicht unbegrenzt blockieren; eine solche Ausnahme wäre eine ausdrücklich zu beschliessende Strategieänderung, keine bereits umgesetzte Regel. + +Die eigene Kohorte erlaubt noch keine belastbaren kausalen Aussagen: sieben erfasste Bewerbungen, durchgehend schwacher Kanal, unterschiedliche Rollen und teilweise noch offene Ergebnisse. SBB erreichte ein Interview und endete später durch Rückzug. Diese Beobachtung darf weder als Beweis für optimale Dokumente noch als Beweis gegen Kontaktaufnahme gelten. Der Rückzug ist zudem eine andere Ergebnisart als eine Arbeitgeberabsage. + +\subsection{Fit, praktische Attraktivität und Ungewissheit auseinanderhalten} + +Die getrennte Betrachtung von Evidence Fit, Document Quality und Channel Strength ist ein klarer Fortschritt und sollte bleiben. Die Zahlen sind aber interne Rubrikergebnisse, keine kalibrierten Einstellungswahrscheinlichkeiten. Unterschiede wie 92 versus 93 sind ohne nachvollziehbare Einzelbewertung und stabile Rubrik kaum entscheidungsrelevant. + +Zusätzlich sollten bekannte Ausschlussgründe, ungeklärte Bedingungen und Präferenzen getrennt werden. Ein bestätigtes zwingendes Kriterium ohne passende Evidenz bleibt ein echtes Gate. Eine noch unbekannte Lohnklasse oder Präsenzregel ist zunächst eine zu klärende Frage. Ein formaler Titel unter Staff kann je nach tatsächlichem Scope unterschiedlich attraktiv sein; weder automatisch verwerfen noch schönrechnen. + +Der stärkste operative Hebel ist wahrscheinlich, vor viel Dokumentarbeit die tatsächliche Passung bei Verantwortung, Vergütung, Arbeitsort und zwingenden Anforderungen zu klären. Das ist eine aus dem Projektverlauf abgeleitete Einschätzung, kein experimentell gemessener Effekt. Die bislang dokumentierten Absagen erlauben insbesondere keine sichere Zuschreibung an Schriftart, ATS oder einzelne fehlende Keywords. + +\section{Umsetzungsreihenfolge und Abnahmekriterien} + +\textbf{Zuerst die Verlässlichkeit reparieren.} Vor einer grossen optischen Überarbeitung sollten die folgenden kleinen, klar abgrenzbaren Änderungen erfolgen: + +\begin{enumerate} +\item \textbf{Regel- und Statusbereinigung:} gemeinsame Skill-Referenzen, aktuelle Session-Ansicht, keine operative Zehn-Bewerbungen-Sperre. Abnahme: beide Agenteneinstiege laden dieselbe Prozessversion; eine elfte Bewerbung lässt sich ohne Quote erfassen. +\item \textbf{Konkrete Evidenzlecks schliessen:} 12+-Jahre-Text entfernen, Capgemini-Ausschluss konsistent machen, PyTorch-Kontext sichern, Zero-Downtime-/Inspektionsvarianten prüfen, BS-6 in Auswahlpfade aufnehmen. Abnahme: jede aktive Variante verweist auf ihren gültigen Claim; historische Abgaben bleiben erhalten. +\item \textbf{Negative Validator-Tests ergänzen:} falsche Identität, erfundene Metrik, Zertifikatskontext, Kommentare, Verneinungen, LaTeX-Auszeichnung und ungültige Datumswerte. Abnahme: eindeutige Fehler werden erkannt, erlaubte Gegenbeispiele nicht blockiert; semantisch offene Punkte erscheinen gesondert. +\item \textbf{Scout-Verluste verhindern:} Requisitions-ID statt Titel, Länderfelder erhalten, Remote-Ungewissheit respektieren, Datumsarten trennen. Abnahme: beide gleichnamigen Teststellen bleiben sichtbar; Baar/CH bleibt zulässig, Bernau/DE wird nicht als Schweiz erkannt. +\item \textbf{Dokumentgewichtung verbessern:} Skills kürzen, Berufserfahrung früher zeigen, Bildungszeilen sauber trennen, Länderprofil wählen. Abnahme: gerenderte Sichtprüfung und Textausleitung; keine neue Behauptung nur zur schöneren Seitengestaltung. +\end{enumerate} + +\textbf{Danach die strukturellen Verbesserungen:} ein kleines Claim-/Dokumentschema, Bewerbung als Zustand plus Ereignisse, reproduzierbare Abgabemanifeste, robuste Scout-Persistenz und gezielte Adaptertests. Ein leichter automatisierter Check sollte mindestens die vorhandenen Tests, Schemas, Referenzintegrität und einen Build der gepflegten professionellen Templates ausführen. Die dafür benötigte Python-/TeX-Umgebung gehört dokumentiert. + +\textbf{Erst danach breiter optimieren:} zusätzliche belegte Auswirkungen und Staff-Verantwortung sammeln, die Länderprofile verfeinern und den Bewerbungstrichter nach Ergebnisarten auswerten. Dabei nicht auf eine neue Quote oder auf immer mehr eigene Scores ausweichen. Eine kleine Anzahl überprüfbarer Regeln ist nützlicher als viele überlappende Dokumente mit Ausnahmen. + +\textbf{Bewusst beibehalten:} Genauigkeit vor Wirkung; keine erfundenen Kennzahlen; keine Skills aus reiner Keyword-Nachfrage; kanonische Eigentumsgrenzen; zwei gut lesbare Seiten als Standard; konditionales Anschreiben mit Länder-/Arbeitgeberkontext; keine automatischen privaten Zusatzangaben; unveränderte Archive bereits versandter Bewerbungen. + +\section{Reproduktionshinweise und Quellen} + +Die Prüfarbeitsdateien liegen unter \file{tmp/project_review/}. \file{probe.py} enthält die isolierten Gegenproben; \file{probe_results.json} deren Resultate. \file{supplement.py} und \file{supplement_results.json} enthalten zusätzliche Adapter- und PDF-Prüfungen. \file{build_review.py}, \file{build_results.json} und \file{copy_comparisons.json} dokumentieren die isolierten Builds und Kopienvergleiche. Diese temporären Dateien gehören nicht zum Bewerbungsoutput und sind kein neuer kanonischer Evidenzbestand. + +Der technische Review belegt die beschriebenen lokalen Gegenbeispiele. Er ersetzt weder einen vollständigen Live-Test aller Jobportale noch eine erneute Quellenprüfung jedes historischen Resume-Satzes. Die Recherche priorisierte Behörden, Arbeitgeber, Berufsverbände, Hochschulen und Originalforschung. Undatierte Ratgeber werden als aktuelle abrufbare Empfehlungen behandelt, nicht als neu publizierte Studien. Nicht zugängliche oder inzwischen leere Stellenseiten wurden nicht als tragende Evidenz verwendet. + +\small +\begin{enumerate}[label=Q\arabic*.,leftmargin=2.7em,itemsep=6pt] +\item SECO / arbeit.swiss. \emph{Bewerbungstipps}. Schweizer Arbeitsmarktverwaltung; Konventionen und Dossieraufbau. \url{https://www.arbeit.swiss/de/stellensuchende/bewerbungstipps} +\item SBB. \emph{Bewerbungstipps}. Direkte Arbeitgebervorgaben, einschliesslich freiwilligem Motivationsschreiben und Upload-Struktur. \url{https://company.sbb.ch/de/jobs-karriere/jobs/bewerbungstipps.html} +\item Universität Zürich, Career Services. \emph{Anschreiben}. Hochschulberatung; Inhalt, Umfang und Nutzen. \url{https://www.careerservices.uzh.ch/de/ratgeber/bewerbung/bewerbungsdossier/Anschreiben.html} +\item ETH Zürich, Welcome Center. \emph{How to apply for a job}. Undatierte, teilweise traditionelle Dossierempfehlungen; im Bericht als widersprechende Konvention eingeordnet. \url{https://ethz.ch/en/the-eth-zurich/working-teaching-and-research/welcome-center/job-search-and-application/how-to-apply-for-a-job.html} +\item Workindenmark. \emph{A Good Personal Profile Counts}. Aktualisiert 25.03.2026. \url{https://www.workindenmark.dk/job-search-in-denmark/your-cv/a-good-personal-profile-counts} +\item Workindenmark. \emph{Work Experience}. Aktualisiert 12.03.2026. \url{https://www.workindenmark.dk/job-search-in-denmark/your-cv/work-experience} +\item Workindenmark. \emph{Your Cover Letter}. Aktualisiert 12.03.2026. \url{https://www.workindenmark.dk/job-search-in-denmark/your-cover-letter} +\item IDA. \emph{How to make a good CV}. Dänischer Ingenieurverband; berufsspezifische Beratung, keine kausale Wirkungsstudie. \url{https://ida.dk/en/career-and-legal-advice/job-search/how-to-make-a-good-cv} +\item Workindenmark. \emph{Personal Details in Your CV}. Aktualisiert 12.03.2026. \url{https://www.workindenmark.dk/job-search-in-denmark/your-cv/personal-details-in-your-cv} +\item NAV / Arbeidsplassen. \emph{Slik skriver du en god CV}. Norwegische Arbeitsmarktverwaltung. \url{https://arbeidsplassen.nav.no/slik-skriver-du-en-god-cv} +\item Tekna. \emph{How to write a good CV}. Norwegischer technisch-wissenschaftlicher Berufsverband. \url{https://www.tekna.no/en/career/cv-and-cover-letter/how-to-write-a-good-cv/} +\item NAV / Arbeidsplassen. \emph{Tips til jobbsøknaden}. Hinweise zu Schreiben und sinnvoller Kontaktaufnahme. \url{https://arbeidsplassen.nav.no/tips-til-jobbsoknaden} +\item Greenhouse Support. \emph{Unsuccessful resume parse}. Aktualisiert 02.03.2026. Technische Herstellerdokumentation; nicht stellvertretend für alle ATS. \url{https://support.greenhouse.io/hc/en-us/articles/200989175-Unsuccessful-resume-parse} +\item Microsoft Careers. \emph{Hiring FAQs}. Direkte Information zur Prüfung und Korrektur extrahierter Bewerberdaten. \url{https://careers.microsoft.com/v2/global/en/hiringfaqs.html} +\item Wiles, Emma; Munyikwa, Zanele; Horton, John J. \emph{Algorithmic Writing Assistance on Jobseekers' Resumes Increases Hires}. NBER Working Paper 30886 (2023, Revision Oktober 2023); veröffentlichte Fassung Management Science 71(12), 2025, S. 10144--10164. \url{https://www.nber.org/papers/w30886} +\item Technische Universität München. \emph{Bayerische Formel}. Zweck, Eingangsparameter und nicht verbindlicher Rechner. \url{https://www.tum.de/studium/bewerbung/infoportal-bewerbung/bayerische-formel} +\item MIT News, Peter Dizikes. \emph{A causal test of the strength of weak ties}. 15.09.2022; institutioneller Bericht zur randomisierten LinkedIn-Studie von Rajkumar et al. in Science, DOI 10.1126/science.abl4476. \url{https://news.mit.edu/2022/weak-ties-linkedin-employment-0915} +\end{enumerate} +\end{document} diff --git a/resume_builder/helpers/application_records.py b/resume_builder/helpers/application_records.py new file mode 100644 index 0000000..9eff6ca --- /dev/null +++ b/resume_builder/helpers/application_records.py @@ -0,0 +1,140 @@ +"""Authoritative application state and generated, read-only projections. + +Run with --check to detect stale views, or --render after editing the registry. +Outcome events describe what happened; current outcome never erases an interview. +""" +from __future__ import annotations + +import argparse +from contextlib import contextmanager +from datetime import date +import json +import math +import os +import tempfile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +REGISTRY = ROOT / "job_scout/state/applications.json" +VIEW = ROOT / "resume_builder/reference/current_applications.md" +COHORT = ROOT / "job_scout/state/application_cohort.json" +OUTCOMES = {"open", "applied", "paused", "not_proceeding", "role_closed", "closed_no_response", "withdrawn_by_candidate", "rejected_no_interview", "rejected_after_interview", "interviewing", "offer", "accepted"} + + +def valid_date(value: str) -> str: + if not isinstance(value, str) or date.fromisoformat(value).isoformat() != value: + raise ValueError("expected ISO date YYYY-MM-DD") + return value + + +def validate(data: dict) -> None: + ids = set() + for record in data["applications"]: + identifier = record.get("id") + if not identifier or identifier in ids: + raise ValueError("application IDs must be nonempty and unique") + ids.add(identifier) + if record.get("outcome") not in OUTCOMES: + raise ValueError(f"{identifier}: invalid outcome") + for key in ("application_date", "rejection_date", "withdrawn"): + if record.get(key): + valid_date(record[key]) + for event in record.get("events", []): + if event.get("date"): + valid_date(event["date"]) + score = record.get("evidence_fit") + if score is not None and (isinstance(score, bool) or not isinstance(score, (float, int)) or not math.isfinite(score) or not 0 <= score <= 100): + raise ValueError(f"{identifier}: evidence_fit must be finite and between 0 and 100") + if record.get("hard_gate") == "fail" and record.get("fit_class") not in {"stretch", "no-go"}: + raise ValueError(f"{identifier}: failed gate requires Stretch or No-go") + if score is not None and record.get("hard_gate") == "pass" and record.get("fit_class") in {"core", "adjacent"}: + expected = "core" if score >= 75 else "adjacent" if score >= 60 else "stretch" + if record["fit_class"] != expected and not record.get("fit_override_reason"): + raise ValueError(f"{identifier}: classification differs from score; record fit_override_reason") + + +@contextmanager +def transaction(): + """Serialize local writers; fail clearly rather than overwrite another update.""" + lock = REGISTRY.with_suffix(".lock") + try: + descriptor = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY) + except FileExistsError as exc: + raise ValueError(f"Application registry is locked: {lock}. Check for an active writer before removing a stale lock.") from exc + try: + with os.fdopen(descriptor, "w") as stream: + stream.write(str(os.getpid())) + data = load() + yield data + validate(data) + atomic_write(REGISTRY, json.dumps(data, indent=2, ensure_ascii=False) + "\n") + render(data) + finally: + lock.unlink(missing_ok=True) + + +def atomic_write(path: Path, text: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + descriptor, temporary = tempfile.mkstemp(dir=path.parent, prefix=path.name + ".", suffix=".tmp") + try: + with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as stream: + stream.write(text) + stream.flush() + os.fsync(stream.fileno()) + os.replace(temporary, path) + finally: + Path(temporary).unlink(missing_ok=True) + + +def load() -> dict: + return json.loads(REGISTRY.read_text(encoding="utf-8")) + + +def interview_reached(record: dict) -> bool: + return bool(record.get("interview")) or any( + event.get("type") in {"interview_invited", "interview_held"} + for event in record.get("events", []) + ) + + +def projections(data: dict) -> dict[Path, str]: + lines = ["# Current applications", "", "Generated from `job_scout/state/applications.json`; do not edit this view.", + "Use current outcome and next action below. Imported legacy notes are historical, not current instructions.", "", + "| Application | Current outcome | Interview reached | Next action |", "|---|---|---|---|"] + for record in data["applications"]: + cells = [record["label"], record["outcome"], "yes" if interview_reached(record) else "not recorded", record["next_action"]] + lines.append("| " + " | ".join(str(cell).replace("|", " / ").replace("\n", " ") for cell in cells) + " |") + lines.extend(["", "## Current targeting notes", "", *["- " + note for note in data.get("targeting_notes", [])], ""]) + cohort = dict(data["cohort_metadata"]) + cohort["generated_from"] = "job_scout/state/applications.json" + cohort["applications"] = [record for record in data["applications"] if record.get("cohort_id") == cohort["cohort_id"]] + return {VIEW: "\n".join(lines), COHORT: json.dumps(cohort, indent=2, ensure_ascii=False) + "\n"} + + +def render(data: dict, check: bool = False) -> list[str]: + validate(data) + stale = [] + for path, expected in projections(data).items(): + if not path.exists() or path.read_text(encoding="utf-8") != expected: + stale.append(str(path.relative_to(ROOT))) + if not check: + atomic_write(path, expected) + return stale + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + mode = parser.add_mutually_exclusive_group(required=True) + mode.add_argument("--check", action="store_true") + mode.add_argument("--render", action="store_true") + args = parser.parse_args() + stale = render(load(), check=args.check) + if args.check and stale: + print("STALE: " + ", ".join(stale)) + return 1 + print("Application views are current.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/resume_builder/helpers/application_tracker.py b/resume_builder/helpers/application_tracker.py new file mode 100644 index 0000000..ec14dcd --- /dev/null +++ b/resume_builder/helpers/application_tracker.py @@ -0,0 +1,117 @@ +"""Record applications without quotas; preserve outcome events and interview history.""" +from __future__ import annotations +import argparse +import hashlib +import application_records as records + +FIT_CLASSES = ("core", "adjacent", "stretch", "no-go") +CHANNELS = ("strong", "moderate", "weak") + + +def summary(data: dict, all_applications: bool = False) -> None: + cohort_id = data["cohort_metadata"]["cohort_id"] + applications = [row for row in data["applications"] if all_applications or row.get("cohort_id") == cohort_id] + print(f"Application record: {'all' if all_applications else cohort_id} ({len(applications)} applications; no quota)") + for field, values in (("fit_class", FIT_CLASSES), ("channel", CHANNELS)): + for value in values: + print(f" {field} {value}: {sum(row.get(field) == value for row in applications)}") + unknown = sum(row.get(field) is None for row in applications) + if unknown: + print(f" {field} unrecorded: {unknown}") + outcomes = sorted({row["outcome"] for row in applications}) + print(" outcomes: " + ", ".join(f"{value}={sum(row['outcome'] == value for row in applications)}" for value in outcomes)) + print(f" interview reached (including later withdrawal/rejection): {sum(records.interview_reached(row) for row in applications)}") + + +def add_application(data: dict, args: argparse.Namespace) -> None: + records.valid_date(args.date) + identity = args.id or "app-" + hashlib.sha256(f"{args.company.casefold()}|{args.job_id or args.role.casefold()}|{args.date}".encode()).hexdigest()[:12] + candidate = { + "id": identity, "company": args.company, "role": args.role, + "label": f"{args.company} — {args.role}", "application_date": args.date, + "fit_class": args.fit_class, "evidence_fit": args.evidence_fit, + "hard_gate": args.hard_gate, "channel": args.channel, "outcome": args.outcome, + "cohort_id": data["cohort_metadata"]["cohort_id"], "job_id": args.job_id, + "url": args.url, "fit_override_reason": args.fit_override_reason, + "rubric_version": args.rubric_version, + "next_action": args.next_action, "current_summary": args.outcome, + "events": [{"type": args.outcome, "date": args.date}], + } + for existing in data["applications"]: + same_job = args.job_id and existing.get("job_id") == args.job_id and existing["company"].casefold() == args.company.casefold() + same_url = args.url and existing.get("url") == args.url + same_application = existing["company"].casefold() == args.company.casefold() and existing["role"].casefold() == args.role.casefold() and existing.get("application_date") == args.date + if existing["id"] == identity or same_job or same_url or same_application: + if all(existing.get(key) == value for key, value in candidate.items() if key not in {"events", "id", "current_summary", "next_action", "outcome"}): + return + raise ValueError(f"Application already exists ({existing['id']}); use update instead") + data["applications"].append(candidate) + records.validate(data) + + +def update_application(data: dict, args: argparse.Namespace) -> None: + records.valid_date(args.date) + matches = [row for row in data["applications"] if row["id"] == args.id] + if not matches: + raise ValueError(f"Unknown application ID: {args.id}") + row = matches[0] + if row.get("application_date") and args.date < row["application_date"]: + raise ValueError("event date precedes application date") + outcome_dates = [event["date"] for event in row.get("events", []) if event.get("outcome") and event.get("date")] + if args.outcome and outcome_dates and args.date < max(outcome_dates): + raise ValueError("older outcome cannot replace current state; record it as a historical note") + event = {"type": args.event or args.outcome or "note", "date": args.date} + if args.note: + event["note"] = args.note + if args.outcome: + event["outcome"] = args.outcome + if event not in row.setdefault("events", []): + row["events"].append(event) + if args.outcome: + row["outcome"] = args.outcome + row["current_summary"] = args.note or args.outcome + row["next_action"] = args.next_action or ("Await response." if args.outcome in {"applied", "interviewing", "open"} else "Review recorded outcome before further action.") + elif args.next_action: + row["next_action"] = args.next_action + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + commands = parser.add_subparsers(dest="command", required=True) + view = commands.add_parser("summary") + view.add_argument("--all", action="store_true") + add = commands.add_parser("add") + for field in ("company", "role", "date"): + add.add_argument("--" + field, required=True) + add.add_argument("--id") + add.add_argument("--job-id") + add.add_argument("--url") + add.add_argument("--fit-class", choices=FIT_CLASSES, required=True) + add.add_argument("--evidence-fit", type=float, required=True) + add.add_argument("--channel", choices=CHANNELS, required=True) + add.add_argument("--hard-gate", choices=("pass", "fail", "unknown"), required=True) + add.add_argument("--outcome", choices=sorted(records.OUTCOMES), default="applied") + add.add_argument("--fit-override-reason") + add.add_argument("--rubric-version", default="critique-framework-2026-09-11") + add.add_argument("--next-action", default="Await response.") + update = commands.add_parser("update") + update.add_argument("--id", required=True) + update.add_argument("--date", required=True, help="Date of this event") + update.add_argument("--outcome", choices=sorted(records.OUTCOMES)) + update.add_argument("--event", choices=("interview_invited", "interview_held", "assessment_passed", "contact_attempted", "contact_conversation", "referral_received", "note")) + update.add_argument("--note") + update.add_argument("--next-action") + args = parser.parse_args() + try: + if args.command == "summary": + summary(records.load(), args.all) + return + with records.transaction() as data: + (add_application if args.command == "add" else update_application)(data, args) + summary(data) + except (ValueError, OSError) as error: + parser.exit(2, f"ERROR: {error}\n") + + +if __name__ == "__main__": + main() diff --git a/resume_builder/helpers/cohort_tracker.py b/resume_builder/helpers/cohort_tracker.py index 39567c6..019a705 100644 --- a/resume_builder/helpers/cohort_tracker.py +++ b/resume_builder/helpers/cohort_tracker.py @@ -1,81 +1,5 @@ -#!/usr/bin/env python3 -"""Record and summarize the controlled ten-application cohort.""" +"""Compatibility entry point; application_tracker owns the uncapped registry workflow.""" +from application_tracker import main -from __future__ import annotations - -import argparse -import json -from pathlib import Path - - -ROOT = Path(__file__).resolve().parents[2] -TRACKER = ROOT / "job_scout" / "state" / "application_cohort.json" -FIT_CLASSES = ("core", "adjacent", "stretch") -CHANNELS = ("strong", "moderate", "weak") - - -def load() -> dict: - return json.loads(TRACKER.read_text(encoding="utf-8")) - - -def save(data: dict) -> None: - TRACKER.write_text(json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") - - -def summary(data: dict) -> None: - applications = data["applications"] - # Slot caps were dropped 2026-09-05 (application_strategy.md). This is a record, not a - # budget: report counts, never counts-against-a-target, so nothing reads as a remaining slot. - print(f"Application record: {data['cohort_id']} ({len(applications)} applications)") - for fit_class in FIT_CLASSES: - count = sum(item["fit_class"] == fit_class for item in applications) - print(f" {fit_class}: {count}") - for channel in CHANNELS: - count = sum(item["channel"] == channel for item in applications) - print(f" channel {channel}: {count}") - outcomes: dict[str, int] = {} - for item in applications: - outcome = item.get("outcome", "open") - outcomes[outcome] = outcomes.get(outcome, 0) + 1 - print(" outcomes: " + ", ".join(f"{key}={value}" for key, value in sorted(outcomes.items()))) - - -def main() -> None: - parser = argparse.ArgumentParser() - subparsers = parser.add_subparsers(dest="command", required=True) - subparsers.add_parser("summary") - add = subparsers.add_parser("add") - add.add_argument("--company", required=True) - add.add_argument("--role", required=True) - add.add_argument("--date", required=True) - add.add_argument("--fit-class", choices=FIT_CLASSES, required=True) - add.add_argument("--evidence-fit", type=float, required=True) - add.add_argument("--channel", choices=CHANNELS, required=True) - add.add_argument("--hard-gate", choices=("pass", "fail"), required=True) - add.add_argument("--outcome", default="open") - - args = parser.parse_args() - data = load() - if args.command == "add": - if len(data["applications"]) >= 10: - raise SystemExit("cohort already contains ten applications") - if args.hard_gate == "fail" and args.fit_class != "stretch": - raise SystemExit("a hard-gate failure must be recorded as stretch") - data["applications"].append( - { - "company": args.company, - "role": args.role, - "application_date": args.date, - "fit_class": args.fit_class, - "evidence_fit": args.evidence_fit, - "hard_gate": args.hard_gate, - "channel": args.channel, - "outcome": args.outcome, - } - ) - save(data) - summary(data) - - -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/resume_builder/helpers/validate_resume_system.py b/resume_builder/helpers/validate_resume_system.py index 62614bb..09b1f72 100644 --- a/resume_builder/helpers/validate_resume_system.py +++ b/resume_builder/helpers/validate_resume_system.py @@ -105,7 +105,7 @@ def workflow_checks() -> list[str]: "<= 3 lines white space", "20-21 variable bullets", ], - ROOT / ".agents" / "skills" / "critique" / "SKILL.md": [ + ROOT / "resume_builder" / "reference" / "workflows" / "critique.md": [ "Publications 10%", "Eight-Dimension Scoring", ], diff --git a/resume_builder/reference/application_strategy.md b/resume_builder/reference/application_strategy.md index ed41b94..ea1e5d1 100644 --- a/resume_builder/reference/application_strategy.md +++ b/resume_builder/reference/application_strategy.md @@ -45,8 +45,8 @@ literals the session had flagged as risky. **What is kept: the recording, not the rationing.** Every application still records fit class, hard gate, channel, date, stage and outcome. That record is what makes patterns visible instead of -re-derived, and it produced every useful finding to date. `application_cohort.json` continues as a -durable log; `cohort_tracker.py summary` remains a read-only view. There is no slot to consume and no +re-derived. `job_scout/state/applications.json` is the durable registry; `application_cohort.json` is +a generated historical-cohort projection. `cohort_tracker.py summary` is a read-only view. There is no slot to consume and no application is blocked for lack of one. **Fit class is now a preparation signal, not a spend limit.** An Evidence Fit of 64 does not mean diff --git a/resume_builder/reference/current_applications.md b/resume_builder/reference/current_applications.md new file mode 100644 index 0000000..99a3059 --- /dev/null +++ b/resume_builder/reference/current_applications.md @@ -0,0 +1,41 @@ +# Current applications + +Generated from `job_scout/state/applications.json`; do not edit this view. +Use current outcome and next action below. Imported legacy notes are historical, not current instructions. + +| Application | Current outcome | Interview reached | Next action | +|---|---|---|---| +| NVIDIA - Senior DevOps Engineer, Zürich (req JR2024256) | applied | not recorded | **Await response. Raise the working model at first contact** - it went in unresolved and a 5-day-onsite seat would be a late no. Note: `claims.json` has no shell-scripting entry, so **Bash is omitted** from Skills although the JD names it and ten years of Debian admin plus Ansible work plainly involve it - same shape as the Ansible/Linux gaps found 2026-08-27. Confirm with the user and record it. Groovy stays out regardless. | +| RUAG C5I - AI Engineer C5I, Thun (application ID 18027) | applied | not recorded | Await response. Optional but still worthwhile: call **Marco Heinzen** about technical scope and level, and **Frank Haugwitz** about compensation and PSP/project eligibility. **NEW CONTACT 2026-09-07 — `Lars Kauz`, Head of ICT Infrastructure C5I, +41 79 293 93 82, lars.kauz@ruag.ch**, published on the System Engineer C5I posting. Channel is the rationed variable since the 09-05 cohort change, so a published, reachable name matters — **but he heads ICT Infrastructure, the function whose req was ruled out for VMware/VDI/Windows content, and that is NOT demonstrably the Kubernetes platform group Senior DevOps C5I sits in.** He is a warm entry into C5I, not a verified channel for rank 1; Haugwitz stays the recorded gate. Do not react to a single outcome with positioning changes (`application_strategy.md` §38/§74). | +| Schweizer Armee - Kommando Cyber (Kdo Cy), DevOps Engineer III (Data Platform), Zimmerwald (Ref JRQ$540-19848) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Citadel Securities — Platform Engineer, Research Platform Engineering, Zürich (also NY/Miami) | applied | not recorded | **Await response** (submitted 2026-08-26). No document work pending. **Two questions travel with this application and must both be asked at first contact, before any interview effort:** (1) **is the application even against the Zürich seat?** — the apply form offered no Zürich, so it may sit against a US req, which is a hard NO on relocation; (2) **hybrid or 5-day onsite?** — unresolved at submission, and 5-day onsite is a likely NO-GO on the Bern constraint. Same shape as the NVIDIA working-model issue: it went out unresolved, so it is now a live screening topic rather than a pre-cleared one. Also unasked: the Zürich compensation figure — the posting's $175–350k is a US/NY pay-transparency disclosure. | +| SBB - Data Engineer (m/w/d), Asset Management Infrastrukturanlagen, Bern (Job ID 103755) | withdrawn_by_candidate | yes | Closed. For future SBB roles assess Niveau L/Kader; do not generalize SBB bands to other employers. | +| AWS (AWS EMEA SARL, Switzerland Branch) — Senior Forward Deployed Engineer, Zürich (job 10504263) | not_proceeding | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Google — Forward Deployed Engineer III, Google Cloud GTM (French, German), Zürich (req 78350205438567110) | not_proceeding | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| SDU Center for Energy Informatics, Odense DK — PhD, Theme 2 Predictive Maintenance & Asset Management of Smart Energy Networks (job 4159) | not_proceeding | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Aker BP ASA — Data Product Architect, AI-ready Data Products (FINN 469067315) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Google - Software Engineer III, Business Home, Zurich (req 93922217108087494) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Microsoft — Principal Forward Deployed Engineer, SWE (German Speaking), Zürich (req 200043897) | applied | not recorded | Done — await response | +| BIS Basel — Senior Data & Analytics Engineer, AI (jr100429, 3-yr term) | applied | not recorded | Prep interview brief when screening lands | +| NATO JWC Stavanger — Staff Officer, AI Engineer (2030 Digitalisation, G15, 3-yr PLN) | applied | not recorded | Await screening; no action until contact | +| Microsoft — Senior SWE, Industry Solutions Engineering (ISE), Zürich (req 200040836) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Kraken (Payward) — SRE, AI Agents (remote, CH-eligible) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Google — Senior Data Engineer (Merchant Data Science), Zürich/MV | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Snowflake — Sr SWE, Enterprise (Observe by Snowflake), Zürich | closed_no_response | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Isovalent (Cisco) Sr Data Engineer, Observability | role_closed | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Google Zürich Sr SWE Infrastructure (Data Pipeline) | not_proceeding | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Kraken AI Infrastructure | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Infineon Doctoral | withdrawn_by_candidate | yes | Closed; no pending application action. Read recorded history before reopening. | +| Infineon AI Engineer | not_proceeding | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Apple Data Engineer (ISE, Zurich) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| Google FDE GenAI (Zurich) | paused | not recorded | Likely abandon | +| Equinor — Manager / AI Architect, Agentic systems (Stavanger/Oslo/Rotvoll/Sandsli; req JR106747) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | +| QuantCo Cloud Engineer (Europe/Zürich) | rejected_no_interview | not recorded | Closed; no pending application action. Read recorded history before reopening. | + +## Current targeting notes + +- SBB Niveau K/L is SBB-specific. The reported approximate CHF 145k K ceiling is not a salary anchor for other employers. +- Assess headroom and total package: working time, holidays, office days, commute and verified benefits. Never decline a Thun-area role on salary band alone. +- Childcare-cost coverage at the Swiss Army/Kdo Cy remains unverified; confirm before pricing it. +- Norway remains open selectively; the Swiss compensation threshold does not automatically apply there. +- An interview does not prove how an ATS or recruiter evaluated the application. No causal inference from a single conversion. diff --git a/resume_builder/reference/shared_ops.md b/resume_builder/reference/shared_ops.md index fe8505b..9014080 100644 --- a/resume_builder/reference/shared_ops.md +++ b/resume_builder/reference/shared_ops.md @@ -5,7 +5,7 @@ Before generation, editing or critique: 1. Read `resume_builder/canonical/claims.json` completely. -2. Read `config.md` and `AGENTS.md` corrections/status. +2. Read `config.md`, `AGENTS.md` corrections and `resume_builder/reference/current_applications.md`. Application status is authoritative in `job_scout/state/applications.json`; the Markdown view and old cohort JSON are generated projections. Historical events are not current instructions. 3. Run `python resume_builder/helpers/validate_resume_system.py`. 4. Treat canonical claims as higher authority than extractions, experience files, bundles, sessions or outputs. 5. Never use a file under `output/` as source content for a new application. Consult `resume_builder/canonical/historical_outputs.json`. @@ -88,4 +88,4 @@ After explicit approval: ## Progress and Recovery -Give short progress updates at major steps. Before ending or clearing context, update session Status and the Active Sessions pointer. If interrupted, add a Resume Point describing completed work and the exact next action. +Give short progress updates at major steps. Before ending or clearing context, update session Status and the application registry through the tracker; regenerate its views. Do not duplicate status in AGENTS or CLAUDE. If interrupted, add a Resume Point describing completed work and the exact next action. diff --git a/resume_builder/reference/workflows/critique.md b/resume_builder/reference/workflows/critique.md new file mode 100644 index 0000000..360d27b --- /dev/null +++ b/resume_builder/reference/workflows/critique.md @@ -0,0 +1,59 @@ + + +# Critique an Application Package + +## Startup + +1. Read `resume_builder/reference/shared_ops.md`. +2. Read `resume_builder/canonical/claims.json` completely. +3. Read `config.md`, the session file, and the real verbatim JD. +4. Stop if the JD is reconstructed, incomplete or inferred. +5. Read `resume_builder/reference/critique_framework.md` and `resume_builder/support/ai_fingerprint_rules.md`. +6. Read the resume and optional cover letter. Never use either as evidence; verify against canonical claims and experience files. + +## Preflight + +Run: + +```powershell +python resume_builder/helpers/validate_resume_system.py --document +``` + +Add `--document ` when a letter exists. Any failure is Tier 1 and prevents approval. + +Compile the documents and inspect the PDFs. Extract resume text with `pdftotext` and confirm that employer, formal title and dates appear in the expected order. + +## Critique + +Follow `critique_framework.md` exactly: + +1. Hard-gate audit. +2. Evidence Fit score, independent of writing. +3. Document Quality score, independent of role fit. +4. Channel Strength rating. +5. Competitive and reader-sequence assessment. +6. Canonical claim audit. +7. Tiered fixes. +8. Cover-letter decision/critique. +9. Mechanical verification. + +Do not issue a single overall score. Do not label a high-quality document submit-ready when Evidence Fit fails a hard gate. + +## Cover-Letter Handling + +If the session records `Cover Letter Decision: NO`, do not penalize absence. If a letter exists, first assess whether it should exist under `cl_reference.md`. + +## Save and Report + +Save `output//critique_.md`. Update the session with: + +- Evidence Fit score and Core/Adjacent/Stretch classification. +- Hard-gate result. +- Document Quality score. +- Channel Strength. +- Tier 1 fixes. +- Critique status and date. + +Present the fit verdict first, then the two scores and actionable fixes. Wait for explicit user approval before finalization or editing. + +When approved, run the finalization checks from `shared_ops.md`. A package with a hard-gate NO-GO may be archived, but must not be described as recommended for submission. diff --git a/resume_builder/reference/workflows/edit-resume.md b/resume_builder/reference/workflows/edit-resume.md new file mode 100644 index 0000000..a0cc673 --- /dev/null +++ b/resume_builder/reference/workflows/edit-resume.md @@ -0,0 +1,48 @@ + + +# Edit a Resume or Cover Letter + +## Startup + +1. Read `shared_ops.md` and `resume_builder/canonical/claims.json` completely. +2. Run the system validator. +3. Read the session, real JD, target `.tex`, critique/feedback, selected profile rules and relevant experience files. +4. Check `historical_outputs.json`. A package marked unsafe must be regenerated from canonical sources rather than lightly polished. +5. If the package was already submitted, preserve it as history. Create a clearly named new version only when the user requests a reusable corrected document. + +## Diagnose + +Classify requested changes as: + +- Truth/provenance correction. +- Fit/framing correction. +- Information hierarchy or readability. +- Content selection. +- Mechanical/LaTeX. +- Blocked because evidence is absent. + +Do not treat whitespace, equal bullet lengths or an exact character target as defects. Do not add filler. A critique cannot authorize an unsupported claim. + +Present a concise edit plan and wait for explicit confirmation unless the user already approved specific edits. + +## Execute + +- Source every work claim from canonical IDs and experience files. +- Keep employer/title/date hierarchy conventional. +- Preserve natural bullet lengths and verified ownership scope. +- Remove unsupported skills rather than replacing them with synonyms. +- Update a cover letter only when its session decision is YES or the user explicitly requests it. +- If edits reveal a hard fit gate, update the Evidence Fit classification; writing improvements cannot remove the gate. + +## Verify + +Run the validator on every edited document, compile, inspect the PDF, and verify extracted text order. Compare before/after for: + +- validator failures; +- page count; +- employer/title/date visibility; +- bullet count and relevance; +- duplicated sections; +- readability at normal size. + +Update the session Edit History, Document Quality, status and next action. Mark the critique stale after material edits. Present results and wait for approval. diff --git a/resume_builder/reference/workflows/make-cl.md b/resume_builder/reference/workflows/make-cl.md new file mode 100644 index 0000000..5a0612a --- /dev/null +++ b/resume_builder/reference/workflows/make-cl.md @@ -0,0 +1,42 @@ + + +# Decide and Generate a Cover Letter + +## Startup + +1. Read `shared_ops.md`, `resume_builder/canonical/claims.json`, `cl_reference.md`, the session, real JD and finished resume. +2. Run the validator on the resume. +3. Confirm that the session has a completed resume and a recorded Cover Letter Decision. + +## Decision Gate + +If the session says NO, explain the recorded reason and stop unless the user explicitly asks to override it. If no decision exists, apply `cl_reference.md` before writing and record YES/NO. + +Do not generate a letter merely because an upload field exists or because older workflow expected a three-page package. + +## Plan + +State in one sentence what the letter adds beyond the resume. Select at most two evidence themes. Verify any named company/product/programme hook from a current primary source and save its URL in the session. + +If the only possible content repeats resume bullets or paraphrases company news, change the decision to NO and explain why. + +## Generate + +- Use the selected audience profile and employer-requested language. +- International Tech/industry: normally 200--300 words in two or three paragraphs. +- Swiss/DACH: one A4 page, appropriate formality. +- Employer-specific: follow the requested format or portal limit. +- Lead with candidate-role connection. +- Add motivation, decision context or a genuine connection. +- Use only claims already present in both the canonical registry and resume. +- Never introduce a new tool, metric, customer, ownership claim or result. + +## Verify + +Run: + +```powershell +python resume_builder/helpers/validate_resume_system.py --document --document +``` + +Compile and inspect the letter. Confirm it is readable, within the employer's limit, and materially different from the resume. Update the session and present the letter for approval. Point to `/critique` next. diff --git a/resume_builder/reference/workflows/make-resume.md b/resume_builder/reference/workflows/make-resume.md new file mode 100644 index 0000000..22dc855 --- /dev/null +++ b/resume_builder/reference/workflows/make-resume.md @@ -0,0 +1,86 @@ + + +# Generate a Resume + +## Input + +Accept a JD file, pasted JD, or URL. Obtain the real posting text before analysis. If it cannot be retrieved, ask the user to paste it; never reconstruct it. + +## Phase 0 — Canonical and JD Preflight + +1. Read `resume_builder/reference/shared_ops.md`. +2. Read `resume_builder/canonical/claims.json` completely. +3. Read `config.md`, `AGENTS.md`, `application_strategy.md`, `resume_reference.md`, and `critique_framework.md`. +4. Run `python resume_builder/helpers/validate_resume_system.py`. +5. Verify and save the verbatim JD; record source/date/status. +6. Create the output folder and session from `session_file_template.md`. + +## Phase 1 — Fit Gate Before Writing + +Build a requirement table with Required/Preferred and Direct/Adjacent/Gap/Constraint classifications. Cite canonical claim IDs for every Direct or Adjacent match. + +Compute Evidence Fit and identify hard gates using `critique_framework.md`. Then assign: + +- Core. +- Adjacent. +- Stretch. +- No-go. + +Record Channel Strength and a warm-channel action for Core roles. + +Stop and present the fit decision before resume planning when: + +- a hard gate fails; +- the role is Stretch; +- a practical constraint is unresolved. + +Proceed on a no-go only after the user explicitly overrides the stated reason. An override does not change the fit classification. + +## Phase 2 — Audience and Content Plan + +Select International Tech by default. Use Swiss/DACH or Employer-specific format only when the employer/context supports it. + +Read the matching role bundle, `skills_taxonomy.md`, `achievement_reframing_guide.md`, and only the experience files needed for selected claims. + +Plan: + +- Optional 2--3 line summary. +- Four to six evidence-backed skills lines. +- Swisscom 4--5 bullets. +- Bosch 3--4 bullets. +- Zero or one bullet for each older role. +- Normally 11--14 bullets total, with no page-fill quota. + +For each proposed bullet show canonical ID, evidence type, scope, and why it belongs. Identify any impact metric that still needs user confirmation. + +Apply `cl_reference.md` and propose `Cover Letter Decision: YES/NO` with one reason. + +Present the plan and wait for explicit user confirmation before generation. + +## Phase 3 — Generate + +Copy `resume.cls` and `resume_template.tex` into the output folder. For a Swiss/DACH audience, also read `resume_template_dach.tex` as an audience-policy overlay; it is not a standalone document. Write fresh content from canonical claims and experience records; never copy a historical output. + +Rules: + +- Employer, formal/transparent title, dates and location come first. +- Do not use tailored themes as job titles. +- Mix natural bullet lengths. +- Use only verified metrics and outcomes. +- Preserve allowed verbs and ownership scope. +- Never add a skill to mirror the JD. +- Certifications appear once. + +## Phase 4 — Validate and Inspect + +Run: + +```powershell +python resume_builder/helpers/validate_resume_system.py --document +``` + +Fix every failure. Compile the LaTeX, inspect both pages, and run `pdftotext` to verify parsing and information order. Do not add filler to reduce whitespace. + +Update the session with the as-built content, validation results, page count, cover-letter decision, and next action. + +Present the finished resume and the original Evidence Fit classification. Wait for approval. If the cover-letter decision is YES, point to `/make-cl`; otherwise point directly to `/critique`. diff --git a/resume_builder/reference/workflows/setup-build-kb.md b/resume_builder/reference/workflows/setup-build-kb.md new file mode 100644 index 0000000..33eb613 --- /dev/null +++ b/resume_builder/reference/workflows/setup-build-kb.md @@ -0,0 +1,79 @@ + + +# Build or Update the Knowledge Base + +## Authority Model + +The pipeline is: + +`source documents -> extractions -> canonical claims -> experience records/bundles -> generated applications` + +Generated applications never flow backward into the KB. + +Read `resume_builder/canonical/claims.json`, `config.md`, and all relevant extractions before changing normalized sources. Preserve raw source documents. A verified correction must update canonical claims first and then every dependent normalized file. + +## Canonical Claim Record + +For each employment item or achievement, record: + +- stable ID; +- employer, title, dates and location; +- factual scope; +- individual vs shared ownership; +- allowed verbs; +- forbidden overstatements; +- metric/result and verification status; +- evidence source; +- last verified date. + +When ownership is ambiguous, use contributing/shared scope and ask the user. Do not default to full ownership. + +## Experience Files + +Experience files explain facts and context. They may contain example phrasings, but those examples cannot override canonical scope and should not be optimized to fixed line counts. + +Every achievement section must include canonical ID, source, ownership scope, status, verified result/metric state, relevant skills and role relevance. + +## Skills Taxonomy + +Use only these evidence levels: + +- Production — current. +- Production — historical. +- Hands-on — current. +- Project / proof of concept. +- Certification / coursework. +- Unverified / never used. + +Do not assign Expert/Proficient/Familiar labels. Add a skill to canonical claims before it can appear in a generated document. + +## Role Bundles + +Each bundle must contain: + +1. Defensible positioning. +2. Good-fit roles and hard-gate cautions. +3. Achievement priorities. +4. Safe reframing rules. +5. Evidence-backed skills guidance. +6. Conditional cover-letter guidance. + +Bundles may rank evidence; they may not broaden it. Title-defining gaps must be stated explicitly. + +## Significance Context + +Keep company/industry context separate from candidate impact. Mark external claims for re-verification at application time. Never attach generic scale, economics, customers or market trends to the candidate. + +## Correction Propagation + +After any correction, search canonical claims, config, extractions, experience files, bundles, support files, templates and active drafts. Historical submitted outputs remain unchanged but update `historical_outputs.json` when they become unsafe for reuse. + +## Validation + +Run: + +```powershell +python resume_builder/helpers/validate_resume_system.py +``` + +Then review the diff for dates, titles, ownership, metrics and skill evidence. Present the changed claim records and affected downstream files to the user for confirmation. diff --git a/resume_builder/reference/workflows/setup-extract.md b/resume_builder/reference/workflows/setup-extract.md new file mode 100644 index 0000000..7d9c418 --- /dev/null +++ b/resume_builder/reference/workflows/setup-extract.md @@ -0,0 +1,98 @@ + + +# Source Evidence Extraction + +Use this skill when adding new evidence to `knowledge_base/extractions/`. Extraction preserves what a source supports; it does not tailor claims to a vacancy. + +## Required inputs + +1. Read `config.md` and the anti-fabrication rules in `AGENTS.md`. +2. Read the complete source material. +3. Check `resume_builder/canonical/claims.json` for existing facts and identifiers. +4. Inspect related extraction files so new evidence does not create a duplicate or contradiction. + +Generated resumes, cover letters, critiques, and session files under `output/` are never evidence sources. + +## Extraction procedure + +### 1. Identify the source + +Record: + +- source filename and type; +- source date, if known; +- whether it is primary evidence, user-authored notes, or a secondary description; +- the person, employer, project, or publication it concerns. + +### 2. Separate evidence from inference + +For every potentially reusable fact, record: + +- the source-supported statement; +- the exact supporting passage or precise location; +- any interpretation separately as `Inference`; +- confidence: `verified`, `user-confirmed`, `needs verification`, or `do not use`. + +Never silently convert an inference into a verified claim. + +### 3. Capture career facts precisely + +When applicable, extract: + +- exact employer, formal title, location, and start/end dates; +- ownership scope and collaborators; +- work performed, method, technology, and domain; +- outcome and beneficiary; +- metrics with units, baseline, period, and attribution; +- publication status, authorship position, and venue; +- current versus historical skill use; +- certification versus practical experience. + +For large-company work, describe only the user's component, domain, pipeline, service, or contribution. Do not infer ownership of organization-wide platforms or transformations. + +### 4. Record claim controls + +For each major claim, include where useful: + +- allowed verbs; +- verbs or framings that would overstate ownership; +- safe resume wording; +- known contradictions requiring resolution; +- whether a metric is verified, qualitative only, or still missing. + +### 5. Write the extraction + +Create or update a focused Markdown file in `knowledge_base/extractions/`. Use a clear structure such as: + +```markdown +# Subject + +## Source +## Verified facts +## Ownership and attribution +## Outcomes and metrics +## Skills and recency +## Safe claim seeds +## Inferences / open questions +## Conflicts with existing KB +``` + +Claim seeds are factual building blocks, not polished resume bullets. Do not add unsupported impact language for relevance. + +## Publication rules + +- Never infer `published`, `accepted`, or `under review` from a manuscript file. +- Preserve the user's author position. +- Institutional funding is not a personal award. +- Follow `config.md` provenance flags. + +## Completion + +Report: + +- files created or updated; +- newly verified facts; +- unresolved contradictions or missing metrics; +- suggested next command: use `setup-build-kb` to promote reviewed evidence into the canonical registry, experience files, bundles, and support files. + +Do not update canonical claims from an unreviewed extraction. diff --git a/tests/test_application_records.py b/tests/test_application_records.py new file mode 100644 index 0000000..e475f1a --- /dev/null +++ b/tests/test_application_records.py @@ -0,0 +1,96 @@ +from __future__ import annotations + +import contextlib +import io +import json +import sys +import tempfile +import unittest +from pathlib import Path +from unittest.mock import patch + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "resume_builder/helpers")) +import application_records as records +import application_tracker as tracker +from argparse import Namespace +import copy + + +class ApplicationRecordTests(unittest.TestCase): + def test_generated_views_are_current(self): + self.assertEqual([], records.render(records.load(), check=True)) + + def test_imported_outcomes_preserve_interview_and_closed_state(self): + applications = records.load()["applications"] + self.assertEqual(len(applications), len({row["id"] for row in applications})) + sbb = next(row for row in applications if row["company"] == "SBB") + self.assertEqual("withdrawn_by_candidate", sbb["outcome"]) + self.assertTrue(records.interview_reached(sbb)) + for token in ("200040836", "JR106747", "JRQ$540-19848"): + row = next(row for row in applications if token in row["label"]) + self.assertEqual("rejected_no_interview", row["outcome"]) + + def test_both_agents_link_to_one_view(self): + for name in ("AGENTS.md", "CLAUDE.md"): + text = (ROOT / name).read_text(encoding="utf-8") + section = text.split("## Active Sessions", 1)[1].split("## KB Corrections", 1)[0] + self.assertIn("resume_builder/reference/current_applications.md", section) + self.assertNotIn("| Session |", section) + + def test_atomic_write_leaves_no_shared_temp_file(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "state.json" + records.atomic_write(path, '{"value": 1}\n') + self.assertEqual({"value": 1}, json.loads(path.read_text())) + self.assertEqual([path], list(Path(directory).iterdir())) + + def args(self, **overrides): + fields = dict(id=None, company="Example", role="Engineer", date="2026-09-11", job_id="req-1", url=None, + fit_class="core", evidence_fit=79, hard_gate="pass", channel="weak", outcome="applied", + fit_override_reason=None, rubric_version="test", next_action="Await response.") + fields.update(overrides) + return Namespace(**fields) + + def test_more_than_ten_applications_and_idempotent_retry(self): + data = records.load() + for index in range(11): + tracker.add_application(data, self.args(job_id=f"new-{index}", role=f"Engineer {index}")) + count = len(data["applications"]) + tracker.add_application(data, self.args(job_id="new-10", role="Engineer 10")) + self.assertEqual(count, len(data["applications"])) + + def test_bad_dates_scores_and_unexplained_classification_are_rejected(self): + for fields in ({"date":"2026-99-11"}, {"evidence_fit":101}, {"evidence_fit":float("nan")}, + {"fit_class":"adjacent"}, {"hard_gate":"fail"}): + with self.subTest(fields=fields), self.assertRaises(ValueError): + tracker.add_application(records.load(), self.args(**fields)) + + def test_outcome_update_preserves_interview_event_and_is_idempotent(self): + data = records.load() + tracker.add_application(data, self.args()) + row = data["applications"][-1] + invited = Namespace(id=row["id"], date="2026-09-12", event="interview_invited", outcome=None, note=None, next_action=None) + tracker.update_application(data, invited) + withdrawn = Namespace(id=row["id"], date="2026-09-13", event=None, outcome="withdrawn_by_candidate", note="Decision", next_action=None) + tracker.update_application(data, withdrawn) + tracker.update_application(data, withdrawn) + self.assertEqual(3, len(row["events"])) + self.assertTrue(records.interview_reached(row)) + self.assertEqual("withdrawn_by_candidate", row["outcome"]) + tracker.add_application(data, self.args()) + self.assertEqual("withdrawn_by_candidate", row["outcome"]) + + def test_transaction_failure_does_not_write_registry(self): + with tempfile.TemporaryDirectory() as directory: + path = Path(directory) / "applications.json" + initial = json.dumps(records.load()) + path.write_text(initial, encoding="utf-8") + with patch.object(records, "REGISTRY", path): + with self.assertRaises(ValueError), records.transaction() as data: + data["applications"][0]["outcome"] = "made-up" + self.assertEqual(initial, path.read_text(encoding="utf-8")) + self.assertFalse(path.with_suffix(".lock").exists()) + path.with_suffix(".lock").write_text("another writer") + with self.assertRaises(ValueError), records.transaction(): + pass diff --git a/tests/test_resume_system.py b/tests/test_resume_system.py index dbe709e..7586f27 100644 --- a/tests/test_resume_system.py +++ b/tests/test_resume_system.py @@ -66,6 +66,20 @@ class CanonicalSystemTests(unittest.TestCase): class WorkflowPolicyTests(unittest.TestCase): + def test_agent_entrypoints_resolve_the_same_shared_workflow(self) -> None: + for entry in sorted((ROOT / ".agents/skills").glob("*/SKILL.md")): + other = ROOT / ".claude/skills" / entry.parent.name / "SKILL.md" + bodies = [] + for path in (entry, other): + body = re.sub(r"\A---\n.*?\n---\n", "", path.read_text(encoding="utf-8"), count=1, flags=re.S) + bodies.append(body) + links = re.findall(r"\]\(([^)]+\.md)\)", body) + self.assertEqual(1, len(links), path) + workflow = (path.parent / links[0]).resolve() + self.assertEqual(ROOT / "resume_builder/reference/workflows" / (entry.parent.name + ".md"), workflow) + self.assertIn("Shared workflow version:", workflow.read_text(encoding="utf-8")) + self.assertEqual(*bodies) + def test_skills_use_minimal_valid_frontmatter(self) -> None: for skill_file in sorted((ROOT / ".agents/skills").glob("*/SKILL.md")): text = skill_file.read_text(encoding="utf-8")