Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,53 @@ jobs:
# first work package adds some, so only that exit code is tolerated.
shell: bash
run: python3 -m pytest -q || [ $? -eq 5 ]

clean-clone:
name: clean-clone
runs-on: ubuntu-latest
env:
PYTHONDONTWRITEBYTECODE: '1'
steps:
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Clone the repo fresh, at the commit under test
shell: bash
run: |
git clone "https://github.com/${{ github.repository }}" fresh
cd fresh
git fetch --no-tags origin "${GITHUB_SHA}"
git checkout --detach "${GITHUB_SHA}"
git log -1 --format='testing commit %H'
- name: Install into a new virtual environment
working-directory: fresh/pipelines/job-assessment
run: |
python3 -m venv "$RUNNER_TEMP/venv"
"$RUNNER_TEMP/venv/bin/pip" install -r requirements.txt
- name: The validator accepts the fictional person and rejects the broken file
working-directory: fresh/pipelines/job-assessment
shell: bash
run: |
"$RUNNER_TEMP/venv/bin/python" scripts/validate_profile.py --fixture fixtures/robin-sample/career-profile.yaml
set +e
"$RUNNER_TEMP/venv/bin/python" scripts/validate_profile.py --fixture fixtures/broken-profile.yaml
[ $? -eq 1 ]
- name: Run the whole chain offline on all three fictional postings
working-directory: fresh/pipelines/job-assessment
shell: bash
run: |
for id in 01-strong-fit 02-job-type-override 03-unlisted-pay-perks; do
echo "=== $id"
"$RUNNER_TEMP/venv/bin/python" scripts/assess_offline.py "fixtures/postings/$id.md" \
--findings "fixtures/findings/$id.findings.json" \
--profile fixtures/robin-sample/career-profile.yaml \
--out "/tmp/ja-out/$id" --date 2026-10-01 2>/dev/null
done
- name: Run the full test suite, including the golden-file comparison
working-directory: fresh/pipelines/job-assessment
run: |
"$RUNNER_TEMP/venv/bin/python" -m pytest -q -p no:cacheprovider
- name: The run left the clone unchanged
working-directory: fresh
shell: bash
run: test -z "$(git status --porcelain)"
12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ Skills, pipelines, and technique write-ups for building AI-agent systems that st

**1. [`pipelines/docs-pipeline`](pipelines/docs-pipeline/)** is the strongest piece in this repo. It is a chain of Claude Code skills that takes a documentation change from the first request through structure review, voice, grammar, links, visuals, expert review, and publishing, with a real gate between every stage. I used a version of it every working day on real documentation. If you only open one thing, open this, and start with its [README](pipelines/docs-pipeline/README.md) and [ARCHITECTURE](pipelines/docs-pipeline/ARCHITECTURE.md).

**2. [`patterns/block-and-tell-hooks.md`](patterns/block-and-tell-hooks.md)** explains how to make an AI agent follow a rule every time instead of hoping it remembers. It is the idea behind most of the guards in my own setup.
**2. [`pipelines/job-assessment`](pipelines/job-assessment/)** scores a job posting against your own written criteria and gives a plain verdict: Apply, Apply with reservations, or Skip. A model reads the posting and writes down findings, quoting the posting for every claim. Ordinary code then checks those quotes, does the scoring and picks the verdict, so the same findings always give the same answer. It was built by directing Claude Code, not hand-typed. What is shown: the whole chain runs offline on three invented postings with no model, no network and no API key, and the 461 tests in that folder (run again in a fresh clone by CI) compare its scores, verdicts and saved output files to written-down expected values. A separate run with the `claude` CLI on Claude Sonnet is saved in [`tests/`](pipelines/job-assessment/tests/): the interview built a file that passed the checker, and the assessment gave the expected verdicts on 2 of 2 postings in its second attempt, after the first attempt got one verdict wrong ([both records are kept](pipelines/job-assessment/tests/e2e-output-run1.md)). What is not shown: that a verdict predicts getting hired, or that it beats any other method. No real postings or real person's data are in it. Its architecture and setup docs are still being written; the folder's README has a status list.

