Skip to content
← Selected projects

Jobforge

2026 · open source · claude code plugin

Every coding-interview tool I tried grades the submitted code. My failures were in the two minutes before it, where I said “I'll DP this” and started typing. Jobforge is a Claude Code plugin that asks for the plan first and grades that. Résumé, target list, teaching and interview debriefs live in the same local corpus, so one tool sees the whole picture. Code on GitHub.

What it is

A plugin, not a service. One SessionStart hook, six skills, sixteen pattern files and a Python harness that runs your solution. State is markdown in ~/jobforge that you can read, edit and rm -rf. I built it mid-prep, with interviews on the calendar, out of a private version that had been running for a few weeks. The constants in it are what that log produced, not what a paper said.

/jobforge:drill generates a problem for a due pattern and asks what you would do before you write anything. /jobforge:interview-debrief records an interview you actually sat, with the same vocabulary, and queues whatever broke. /jobforge:status shows the streak and which element keeps failing. /jobforge:archive is the intended ending: past your target date, it stops.

The plan, not the code

Every mistake-classifier in this space fires on a rejected submission. None of them can fire when you wrote correct code for the wrong reason, and none of them has any notion of a plan you stated before typing. That is the one gap I found after reading six of them at source level, and it is the only thing this plugin claims.

Each pattern declares three to five required elements from a global list of eleven: base case seeded, transition stated as a formula, iteration order justified, and so on. The drill marks each one present, vague or missing under one rule: no quote of your own words, not present. “I'll build up the table” is a vague transition. It passes casual listening, and it is the exact sentence that turns into a stall in the room.

/jobforge:drill · grading

> Before you write anything: what are you going to do, and why that? pattern          dp-2dstate-definition present   "dp[i][j] is the best score using the first i and j"base-case        present   "row zero and column zero are all zero"transition       vague     "then I fill in the table"iteration-order  missing   - verdict: failediteration-order was missing. You said "then I fill in the table" —fill it in which direction, and what does each cell need already computed?

bank.md · one row per rep

| date       | pattern         | verdict | missing         | due        ||------------|-----------------|---------|-----------------|------------|| 2026-08-27 | monotonic-stack | half    | iteration-order | 2026-08-30 |
fig. 1 — a graded plan and the row it writes. The verdict is on the stated plan; a recovery after prompting does not change it, because an interview measures what you produced unprompted. The due date is computed at grading time and stored in the row. There is no scheduler and no queue file, so there is nothing to desynchronise.

The element ids being global is the point. A missing base-case on dp-2d and a missing base-case on prefix-sum land in the same column, so after sixty days /jobforge:status can say that one seed has been missed on five unrelated patterns. The element you missed is also what gets scheduled: the next rep is a different pattern that depends on it, never the same problem again in three days.

One banner, one subject

The hook prints one line at session start when you have not drilled today, and it is the only surface that speaks before you do. It reads rep-log.md and nothing else. There is no code path from it to the résumé, the target list or the bank, so it cannot nag about them. The first time a banner can also say your LinkedIn headline is stale, it stops being an instruction and becomes wallpaper, and the drill loop dies with it.

That line is enforced by a test, not a comment. tests/test_push_pull_boundary.py strips the comments and asserts the hook contains exactly one os.path.join, one open(, one markdown filename, no networking imports, and no occurrence of resume, targets, profile, bank or interviews. A contributor who adds a helpful second line fails CI.

hooks/drill-banner.py

REP_LOG_FILENAME = "rep-log.md"  # the only filename this process may ever open def rep_log_path() -> str:    """The only path-producing function in this module."""    return os.path.join(jobforge_home(), REP_LOG_FILENAME) def _read() -> str:    path = rep_log_path()    if os.path.basename(path) != REP_LOG_FILENAME:        return ""    ...

session start

🔨 3-day streak, not yet logged today. /jobforge:drill   Median session 24 min — the floor is 20.
fig. 2 — the hook and what it prints. The banner is silent on a day already logged, silent past the target date, and silent before setup. The fact on the second line rotates by day so it does not become furniture.

The same tiering runs inside the skills. The drill reads the rep log, the bank and the pattern files. It never opens the résumé, because a tool that reads a résumé to pick a graph problem has no reason to, and you cannot audit what it never opened. Nothing in the plugin sends anything anywhere, so there is no telemetry setting. The README says the quiet part: if your employer manages the machine, put JOBFORGE_HOME on a personal volume.

form
Claude Code plugin · 1 SessionStart hook · 6 skills
patterns
16 · 8 Tier A, 8 Tier B
element ids
11, global across patterns
verdicts
failed · half · coded · named-clean
intervals
+3 days / +14 days, written into the row
tests
35, unittest, all deterministic
runtime
python 3 · stdlib only · 0 dependencies
state
markdown in ~/jobforge, no telemetry
tracked files
53
status
v0.1.0 · MIT · derived from swe-interview-coach

What it is not

It does not capture submissions. A browser extension sits at the moment you hit submit and can interrupt you; a CLI agent exists only when invoked. Several extensions already do auto-capture with FSRS scheduling well, and I read them before deciding not to compete there. An agent wins only where thinking out loud is the input, and anything drifting away from that is drifting onto ground where an extension is structurally better.

It is not a problem bank. Nothing ships and nothing generated is written to disk. Problems come from the pattern's discriminator, never from a title, re-skinned to your own domain, and the state files hold a pattern, a verdict, an element id and a date. A problems directory would slowly become a derivative-works corpus, so there is none.

The system-design references, the harness and the Excalidraw canvas are derived from swe-interview-coach under MIT, with attribution per file. The taxonomy, the grading mechanism, the hook, the bank and the interview schema are new. Every constant generalises from one person's log, so they sit in frontmatter and are meant to be changed.

Ong Jun Xiong

ENGINEER · BUILDER · SINGAPORE

EmailHobbiesArchiveNotesGitHubLinkedInSource

© 2026 Ong Jun Xiong