From e92f7d906502d72f4501f423bb7ce9de912ac959 Mon Sep 17 00:00:00 2001 From: Oscar Madera <80536682+oscarbol09@users.noreply.github.com> Date: Tue, 15 Sep 2026 23:47:15 -0500 Subject: [PATCH] feat(setup): add documents/projects/ ingestion for independent projects (Path A) (#468) --- .claude/commands/reset.md | 8 ++++++-- .claude/commands/setup.md | 13 +++++++++---- .gitignore | 1 + CHANGELOG.md | 7 +++++++ documents/README.md | 20 ++++++++++++++++++++ documents/projects/.gitkeep | 0 tests/test_setup_command.py | 23 +++++++++++++++++++++++ tools/security_guards.py | 1 + 8 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 documents/projects/.gitkeep diff --git a/.claude/commands/reset.md b/.claude/commands/reset.md index 234d4b8..6ca4bbb 100644 --- a/.claude/commands/reset.md +++ b/.claude/commands/reset.md @@ -20,7 +20,7 @@ If `$ARGUMENTS` is empty or does not contain a recognized scope keyword, ask: > > - **`profile`** — Clears candidate data from the skill files (profile, behavioral, STAR examples, profile statements, personalized evaluation criteria, search queries). The framework structure, scoring framework, and writing rules are preserved. Use this to re-run `/setup` from scratch. > -> - **`documents`** — Deletes all files you've placed in the `documents/` folder (CV PDFs, LinkedIn export, diplomas, references, pasted job postings, past applications). The folder structure and `README.md` are preserved. +> - **`documents`** — Deletes all files you've placed in the `documents/` folder (CV PDFs, LinkedIn export, diplomas, references, project summaries, pasted job postings, past applications). The folder structure and `README.md` are preserved. > > - **`all`** — Both of the above. > @@ -86,7 +86,7 @@ cv/main_example.tex. This scope covers skill files only. ### If scope includes `documents`: -Use Glob to list all files present in `documents/cv/`, `documents/linkedin/`, `documents/diplomas/`, `documents/references/`, `documents/postings/`, and `documents/applications/`. Present as: +Use Glob to list all files present in `documents/cv/`, `documents/linkedin/`, `documents/diplomas/`, `documents/references/`, `documents/projects/`, `documents/postings/`, and `documents/applications/`. Present as: ``` ## Documents reset will delete: @@ -103,6 +103,9 @@ documents/diplomas/ documents/references/ - [filename] or "(empty)" +documents/projects/ + - [filename] or "(empty)" + documents/postings/ - [filename] or "(empty)" @@ -246,6 +249,7 @@ rm -f documents/cv/* rm -f documents/linkedin/* rm -f documents/diplomas/* rm -f documents/references/* +rm -f documents/projects/* rm -f documents/postings/* rm -rf documents/applications/*/ ``` diff --git a/.claude/commands/setup.md b/.claude/commands/setup.md index 82930df..34dcbcd 100644 --- a/.claude/commands/setup.md +++ b/.claude/commands/setup.md @@ -29,7 +29,7 @@ visibility cannot be determined — warn now and wait: Wait for the user's confirmation before showing the path prompt. A private origin, no origin, or a non-fork remote needs no warning — continue silently. -Then, before greeting the user, scan the `documents/` folder. Use Glob with `documents/**/*` and count files per subfolder (`cv/`, `linkedin/`, `diplomas/`, `references/`, `applications/`). +Then, before greeting the user, scan the `documents/` folder. Use Glob with `documents/**/*` and count files per subfolder (`cv/`, `linkedin/`, `diplomas/`, `references/`, `projects/`, `applications/`). Then welcome the user with a single message that lists three paths. The wording changes based on what was found. @@ -57,7 +57,7 @@ Then welcome the user with a single message that lists three paths. The wording > > Three ways to start: > -> **Path A: Documents folder** (best signal if you have several materials) - Drop your CV / LinkedIn export / diplomas / reference letters in the `documents/` folder, then say "go". I'll read everything and build your profile from it. See `documents/README.md` for the folder layout. +> **Path A: Documents folder** (best signal if you have several materials) - Drop your CV / LinkedIn export / diplomas / reference letters / project summaries in the `documents/` folder, then say "go". I'll read everything and build your profile from it. See `documents/README.md` for the folder layout. > > **Path B: Single CV import** - Paste or @-mention a single CV/resume here. I'll extract it and ask follow-up questions for what's missing. > @@ -86,6 +86,7 @@ Use Glob with `documents/**/*` to scan the full tree. Print: **linkedin/**: [list files, or "(empty)"] **diplomas/**: [list files, or "(empty)"] **references/**: [list files, or "(empty)"] +**projects/**: [list files, or "(empty)"] **applications/**: [list subfolders with their files, or "(empty)"] I will read these and cross-reference before proposing any changes. @@ -109,7 +110,7 @@ Hold this content in context throughout Path A. Do not re-read. ### Step A3: Parse Documents -Read each document found in Step A1. Process subfolders in this order: `cv/`, `linkedin/`, `diplomas/`, `references/`, `applications/`. +Read each document found in Step A1. Process subfolders in this order: `cv/`, `linkedin/`, `diplomas/`, `references/`, `projects/`, `applications/`. **`cv/` documents:** name, contact (email, phone, LinkedIn, GitHub), education (degree, institution, dates, thesis), work experience (title, company, dates, location, bullets), skills, languages (with any stated proficiency), publications, awards, profile/summary. @@ -119,6 +120,8 @@ Read each document found in Step A1. Process subfolders in this order: `cv/`, `l **`references/` documents:** referee name, title, organization; full text of the letter (extract specific quotes); competency language used. +**`projects/` documents:** project name, summary/description, problem domain, tech stack (languages, frameworks, tools), key technical challenges and architectural decisions, measurable outcomes/metrics (e.g. users, performance, stars, impact). + **`applications/_/` subfolders:** - `job_posting.md`: role title, company, required skills, experience level, sector, role type - `cover_letter.tex`: opening structure, body structure, bullet style, closing, recurring phrases @@ -157,12 +160,13 @@ If no inconsistencies, state "No cross-reference issues found." and continue. For each skill file, compare extracted document content against the current file content from Step A2. Build two buckets. -**Additive changes:** entirely new content not in the skill file in any form. Examples: a certification not in `01-candidate-profile.md`, a new endorsement skill, a referee not yet listed, a new behavioral quote from a reference letter, a new award. +**Additive changes:** entirely new content not in the skill file in any form. Examples: a certification not in `01-candidate-profile.md`, a new independent project not in `01-candidate-profile.md`, a new endorsement skill, a referee not yet listed, a new behavioral quote from a reference letter, a new award. **Conflicting changes:** content that touches something already in a skill file but disagrees. Examples: a different date range for an existing job, a different job title for the same role, a different graduation date than what is recorded. **Inference rules** (apply when populating from inferred sources): +- **`01-candidate-profile.md` (`## Independent Projects`):** Source is `projects/` documents. Extract structured project entries formatted as `- **[PROJECT_NAME]**: [DESCRIPTION with tech stack and measurable outcome]`. Ground all claims in the document text. - **`02-behavioral-profile.md`:** Source is LinkedIn About + recommendation letters. Extract recurring themes, adjectives, phrases about how the candidate works. Add only to "Strongest Behavioral Traits", "How [Candidate] Works Best", or "Management Style Preferences" sections. Do not overwrite existing scored assessments. Always label inferred additions: *[Inferred from LinkedIn About / Reference letter - review before relying on this]* - **`03-writing-style.md`:** Source is `cover_letter.tex` files. Extract recurring patterns. Add as observations under "## Patterns Observed in Past Applications". Do not modify existing rules. Only add if 2+ cover letters show a genuine pattern. - **`04-job-evaluation.md`:** Source is `job_posting.md` + `outcome.md` pairs. If an application reached interview or offer: note role type and sector as a confirmed strong-fit signal. If 2+ applications repeat a no-response or rejection pattern: note it. Add findings under "## Calibration from Past Applications". Do not modify the existing scoring framework. @@ -193,6 +197,7 @@ Present the full change set before writing anything. ### 01-candidate-profile.md - [ ] New certification: [title], [issuer], [date] - extracted from LinkedIn +- [ ] New independent project: [PROJECT_NAME] - [description, tech stack, key outcome] - [ ] New reference: [name, title, company] Quote: "[relevant quote]" diff --git a/.gitignore b/.gitignore index 7a25891..5016ef7 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,7 @@ documents/cv/** documents/linkedin/** documents/diplomas/** documents/references/** +documents/projects/** # Also where /interview saves its prep packs (interview_prep_.md): these # name the employers applied to, quote what was submitted, and set out the # candidate's weak points. diff --git a/CHANGELOG.md b/CHANGELOG.md index f13fb46..40909fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,13 @@ per-file diff commands. ### Added +- **`documents/projects/` portfolio ingestion in `/setup` (Path A)** (`documents/README.md`, + `.claude/commands/setup.md`, `.claude/commands/reset.md`, `tests/test_setup_command.py`) - + onboards project writeups, case studies, and documentation (`.md`, `.txt`, `.pdf`) + from `documents/projects/`, extracting structured summaries (problem domain, tech stack, + technical challenges, and measurable outcomes) to populate `## Independent Projects` + in `01-candidate-profile.md`. + - **Source host verification in `/apply` Step 1** (#431, `.claude/commands/apply.md`, `tests/test_apply_host_check.py`) - before proceeding to draft CV and cover letters, Step 1 verifies the posting URL's provenance against installed portal boards and the diff --git a/documents/README.md b/documents/README.md index ebcc123..c97f2a7 100644 --- a/documents/README.md +++ b/documents/README.md @@ -12,6 +12,7 @@ documents/ ├── linkedin/ # LinkedIn profile export (PDF) ├── diplomas/ # Degree certificates and transcripts ├── references/ # Reference letters +├── projects/ # Independent project summaries, case studies, or portfolio docs ├── postings/ # Raw job posting text, pasted manually for pages Claude can't fetch │ └── - .txt # Filename = company + job title, content = full posting text ├── applications/ # Past job applications @@ -97,6 +98,25 @@ Reference letters from former managers, supervisors, or collaborators. --- +## projects/ + +Summaries, case studies, READMEs, writeups, or documentation for independent, open-source, freelance, or personal portfolio projects. + +**Supported formats:** `.md`, `.txt`, `.pdf` + +**What `/setup` extracts:** +- Project name and description +- Problem domain and target audience +- Tech stack, tools, and libraries used +- Key technical challenges and architectural decisions +- Measurable outcomes, metrics, or performance improvements (added to `01-candidate-profile.md` under `## Independent Projects`) + +**Naming:** Use descriptive project names, e.g. `project_realtime_chat.md`, `portfolio_compiler.txt`, `open_source_etl.pdf`. + +**Tip:** These feed into the `## Independent Projects` section of `01-candidate-profile.md` and provide concrete technical evidence that `/apply` can weave into tailored CVs and cover letters. + +--- + ## postings/ A drop folder for raw job posting text when Claude can't fetch a page directly (bot-blocked ATS platforms like Lever, Greenhouse behind Cloudflare, JS-heavy SPAs that return empty content, etc.). You open the posting yourself and paste the full text into a `.txt` file here. diff --git a/documents/projects/.gitkeep b/documents/projects/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_setup_command.py b/tests/test_setup_command.py index 8e85f33..902d215 100644 --- a/tests/test_setup_command.py +++ b/tests/test_setup_command.py @@ -90,5 +90,28 @@ class TemplatesStillCarryThePlaceholders(unittest.TestCase): self.assertIn("\\signature{[YOUR_NAME]}", text) +class SetupPathAProjectsIngestion(unittest.TestCase): + """Guards for /setup Path A document ingestion of documents/projects/.""" + + def setUp(self): + self.text = COMMAND.read_text(encoding="utf-8") + self.sections = _sections(self.text) + + def test_step0_scan_includes_projects(self): + step0 = self.sections["Step 0: Welcome & Choose Path"] + self.assertIn("projects/", step0) + + def test_step_a1_inventory_includes_projects(self): + self.assertIn("**projects/**:", self.text) + + def test_step_a3_parsing_includes_projects_spec(self): + self.assertIn("`projects/` documents:", self.text) + self.assertIn("measurable outcomes", self.text) + + def test_step_a5_and_a6_map_to_independent_projects(self): + self.assertIn("## Independent Projects", self.text) + self.assertIn("New independent project:", self.text) + + if __name__ == "__main__": unittest.main() diff --git a/tools/security_guards.py b/tools/security_guards.py index 00e2ac0..7f16b1a 100644 --- a/tools/security_guards.py +++ b/tools/security_guards.py @@ -84,6 +84,7 @@ REQUIRED_IGNORE_RULES = [ "documents/linkedin/**", "documents/diplomas/**", "documents/references/**", + "documents/projects/**", "documents/applications/**", "documents/postings/**", # Belt-and-braces, not the primary guard: nothing writes here.