**3. [`patterns/typed-memory-system.md`](patterns/typed-memory-system.md) and [`patterns/rules-index-architecture.md`](patterns/rules-index-architecture.md)** cover how to keep an agent's memory and standing rules organized as they grow.
**3. [`patterns/block-and-tell-hooks.md`](patterns/block-and-tell-hooks.md)** explains how to make an AI agent follow a rule every time instead of hoping it remembers. It is the idea behind most of the guards in my own setup.

**4. [`pipelines/job-assessment`](pipelines/job-assessment/)** is a job-posting assessment system, being built in small pull requests. It is a scaffold only right now and nothing in it runs yet. Its README has a status list that shows what exists.

**5. [`tools/job-fit-screen`](tools/job-fit-screen/)** is an early job-posting scorer. A much larger version is being published to replace it.
**4. [`patterns/typed-memory-system.md`](patterns/typed-memory-system.md) and [`patterns/rules-index-architecture.md`](patterns/rules-index-architecture.md)** cover how to keep an agent's memory and standing rules organized as they grow.

## Why this exists

Expand All @@ -28,11 +26,9 @@ Every piece in here started as a real problem: a rule that kept getting skipped,

**`patterns/`** — written technique docs for ideas that are more valuable described in prose than shipped as literal runnable code, either because the real implementation is too specific to one project to be useful as-is, or because the idea itself is the point. Covers: how to make an "always do X first" instruction actually reliable instead of hoped-for (block-and-tell hooks), how to keep an agent's standing instructions from becoming an unmaintainable single file as they grow (rules-index architecture), and how to give an agent memory that survives months of use without turning into an unreadable dump (typed, size-bounded memory).

**`tools/`** — small standalone utilities. A job-fit screener that scores a real job posting against your own personal fit criteria — culture, autonomy, and actual eligibility, not just keyword matching — using Claude to make the judgment calls a plain keyword scan can't. It is an early version (August 2026), and a much larger job assessment system is being published to replace it.

## How the pieces relate

A skill is one task. A pipeline is several skills chained with real gates between them (nothing moves to the next stage until the current one passes). A pattern is the idea behind a mechanism, written down so it can be rebuilt in a different codebase without copying code that won't fit. The tools are the smaller, single-purpose things that don't need any of that structure.
A skill is one task. A pipeline is several skills chained with real gates between them (nothing moves to the next stage until the current one passes). A pattern is the idea behind a mechanism, written down so it can be rebuilt in a different codebase without copying code that won't fit.

## A note on how this was built

Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ What's here now is a first batch. This is what's planned next, so it's clear wha

## In progress

**Job assessment system.** Being built in small pull requests under `pipelines/job-assessment/`. Only the scaffold and safety checks exist so far. Its README has the status list.
**Job assessment system.** Built in small pull requests under `pipelines/job-assessment/`. The scripts, schemas, fictional sample data, both skills and the offline end-to-end run are merged and tested; the earlier `tools/job-fit-screen` it replaces has been removed. Still to come: `ARCHITECTURE.md`, `SETUP.md` and the tested prompt blocks. Its README has the status list.

## Planned — batch 2

Expand Down
16 changes: 11 additions & 5 deletions pipelines/job-assessment/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Job assessment system

**Status: scaffold only. Nothing in this folder is runnable yet.** This folder holds the repo checks and test setup that the rest of the system will be built on. The scripts, schemas, sample data and skills listed below do not exist yet, and each one will be added in its own pull request.
**Status: the chain runs offline on fictional data; the architecture, setup and tested-prompt docs are still to come.** Everything listed as done below exists in this folder and is covered by tests. The last two bullets are the only work left.

The system is meant to score a job posting against a person's own written criteria and give a plain verdict: Apply, Apply with reservations, or Skip. Its docs will follow the style guides and prompt-block standard of [`pipelines/docs-pipeline`](../docs-pipeline/), which stays the first thing to look at in this repo.

