Running the tests, coverage, the folder layout
Coverage · Layout
npm run coverage Summary
node test/coverage.mjs --file src/repl.js One file in detail
node test/coverage.mjs --json Machine-readableZero dependencies rules out c8 and nyc, so this reads Node's own
NODE_V8_COVERAGE instead — nothing new to get through an import review. It picks up
child processes too, so the cli suite that spawns deel counts like everything else.
Currently 90% overall - and it depends on where you measure.
| Where | How much | Measured on | |
|---|---|---|---|
| Windows | 90.6% | 20,947/23,119 | 1.15.0 |
| Linux | 90.0% | 20,800/23,119 | 1.15.0 (CI) |
Windows-only paths (tools/excel-com.js and friends) are never executed on
Linux, so neither figure is the "right" one - both are right. It also moves
by a few lines per run.
Why the "measured on" column: Linux cannot be measured on the development machine, so the Linux figure here is what that release's CI last reported, not what today's source scores. Putting two numbers side by side without saying they were taken at different times is exactly the mistake that set the floor wrong in 1.14.0.
CI stops the build below 88% on Linux - the lower figure rounded down, minus a point. Setting the floor flush against the measurement makes it a coin toss rather than a gate (it was first set at 90 and went red by 0.04 points). Raise it by measuring on Linux first. The floor is on the total, not per file: forcing the three below up would make the tests start lying.
Three files are deliberately left short.
| File | Now | Why it stops there |
|---|---|---|
tools/excel.js |
71% | The password path needs Excel installed and a genuinely encrypted file. Faking it would produce a test that only looks like it passes |
repl.js |
73% | The keypress paths — Shift+Tab, Ctrl+C, password entry, paste. Reaching them needs a pty, and a pty is a dependency. What the screen prints is measured instead, as a value (ui and tui suites) |
plugins/manage.js |
79% | The GitHub download path. Tests not reaching the network matters more. Folder installs are covered |
npm run mutate break the lines that matter, on purpose
node tools/mutate.mjs --json machine-readableCoverage only tells you a line was executed. A test that runs a line and asserts nothing about it still reports 100%. So it is measured the other way round too: the lines that matter are broken on purpose and the paired test has to turn red. If it stays green, that test is not guarding that line.
What gets broken, and what would go wrong if it stayed broken, lives in
test/mutants.json. When adding one, pair it with exactly one test — pair
it with several and you cannot tell which one caught it; pair it with a slow
one and the whole pass gets slow.
The source is never edited in place. A copy goes to a temp folder and only that copy is mutated, so a crash mid-run cannot leave a mangled working tree. Each paired test is run unmutated first — a test that was already red would look like it caught everything.
CI runs one pass after npm test. A single survivor fails the run.
npm run review2 what is not committed yet
node tools/review2.mjs --since HEAD~3 the last three commits
node tools/review2.mjs --files a.js b.js
node tools/review2.mjs --out report.md write it down
node tools/review2.mjs --model <see: agy models> a different oneThe bugs that keep coming back in this repo are all one family: having a rule is not the same as the rule firing. A pattern that is written down but never matches real input. A test that exists but nobody runs. A fix that is recorded but not actually held.
That family survives because the person who wrote the code also wrote the test. Whoever got the pattern wrong picks the assertion with the same misunderstanding. Put the same mistake in both places and they pass each other.
So the first pass is done by the side that wrote it, and the second pass by a different model — currently Gemini. It does not know this repo, so the house rules (Korean identifiers, zero dependencies, exactly one outbound door) are handed to it along with the diff. Without them it flags those three every time, and filtering that out costs human time — which is exactly what the second review was supposed to buy.
It is not called from our source. agy (the Antigravity CLI) is spawned as a
process, the same way rg and git are borrowed. Calling it from source would
break the "the only outbound door is src/backend/http.js" rule. It runs with
--mode plan, so the second reviewer can only read.
The house rules and the diff go into a temp file and only the path is passed as
an argument. Command lines have a length cap (about 8k on Windows cmd), so a diff
passed as an argument dies on the spot with ENAMETOOLONG.
This is an eye, not a gate. It writes down what it found and blocks nothing — make the second review a red light and people start silencing it first, and then there is one eye fewer.
The files in tools/ that are not npm scripts. They are the run-once-and-commit-
the-result kind, so nothing runs them automatically.
node tools/make-hero.mjs the README header art - rerun when the banner font changes
node tools/split-docs.mjs move the bulk of a fat README out into docs/
node tools/polish-docs.mjs fix the links and "see above" that the split broke
node tools/shot.mjs take the terminal screenshots the docs useA new file in tools/ must be either an npm script or listed here. Neither, and
the no-bundle test flags it as "nobody calls this" — this repo already has
several harnesses that were written and then forgotten.
The tests run through a pipe, so they never see the real screen. These three do.
npm run demo One short task - tools, todo list, undo, the tally
node test/demo-tui.mjs The input box border - one column off and you see it
node test/demo-bigfile.mjs Where a large file gets cut - a fake gateway that really enforces the capAll three replace the model with a fake gateway and keep their settings in a
temporary home (DEEL_HOME) - your real connection is never used. npm run demo
had never actually run: it did not set that temporary home, so it printed one
line ("no saved connection") and exited 0, and nobody asked. It now says so when
it does not run.
bin/deel.js entry point
src/
repl.js the conversation screen — what a person faces
oneshot.js run once and exit (-p)
commands.js 52 slash commands
cmdnames.js just their names - batch mode reads it too
setup.js first-run connection setup
config.js reading and writing config
ui/ansi.js colour · East Asian width
ui/screen.js picking a screen (line mode / box mode)
ui/inputbox.js the box at the bottom — overwrite-in-place, cursor position
ui/status.js status line — model, context, mode, approvals
ui/working.js working phrases — they follow what is happening
ui/motion.js the braille drawing next to the phrase
ui/intro.js the startup motion
ui/banner.js the large name — a line closes and names the boundary
ui/approve.js approval mode display (auto / risky only / everything)
ui/diff.js showing what changed, where it changed
ui/export.js conversation → a one-page report (/export)
ui/wrap.js wrapping to width without breaking colour
ui/level.js simple vs developer
agent/loop.js the agent loop
agent/session.js conversation state + context accounting
agent/modes.js work modes (auto · code · plan · architect · debug · inspect · ask · orchestrator)
agent/route.js picking the mode from what was said
agent/effort.js per-stage reasoning effort
agent/budget.js shares that follow the window — lines read, description length, steps
agent/project.js working out what kind of project this folder is
agent/compact.js summarising compaction
agent/store.js saving and resuming conversations
agent/recall.js searching past conversations (no index, within budget)
agent/memory.js what outlives the conversation
agent/mention.js attaching files with `@`
agent/card.js experienced habits → harness adjustments (/model card)
agent/preset.js models known before they are experienced — Korean-model name tags
backend/http.js the single HTTP layer (the only door out)
backend/detect.js protocol and auth detection
backend/adapter.js absorbing OpenAI/Ollama differences + streaming parser
backend/retry.js when to wait and call again (429 · 5xx · dropped connections)
backend/proxy.js reading HTTPS_PROXY · NO_PROXY — choosing the way out
backend/learn.js learning limits from what the server said (context · reply length)
backend/ctxsize.js reading context length off the model
backend/probe.js 8 diagnostic checks
backend/scan.js scanning for local servers
backend/mcp.js attaching outside tools (MCP, stdio)
tools/index.js 17 tools
tools/edit-match.js staged-relaxation edit matching
tools/outline.js a file's shape, cheaply
tools/verify.js checking what was built
tools/task.js splitting big work off
tools/jobs.js commands that run in the background
tools/todo.js checklists
tools/webfetch.js reading the web (read-only)
tools/encoding.js writing back in the encoding it was read in
tools/xlsx.js Excel → CSV (written here)
tools/docs.js hwpx·docx·pptx → text (written here)
tools/lsp.js Def · Refs — asking the language server
lsp/rpc.js LSP framing (Content-Length + JSON-RPC, written here)
lsp/servers.js finding installed language servers (installs nothing)
lsp/client.js one server: spawn, talk, time out, clean up
lsp/diag.js is the file you just edited sound?
preview/serve.js serving what you built (127.0.0.1 only)
skills/discover.js finding skills, commands and plugins on the machine
plugins/manage.js installing, removing and packing plugins
pack/zip.js ZIP writing (written here, keeps non-ASCII names)
pack/tar.js TAR reading (written here)
pack/selfpack.js review dossier + source bundle
safety/network.js the lock on the way out
safety/guard.js working scope + dangerous-command blocking
safety/undo.js snapshots and undo
safety/audit.js recording what happened, and when
test/ tests (excluded from the published package)
Releases are not published by hand. Push a tag and GitHub Actions runs the full test suite, publishes, and attaches a signed statement that this version was built from this repository at this commit (npm provenance). Anyone can verify the commit hash on the npm page — which is exactly what a corporate review asks for when it wants to know whether the tarball matches the published source.
# after bumping the version in package.json and committing
git tag v1.4.0
git push origin v1.4.0If the tag and package.json disagree, it stops before publishing. npm never lets
the same version be published twice, so a mismatch that gets out cannot be undone.
This repository publishes through trusted publishing (OIDC). The package
settings on npmjs.com name this repository and the workflow filename
(publish.yml), and the workflow publishes with a short-lived credential it
receives at that moment.
Do not add an NPM_TOKEN secret. If a token is present, setup-node writes it
into .npmrc and npm stops using OIDC. This package forbids token publishing
(mfa=publish), so that path ends in 403. An earlier version of this document
told you to create an Automation token; that route is closed.
Renaming publish.yml means changing the registration on npmjs.com too.
Otherwise it becomes an unregistered workflow and is rejected on the spot.
Every file that ships must use LF. .gitattributes pins eol=lf, and
npm run check verifies it on every release.
One thing to know if you work on Windows: git does not retroactively fix files
that were already checked out when .gitattributes appeared. Files pulled down
before that rule existed, under core.autocrlf=true, stay on disk as CRLF — and
npm pack packs the working tree, not git. So git status can be clean while
the published artifact is wrong.
That is exactly how 1.13.0 shipped: 47 of 148 files had CRLF, and the tarball's
shasum did not match the one CI built. Had the shebang been affected, the program
would not have started at all on Linux or macOS
(env: 'node\r': No such file or directory).
When npm run check reports CRLF, this is how to undo it:
# which files disagree
git ls-files --eol | grep 'w/crlf'
# delete and re-check-out (only with a clean working tree)
git ls-files --eol | grep 'w/crlf' | sed 's/.*\t//' | while read -r f; do rm -f "$f"; done
git checkout -- .git add --renormalize . will not fix it: the index is already correct: only the
working tree drifted.
To confirm, compare shasums — for a given commit, npm pack must produce the same
shasum on every OS.