Map your task runner's dependency graph — and fail CI on the cycles, typos, and dead ends hiding in it.
Every repo grows a task runner — a Makefile, a justfile, a pile of npm
"scripts". And every one of them quietly rots: a target depends on a
prerequisite that was renamed last month, two recipes drift into exact copies,
a ci script runs npm run buld, and one fine day two targets end up depending
on each other and make bails with "Circular dependency dropped." None of it
shows up until someone runs the wrong target at the wrong time — usually in CI,
usually on a Friday.
chorewright reads those task definitions statically — no execution, no
network, no config — builds the dependency graph, and reports the structural
problems before they ship.
$ chorewright
chorewright make · 6 target(s), 5 edge(s)
● 2 blocker(s):
Makefile:5 Dependency cycle: build → test → build
↳ Break the loop — one of these targets must not (transitively) depend on itself.
[CW001]
Makefile:13 Target 'release' depends on 'compil', which is not defined.
↳ Define target 'compil', or remove the stale prerequisite (if it was a file, it is missing).
[CW002]
● 1 warning(s):
Makefile:17 Targets 'lint', 'vet' share an identical recipe.
↳ Deduplicate — have 'vet' depend on 'lint' instead of repeating it.
[CW003]
2 blocker · 1 warning · 0 info
Exit code 1 on a blocker, so it drops straight into a pre-commit hook or CI.
Three of the most common task-runner dialects, auto-detected:
| Format | File | Edges are… |
|---|---|---|
| Make | Makefile / GNUmakefile |
prerequisites (target: dep1 dep2) |
| just | justfile |
recipe dependencies (recipe: dep1 dep2) |
| npm | package.json "scripts" |
npm run X / yarn X / pnpm run X found in a script body |
Point it at a directory and it picks the first of Makefile, justfile,
package.json it finds; or name a file and force the dialect with --format.
| Rule | Severity | What it catches |
|---|---|---|
| CW001 | blocker | A dependency cycle among targets (a → b → a). |
| CW002 | blocker | A target references a prerequisite/dependency/script that does not exist — a typo'd or removed name. (For Make, a prerequisite that is an existing file on disk is fine.) |
| CW003 | warning | Two or more targets with an identical recipe body — duplication worth deduping. |
| CW004 | info | An unreferenced target: defined but never listed as anyone's dependency. Likely an intentional entrypoint, possibly dead. Shown only under --dead; never fails the build on its own. |
Severity maps to the exit code: blockers fail, warnings fail only under
--strict, info never fails.
go install github.com/jay-tank/chorewright@latestOr build from source: go build -o chorewright .
chorewright # auto-detect a task file in the current dir
chorewright ./build # inspect a directory
chorewright --format npm . # force the dialect
chorewright --dead # also list unreferenced targets (CW004)
chorewright --dot | dot -Tsvg # emit Graphviz DOT of the dependency graph
chorewright --json # machine-readable output
chorewright --strict # duplicate-recipe warnings fail too
chorewright --no-color # plain output- run: go run github.com/jay-tank/chorewright@latest .Exit codes: 0 clean · 1 a blocker (CW001/CW002), or any finding under
--strict · 2 usage error.
chorewright is a static parser — it never executes make, just, npm, or your recipes, and it makes no network calls. That honesty has edges:
- Make variable expansion and
includeare only partially handled. Target or prerequisite names computed from$(VAR)or pulled in viaincludemay be missed, and pattern rules (%.o: %.c) are skipped rather than modeled. - The CW002 file-existence check for Make is best-effort, resolved against the working directory. A prerequisite that is a generated file not yet on disk can look "undefined."
- npm
rundetection is textual. It findsnpm run X/yarn X/pnpm run Xwritten literally in a script body; it won't follow a script that reaches another through a shell variable, a wrapper, or&&-chained indirection. - justfile parsing covers common syntax, not 100% of just's grammar (parameters, attributes, and expressions in dependencies are simplified).
When in doubt it under-reports rather than inventing edges — a finding is meant to be real.
MIT © Jay Tank