Expand Down Expand Up @@ -28,16 +28,22 @@ Each bullet flips from planned to done in the pull request that delivers it.

- [x] **Skills catalog and survey.** A generic skills list and an offline form for scoring yourself against it.

- [ ] **Integration and proof.** The whole chain run end to end from a clean clone, plus the removal of the earlier `tools/job-fit-screen`.
- [x] **Integration and proof.** The whole chain run end to end offline, a separate live run with the `claude` CLI, a clean-clone check in CI, and the removal of the earlier `tools/job-fit-screen` (git history keeps it).

- [ ] **Docs and prompt blocks.** `ARCHITECTURE.md`, `SETUP.md`, and the tested prompts for each doc.

## What can be run today

Only the repo checks:
From this folder, after `pip install -r requirements.txt`:

```text
python3 .github/scripts/safety_scan.py --tree
python3 -m pytest -q
python3 scripts/assess_offline.py fixtures/postings/03-unlisted-pay-perks.md \
--findings fixtures/findings/03-unlisted-pay-perks.findings.json \
--profile fixtures/robin-sample/career-profile.yaml --out /tmp/ja-out
python3 ../../.github/scripts/safety_scan.py --tree
```

It prints `safety_scan: clean (tree)` and exits 0 when no email address, home path, private network address, tracker link, ticket id or secret-shaped string is in a tracked file.
The second command runs the whole chain with no model and no network: it checks the findings, scores them, picks the verdict, writes the assessment into an archived note, renames the note and writes the email card as a local HTML file. The tests in `tests/test_e2e_offline.py` compare that output for all three fictional postings to `fixtures/expected.yaml` and `fixtures/golden/`.

The live runs in `tests/live/` need the `claude` CLI and `JA_LIVE=1`, are skipped otherwise, and never run in CI. Their saved records are `tests/intake-transcript.md`, `tests/e2e-output.md` and the first attempt, `tests/e2e-output-run1.md`.
148 changes: 148 additions & 0 deletions pipelines/job-assessment/fixtures/golden/01-strong-fit/email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<!-- FICTIONAL EXAMPLE DATA. Not a real person. -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Job Assessment</title>
</head>
<body style="margin:0; padding:0; background-color:#f3f4f6; font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;">
<!-- Assessment card. render_email.py fills every double-brace field and writes a local HTML file. It never sends anything. -->
<!-- Verdict colors: Apply bg #ecfdf5 text #059669. Apply with reservations bg #fffbeb text #d97706. Skip bg #fef2f2 text #dc2626. -->
<!-- Rows between BEGIN and END markers repeat once per item and the markers themselves are removed. -->
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color:#f3f4f6;">
<tr><td align="center" style="padding:24px 16px;">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width:600px; background-color:#ffffff; border-radius:12px; overflow:hidden; border:1px solid #e5e7eb;">

<!-- Job link: the posting's own page. It opens the card, above the verdict. -->
<tr><td style="padding:18px 24px 16px 24px; background-color:#ffffff;">
<a href="https://example.com/fictional/jobs/copperline-docs-platform" style="display:inline-block; background-color:#111827; color:#ffffff; font-size:14px; font-weight:700; text-decoration:none; padding:12px 18px; border-radius:6px;">View the job posting</a>
</td></tr>

<tr><td data-card="verdict" style="background-color:#ecfdf5; padding:20px 24px;">
<div style="font-size:12px; font-weight:700; letter-spacing:0.08em; color:#059669; text-transform:uppercase; margin-bottom:4px;">Apply</div>
<div style="font-size:20px; font-weight:700; color:#111827;">Copperline Example Co. &middot; Docs Platform Engineer</div>
</td></tr>

<tr><td style="padding:20px 24px 4px 24px;">
<p style="font-size:15px; line-height:1.6; color:#374151; margin:0;">This is the job Robin is looking for: build the docs system, with AI drafting and a say in the tools. The only gap is reading Go code.</p>
</td></tr>

