Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

troublemaker

Deliberately inject a small, known bug into a disposable git worktree, so learners can practice finding and fixing it.

中文说明

troublemaker is a teaching tool. It makes trouble on purpose — but only inside a disposable git worktree it creates for you, never in your real code — and it grades the learner's fix against a probe-oracle pipeline.

Status: v0.1.0 — teaching prototype. Read Current limitations before relying on it for anything beyond practice.

Why this exists (teaching philosophy)

Debugging is a skill you learn by debugging. Real codebases are too big, too noisy, and too precious to practice on; synthetic bugs in throwaway exercises are too artificial to be motivating.

troublemaker sits in between: it takes your repository (or the bundled example), checks out a disposable worktree, and injects one small, known, describable bug — a single-character mutation like an off-by-one. The learner then:

  1. reads the bug description,
  2. finds the mutation in the worktree,
  3. fixes it by hand,
  4. re-runs the check until it reports solved,
  5. rolls the worktree back.

The tool exists to make this loop verifiable, not to trick anyone. Every injected scenario ships with:

  • a probe — a log line inserted into the source that reports the buggy behavior at runtime,
  • an oracle — the expected, correct value of that probe,
  • a kill check at injection time proving the oracle actually detects the injected bug (a mutation the oracle cannot detect is rolled back immediately),
  • a gate and a regression command the fixed code must keep passing.

This is the same pattern used by real bug-injection teaching systems: the difficulty lives in the scenario, and the machinery is honest — the oracle never lies about what correct behavior is. The injected bug is never hidden from you; the bug description names it, and the source of truth is the diff between the worktree and your committed code.

What this tool is NOT

  • It is not a sabotage tool. It cannot touch your working tree or any repository you did not point it at, and it never modifies a committed branch — see Safety guarantees.
  • It is not a security tool, not a fuzzer, and not a benchmark against your ability to be fooled.
  • It is not production software. It is an MVP for learning how bug-injection teaching works, published openly so others can study, reuse, and improve the idea.

If you use this tool to inject bugs into someone else's code without their consent, you are misusing it. The design deliberately makes that misuse hard (worktrees, clean-repo requirement, rollback), but no tool can stop a determined abuser. Please use it for what it is for: teaching and learning.

Safety guarantees

These are the technical guarantees the tool actually enforces, not promises:

  1. Only disposable worktrees are touched. Every scenario lives in <repo>/.dsh-teaching-worktrees/<scenarioId>, created with git worktree add --detach <path> HEAD. Your working tree, your branches, your stashes, and your committed history are never modified. rollback removes the worktree and prunes stale worktree records.
  2. A dirty repo is refused. inject fails unless both git diff and git diff --cached are clean, so uncommitted work can never be carried into or corrupted by a scenario.
  3. The bug is always detectable. Injection runs a kill check: the driver must exercise the probe path, and the oracle must observe a mismatch with the mutated code. If the mutation is not detected, the scenario is rolled back and injection fails. You are never handed a bug the checker cannot see.
  4. The repo must contain the scenario's target file, and every anchor must appear exactly once. Injection fails loudly (and rolls back) if the expected pristine source is not present — the tool never guesses or partially mutates.
  5. Failure rolls everything back. Every failed step in inject deletes the worktree it created before the error is returned.
  6. No network, no telemetry, no hidden behavior. The package has zero runtime dependencies; it only runs local git and node commands you can read in the source.
  7. check is read-only. It runs the scenario's gate, driver, and regression commands inside the worktree and reads files; it writes nothing outside .dsh-teaching artifacts already produced by inject.
  8. Commands are not executed through a shell. The tool spawns git and node directly with argument vectors (and whitespace-split manifest commands); a scenario cannot smuggle shell metacharacters through quoting — see the limitation below.

One caveat you should know

The gate, driver, and regression strings in a scenario are commands that execute on your machine with your user privileges, inside the worktree. This is inherent to the design — a scenario must run code to be checked. Treat scenario manifests the way you would treat any executable: only inject scenarios you trust, and only into repositories you own. The built-in scenario runs nothing but node --check and a plain-node regression script.

Current limitations

