From 31d94e7c1f5f2dda5083c71097a73c0cf50370c6 Mon Sep 17 00:00:00 2001 From: Deibyg <40776476+deiybg@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:10:47 +0200 Subject: [PATCH] docs: archive multi-agent-pipeline and onboarding-fixes specs Both specs were implemented and shipped (v1.2.0 and v1.4.0 respectively per CHANGELOG.md) but never committed. Marks all 10 tasks in multi-agent-pipeline/tasks.md as done to match the already-completed OC-REPORT.md in onboarding-fixes. --- specs/multi-agent-pipeline/contracts/gaps.md | 188 ++++++++++++++++++ .../contracts/review-blind.md | 143 +++++++++++++ specs/multi-agent-pipeline/data-model.md | 67 +++++++ specs/multi-agent-pipeline/plan.md | 93 +++++++++ specs/multi-agent-pipeline/spec.md | 123 ++++++++++++ specs/multi-agent-pipeline/tasks.md | 84 ++++++++ specs/onboarding-fixes/OC-REPORT.md | 33 +++ specs/onboarding-fixes/plan.md | 66 ++++++ specs/onboarding-fixes/spec.md | 92 +++++++++ specs/onboarding-fixes/tasks.md | 48 +++++ 10 files changed, 937 insertions(+) create mode 100644 specs/multi-agent-pipeline/contracts/gaps.md create mode 100644 specs/multi-agent-pipeline/contracts/review-blind.md create mode 100644 specs/multi-agent-pipeline/data-model.md create mode 100644 specs/multi-agent-pipeline/plan.md create mode 100644 specs/multi-agent-pipeline/spec.md create mode 100644 specs/multi-agent-pipeline/tasks.md create mode 100644 specs/onboarding-fixes/OC-REPORT.md create mode 100644 specs/onboarding-fixes/plan.md create mode 100644 specs/onboarding-fixes/spec.md create mode 100644 specs/onboarding-fixes/tasks.md diff --git a/specs/multi-agent-pipeline/contracts/gaps.md b/specs/multi-agent-pipeline/contracts/gaps.md new file mode 100644 index 0000000..2acc82b --- /dev/null +++ b/specs/multi-agent-pipeline/contracts/gaps.md @@ -0,0 +1,188 @@ +# API Contract: gaps + +## applyr gaps save '' + +### Description +Save learning gaps for a job offer to the learning_gaps table. + +### Request + +**Positional Arguments:** + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| offer_id | integer | yes | ID of the offer | +| gaps_json | string | yes | JSON array of gap objects | + +**JSON Schema:** + +```json +{ + "gaps": [ + { + "topic": "tech_stack", + "gap_detail": "Missing LangChain and RAG experience", + "severity": "high", + "suggested_action": "Build a RAG project with LangChain" + } + ] +} +``` + +| Field | Type | Required | Default | Values | +|-------|------|----------|---------|--------| +| topic | string | yes | — | tech_stack, projects, experience, education, english, cultural_fit | +| gap_detail | string | yes | — | Free text description | +| severity | string | no | "medium" | low, medium, high | +| suggested_action | string | no | null | Free text recommendation | + +### Response + +**Success (stdout):** + +``` +Saved 3 gaps for offer #42 (American Language Academy) +``` + +**Success (--json):** + +```json +{ + "offer_id": 42, + "gaps_saved": 3 +} +``` + +**Error Codes:** + +| Exit Code | Code | When | +|-----------|------|------| +| 1 | not_found | offer_id does not exist | +| 1 | missing_field | gaps array is empty or missing | +| 1 | invalid_value | topic or severity not in valid set | + +--- + +## applyr gaps list [--topic ] [--severity ] + +### Description +List learning gaps with optional filters. + +### Request + +**Flags:** + +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| --topic | string | no | all | Filter by topic | +| --severity | string | no | all | Filter by severity | +| --json | flag | no | false | Output as JSON | + +### Response + +**Success (stdout):** + +``` +Learning Gaps (5 total) + + # Offer Topic Severity Gap Detail + 1 American Language Academy tech_stack high Missing LangChain + 2 American Language Academy english medium B1 level, needs B2 + 3 Google - Backend tech_stack high No GCP experience + 4 Google - Backend experience medium Junior level expected + 5 Startup XYZ projects low No open source contributions +``` + +**Success (--json):** + +```json +{ + "total": 5, + "gaps": [ + { + "id": 1, + "offer_id": 42, + "offer_title": "Programador Junior E-Learning", + "company": "American Language Academy", + "topic": "tech_stack", + "gap_detail": "Missing LangChain", + "severity": "high", + "suggested_action": "Build a RAG project", + "created_at": "2026-08-09" + } + ] +} +``` + +**Empty result:** + +``` +No learning gaps found. +``` + +--- + +## applyr gaps stats + +### Description +Show summary statistics of learning gaps. + +### Request + +**Flags:** + +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| --json | flag | no | false | Output as JSON | + +### Response + +**Success (stdout):** + +``` +Learning Gaps Summary + + Total gaps: 12 + + By Topic: + tech_stack 5 ██████████████ + english 3 █████████ + experience 2 ██████ + projects 1 ███ + cultural_fit 1 ███ + + By Severity: + high 6 ██████████████████ + medium 4 ████████████ + low 2 ██████ + + Top Gaps (by frequency): + 1. Missing LangChain/RAG experience (3 offers) + 2. English B1 needs B2 (3 offers) + 3. No cloud platform experience (2 offers) +``` + +**Success (--json):** + +```json +{ + "total": 12, + "by_topic": { + "tech_stack": 5, + "english": 3, + "experience": 2, + "projects": 1, + "cultural_fit": 1 + }, + "by_severity": { + "high": 6, + "medium": 4, + "low": 2 + }, + "top_gaps": [ + {"detail": "Missing LangChain/RAG experience", "count": 3}, + {"detail": "English B1 needs B2", "count": 3}, + {"detail": "No cloud platform experience", "count": 2} + ] +} +``` diff --git a/specs/multi-agent-pipeline/contracts/review-blind.md b/specs/multi-agent-pipeline/contracts/review-blind.md new file mode 100644 index 0000000..cee2007 --- /dev/null +++ b/specs/multi-agent-pipeline/contracts/review-blind.md @@ -0,0 +1,143 @@ +# API Contract: review-blind + +## applyr cv review-blind + +### Description +Independently evaluate cv-master.md against a job offer without referencing the Matcher's compatibility score. + +### Authentication +None (local CLI). + +### Request + +**Positional Arguments:** + +| Name | Type | Required | Description | +|------|------|----------|-------------| +| offer_id | integer | yes | ID of the offer to evaluate | + +**Flags:** + +| Name | Type | Required | Default | Description | +|------|------|----------|---------|-------------| +| --json | flag | no | false | Output as JSON | + +### Response + +**Success (stdout):** + +``` +ATS Score: 74/100 + +Verdict: CLOSE MATCH + +Strengths: + ✓ Strong Python and FastAPI experience + ✓ Relevant project portfolio + +Weaknesses: + ✗ No LangChain or RAG experience + ✗ English level not demonstrated + +Recommendations: + 1. Add a LangChain project to your portfolio + 2. Include English certification in CV + 3. Highlight API design experience more prominently + +Conditional Advice: + Consider applying if you: + - Highlight your Python API experience in the first paragraph + - Message the recruiter about your passion for AI tooling +``` + +**Success (--json):** + +```json +{ + "offer_id": 42, + "ats_score": 74, + "verdict": "CLOSE_MATCH", + "strengths": [ + "Strong Python and FastAPI experience", + "Relevant project portfolio" + ], + "weaknesses": [ + "No LangChain or RAG experience", + "English level not demonstrated" + ], + "recommendations": [ + "Add a LangChain project to your portfolio", + "Include English certification in CV", + "Highlight API design experience more prominently" + ], + "conditional_advice": { + "apply_with_conditions": true, + "conditions": [ + "Highlight your Python API experience in the first paragraph", + "Message the recruiter about your passion for AI tooling" + ] + } +} +``` + +**When verdict is NO_MATCH, conditional_advice is null:** + +```json +{ + "offer_id": 42, + "ats_score": 35, + "verdict": "NO_MATCH", + "strengths": ["Basic Python knowledge"], + "weaknesses": [ + "Missing required React experience", + "No cloud infrastructure skills", + "English level below requirement" + ], + "recommendations": [ + "Build 2-3 React projects before applying", + "Study AWS or GCP fundamentals", + "Obtain B2+ English certification" + ], + "conditional_advice": null +} +``` + +**When verdict is STRONG_MATCH, conditional_advice is null:** + +```json +{ + "offer_id": 42, + "ats_score": 85, + "verdict": "STRONG_MATCH", + "strengths": ["Expert in all required technologies"], + "weaknesses": [], + "recommendations": ["Consider adding metrics to project descriptions"], + "conditional_advice": null +} +``` + +**Error Codes:** + +| Exit Code | Code | When | +|-----------|------|------| +| 1 | not_found | offer_id does not exist in DB | +| 1 | cv_master_missing | cv-master.md does not exist or is still template | +| 1 | db_error | Database cannot be opened | + +### Verdict Logic + +| Score Range | Verdict | conditional_advice | +|-------------|---------|-------------------| +| >= 80 | STRONG_MATCH | null | +| 60–79 | CLOSE_MATCH | {apply_with_conditions: true, conditions: [...]} | +| < 60 | NO_MATCH | null | + +### AC Coverage + +- AC-1: Blind Recruiter command — reads cv-master.md, evaluates independently +- AC-2: Verdict logic — classifies based on thresholds from config +- AC-3: Conditional advice — included only for CLOSE_MATCH +- AC-7: Reads cv-master.md directly — not the generated CV +- AC-8: Blind — does not load compatibility_pct from DB +- AC-E1: Missing offer → exit 1, code "not_found" +- AC-E2: Missing cv-master.md → exit 1, code "cv_master_missing" diff --git a/specs/multi-agent-pipeline/data-model.md b/specs/multi-agent-pipeline/data-model.md new file mode 100644 index 0000000..43fc46f --- /dev/null +++ b/specs/multi-agent-pipeline/data-model.md @@ -0,0 +1,67 @@ +# Data Model: Multi-Agent Pipeline + +## Spec Reference +Implements: `specs/multi-agent-pipeline/spec.md` + +## Entities + +### learning_gaps (NEW) + +| Field | Type | Constraints | Description | +|-------|------|-------------|-------------| +| id | INTEGER | PK, AUTOINCREMENT | Primary key | +| offer_id | INTEGER | FK offers(id) ON DELETE CASCADE | Associated offer | +| topic | TEXT | NOT NULL | Gap topic (tech_stack, projects, experience, education, english, cultural_fit) | +| gap_detail | TEXT | NOT NULL | Specific gap description | +| severity | TEXT | DEFAULT 'medium' | low / medium / high | +| suggested_action | TEXT | nullable | What to do about it | +| created_at | TEXT | DEFAULT CURRENT_TIMESTAMP | When the gap was recorded | + +### offers (EXISTING — no schema changes) + +No new columns needed. The `learning_gaps` table references `offers.id` via foreign key. + +### offer_topics (EXISTING — no schema changes) + +No changes needed. + +## Relationships + +- `learning_gaps` belongs to `offers` (via `learning_gaps.offer_id`) +- `offers` has many `learning_gaps` (via cascade delete) +- `offers` has many `offer_topics` (existing, unchanged) + +## Indexes + +| Table | Columns | Type | Rationale | +|-------|---------|------|-----------| +| learning_gaps | offer_id | btree | Lookup gaps by offer | +| learning_gaps | topic | btree | Filter gaps by topic | +| learning_gaps | severity | btree | Filter gaps by severity | + +## Constraints + +- `CHECK (severity IN ('low', 'medium', 'high'))` on learning_gaps.severity +- `CHECK (topic IN ('tech_stack', 'projects', 'experience', 'education', 'english', 'cultural_fit'))` on learning_gaps.topic + +## Migrations + +### Migration v4 → v5: Add learning_gaps table + +```sql +CREATE TABLE IF NOT EXISTS learning_gaps ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + offer_id INTEGER REFERENCES offers(id) ON DELETE CASCADE, + topic TEXT NOT NULL, + gap_detail TEXT NOT NULL, + severity TEXT DEFAULT 'medium', + suggested_action TEXT, + created_at TEXT DEFAULT CURRENT_TIMESTAMP +); + +CREATE INDEX IF NOT EXISTS idx_learning_gaps_offer_id ON learning_gaps(offer_id); +CREATE INDEX IF NOT EXISTS idx_learning_gaps_topic ON learning_gaps(topic); +CREATE INDEX IF NOT EXISTS idx_learning_gaps_severity ON learning_gaps(severity); +``` + +- **Rollback:** DROP TABLE IF EXISTS learning_gaps; diff --git a/specs/multi-agent-pipeline/plan.md b/specs/multi-agent-pipeline/plan.md new file mode 100644 index 0000000..fe74a30 --- /dev/null +++ b/specs/multi-agent-pipeline/plan.md @@ -0,0 +1,93 @@ +# Technical Plan: Multi-Agent Pipeline + +## Spec Reference +Implements: `specs/multi-agent-pipeline/spec.md` + +## Architecture Overview + +The pipeline adds two new capabilities to applyr: +1. A `review-blind` command that independently evaluates cv-master.md against a job offer + without referencing the Matcher's compatibility score. +2. A `learning_gaps` table and CRUD commands to persist skill gaps for future consultation. + +The agent calling applyr orchestrates the flow: Matcher → Recruiter → apply recommendations. +Applyr remains a CLI storage layer — no LLM calls, no internet, no orchestration logic. + +## Component Breakdown + +### Blind Recruiter (`review-blind`) +- **Responsibility:** Read cv-master.md fresh, evaluate against offer, return ATS assessment +- **Location:** `applyr/cv.py` (new function `review_blind()`) + `applyr/commands/workflow.py` (routing) +- **Accepts:** offer_id (int) +- **Returns:** JSON with ats_score, verdict, strengths, weaknesses, recommendations, conditional_advice +- **AC Coverage:** AC-1, AC-2, AC-3, AC-7, AC-8 + +### Gap Management (`gaps save/list/stats`) +- **Responsibility:** Persist and query learning gaps across offers +- **Location:** `applyr/commands/analytics.py` (new functions) + `applyr/db.py` (new table) +- **Accepts:** offer_id, gaps JSON (save); filters (list); none (stats) +- **Returns:** Confirmation (save); table/JSON (list); summary (stats) +- **AC Coverage:** AC-4, AC-5, AC-6 + +### Schema Migration v5 +- **Responsibility:** Add learning_gaps table to existing databases +- **Location:** `applyr/db.py` (MIGRATIONS dict, SCHEMA_SQL, SCHEMA_VERSION) +- **Accepts:** N/A (automatic on first run) +- **Returns:** N/A +- **AC Coverage:** AC-4 + +### AGENT_INSTRUCTIONS.md Update +- **Responsibility:** Document the two-agent workflow for external agents +- **Location:** `applyr/templates/AGENT_INSTRUCTIONS.md` +- **Accepts:** N/A (template) +- **Returns:** N/A +- **AC Coverage:** AC-W1 + +## Technology Choices + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Blind review location | cv.py (new function) | Review logic already lives here | +| Gap storage | New table (not extending offer_topics) | Cleaner separation, avoids NULL columns | +| Verdict thresholds | Read from config (existing thresholds) | User-configurable, consistent with Matcher | +| JSON output | Same pattern as other commands | Agent-consumable, backward compatible | + +## Integration Points + +- `applyr add` → saves offer with compatibility_pct (existing, unchanged) +- `applyr cv review-blind` → reads offer from DB, reads cv-master.md, returns assessment +- `applyr gaps save` → writes to learning_gaps table +- `applyr gaps list/stats` → reads from learning_gaps + offers +- `applyr cv generate` → unchanged, called after Recruiter recommendations applied + +## AC Coverage Map + +| AC | Component(s) | Contract(s) | +|----|-------------|-------------| +| AC-1 | Blind Recruiter | contracts/review-blind.md | +| AC-2 | Blind Recruiter | contracts/review-blind.md | +| AC-3 | Blind Recruiter | contracts/review-blind.md | +| AC-4 | Gap Management | contracts/gaps.md | +| AC-5 | Gap Management | contracts/gaps.md | +| AC-6 | Gap Management | contracts/gaps.md | +| AC-7 | Blind Recruiter | contracts/review-blind.md | +| AC-8 | Blind Recruiter | contracts/review-blind.md | +| AC-E1 | Blind Recruiter | contracts/review-blind.md | +| AC-E2 | Blind Recruiter | contracts/review-blind.md | +| AC-E3 | Gap Management | contracts/gaps.md | +| AC-W1 | AGENT_INSTRUCTIONS | N/A (template update) | + +## Risks + +| Risk | Likelihood | Impact | Mitigation | +|------|-----------|--------|-----------| +| Recruiter accidentally reads Matcher's score | Low | High | Code review: review_blind() must not query compatibility_pct | +| Migration v5 fails on existing DB | Low | High | Test with DB at each previous version | +| Agent doesn't follow two-agent sequence | Medium | Medium | AGENT_INSTRUCTIONS.md documents exact flow | +| Gap table grows unbounded | Low | Low | CASCADE on offer delete; no auto-cleanup needed | + +## Out of Scope (Technical) +- No new dependencies (stdlib only) +- No network calls +- No changes to existing scoring formula +- No changes to cv generate or cv review (existing commands) diff --git a/specs/multi-agent-pipeline/spec.md b/specs/multi-agent-pipeline/spec.md new file mode 100644 index 0000000..7cd16a0 --- /dev/null +++ b/specs/multi-agent-pipeline/spec.md @@ -0,0 +1,123 @@ +# Multi-Agent Pipeline: Blind Recruiter + Gap Tracking + +Status: Draft +Version: 1.0 +Last updated: 2026-08-09 + +## Overview +Evolve applyr from a single-agent workflow to a two-agent blind pipeline where +a Matcher evaluates compatibility and an independent Recruiter analyzes the CV +without knowing the Matcher's score. Gaps are persisted for future consultation. + +## User Stories + +### Primary +As a job seeker, I want a Recruiter agent to independently evaluate my CV against +a job offer so that I get an unbiased ATS assessment before applying. + +### Secondary +As a job seeker, I want my skill gaps saved automatically so that I can track +what to improve across multiple job applications. + +## Boundaries + +**Always do:** +- Recruiter reads cv-master.md fresh, never the Matcher's score +- Gaps are saved to DB when score < threshold +- conditional_advice is provided when score is near the threshold + +**Ask first:** +- Changing the threshold defaults in constants.py +- Adding new topic keys beyond the existing 6 + +**Never do:** +- Recruiter receives or reads the Matcher's compatibility_pct +- Gaps are deleted when an offer is deleted (CASCADE is acceptable) +- Internet calls for research (agent handles this, not applyr) + +## Acceptance Criteria + +### AC-1: Blind Recruiter command [MUST] +Given an offer ID in the DB +When `applyr cv review-blind ` is executed +Then the system SHALL read cv-master.md fresh and evaluate it against the offer + independently, returning ats_score (0-100), verdict, strengths, weaknesses, + recommendations, and conditional_advice + +### AC-2: Verdict logic [MUST] +Given the Recruiter's ATS score +When the score is evaluated against thresholds from config +Then the system SHALL classify as: + - STRONG_MATCH when score >= threshold_apply (default 80) + - CLOSE_MATCH when score >= threshold_maybe (default 60) and < threshold_apply + - NO_MATCH when score < threshold_maybe + +### AC-3: Conditional advice [SHOULD] +Given a CLOSE_MATCH verdict +When the Recruiter returns its assessment +Then the output SHALL include conditional_advice with specific conditions + for applying (e.g., "highlight X", "message recruiter about Y") + +### AC-4: Gap persistence [MUST] +Given a NO_MATCH or CLOSE_MATCH verdict +When `applyr gaps save ''` is executed +Then the system SHALL store each gap in the learning_gaps table with + topic, gap_detail, severity, and suggested_action + +### AC-5: Gap listing [MUST] +Given gaps stored in the DB +When `applyr gaps list [--topic T] [--severity S]` is executed +Then the system SHALL display matching gaps with offer context (title, company) + +### AC-6: Gap stats [SHOULD] +Given gaps stored in the DB +When `applyr gaps stats` is executed +Then the system SHALL show summary: gaps per topic, severity distribution, + and top gaps across offers + +### AC-7: Recruiter reads cv-master.md [MUST] +Given the Recruiter command is executed +When it reads the candidate's profile +Then it SHALL read cv-master.md directly (NOT the generated CV skeleton) + to ensure independent evaluation + +### AC-8: Recruiter is blind [MUST] +Given the Recruiter command receives an offer ID +When it loads offer data from DB +Then it SHALL NOT load or reference the compatibility_pct from the Matcher + +### AC-E1: Missing offer [MUST] +Given an invalid offer ID +When `applyr cv review-blind ` is executed +Then the system SHALL exit with code 1 and error code "not_found" + +### AC-E2: Missing cv-master.md [MUST] +Given cv-master.md is missing or still template +When `applyr cv review-blind ` is executed +Then the system SHALL exit with code 1 with appropriate error + +### AC-E3: Empty gaps save [MUST] +Given `applyr gaps save ''` with empty gaps array +When executed +Then the system SHALL exit with code 1 and error code "missing_field" + +### AC-W1: Workflow integration [SHOULD] +Given the AGENT_INSTRUCTIONS.md template +When updated for v1.2.0 +Then it SHALL document the two-agent flow: + 1. Matcher: applyr add (evaluates compatibility) + 2. If score >= 60: Recruiter: applyr cv review-blind (independent analysis) + 3. If CLOSE_MATCH: apply conditional advice + 4. If NO_MATCH: applyr gaps save (persist gaps) + 5. Matcher: apply recommendations, generate final CV + +## Out of Scope +- [WONT] Internet research for trends (agent handles this, not applyr) +- [WONT] Multiple Recruiter agents or scoring rounds +- [WONT] Automatic gap resolution (user must act on recommendations) +- [WONT] New topic keys beyond the existing 6 + +## Open Questions +- [RESOLVED] Should Recruiter read cv-master.md or generated CV? → cv-master.md (independent) +- [RESOLVED] Where to store gaps? → New learning_gaps table +- [RESOLVED] When to trigger Recruiter? → Both above and below threshold diff --git a/specs/multi-agent-pipeline/tasks.md b/specs/multi-agent-pipeline/tasks.md new file mode 100644 index 0000000..0f54f83 --- /dev/null +++ b/specs/multi-agent-pipeline/tasks.md @@ -0,0 +1,84 @@ +# Task List: Multi-Agent Pipeline + +## Plan Reference +Implements: `specs/multi-agent-pipeline/plan.md` + +## Tasks + +### Phase A: Schema + Gap Commands + +- [x] **TASK-001** [S] Add learning_gaps table — migration v5 + - Modify: `applyr/db.py` (SCHEMA_VERSION, MIGRATIONS, SCHEMA_SQL) + - Tests: `tests/test_db.py` — test migration from v4 DB, test fresh install + - Depends on: none + +- [x] **TASK-002** [M] Implement `applyr gaps save` + - Modify: `applyr/commands/analytics.py` (new function `cmd_gaps_save`) + - Modify: `applyr/cli.py` (routing for `gaps save`) + - Modify: `applyr/commands/__init__.py` (export) + - Contract: `specs/multi-agent-pipeline/contracts/gaps.md` + - Tests: `tests/test_gaps.py` — test save, test empty gaps error, test invalid topic, test invalid offer + - Depends on: TASK-001 + +- [x] **TASK-003** [M] Implement `applyr gaps list` + - Modify: `applyr/commands/analytics.py` (new function `cmd_gaps_list`) + - Modify: `applyr/cli.py` (routing for `gaps list`) + - Contract: `specs/multi-agent-pipeline/contracts/gaps.md` + - Tests: `tests/test_gaps.py` — test list, test filter by topic, test filter by severity, test empty + - Depends on: TASK-001 + +- [x] **TASK-004** [S] Implement `applyr gaps stats` + - Modify: `applyr/commands/analytics.py` (new function `cmd_gaps_stats`) + - Modify: `applyr/cli.py` (routing for `gaps stats`) + - Contract: `specs/multi-agent-pipeline/contracts/gaps.md` + - Tests: `tests/test_gaps.py` — test stats with data, test empty stats + - Depends on: TASK-001 + +### Phase B: Blind Recruiter + +- [x] **TASK-005** [M] Implement `review_blind()` in cv.py + - Modify: `applyr/cv.py` (new function `review_blind(offer_id, as_json=False)`) + - Must NOT load compatibility_pct from DB + - Must read cv-master.md fresh via `inspect_cv_master()` + - Contract: `specs/multi-agent-pipeline/contracts/review-blind.md` + - Tests: `tests/test_cv_review_blind.py` — test blind evaluation, test verdict logic, test missing offer, test missing cv-master + - Depends on: TASK-001 + +- [x] **TASK-006** [S] Wire `review-blind` into CLI + - Modify: `applyr/commands/workflow.py` (routing for `cmd_review_blind`) + - Modify: `applyr/cli.py` (argument parsing for `cv review-blind`) + - Modify: `applyr/commands/__init__.py` (export) + - Contract: `specs/multi-agent-pipeline/contracts/review-blind.md` + - Tests: `tests/test_cli_routing.py` — test `cv review-blind 42` routes correctly + - Depends on: TASK-005 + +### Phase C: Documentation + +- [x] **TASK-007** [S] Update AGENT_INSTRUCTIONS.md template + - Modify: `applyr/templates/AGENT_INSTRUCTIONS.md` — document two-agent workflow + - This is the end-user contract — changes must be deliberate + - AC-W1 from spec + - Depends on: TASK-002, TASK-006 + +- [x] **TASK-008** [S] Update version to 1.2.0 + - Modify: `applyr/__init__.py` (__version__) + - Modify: `pyproject.toml` (version) + - Depends on: TASK-007 + +### Phase D: Integration + +- [x] **TASK-009** [M] Integration test: full two-agent flow + - Test: add offer → review-blind → gaps save → gaps list → gaps stats + - Tests all ACs end-to-end + - Depends on: TASK-002, TASK-003, TASK-004, TASK-006 + +- [x] **TASK-010** [S] Run lint + full test suite + - pylint applyr/ --disable=C0114,C0115,C0116,R0913,R0914,R0801 --fail-under=7.0 + - pytest — all tests pass + - Depends on: TASK-009 + +## Legend +- `[S]` Small — under 1 hour +- `[M]` Medium — 1–3 hours +- `[L]` Large — 3–6 hours (consider splitting) +- `[P]` Parallelizable — can run concurrently with other `[P]` tasks at same level diff --git a/specs/onboarding-fixes/OC-REPORT.md b/specs/onboarding-fixes/OC-REPORT.md new file mode 100644 index 0000000..1afd6b9 --- /dev/null +++ b/specs/onboarding-fixes/OC-REPORT.md @@ -0,0 +1,33 @@ +# OC-REPORT — Onboarding fixes v1.4.0 + +## Status: DONE + +## Completed tasks +- [x] TASK-001 — Invert null check in `cmd_cv_keywords` (`if offer is None`); regression test added +- [x] TASK-002 — `cmd_cv_review_blind` reads `threshold_apply`/`threshold_maybe` (not legacy `threshold`/`maybe_threshold`); config test added +- [x] TASK-003 — Removed duplicate `cmd_cv_ats_check` (cv.py:1261-1306); single definition remains +- [x] TASK-004 — `_is_initialized()` only checks `jobs.db` exists (a lone `applyr.toml` no longer counts); test added +- [x] TASK-005 — `cmd_init` ships the rich packaged template via `_cv_master_template_text()` loader; template rewritten (9 sections, all leave-bare `...`) +- [x] TASK-006 — audit false positive: `_check_cv_master()` already present in `cmd_doctor` (workflow.py) → no-op +- [x] TASK-007 — `_warn_if_profile_empty()` called by `cmd_setup_agent`; covers missing and unfilled cv-master.md; tests added +- [x] TASK-008 — Full suite + CI pylint pass + +## Incomplete tasks +- [ ] None + +## Spec deviations [DISAGREEMENT] +| # | Spec said | OC did | Reason | CC decides | +|---|-----------|--------|--------|-----------| +| 1 | TASK-005 template should read as "filled" so `inspect_cv_master` passes | Template keeps bare `...` placeholders (still "not filled") | Empty placeholders are what the shipped-template guard (`test_shipped_template_is_not_filled`) checks; a "filled" shipped template breaks the guard that stops `cv generate` on nothing | [x] accept / [ ] reject / [ ] escalate | +| 2 | TASK-007 assertion assumed offers without tech_stack die `no_keywords` | Existing offer proceeds to CV lookup and dies `no_cv` | `extract_keywords` yields tokens from the title alone, so the no-keywords branch is unreachable for seeded offers; the meaningful regression is `not found` vs proceeding — test asserts exactly that | [x] accept / [ ] reject / [ ] escalate | + +## Simplifications applied [SIMPLIFIED] +- TASK-007 warning reuse `warn()` (stderr) instead of a new output channel — matches existing diagnostics, keeps tests on `capsys.err`. +- Lazy imports inside `_warn_if_profile_empty` to avoid a circular import between `commands.core` and `cv`/`cv_master`. + +## Doubts / Blockers +- Test-side effect: `setup-agent` writes `CLAUDE.md` into the pytest cwd (repo root). `CLAUDE.md` is gitignored, so no diff pollution — but this predates these changes (`test_setup_agent_with_agent` already did it) and is worth isolating later via `chdir`. + +## Check results +- `python -m pytest tests/ -q` → 418 passed +- `pylint applyr/ --disable=C0114,C0115,C0116,R0913,R0914,R0801 --fail-under=7.0` → 9.31/10 (threshold 7.0) \ No newline at end of file diff --git a/specs/onboarding-fixes/plan.md b/specs/onboarding-fixes/plan.md new file mode 100644 index 0000000..a7f32fc --- /dev/null +++ b/specs/onboarding-fixes/plan.md @@ -0,0 +1,66 @@ +## Architecture + +No new components. All fixes are inline changes to existing functions: + +``` +cv.py: 3 changes (2 bug fixes + 1 dead code removal) +cli.py: 1 change (_is_initialized) +core.py: 2 changes (template + setup-agent warning) +workflow.py: 1 change (doctor validation) +tests: 2 new test functions +``` + +## Data Model + +No changes. No new tables, columns, or migrations. + +## Changes by file + +### applyr/cv.py +1. **Line 904**: `if not offer is None:` → `if offer is None:` +2. **Lines 725-726**: Replace config key lookups: + - `config["general"].get("threshold", 80)` → `config["general"].get("threshold_apply", 80)` + - `config["general"].get("maybe_threshold", 60)` → `config["general"].get("threshold_maybe", 60)` +3. **Lines 1261-1306**: Delete second `cmd_cv_ats_check` definition (exact duplicate of lines 824-881) + +### applyr/cli.py +4. **Lines 117-120**: Rewrite `_is_initialized()`: + ```python + def _is_initialized() -> bool: + from applyr.config import APPLYR_DIR + return (APPLYR_DIR / "jobs.db").exists() + ``` + Rationale: TOML alone is insufficient — all commands need the DB. If DB is missing, user must run `applyr init`. + +### applyr/commands/core.py +5. **Lines 48-62**: Replace `_CV_MASTER_TEMPLATE` with a richer template that includes: + - Realistic section headers (Summary, Experience, Education, Skills, Projects) + - Placeholder examples per section (e.g., "Senior Software Engineer at Acme Corp") + - Enough content words to pass `inspect_cv_master()` validation +6. **Lines 348-397** (`cmd_setup_agent`): After getting `instructions`, before writing to target file: + ```python + cv_master = APPLYR_DIR / "cv-master.md" + if cv_master.exists(): + report = inspect_cv_master(cv_master.read_text(encoding="utf-8")) + if not report.filled: + warn(f"Warning: {cv_master} is {report.reason}.") + warn(f" CVs generated without a filled profile will have empty sections.") + warn(f" Edit {cv_master} with your professional details before generating CVs.") + ``` + +### applyr/commands/workflow.py +7. In `cmd_doctor()`, after existing health checks, add: + ```python + from applyr.cv_master import inspect_cv_master + cv_master = APPLYR_DIR / "cv-master.md" + if not cv_master.exists(): + warn("cv-master.md not found — run 'applyr init' to create it") + else: + report = inspect_cv_master(cv_master.read_text(encoding="utf-8")) + if not report.filled: + warn(f"cv-master.md is {report.reason} — fill it before generating CVs") + ``` + +## Risks +- Risk: New cv-master.md template has more content → `inspect_cv_master()` might reject it → Mitigation: test the template against the validator in the test suite +- Risk: `_is_initialized` strictness could surface errors for users with partial installs → Mitigation: these users already can't use commands, so this surfaces the real problem earlier diff --git a/specs/onboarding-fixes/spec.md b/specs/onboarding-fixes/spec.md new file mode 100644 index 0000000..906d778 --- /dev/null +++ b/specs/onboarding-fixes/spec.md @@ -0,0 +1,92 @@ +## Spec: Onboarding Fixes — 7 bug/UX corrections + +### Status: DRAFT +### Version: 1.0 + +### Recovered context +- Project constitution: `/Users/db/Documents/GitHub/applyr/constitution.md` +- Relevant ADRs: ADR 003 (no LLM calls), ADR 006 (errors to stderr) +- Audit date: 2026-08-10 +- Scope: 2 bugs, 1 dead code cleanup, 4 UX improvements + +### What does it do? +Fix 7 issues found during onboarding audit: 2 logic bugs that cause incorrect behavior, 1 duplicated function, and 4 UX gaps that confuse new users during first-time setup. + +### What files does it touch? +| File | Action | Reason | +|------|--------|--------| +| applyr/cv.py | MODIFY | Fix bug #1 (keywords null check), bug #2 (threshold keys), remove duplicate cmd_cv_ats_check | +| applyr/cli.py | MODIFY | Fix #4 (_is_initialized checks DB) | +| applyr/commands/core.py | MODIFY | Improve cv-master.md template (#5), setup-agent warn on empty profile (#7) | +| applyr/commands/workflow.py | MODIFY | Add cv-master.md validation to doctor (#6) | +| tests/test_cv.py | MODIFY | Add regression tests for bugs #1 and #2 | + +### Dependencies +- No new APIs, endpoints, or DB schema changes +- Uses existing `inspect_cv_master()` from `applyr/cv_master.py` +- Uses existing `load_config()` from `applyr/config.py` + +### Acceptance criteria + +#### Bug #1 — cv keywords null check +- `[MUST]` WHEN `applyr cv keywords ` is called with a valid offer_id THAT does NOT exist in the database, THE system SHALL exit with code `not_found` and an error message on stderr +- `[MUST]` WHEN `applyr cv keywords ` is called with a valid offer_id that DOES exist, THE system SHALL proceed to keyword extraction without error +- `[MUST]` GIVEN an existing offer with a generated CV, WHEN `applyr cv keywords ` runs, THEN it returns matched/missing keywords with `--json` support + +#### Bug #2 — review-blind threshold keys +- `[MUST]` WHEN `applyr cv review-blind ` runs, THE system SHALL read `threshold_apply` from `config["general"]` (not `"threshold"`) +- `[MUST]` WHEN `applyr cv review-blind ` runs, THE system SHALL read `threshold_maybe` from `config["general"]` (not `"maybe_threshold"`) +- `[MUST]` GIVEN a user with `threshold_apply = 80` in applyr.toml, WHEN review-blind outputs thresholds, THEN it shows `APPLY >= 80%` (not `>= 65%`) + +#### Cleanup #3 — duplicate cmd_cv_ats_check +- `[MUST]` THE system SHALL have exactly one `cmd_cv_ats_check` function definition in cv.py +- `[MUST]` WHEN `applyr cv ats-check ` runs, THE system SHALL produce the same output as before (no behavioral change) + +#### UX #4 — _is_initialized checks DB +- `[MUST]` WHEN `_is_initialized()` is called AND `~/.applyr/jobs.db` does not exist AND `~/.applyr/applyr.toml` exists, THE system SHALL return `False` +- `[MUST]` WHEN `_is_initialized()` is called AND `~/.applyr/jobs.db` exists, THE system SHALL return `True` +- `[SHOULD]` WHEN `_is_initialized()` returns False, THE system SHALL show the `_GETTING_STARTED` message suggesting `applyr init` + +#### UX #5 — cv-master.md template improvement +- `[MUST]` WHEN `applyr init` creates `cv-master.md` for the first time, THE system SHALL include field labels with examples (name, email, skills, experience, projects) +- `[MUST]` THE new template SHALL be valid markdown that passes `inspect_cv_master()` validation (not flagged as "unfilled template") +- `[SHOULD]` THE template SHALL include 3-5 realistic placeholder examples per section + +#### UX #6 — doctor validates cv-master.md +- `[MUST]` WHEN `applyr doctor` runs AND `cv-master.md` is missing or unfilled, THE system SHALL report a warning on stderr +- `[MUST]` WHEN `applyr doctor` runs AND `cv-master.md` is filled with real content, THE system SHALL report OK +- `[SHOULD]` THE doctor output SHALL include the reason when cv-master.md is invalid (missing, unfilled, template-only) + +#### UX #7 — setup-agent warns on empty profile +- `[MUST]` WHEN `applyr setup-agent` runs AND `cv-master.md` fails `inspect_cv_master()`, THE system SHALL print a warning before writing agent instructions +- `[MUST]` THE warning SHALL explain that CVs generated without a filled profile will be empty +- `[SHOULD]` THE warning SHALL suggest running a text editor to fill `~/.applyr/cv-master.md` + +### Explicit assumptions +- `inspect_cv_master()` correctly detects unfilled templates (already tested in test_cv_master.py) +- The existing doctor command in `workflow.py` is the right place to add cv-master validation +- Users won't be confused by a warning during setup-agent (it's additive, not blocking) + +### Edge cases / risks +- Risk: New cv-master.md template might not pass `inspect_cv_master()` if the word count threshold is too high → Mitigation: test the template against the validator before shipping +- Risk: `_is_initialized` change could break existing users who have applyr.toml but no DB → Mitigation: they already can't use any command, so returning False is correct + +### Task breakdown (execution order) +1. Fix bug #1: invert null check in cmd_cv_keywords (cv.py:904) [S] +2. Fix bug #2: correct threshold keys in cmd_cv_review_blind (cv.py:725-726) [S] +3. Remove duplicate cmd_cv_ats_check (cv.py:1261-1306) [S] +4. Fix _is_initialized to check DB existence (cli.py:117-120) [S] +5. Improve cv-master.md template (core.py:48-62) [S] +6. Add cv-master.md validation to doctor (workflow.py) [S] +7. Add setup-agent warning for empty profile (core.py:348-397) [S] +8. Write regression tests for bugs #1 and #2 [S] +9. Run full test suite + pylint [S] + +### Out of scope +- `[WONT]` Changing the `inspect_cv_master()` logic or thresholds +- `[WONT]` Adding new CLI commands or aliases +- `[WONT]` Modifying the DB schema or migrations +- `[WONT]` Changing AGENT_INSTRUCTIONS.md template + +### Open questions +- None — all 7 issues have clear fixes identified in the audit diff --git a/specs/onboarding-fixes/tasks.md b/specs/onboarding-fixes/tasks.md new file mode 100644 index 0000000..8b0f769 --- /dev/null +++ b/specs/onboarding-fixes/tasks.md @@ -0,0 +1,48 @@ +## Tasks — Onboarding Fixes + +### TASK-001: Fix cmd_cv_keywords null check [S] +**File:** `applyr/cv.py:904` +**Change:** `if not offer is None:` → `if offer is None:` +**Test:** Add test in `tests/test_cv.py` — call `cmd_cv_keywords` with non-existent offer_id, assert exits with `not_found` + +### TASK-002: Fix cmd_cv_review_blind threshold keys [S] +**File:** `applyr/cv.py:725-726` +**Change:** +- `config["general"].get("threshold", 80)` → `config["general"].get("threshold_apply", 80)` +- `config["general"].get("maybe_threshold", 60)` → `config["general"].get("threshold_maybe", 60)` +**Test:** Add test in `tests/test_cv.py` — mock config with custom thresholds, verify review-blind output shows correct values + +### TASK-003: Remove duplicate cmd_cv_ats_check [S] +**File:** `applyr/cv.py:1261-1306` +**Change:** Delete the second `cmd_cv_ats_check` function definition +**Test:** Run existing `tests/test_ats.py` — all tests must still pass (function at lines 824-881 is the canonical one) + +### TASK-004: Fix _is_initialized to check DB [S] +**File:** `applyr/cli.py:117-120` +**Change:** Rewrite to only check for `jobs.db` existence +**Test:** Add test in `tests/test_cli_routing.py` — scenario: applyr.toml exists but jobs.db missing → `_is_initialized()` returns False + +### TASK-005: Improve cv-master.md template [S] +**File:** `applyr/commands/core.py:48-62` +**Change:** Replace `_CV_MASTER_TEMPLATE` with richer template containing realistic examples +**Test:** Add test in `tests/test_cv_master.py` — new template passes `inspect_cv_master()` (report.filled == True or at least not "unfilled template") + +### TASK-006: Add cv-master.md validation to doctor [S] +**File:** `applyr/commands/workflow.py` (in `cmd_doctor`) +**Change:** After existing checks, validate cv-master.md existence and content +**Test:** Add test in existing test file — doctor warns when cv-master.md is missing, doctor warns when cv-master.md is unfilled template + +### TASK-007: Add setup-agent warning for empty profile [S] +**File:** `applyr/commands/core.py:348-397` (in `cmd_setup_agent`) +**Change:** After getting instructions, before writing target file, check cv-master.md and warn if unfilled +**Test:** Add test in `tests/test_commands.py` — setup-agent with unfilled cv-master.md prints warning + +### TASK-008: Run full test suite + pylint [S] +**Command:** `pytest && pylint applyr/ --disable=C0114,C0115,C0116,R0913,R0914,R0801 --fail-under=7.0` +**Expected:** All tests pass, pylint >= 7.0 + +## Execution order +``` +TASK-001 → TASK-002 → TASK-003 → TASK-004 → TASK-005 → TASK-006 → TASK-007 → TASK-008 +``` +All tasks are sequential (each touches a different file section, no conflicts, but testing needs the fixes in place first).