deel-local-cli 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -59,6 +59,8 @@ Zero dependencies · Node 20+ · Exactly one place your source can go
59
59
  - [Auto-compaction](#auto-compaction)
60
60
  - [Resuming a conversation](#resuming-a-conversation)
61
61
  - [Attaching tools from outside (MCP)](#attaching-tools-from-outside-mcp)
62
+ - [Inside your editor (ACP)](#inside-your-editor-acp)
63
+ - [Keeping secrets out of the conversation](#keeping-secrets-out-of-the-conversation)
62
64
  - [Safety](#safety)
63
65
  - [Corporate review package](#corporate-review-package)
64
66
  - [Configuration](#configuration)
@@ -97,6 +99,29 @@ deel audit # full review sheet
97
99
 
98
100
  ## Quick start
99
101
 
102
+ ### The screen speaks English too
103
+
104
+ deel is written in Korean — the code, the function names, the comments. That part stays.
105
+ What you see on screen does not have to.
106
+
107
+ ```bash
108
+ DEEL_LANG=en deel # this run only
109
+ /lang en # and remember it
110
+ /lang # how much is translated so far
111
+ ```
112
+
113
+ Untranslated lines come through in Korean rather than as blanks, and `/lang` tells you exactly
114
+ how many are left.
115
+
116
+ What the model reads follows the same switch. Set it to English and the rules, the mode
117
+ instructions, and the tool descriptions all go out in English — so the model answers you in
118
+ English instead of Korean. That side is cheaper, too: the part of the window that ships on every
119
+ single request drops from about 4,900 tokens to about 3,450 — on a 32k model, from 15% of the
120
+ window to 10.5%.
121
+
122
+ Tool names and argument names stay Korean (`목적`, `할일`, `번호`). Those are identifiers, not
123
+ prose — rename them and the tool stops being called at all.
124
+
100
125
  ### Install
101
126
 
102
127
  ```bash
@@ -351,6 +376,10 @@ Names follow Claude Code / Codex conventions.
351
376
  | Command | What it does |
352
377
  |---|---|
353
378
  | `/help` | Command list |
379
+ | `/lang [ko\|en]` | Screen language. Falls back to Korean for anything not translated yet |
380
+ | `/bell [on\|off]` | Ring and set the window title when a turn ends, or when deel needs an answer |
381
+ | `/consult <profile> <question>` | Ask a second model one question. Your current model stays put |
382
+ | `/lsp [on\|off]` | Language servers — what is installed, and whether `Def`/`Refs` are available. `off` turns post-edit diagnostics off only |
354
383
  | `/context` | What is consuming the context window |
355
384
  | `/ctx [auto\|number]` | Context **length** — re-read it off the model, or set it yourself |
356
385
  | `/grade [small\|medium\|large\|auto]` | Model **grade** — how much it does on its own. A different axis from `/ctx` |
@@ -359,7 +388,10 @@ Names follow Claude Code / Codex conventions.
359
388
  | `/clear` | Clear the conversation (keeps link and rules) |
360
389
  | `/thread [new\|fork\|close\|n]` | Conversation threads — side work in its own context. Link and undo stay shared |
361
390
  | `/learned [clear]` | What deel has picked up on its own — commands that work here, this model's habits |
391
+ | `/pin <text>` | Pin a line — folding and compaction **cannot reach it** |
392
+ | `/evidence [file]` | Evidence — what changed, and what proves it. **What is unproven is listed too** |
362
393
  | `/model` | Switch connection / model |
394
+ | `/model 카드` | Model card — what this model has actually done here, and what deel changed because of it |
363
395
  | `/think <level>` | Reasoning level (`off·low·medium·high·max`) |
364
396
  | `/think 배분 <profile>` | Per-stage profile (`even·save·deep`) |
365
397
  | `/think 자세히` | Stage table — which stage runs at which level and cap |
@@ -782,6 +814,8 @@ Names and arguments match Claude Code, so skills written for that convention wor
782
814
  | `Verify` | Check that what was built **actually works** |
783
815
  | `Task` | Run one chunk of a big job in a **separate context** |
784
816
  | `Jobs` | Inspect, read and stop **background commands** — the other half of `Bash`'s `background` |
817
+ | `Def` | **Where a name is defined** — only shown when a language server is installed |
818
+ | `Refs` | **Every place a name is used** — only shown when a language server is installed |
785
819
 
786
820
  Seven tools here are not in Claude Code — `Append`, `Recall`, `Remember`, `Outline`,
787
821
  `Verify`, `Task`, `Jobs`. Each tool costs 150-400 tokens of schema on **every request**,
@@ -789,7 +823,7 @@ so a test stops you every time the list grows (`test/loop.test.js`). The last fo
789
823
  their cost; here is why.
790
824
 
791
825
  <details>
792
- <summary><b>More</b> — Outline · Verify · Task · Commands that never finish and 9 more</summary>
826
+ <summary><b>More</b> — Outline · Verify · Task · Def · Refs · Commands that never finish and 9 more</summary>
793
827
 
794
828
  ### Seeing a project's shape cheaply — `Outline`
795
829
 
@@ -1181,6 +1215,113 @@ last edit did. That original state comes from the earliest undo snapshot.
1181
1215
  `/diff` is **in the simple level's command list.** As long as `auto` edits without asking,
1182
1216
  a beginner needs a way to see what changed more than anyone.
1183
1217
 
1218
+ ### With a language server, it sees meaning — `Def` and `Refs`
1219
+
1220
+ `Grep` finds **text**; a language server knows **meaning**. Grep for `run` and you get the
1221
+ `run` in a comment, the `run` in a third-party library, the `run` inside a string. Which of
1222
+ those actually call that function is something a person has to open one by one. The model
1223
+ cannot afford that, so it edits based on the first few hits, and **the ones it missed only
1224
+ surface once you run the thing.**
1225
+
1226
+ ```
1227
+ ⏺ Refs(add_up)
1228
+ add_up — used in 3 places · 2 files
1229
+
1230
+ src/use.py (2)
1231
+ 4: return add_up(1, 2)
1232
+ 9: return add_up(x, x)
1233
+ src/other.py (1)
1234
+ 2: value = add_up(9, 9)
1235
+ ```
1236
+
1237
+ `Grep` stays. When you rename something outright you actually need it — a language server
1238
+ does not look at comments, config or docs. These two **add to** what was here; they do not
1239
+ replace it.
1240
+
1241
+ They take a **name**, not a position. LSP asks "this file, this line, this column", but the
1242
+ model does not know the column. Finding out means reading the file first, and that throws
1243
+ away the whole point of the tool. So it resolves the name through `workspace/symbol` first
1244
+ and asks again at that position. When a name exists in several places you get **the list** —
1245
+ it does not pick one and pretend.
1246
+
1247
+ ### It checks the file you just edited
1248
+
1249
+ `Verify` stays too. They do different jobs.
1250
+
1251
+ | | When | What |
1252
+ |---|---|---|
1253
+ | `Verify` | Once, before you finish | **Syntax** (`node --check`, `py_compile`) |
1254
+ | Post-edit diagnostics | Right after an edit, that file only | **Meaning** (undefined names, wrong types, missing arguments) |
1255
+
1256
+ Some things are syntactically fine and still wrong. `node --check` passes all of them.
1257
+
1258
+ ```
1259
+ ⏺ Write(pkg/bad.py)
1260
+ 3 lines
1261
+ language server — pkg/bad.py: 2 errors
1262
+ line 1 error: Type "Literal['x']" is not assignable to declared type "int"
1263
+ line 2 error: "missing_name" is not defined
1264
+ ```
1265
+
1266
+ Until now that only showed up **once something was run**, and running goes through user
1267
+ approval, so it was several steps later. In between, the model treats that file as finished
1268
+ and moves to the next one. When the error finally surfaces you have to trace back, and
1269
+ tracing back costs more than the fix.
1270
+
1271
+ When everything is fine it **says nothing.** A line of "0 errors" after every edit fills the
1272
+ window. And not receiving diagnostics is not the same as having none — when nothing came
1273
+ back, it says nothing rather than inventing an answer.
1274
+
1275
+ ### It installs nothing
1276
+
1277
+ **deel does not install language servers.** It scans PATH; if one is there it uses it, and
1278
+ if not it falls back to `Grep` and `Outline`. This program exists for places where you cannot
1279
+ bring in unapproved software, so a tool running `npm i -g` on its own is out of the question.
1280
+
1281
+ With no server, `Def` and `Refs` **do not appear in the model's tool list at all** — the same
1282
+ way web tools are hidden offline. Leave an unusable tool standing and the model calls it,
1283
+ gets "not available", and calls it again. That round trip costs more than the schema does.
1284
+
1285
+ `/lsp` shows you what is there.
1286
+
1287
+ ```
1288
+ $ /lsp
1289
+
1290
+ ◈ 2 language server(s) on this machine
1291
+ ✓ ts typescript-language-server
1292
+ ✓ py pyright-langserver
1293
+
1294
+ Language of this folder: py · 12 files
1295
+ Tools: Def · Refs
1296
+ Diagnostics after an edit: on
1297
+ Turn post-edit diagnostics on or off: /lsp on · /lsp off
1298
+ ```
1299
+
1300
+ It looks for `ts`, `py`, `go`, `rs`, `java`, `cs`, `cpp`, `rb`, `php` and `lua`. When one is
1301
+ missing it prints the install command **as text only.** Whether to run it is your call.
1302
+
1303
+ <details>
1304
+ <summary>Four things a real server (pyright) taught us</summary>
1305
+
1306
+ A stub server alone would have shown green for all of these.
1307
+
1308
+ - **Servers spell URIs differently than we do.** We send `file:///C:/…`; pyright answers with
1309
+ `file:///c%3A/…` — lowercase drive letter, percent-encoded colon. Compared as strings they
1310
+ never match. Diagnostics arrive correctly, are not found in our table, and become "nothing
1311
+ came back" — and **saying nothing means the file is sound**, so a broken file gets reported
1312
+ as fine. We compare paths, not URIs.
1313
+ - **A server that just started answers empty.** Not because the name is missing but because
1314
+ it has not finished indexing. Asked 0.2s after startup it said no; 0.5s later it said yes.
1315
+ Turning that into "no such name" makes the model create something that already exists. So
1316
+ it asks again a few times, but only while the server is young.
1317
+ - **npm installs two names on Windows.** An extension-less sh script and a `.cmd`. Find the
1318
+ first one and the file plainly exists, so it reports "installed" — but Windows cannot run
1319
+ it. Claiming it is there and then failing is the hardest failure to spot.
1320
+ - **`cmd /s /c` strips the outer pair of quotes.** Wrap the command once and it breaks
1321
+ entirely, and all you see from the outside is "no language server".
1322
+
1323
+ </details>
1324
+
1184
1325
  </details>
1185
1326
 
1186
1327
  ---
@@ -1643,6 +1784,108 @@ missing?" unanswerable.
1643
1784
 
1644
1785
  ---
1645
1786
 
1787
+ ## Inside your editor (ACP)
1788
+
1789
+ A tool that makes you open one more terminal window stops being used after about two weeks.
1790
+ Developers live inside the IDE. So deel speaks **ACP** (Agent Client Protocol) — Zed,
1791
+ JetBrains, Neovim and Emacs attach to it **without changing a line on their side**.
1792
+
1793
+ One command in your editor's settings:
1794
+
1795
+ ```
1796
+ deel acp
1797
+ ```
1798
+
1799
+ The editor spawns that as a child process and exchanges newline-delimited JSON-RPC 2.0 over
1800
+ stdio. It is not a command you type yourself.
1801
+
1802
+ **What you get once it is attached:**
1803
+
1804
+ | In the editor | From deel |
1805
+ |---|---|
1806
+ | Streaming reply pane | The model's text and its reasoning |
1807
+ | Tool list with icons and status | `Read` is a read, `Edit` is an edit, `Bash` is an execution — **the kind is sent**, not just a name |
1808
+ | Clickable file links | The **absolute path** of every file touched |
1809
+ | Approval dialog | deel's safety rails, rendered as the editor's own prompt (`allow once` · `always allow` · `reject`) |
1810
+ | Mode picker | deel's seven work modes (auto · code · plan · architect · debug · ask · orchestrator) |
1811
+ | Stop button | Reaches the turn mid-flight, even while waiting on the model |
1812
+
1813
+ **Still zero dependencies.** Same reason as MCP — newline-delimited JSON-RPC 2.0 is the whole
1814
+ transport, so no SDK is needed.
1815
+
1816
+ <details>
1817
+ <summary><b>Details</b> — the places this breaks silently</summary>
1818
+
1819
+ This protocol fails quietly. The editor shows "the agent is not responding" and nothing
1820
+ anywhere explains why. So these are nailed down by tests (`test/acp.test.js` spawns a real
1821
+ process and talks over a real pipe).
1822
+
1823
+ | The place | Why it matters |
1824
+ |---|---|
1825
+ | **Nothing but ACP on stdout** | The spec says `MUST NOT`. deel has dozens of places that print to the screen; one of them firing in this mode breaks the pipe. Rather than guarding each call site, **the pipe itself is swapped out** — so code written later is safe without knowing about this. What gets printed is not dropped, it goes to stderr |
1826
+ | **Korean split across chunk boundaries** | Pipes break on bytes, not characters. Decoding each chunk separately turns `안녕` into `안<?>하` — and **the JSON still parses**, so no error is raised. The characters are quietly mangled |
1827
+ | **A request with `id: 0`** | ACP clients count from zero. Reading `if (msg.id)` treats the very first `initialize` as a notification and never answers — it hangs the moment it connects |
1828
+ | **Cancellation reaching a running turn** | Cancellation always arrives while something is running; that is what cancellation is. Awaiting each incoming line in order means it **never arrives** |
1829
+ | **When permission cannot be asked** | It is tempting to just run the tool — otherwise nothing works against a client that has not built the approval dialog yet. But that means "if I can't ask, I do as I please". **It does not** |
1830
+
1831
+ **What it does not do yet, stated plainly:**
1832
+
1833
+ | | |
1834
+ |---|---|
1835
+ | `session/load` | Restoring a past conversation means replaying every message as an update. Half-built, the editor opens an empty conversation and the user assumes the history is gone. It reports **`loadSession: false`** |
1836
+ | Image / audio attachments | Most local models cannot read them. Rather than dropping them silently, deel tells the model what it could not read |
1837
+ | MCP servers passed in by the editor | Not launched. That would mean **deel spawning processes named in the editor's config**. "What does this tool launch?" is the first question in a corporate review, and "whatever the editor says" is not an acceptable answer. Only `.deel/mcp.json`, written by a person, is launched |
1838
+
1839
+ </details>
1840
+
1841
+ ---
1842
+
1843
+ ## Keeping secrets out of the conversation
1844
+
1845
+ People rarely paste a key. The leak is almost always **command output**.
1846
+
1847
+ ```
1848
+ env OPENAI_API_KEY=sk-proj-…
1849
+ git remote -v https://user:token@github.com/…
1850
+ curl -v > Authorization: Bearer eyJ…
1851
+ a failing test log the whole connection string
1852
+ ```
1853
+
1854
+ That text goes to the model **and** gets written to `.deel/sessions/*.jsonl` on disk. That
1855
+ file is later re-read by `/recall` and can end up inside a `deel pack` bundle. Leak once and
1856
+ you have several copies.
1857
+
1858
+ So it is masked at the single point where tool output enters the conversation.
1859
+
1860
+ ```
1861
+ ⏺ Bash(env | grep API) done
1862
+ ⊘ 2 secret-looking values entered the conversation (openai · env var) — masked before the model
1863
+ ```
1864
+
1865
+ What it looks for: private-key blocks · OpenAI/Anthropic keys · GitHub tokens · Slack tokens ·
1866
+ AWS keys · Google keys · JWTs · credentials embedded in URLs · `Authorization`-family headers ·
1867
+ env vars named `…KEY` / `…TOKEN` / `…SECRET` / `…PASSWORD`. Plus **the configured gateway key
1868
+ regardless of its shape** — that one is not a guess, it is a known value.
1869
+
1870
+ ### File contents are deliberately not masked
1871
+
1872
+ `.env` is exactly where masking feels most tempting, and exactly where it backfires: the model
1873
+ sees the masked text, edits it, writes it back — and `«가림»` lands where the real key was.
1874
+ **Protecting the secret would destroy it.**
1875
+
1876
+ So on the file side it reports instead of rewriting.
1877
+
1878
+ ```
1879
+ ⏺ Read(.env) 12 lines
1880
+ ! 3 secret-looking values entered the conversation (env var)
1881
+ — file contents are not masked (masking them would erase the key on write-back)
1882
+ ```
1883
+
1884
+ Saying plainly what cannot be stopped beats claiming it was stopped while corrupting the file.
1885
+ Either way it lands in the audit log.
1886
+
1887
+ ---
1888
+
1646
1889
  ## Safety
1647
1890
 
1648
1891
  Instead of approval prompts, the design makes things **reversible**. The default `auto` mode
@@ -1753,25 +1996,36 @@ deel pack --out deel-import.zip
1753
1996
 
1754
1997
  ```
1755
1998
  ✓ deel-import.zip
1756
- 39 files · 100.2KB
1999
+ 94 files · 509.6KB
1757
2000
 
1758
2001
  Dependencies 0
1759
2002
  Install scripts none
1760
2003
  External imports 0
1761
2004
  Network calls 3 sites (configured address only)
1762
- Ports opened none
2005
+ Ports opened 1 site (/preview only)
1763
2006
  ```
1764
2007
 
1765
- The bundled review sheet contains:
2008
+ The zip carries **one document for people and two for machines.** A corporate review is
2009
+ not a human-only process — security feeds an SBOM to a scanner, and operations reads the
2010
+ audit-log spec to write SIEM ingestion rules.
1766
2011
 
1767
- - Dependency list and every external `import` in the source
1768
- - Presence of `preinstall` / `install` / `postinstall` / `prepare`
1769
- - **Every network and process-spawn call site found by scanning the source** (file:line)
1770
- - The three outbound lanes, explained
1771
- - SHA-256 per file (verify with `certutil -hashfile`)
2012
+ | File | What |
2013
+ |---|---|
2014
+ | `반입심사서.txt` | Dependencies · install scripts · **every network and process-spawn call site found by scanning the source** (file:line) · the three outbound lanes · SHA-256 per file |
2015
+ | `sbom.cdx.json` | **SBOM (CycloneDX 1.5).** Feed it straight to a scanner. One component per file with SHA-256; dependencies stated as an **explicit empty array** — "not declared" and "none" are different claims |
2016
+ | `심사명세.json` | Egress list (per lane: when, where, what, how it's stopped, and the source location) · **audit-log spec** (field names and meanings, plus what is never recorded) · file hashes |
1772
2017
 
1773
- It is generated by scanning the source, not written by hand — hand-written sheets drift from reality.
1774
- Use `deel audit` to read it without building a zip.
2018
+ ```bash
2019
+ deel audit # the human-readable sheet only
2020
+ deel sbom # the two machine-readable ones, on stdout (deel sbom | jq)
2021
+ deel sbom --out review.json # to a file
2022
+ deel sbom --only sbom # just the SBOM
2023
+ ```
2024
+
2025
+ All three are generated by scanning the source, never written by hand — hand-written sheets
2026
+ drift, and **a review document that drifts is worse than none.** Find one wrong line and the
2027
+ reviewer stops trusting the rest. The audit-log spec is the one hand-written part, so a test
2028
+ checks it against real log records on every run.
1775
2029
 
1776
2030
  <details>
1777
2031
  <summary><b>More</b> — Diagnosing a corporate gateway</summary>
@@ -1985,7 +2239,7 @@ src/
1985
2239
  backend/scan.js scanning for local servers
1986
2240
  backend/mcp.js attaching outside tools (MCP, stdio)
1987
2241
 
1988
- tools/index.js 15 tools
2242
+ tools/index.js 17 tools
1989
2243
  tools/edit-match.js staged-relaxation edit matching
1990
2244
  tools/outline.js a file's shape, cheaply
1991
2245
  tools/verify.js checking what was built
@@ -1995,6 +2249,12 @@ src/
1995
2249
  tools/webfetch.js reading the web (read-only)
1996
2250
  tools/encoding.js writing back in the encoding it was read in
1997
2251
  tools/xlsx.js Excel → CSV (written here)
2252
+ tools/lsp.js Def · Refs — asking the language server
2253
+
2254
+ lsp/rpc.js LSP framing (Content-Length + JSON-RPC, written here)
2255
+ lsp/servers.js finding installed language servers (installs nothing)
2256
+ lsp/client.js one server: spawn, talk, time out, clean up
2257
+ lsp/diag.js is the file you just edited sound?
1998
2258
 
1999
2259
  preview/serve.js serving what you built (127.0.0.1 only)
2000
2260
  skills/discover.js finding skills, commands and plugins on the machine
@@ -2016,6 +2276,215 @@ test/ tests (excluded from the published package)
2016
2276
 
2017
2277
  ## Release notes
2018
2278
 
2279
+ <details>
2280
+ <summary><b>▸ 1.4.0 — deel gets a face, speaks English, and sees meaning</b> · what changed in seven places</summary>
2281
+
2282
+ <br>
2283
+
2284
+ | | Before | After |
2285
+ |---|---|---|
2286
+ | `/undo` | rolled back files only — the conversation still believed it happened | rewinds **the conversation too** |
2287
+ | Turns that take minutes | you sat watching the screen | a bell and the window title — you can be in another window |
2288
+ | Startup · status bar | looked like any other CLI | letters that grow on start, a status bar that names the boundary |
2289
+ | English speakers | the screen was all unreadable | `/lang en` — the screen **and what the model reads** |
2290
+ | Models per session | one | a different model per chunk — routine work to a small one |
2291
+ | Finding a name | `Grep` only — comments and strings mixed in | `Def` / `Refs` — by **meaning**, when a server is installed |
2292
+ | A file you just edited | you found out by running it | checked **right after** the edit, that file only |
2293
+
2294
+ <br>
2295
+
2296
+ #### 1. `/undo` rewinds the conversation
2297
+
2298
+ Roll back only the files and the conversation still holds the edit. The model believes it
2299
+ just changed that file and builds the next step on top — and nothing on screen says
2300
+ otherwise. Now the messages fold back with the files. Folding can orphan a tool call, which
2301
+ the server answers with a 400, so the same `repairToolPairs` runs over the result.
2302
+
2303
+ #### 2. It tells you when it is done — `/bell`
2304
+
2305
+ A local model can take minutes per turn. A bell and the window title say when it finishes.
2306
+ The bell is ``, but **not one byte reaches a pipe** — with no TTY it writes nowhere. The
2307
+ title ends with ST, not BEL; ending with BEL rings the bell on every title update.
2308
+
2309
+ #### 3. A screen that is deel's own
2310
+
2311
+ On start, `deel` grows into `deel-local`. The status bar names the boundary you are inside
2312
+ with one glyph (`⌂` this folder · `↗` outside · `?` unknown). The animation uses braille and
2313
+ box-drawing only — emoji and geometric shapes are East Asian Ambiguous, so their width
2314
+ varies per terminal and the line drifts by a column.
2315
+
2316
+ #### 4·5. English on screen, and in what the model reads
2317
+
2318
+ `/lang en` switches the screen. Untranslated strings come through in Korean rather than as
2319
+ blanks, and `/lang` counts honestly how many are left.
2320
+
2321
+ But switching only the screen leaves the model answering in Korean — its rules say to. So
2322
+ what the model reads switches too (base rules, mode instructions, all sixteen tool
2323
+ descriptions). There is a bonus: Korean costs about one token per character and English
2324
+ about one per 3.6, so the fixed share of a 32k window dropped from **4,910 to 3,446 tokens.**
2325
+
2326
+ Tool names and argument names are **not** translated. Those are identifiers.
2327
+
2328
+ #### 6. Several models in one session — `Task`'s `모델`
2329
+
2330
+ A large model and a small one, together, on 8GB of RAM. Routine work (formatting, repetitive
2331
+ edits, short summaries) goes to the small one; you keep what needs judgement. The subtask's
2332
+ endpoint opens through `allowTemporarily` and **always closes in `finally`** — afterwards
2333
+ exactly one endpoint is open again.
2334
+
2335
+ #### 7. Language servers — `Def`, `Refs`, post-edit diagnostics
2336
+
2337
+ See "With a language server, it sees meaning" above. **It installs nothing.**
2338
+
2339
+ </details>
2340
+
2341
+ <details>
2342
+ <summary><b>▸ 1.3.0 — evidence instead of claims, the editor instead of a terminal</b> · what changed in six places</summary>
2343
+
2344
+ <br>
2345
+
2346
+ | | Before | After |
2347
+ |---|---|---|
2348
+ | Rules that must hold | **vanished** when folded or summarised | live outside the message list, where folding cannot reach |
2349
+ | This model's habits | the prompt **asked** it to behave | the harness changes instead — no cooperation needed |
2350
+ | Finishing | "all done" | what changed, and what proves it. **Including what doesn't** |
2351
+ | Where you use it | one more terminal window | inside your editor (Zed · JetBrains · Neovim · Emacs) |
2352
+ | Review paperwork | one document, for humans | SBOM · egress list · audit spec — **straight into a scanner** |
2353
+ | Keys | one `env` and they were in the conversation and on disk | masked where output enters. Files are **deliberately** left alone |
2354
+
2355
+ <br>
2356
+
2357
+ #### 1. Rules that must hold were vanishing into the fold — `/pin`
2358
+
2359
+ Long conversations fold and summarise earlier turns to make room. A 2026 measurement found
2360
+ **summarisation preserves only about 50% of safety constraints.** If "never touch this
2361
+ folder" lands in the missing half, the model is in a state where it was never told. Nothing
2362
+ appears on screen.
2363
+
2364
+ Pinned lines are **not kept with the messages.** They are appended to the end of the system
2365
+ prompt — and since folding and compaction only touch messages, they are structurally out of
2366
+ reach. Not carefully preserved: **impossible to remove.**
2367
+
2368
+ ```
2369
+ /pin never touch src/legacy
2370
+
2371
+ ✓ Pinned — 2 now (78 tokens)
2372
+ ```
2373
+
2374
+ Up to 12 lines / 240 tokens. Past that it says so and carries the most recent — dropping
2375
+ them quietly would defeat the point of pinning.
2376
+
2377
+ #### 2. Observed habits stayed as words — `/model 카드`
2378
+
2379
+ deel already watched what the model did. But watching was all it did — it **wrote advice
2380
+ into the prompt**: "you keep truncating arguments, use Append." Small models don't follow
2381
+ that advice. That is what makes them small models.
2382
+
2383
+ Now what it observes becomes **harness settings**. Instead of asking the model, deel changes
2384
+ its own behaviour.
2385
+
2386
+ | Observed | What changes |
2387
+ |---|---|
2388
+ | Arguments truncate often (over 15%) | The cap is raised up front — no wasted first call |
2389
+ | It repeats itself | Three identical calls tolerated becomes two |
2390
+ | Edits miss often | More surrounding lines are shown on a near-miss |
2391
+
2392
+ **Nothing changes before 12 steps.** Pinning down a healthy model because of one unlucky
2393
+ truncation is worse than not learning at all.
2394
+
2395
+ #### 3. Evidence instead of "all done" — `/evidence`
2396
+
2397
+ A 2026 survey found **96% of developers don't fully trust AI-written code, while 48% verify
2398
+ it every time.** 38% said it is harder to review than human code.
2399
+
2400
+ Why harder? Ask a person why they wrote it that way and you get an answer. Agent-written
2401
+ code arrives with **one line: "done."** That line cannot be reviewed.
2402
+
2403
+ ```
2404
+ /evidence
2405
+
2406
+ Changed 3 files · +142 −38
2407
+ Ran 5 commands (1 failed)
2408
+ Unproven 1
2409
+
2410
+ ✗ src/worker.js — the last `npm test` failed — an earlier pass
2411
+ does not prove the current state.
2412
+ ```
2413
+
2414
+ Listing what changed is something `/diff` already does. What only this does is **say that
2415
+ the unproven is unproven.** Three things get caught —
2416
+
2417
+ - Changed something and ran nothing? Nothing was proven
2418
+ - Counting a red test as green means **offering a failing test as evidence**
2419
+ - A check run *before* the edit proves nothing about it — "I ran it earlier" is the most
2420
+ common form of self-deception
2421
+
2422
+ If the build passed and the tests broke *after* it, the earlier green is not evidence. The
2423
+ last thing you ran is red; it cannot have been verified.
2424
+
2425
+ `/evidence filename` writes it as markdown under `.deel/증거/`. The screen scrolls away, and
2426
+ the review happens later, by someone else.
2427
+
2428
+ #### 4. It made you open one more terminal — `deel acp`
2429
+
2430
+ Developers live inside the IDE. A tool that makes you switch windows stops being used after
2431
+ about two weeks. A build that cleared corporate review and then nobody uses is the saddest
2432
+ possible outcome.
2433
+
2434
+ deel now speaks **ACP** (Agent Client Protocol). One line — `deel acp` — in your editor's
2435
+ settings and Zed, JetBrains, Neovim and Emacs attach **without changing a line on their side**.
2436
+
2437
+ The work isn't connecting the pipe; it is **making the editor able to show something**.
2438
+ Kind, location and status are all optional in the spec, so a quick implementation omits all
2439
+ three — and then every tool is the same grey dot and no changed file is clickable.
2440
+
2441
+ Approval flows through too. deel's safety rails render as the editor's own dialog, and
2442
+ "always allow" is remembered for that session. Against a client that cannot ask, it
2443
+ **does not run** — "if I can't ask, I do as I please" is not an option.
2444
+
2445
+ **Still zero dependencies.** Newline-delimited JSON-RPC 2.0 is the whole transport, so no SDK.
2446
+
2447
+ #### 5. Review paperwork only a human could read — `deel sbom`
2448
+
2449
+ A Korean financial-sector rule change on 2026-04-20 opened an exemption to the network-
2450
+ separation mandate. The paperwork demanded at that door is not prose — security feeds an
2451
+ **SBOM to a scanner** for a vulnerability list, and operations reads the audit-log spec to
2452
+ write SIEM ingestion rules.
2453
+
2454
+ `deel pack` now emits three documents.
2455
+
2456
+ | | |
2457
+ |---|---|
2458
+ | `반입심사서.txt` | The human-readable sheet, as before |
2459
+ | `sbom.cdx.json` | CycloneDX 1.5. One component per file with SHA-256. Dependencies as an **explicit empty array** — "not declared" and "none" are different claims |
2460
+ | `심사명세.json` | Egress list (per lane: when, where, what, how it's stopped, source location) · audit-log spec · file hashes |
2461
+
2462
+ All three are generated by scanning the source. The audit-log spec is the one hand-written
2463
+ part, so **a test compares it against real log records on every run** — a review document
2464
+ that drifts is worse than none, and one wrong line costs you the reviewer's trust in the rest.
2465
+
2466
+ #### 6. One `env` put your keys in the conversation and on disk
2467
+
2468
+ People rarely paste a key. The leak is almost always command output — `env`, `git remote -v`,
2469
+ `curl -v`, a failing test log. That text goes to the model **and** is written to
2470
+ `.deel/sessions/*.jsonl`. Leak once, and you have several copies.
2471
+
2472
+ Masking now happens at the single point where tool output enters the conversation:
2473
+ private-key blocks, OpenAI, Anthropic, GitHub, Slack, AWS, Google, JWTs, credentials in URLs,
2474
+ `Authorization`-family headers, and env vars named `…KEY` / `…TOKEN` / `…SECRET` /
2475
+ `…PASSWORD`. The configured gateway key is removed regardless of shape — that one is a known
2476
+ value, not a guess.
2477
+
2478
+ **File contents are deliberately not masked.** Mask `.env` and the model edits the masked text
2479
+ and writes it back, landing a placeholder where the real key was — protecting the secret would
2480
+ destroy it. So on the file side it reports instead of rewriting.
2481
+
2482
+ <br>
2483
+
2484
+ Tests 2,578 → **2,860** · 54/54 files. Earlier releases are on the [tags](https://github.com/jysvai/deel-local-cli/tags) page.
2485
+
2486
+ </details>
2487
+
2019
2488
  <details>
2020
2489
  <summary><b>▸ 1.2.0 — so the conversation doesn't break</b> · what changed in six places</summary>
2021
2490