Honest inventory of what v0.1.0 does not do:

  • One built-in scenario. Only v0-retain-items (an off-by-one in a tiny retainer class) ships. It targets the bundled example practice repo (examples/practice-repo); to use it elsewhere the repo must contain the same target file and anchors.
  • No scenario authoring format yet. Scenarios are code, not data: there is no documented manifest-only way to write a new scenario without editing the package. The on-disk worktree manifest (dsh-teaching-scenario format v1) is produced and consumed, but authoring tooling is future work.
  • Zero-dependency scenarios only. The gate and regression run with plain node; no node_modules install or toolchain is provisioned in the worktree (the harness version behind this tool links node_modules; this standalone CLI does not).
  • Whitespace-split commands only. Manifest gate/regression strings cannot contain quoted arguments or shell operators. That is a deliberate safety trade-off (no shell), and it means complex gate commands need a wrapper script.
  • No auth or remote surface. Everything is local CLI. There is no server, no GUI, and no way to use this over a network.
  • Windows-tested, expects git on PATH. The EOL handling normalizes injected files to LF; other platforms should work but are untested.
  • fix works only for built-in scenarios. The known-fix command needs the scenario definition; it cannot fix a worktree whose scenario the package does not ship.
  • No concurrency control. Injecting into the same repo from two processes at once is not coordinated.
  • The injected probe line is plain console.log. A learner who deletes or renames it trips the tampered verdict — by design — but heavy logging can also slow the driver.

Install

npm install -g dsh-troublemaker
troublemaker --help

Or run without installing:

npx dsh-troublemaker --help

Quick start (5 minutes)

The bundled example is a tiny dependency-free repo. Initialize it, inject, break the loop by fixing the bug, and roll back:

git clone https://github.com/wonderfulcode1/troublemaker.git
cd troublemaker/examples/practice-repo

git init -b main
git add -A
git commit -m "pristine practice repo"

troublemaker inject .
# Injected scenario v0-retain-items into .../.dsh-teaching-worktrees/v0-retain-items
#   gate: PASS
#   kill check: mismatch (oracle detects the injected bug)

troublemaker check . v0-retain-items
# verdict: bug-alive

# Fix the bug by hand in
#   .dsh-teaching-worktrees/v0-retain-items/src/retainer.js
# (`<=` back to `<`), then:

troublemaker check . v0-retain-items
# verdict: solved

troublemaker rollback . v0-retain-items

CLI reference

troublemaker list <repo>                     list injected scenarios under a repo
troublemaker inject <repo> [--scenario ID]   inject a built-in scenario (default: v0-retain-items)
troublemaker check <repo> <scenarioId>       run the full check pipeline (verdict + probes)
troublemaker fix <repo> <scenarioId>         apply the known fix (built-in scenarios only, demo convenience)
troublemaker rollback <repo> <scenarioId>    remove the scenario worktree
troublemaker scenarios                       list built-in scenarios
troublemaker --version | --help

--json   print the command result as JSON

How it works

The learner loop

  1. inject — requires a clean repo. Creates a detached worktree, applies the mutation (each anchor must match exactly once), writes the scenario manifest, runs the gate, then runs the driver and proves the oracle detects the bug (kill check).
  2. check — runs the verdict pipeline and short-circuits at the first failure:
    • tampered — a probe line no longer appears verbatim in its source file.
    • type-fail — the gate command failed on the injected source.
    • bug-alive — the driver ran but at least one probe differs from the oracle.
    • incomplete — the driver never fired a required probe.
    • regression-failed — all probes match but the regression suite failed.
    • solved — all probes match and the regression suite passes.
  3. fix — applies the known fix (built-in scenarios only; learners are expected to fix by hand).
  4. rollback — removes the worktree and prunes stale records.

State lives on disk

Every answer is derived from the worktree's manifest.json and source at call time. The tool holds no per-scenario memory, so any number of shells observe the same truth, and a worktree created by one process can be checked or rolled back by another.

The built-in scenario

v0-retain-items injects an off-by-one into ItemRetainer.push in the bundled example repo: the comparison < is mutated to <=, so the retainer keeps maxItems + 1 items. The probe fires inside finish() and reports the retained items, seen count, and omitted count; the oracle pins the correct values. This is the same scenario used by the DeepSeek Harness teaching feature (bug-injection teaching), ported to run standalone with zero dependencies.

Project status

v0.1.0 is published as an honest MVP. It is deliberately small, the limitations above are written down rather than hidden, and the tool is intended as an educational artifact you can read end to end. Bug reports and ideas for the next scenario are welcome in the issue tracker.

License

MIT — see LICENSE.

About

Deliberately inject a small, known bug into a disposable git worktree so learners can practice finding and fixing it. A teaching tool - read the README for its educational philosophy and safety guarantees.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages