Skip to content
← Selected projects

Jobforge

2026 · open source · claude code plugin

The coding-interview tools I tried focused on the submitted code. I wanted help with the part before that, when I said “I'll DP this” and started typing. Jobforge is a Claude Code plugin that asks for the plan first and grades that. It also keeps my résumé, target roles, lessons and interview debriefs in local Markdown files.

Figure 01 · The drill loop

One file read, one plan graded, one row written

The hook reads rep-log.md and nothing else. The drill picks what bank.md says is due, generates from the pattern's discriminator, grades the spoken plan against that pattern's required elements, and writes one row with verdict, missing element and due date. A debrief of a real interview writes failures into the same bank.

  • Reads
  • The plan, out loud
  • Row written
SESSIONPLUGIN~/JOBFORGESessionStart hookdrill-banner.py1BANNERYouplan before code2/JOBFORGE:DRILL · ONE REPPick the due patterndue rows in bank.mdGenerate a problemfrom the discriminatorGrade the planpresent · vague · missingWrite the rowverdict · missing · due345patterns/required_elements/jobforge:interview-debrieffailed rows · due +3d6STATE · MARKDOWNrep-log.mdone row per daybank.mdone row per repinterviews/one file eachREADS · ITS ONLY FILEWHAT IS DUEROW
fig. 1 — the drill loop. ① The SessionStart hook reads rep-log.md, the only file it may open, and prints one banner. ② You state the plan before any code. ③ The drill picks the pattern bank.md says is due and generates a problem from that pattern's discriminator. ④ It grades the plan against the pattern's required elements. ⑤ It writes one row: verdict, missing element, due date. ⑥ An interview debrief writes each failed question into the same bank, due three days on.

What it is

It runs as a plugin with one SessionStart hook, six skills, sixteen pattern files and a Python harness for running solutions. State is markdown in ~/jobforge that you can read, edit and remove. I built it mid-prep, interviews on the calendar, from a private version that had run for a few weeks. I chose the initial settings from my practice log.

/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 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.

Explaining the plan before coding

I read the source of six tools before building this. Their mistake classifiers worked from submissions. I wanted to check whether I could explain the solution before typing it, including cases where I could produce correct code without explaining why it worked.

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 and must quote the part of your explanation that supports its mark. “I'll build up the table” is a vague transition. It does not explain the recurrence or the order in which to fill the cells.

/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. 2 — 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. No scheduler, no queue file, nothing to desynchronise.

The same element IDs are used across patterns. 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 one seed was missed on five unrelated patterns. The missed element is also what gets scheduled: the next rep is a different pattern that depends on it, never the same problem three days later.

One banner, one subject

The hook prints one line at session start when you have not drilled today. That is the plugin's only unsolicited reminder. It reads rep-log.md and nothing else. No code path leads from it to the résumé, the target list or the bank, so it cannot nag about them. I did not want it also telling me your LinkedIn headline is stale, when I had opened the tool to practise.

A test checks that the hook stays within those limits. 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 mention of resume, targets, profile, bank or interviews. An extra file read would fail that check.

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. 3 — the hook and what it prints. The banner stays silent on a day already logged, past the target date, and 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é: a tool picking 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 also notes that 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

Where I kept the scope small

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. I kept this plugin focused on the spoken plan, which is the part I wanted an agent to review.

The plugin does not ship a problem bank or save generated questions. Problems come from the pattern's discriminator, never from a title, re-skinned to your own domain. The state files hold a pattern, a verdict, an element id and a date. I only keep the practice record.

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 it sits in frontmatter and is meant to be changed.

Ong Jun Xiong

ENGINEER · BUILDER · SINGAPORE

ContactHobbiesArchiveNotesUI PackGitHubLinkedInSource

© 2026 Ong Jun Xiong