From sim
Interactive test proctor for progressive coding assessments. Trigger on: 'new sim', 'generate a problem', 'practice problem', 'another simulation', or anything about creating timed coding exercises. Also trigger on: 'done', 'finished', 'next level', 'tests pass', 'I'm done' (to verify and advance), 'status', 'timer', 'how am I doing', 'where am I' (to check progress), and 'abandon', 'cancel sim', 'start over' (to end session). Any trigger while a session is active resumes the session. Generates progressive L1-L4 TypeScript projects incrementally, one level at a time, with vitest tests.
How this skill is triggered — by the user, by Claude, or both
Slash command
/sim:sim-progressiveThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Interactive test proctor for progressive coding assessments modeled on the CodeSignal Industry Coding Assessment (ICA) format. Generates levels one at a time, tracks time, verifies test results, and advances automatically. See `references/problem-bank.md` for domain provenance and source links.
Interactive test proctor for progressive coding assessments modeled on the CodeSignal Industry Coding Assessment (ICA) format. Generates levels one at a time, tracks time, verifies test results, and advances automatically. See references/problem-bank.md for domain provenance and source links.
A progressively-built project with:
package.json with vitest (test scripts added per-level)tsconfig.json.node-version set to 22.14.0 (matches CodeSignal's Node environment)throw new Error("Not implemented") stubs (grows per level)Files start with only L1 content. Each subsequent level is appended via Edit when the user completes the current level.
.session.json — Active proctor stateLocation: ~/.claude/skills/sim-progressive/.session.json
Tracks the active session. Created on new sim, cleared on final completion or abandon.
{
"domain": "in-memory-database",
"domainLabel": "In-Memory Database",
"projectDir": "/Users/kellymears/code/anthro/in-memory-database",
"maxLevel": 4,
"currentLevel": 1,
"startedAt": "2026-03-07T14:30:00.000Z",
"levels": {
"1": {
"startedAt": "2026-03-07T14:30:00.000Z",
"completedAt": null,
"methods": ["set", "get", "delete", "keys"],
"targetMinutes": 15
},
"2": {
"startedAt": null,
"completedAt": null,
"methods": ["scan", "scanByValuePrefix", "count", "getRange"],
"targetMinutes": 20
},
"3": {
"startedAt": null,
"completedAt": null,
"methods": ["setAt", "setAtWithTTL", "getAt", "scanAt", "deleteAt"],
"targetMinutes": 25
},
"4": {
"startedAt": null,
"completedAt": null,
"methods": ["backup", "restore", "listBackups", "compact"],
"targetMinutes": 25
}
}
}
All levels are pre-populated at session start (methods come from the problem bank), but only the current level has content generated in the actual files.
.history.json — Completed domain trackingLocation: ~/.claude/skills/sim-progressive/.history.json
Tracks completed sessions to avoid repetition. Append after final completion.
{
"generated": [
{
"domain": "in-memory-database",
"timestamp": "2026-03-07T16:00:00.000Z",
"levels": "L1-L4",
"totalMinutes": 78.88,
"levelTimes": { "1": 12.25, "2": 17.38, "3": 28.17, "4": 21.08 }
}
]
}
Backward-compatible — old entries without timing data still work for domain avoidance.
| User says | Workflow |
|---|---|
| "new sim", "generate a problem", "practice problem", etc. | New Simulation |
| "done", "finished", "next", "tests pass", "I'm done" | Done / Verify |
| "status", "timer", "how am I doing", "where am I" | Status Check |
| "abandon", "cancel sim", "start over" | Clear .session.json, confirm |
Any skill trigger while .session.json exists with active level | Session Resume -> Status Check |
On any trigger, before doing anything else:
~/.claude/skills/sim-progressive/.session.jsoncurrentLevel and projectDir:
ls via Bash).session.json silently, proceed as no sessionabandon or continueRead references/problem-bank.md (in this skill's directory) to see available domains and their level breakdowns. Read .history.json and avoid the most recent 3 domains. Pick one the user hasn't done recently, or invent a new one that follows the same structural pattern.
If the user requests a specific domain, honor that regardless of history. If they request specific levels (e.g., "just L1-L3"), set maxLevel accordingly.
Create the project directory (kebab-case) inside the current working directory.
.node-version22.14.0
package.json{
"name": "<domain-name>",
"type": "module",
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:l1": "vitest run -t L1"
},
"devDependencies": {
"typescript": "^5.0.0",
"vitest": "^3.0.0"
}
}
Only the test:l1 script is included. Additional level scripts are added via Edit when levels are unlocked.
tsconfig.json{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["*.ts"]
}
<DomainName>Interface.tsAbstract class with L1 methods only:
// --- L1: <description> ---Do NOT include L2-L4 methods. They will be appended when those levels are unlocked.
<DomainName>.tsImplementation scaffold with L1 stubs only:
private now: () => number = Date.now for injectable clock (used later by L3/L4)throw new Error("Not implemented")<DomainName>.test.tsVitest tests with L1 describe block only:
describe("L1", () => { ... })beforeEach to set up fresh instancesbeforeEachRun cd <projectDir> && npm install via Bash.
.session.jsonCreate ~/.claude/skills/sim-progressive/.session.json with all level metadata pre-populated. Set currentLevel: 1 and level 1's startedAt to the current ISO timestamp.
Show the Level Dashboard (see Terminal display formats below).
Output clean, tree-structured text. No boxes. Minimal and readable — feel like Claude Code's own terminal output.
<DomainLabel> -- Level <N> of <maxLevel>
Started <time>
Target <targetMinutes> minutes
Implement:
├── method1(args)
├── method2(args)
├── method3(args)
└── method4(args)
Run npm run test:l<N>
Done say "done" when tests pass
.session.json -> get currentLevel (N), projectDircd <projectDir> && npx vitest run -t "L<N>" via Bash.session.json: set level N's completedAt to nowtest:l<N+1> script to package.json via Edit.session.json: set currentLevel to N+1, set level N+1's startedAt to now.history.json with timing data.session.jsoncurrentLevel at N, timer keeps running.session.json.session.json.session.json, output "Session cleared."Files grow level-by-level using Edit, not Write. This is the core behavioral difference from the old skill.
Always read the existing interface, implementation, and test files first to:
Interface file — Insert new level's abstract methods before the final closing } of the abstract class:
old_string: the final "}" of the abstract class (with its surrounding context to be unique)
new_string: new level section comment + abstract method declarations + closing "}"
Implementation file — Insert new level's stubs before the final closing } of the class:
old_string: the final "}" of the class (with surrounding context to be unique)
new_string: new level section comment + stub methods + closing "}"
Test file — Append new describe block at the end of the file, after the last });. For L3+ test blocks, set up the injectable clock: let clock = 0; instance = new Thing(() => clock); in beforeEach.
package.json — Add the new test:l<N> script via Edit on the scripts object.
Style: No boxes. Use tree-style branching for lists, status indicators for levels, and clean spacing. Minimal and readable.
Status indicator convention (plain text, no color emoji):
Level <N> complete -- <elapsed> / <target>
Tests <passed>/<total> passed
● L1 12:34 / 15:00
● L2 17:23 / 20:00
○ L3 --:-- / 25:00
○ L4 --:-- / 25:00
Total <totalElapsed> / <totalTarget>
Loading Level <N+1>...
Completed levels show ●. Levels over target show ● with "over target" suffix. Not-started levels show ○.
Level <N> -- <passed>/<total> passing
Failing:
├── <test name 1>
├── <test name 2>
└── <test name 3>
Elapsed <elapsed> / <target>
Keep going -- say "done" when ready.
<DomainLabel> -- Complete
● L1 12:15 / 15:00
● L2 17:23 / 20:00
● L3 28:10 / 25:00 over target
● L4 21:05 / 25:00
Total 78:53 / 85:00 (+5:00 buffer = 90:00)
Result Pass -- within 90 minute window
All completed levels show ●. Levels over target append "over target" suffix.
<DomainLabel> -- Level <N> of <maxLevel>
● L1 12:15 / 15:00
▸ L2 8:32 elapsed / 20:00 target
○ L3 --:-- / 25:00
○ L4 --:-- / 25:00
Total <totalElapsed> elapsed
Active level shows ▸. Completed levels show ●. Not-started levels show ○.
Follow these rules when designing problems:
Progressive complexity must be structural, not just additive.
Each level should require extending the data model, not rewriting it. The candidate who uses Map<string, string> at L1 and switches to Map<string, { value: string, tags: Set<string> }> at L2 should not have to restructure their L1 code. The test suite enforces this -- L1 tests must still pass at L4.
L4 should be the hardest but not tricky. Difficulty comes from managing more state and more interactions, not from algorithmic puzzles or gotcha edge cases.
Use idiomatic TypeScript return types. Methods should return native types: boolean for success/failure, string | null for lookups that may miss, typed arrays or tuples for collection returns. Define small interfaces or type aliases (e.g., type KeyValue = { key: string; value: string }) for structured returns. Avoid returning formatted strings or stringified booleans -- the tests and interface should feel like real TypeScript code.
L2 is about operations over L1's data, not new entity types. L2 methods should scan, filter, or query L1's data in new ways. Avoid introducing entirely new entity types at L2.
L3 adds time-parameterized variants, not new entities. L3 should add time-parameterized variants of existing L1-L2 methods (e.g., set_at_with_ttl, get_at, scan_at). Timestamps and TTL/expiration are the core L3 pattern.
L4 is the state snapshot pattern. L4 should test backup/restore/transform of the full data state. Avoid making L4 about analytics or computed aggregates.
Include at least one sorted-output method per level.
Time-dependent behavior belongs in L3 or L4, not earlier. Keep L1 and L2 time-independent. L1-L2 tests don't need the injectable clock in beforeEach. L3+ tests should set up let clock = 0; instance = new Thing(() => clock);.
When generating L2+ content, read the existing interface and implementation files first to ensure Edit anchors are correct and new content references existing types/patterns.
Tests should catch common mistakes:
break instead of continue in loops.session.json is the persistence mechanism. Always check it on any trigger.projectDir doesn't exist, clear session silently.maxLevel: 3. L3 completion triggers final summary..node-version file. The CodeSignal platform runs Node 22.14.0.npx claudepluginhub kellymears/agents --plugin simManages AI pair programming sessions with driver/navigator roles, TDD, real-time code review, and quality verification. Invoke when collaborating on code with role-based workflows.
Generates a file of graded practical exercises with hidden solutions for users to practice a topic or code artefact. Verifies answers against real code.
Runs adaptive gauntlet challenges testing codebase understanding via multiple choice, code completion, trace exercises, scoring, and progress tracking.