<tr><td style="padding:16px 24px 8px 24px;">
<div style="font-size:12px; font-weight:700; letter-spacing:0.06em; color:#6b7280; text-transform:uppercase; margin-bottom:10px;">Scores</div>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
<tr>
<td style="width:110px; font-size:14px; color:#374151; padding:5px 0;">Fit</td>
<td style="padding:5px 0;">
<table role="presentation" cellpadding="0" cellspacing="0" width="100%"><tr>
<td style="background-color:#16a34a; width:90%; height:10px; border-radius:5px 0 0 5px; font-size:1px; line-height:1px;">&nbsp;</td>
<td style="background-color:#e5e7eb; width:10%; height:10px; border-radius:0 5px 5px 0; font-size:1px; line-height:1px;">&nbsp;</td>
</tr></table>
</td>
<td style="width:28px; font-size:14px; font-weight:700; color:#111827; text-align:right; padding:5px 0 5px 8px;">9</td>
</tr>
<tr>
<td style="width:110px; font-size:14px; color:#374151; padding:5px 0;">Comp</td>
<td style="padding:5px 0;">
<table role="presentation" cellpadding="0" cellspacing="0" width="100%"><tr>
<td style="background-color:#7c3aed; width:100%; height:10px; border-radius:5px 0 0 5px; font-size:1px; line-height:1px;">&nbsp;</td>
<td style="background-color:#e5e7eb; width:0%; height:10px; border-radius:0 5px 5px 0; font-size:1px; line-height:1px;">&nbsp;</td>
</tr></table>
</td>
<td style="width:28px; font-size:14px; font-weight:700; color:#111827; text-align:right; padding:5px 0 5px 8px;">10</td>
</tr>
<tr>
<td style="width:110px; font-size:14px; color:#374151; padding:5px 0;">Qualifications</td>
<td style="padding:5px 0;">
<table role="presentation" cellpadding="0" cellspacing="0" width="100%"><tr>
<td style="background-color:#16a34a; width:90%; height:10px; border-radius:5px 0 0 5px; font-size:1px; line-height:1px;">&nbsp;</td>
<td style="background-color:#e5e7eb; width:10%; height:10px; border-radius:0 5px 5px 0; font-size:1px; line-height:1px;">&nbsp;</td>
</tr></table>
</td>
<td style="width:28px; font-size:14px; font-weight:700; color:#111827; text-align:right; padding:5px 0 5px 8px;">9</td>
</tr>
<tr>
<td style="width:110px; font-size:14px; color:#374151; padding:5px 0;">Culture</td>
<td style="padding:5px 0;">
<table role="presentation" cellpadding="0" cellspacing="0" width="100%"><tr>
<td style="background-color:#7c3aed; width:100%; height:10px; border-radius:5px 0 0 5px; font-size:1px; line-height:1px;">&nbsp;</td>
<td style="background-color:#e5e7eb; width:0%; height:10px; border-radius:0 5px 5px 0; font-size:1px; line-height:1px;">&nbsp;</td>
</tr></table>
</td>
<td style="width:28px; font-size:14px; font-weight:700; color:#111827; text-align:right; padding:5px 0 5px 8px;">10</td>
</tr>
</table>
</td></tr>

<tr><td style="padding:16px 24px 4px 24px;">
<div style="font-size:12px; font-weight:700; letter-spacing:0.06em; color:#6b7280; text-transform:uppercase; margin-bottom:10px;">Checks</div>
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="border-collapse:collapse;">
<tr>
<td style="padding:10px 0; border-top:1px solid #f3f4f6; vertical-align:top; width:34%;">
<div style="font-size:13px; font-weight:600; color:#111827;">✨ AI-forward team</div>
<div style="font-size:12px; font-weight:600; color:#16a34a; margin-top:2px;">&#9679; Strong</div>
</td>
<td style="padding:10px 0 10px 12px; border-top:1px solid #f3f4f6; vertical-align:top;">
<div style="font-size:13px; line-height:1.5; color:#4b5563;">AI drafting is normal here and people still check the result.</div>
</td>
</tr>
<tr>
<td style="padding:10px 0; border-top:1px solid #f3f4f6; vertical-align:top; width:34%;">
<div style="font-size:13px; font-weight:600; color:#111827;">📦 Docs live in version control</div>
<div style="font-size:12px; font-weight:600; color:#16a34a; margin-top:2px;">&#9679; Strong</div>
</td>
<td style="padding:10px 0 10px 12px; border-top:1px solid #f3f4f6; vertical-align:top;">
<div style="font-size:13px; line-height:1.5; color:#4b5563;">Docs are built like code, which is how Robin works.</div>
</td>
</tr>
<tr>
<td style="padding:10px 0; border-top:1px solid #f3f4f6; vertical-align:top; width:34%;">
<div style="font-size:13px; font-weight:600; color:#111827;">🧪 Docs are built and checked automatically</div>
<div style="font-size:12px; font-weight:600; color:#ea580c; margin-top:2px;">&#9679; Weak</div>
</td>
<td style="padding:10px 0 10px 12px; border-top:1px solid #f3f4f6; vertical-align:top;">
<div style="font-size:13px; line-height:1.5; color:#4b5563;">No automatic checks yet. The job is to build them.</div>
</td>
</tr>
<tr>
<td style="padding:10px 0; border-top:1px solid #f3f4f6; vertical-align:top; width:34%;">
<div style="font-size:13px; font-weight:600; color:#111827;">🦸 Owns the docs system</div>
<div style="font-size:12px; font-weight:600; color:#ca8a04; margin-top:2px;">&#9679; Fair</div>
</td>
<td style="padding:10px 0 10px 12px; border-top:1px solid #f3f4f6; vertical-align:top;">
<div style="font-size:13px; line-height:1.5; color:#4b5563;">Robin would own the system. This is a bonus item, so it never costs points.</div>
</td>
</tr>
<tr>
<td style="padding:10px 0; border-top:1px solid #f3f4f6; vertical-align:top; width:34%;">
<div style="font-size:13px; font-weight:600; color:#111827;">Autonomy</div>
<div style="font-size:12px; font-weight:600; color:#16a34a; margin-top:2px;">&#9679; Strong</div>
</td>
<td style="padding:10px 0 10px 12px; border-top:1px solid #f3f4f6; vertical-align:top;">
<div style="font-size:13px; line-height:1.5; color:#4b5563;">Robin picks the tools and the priorities.</div>
</td>
</tr>
<tr>
<td style="padding:10px 0; border-top:1px solid #f3f4f6; vertical-align:top; width:34%;">
<div style="font-size:13px; font-weight:600; color:#111827;">Pay</div>
<div style="font-size:12px; font-weight:600; color:#9ca3af; margin-top:2px;">&#9679; Unknown</div>
</td>
<td style="padding:10px 0 10px 12px; border-top:1px solid #f3f4f6; vertical-align:top;">
<div style="font-size:13px; line-height:1.5; color:#4b5563;">The posting lists base pay up to 150000.</div>
</td>
</tr>
</table>
</td></tr>


<tr><td style="padding:0 24px 20px 24px;">
<hr style="border:none; border-top:1px solid #e5e7eb; margin:0 0 12px 0;">
<p style="font-size:11px; color:#9ca3af; margin:0 0 6px 0;">Continue in your AI tool:</p>
<pre style="font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace; font-size:11px; line-height:1.5; color:#9ca3af; background-color:#f9fafb; border-radius:6px; padding:10px 12px; margin:0; white-space:pre-wrap; word-break:break-word;">Open the saved assessment for Copperline Example Co., Docs Platform Engineer. Check each claim in it against the posting text, then help me decide whether to apply.</pre>
</td></tr>

</table>
</td></tr>
</table>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FICTIONAL EXAMPLE DATA. Not a real person.
🟢 📋 🔧 Copperline Example Co. - Docs Platform Engineer - 2026-10-01.md
Loading
Loading