deel-local-cli 2.0.3 → 2.1.1

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.ko.md CHANGED
@@ -20,7 +20,7 @@
20
20
 
21
21
  [![Node.js CI](https://img.shields.io/github/actions/workflow/status/jysvai/deel-local-cli/test.yml?branch=main&logo=github&logoColor=white&label=Node.js%20CI)](https://github.com/jysvai/deel-local-cli/actions/workflows/test.yml)
22
22
  [![CodeQL](https://img.shields.io/github/actions/workflow/status/jysvai/deel-local-cli/codeql.yml?branch=main&logo=github&logoColor=white&label=CodeQL)](https://github.com/jysvai/deel-local-cli/actions/workflows/codeql.yml)
23
- [![tests](https://img.shields.io/badge/tests-14%2C151%20passing-1a7f37?logo=checkmarx&logoColor=white)](docs/ko/develop.md)
23
+ [![tests](https://img.shields.io/badge/tests-14%2C322%20passing-1a7f37?logo=checkmarx&logoColor=white)](docs/ko/develop.md)
24
24
 
25
25
  [![dependencies](https://img.shields.io/badge/dependencies-0-1a7f37)](https://www.npmjs.com/package/deel-local-cli?activeTab=dependencies)
26
26
  [![ESM](https://img.shields.io/badge/ESM-Node%2020%2B-5FA04E?logo=javascript&logoColor=white)](package.json)
@@ -127,6 +127,7 @@
127
127
  | [늘려 쓰기](docs/ko/extend.md) | 스킬 · 플러그인 · MCP · 에이전트 · 훅 · ACP |
128
128
  | [속도와 씀씀이](docs/ko/tuning.md) | 단계별 추론 강도 · 프리픽스 캐시 · 컨텍스트 길이 |
129
129
  | [안전망과 사내 반입](docs/ko/safety.md) | 되돌리기 · 작업 범위 · 감사기록 · 심사 서류 |
130
+ | [사내 배포 안내](docs/ko/company.md) | 처음 쓰는 사람 · 관리 정책 한 장 · 어디로 나가나 · 막는 것의 한계 |
130
131
  | [설정](docs/ko/config.md) · [개발](docs/ko/develop.md) | 환경변수 · 실행 옵션 · 검사 돌리기 · 폴더 구조 |
131
132
  | [릴리스 노트](docs/ko/releases.md) | [1.16.x](docs/ko/releases/1.x.md#116x) · [1.15.x](docs/ko/releases/1.x.md#115x) · [1.14.x](docs/ko/releases/1.x.md#114x) · [1.13.x](docs/ko/releases/1.x.md#113x) · [1.12.x](docs/ko/releases/1.x.md#112x) · [1.10.x](docs/ko/releases/1.x.md#110x) · [1.9.x](docs/ko/releases/1.x.md#19x) · [그 앞](docs/ko/releases.md) |
132
133
 
@@ -322,7 +323,7 @@ deel --offline
322
323
  무엇이 어디로 갈 수 있는지는 켤 때 화면 맨 위에 늘 적혀 있습니다.
323
324
 
324
325
  ```
325
- deel 2.0.3 ⌂ 이 안
326
+ deel 2.1.1 ⌂ 이 안
326
327
  보냄 이 컴퓨터 안 127.0.0.1:11434 ← 여기 말고는 어디로도 안 갑니다
327
328
  ```
328
329
 
@@ -590,7 +591,7 @@ LM Studio 는 `/api/v0/models`, llama.cpp 는 `/props`. 못 알아보면 `(추
590
591
  </picture>
591
592
 
592
593
  무슨 일을 하는 중인지에 따라 **줄 수 있는 도구와 추론 설정이 같이 바뀝니다.**
593
- `Shift+Tab` 으로 차례로 돌리거나, 이름을 그대로 치면 됩니다.
594
+ `Ctrl+O` 로 차례로 돌리거나, 이름을 그대로 치면 됩니다. (`Shift+Tab` 은 승인 방식을 돌립니다.)
594
595
 
595
596
  | 모드 | 하는 일 | 파일을 고치나 | 추론 |
596
597
  |---|---|---|---|
@@ -1140,9 +1141,26 @@ AWS 키 · Google 키 · JWT · 주소에 박힌 열쇠 · `Authorization` 계
1140
1141
 
1141
1142
  | 모드 | 언제 물어보나 |
1142
1143
  |---|---|
1143
- | `auto` (기본) | 안 물어봄. 되돌리기가 안전망 |
1144
+ | `auto` (기본) | 안 물어봄 — **맡기는 모드**. 되돌리기가 안전망. 모델이 되묻거나 계획부터 내도 60초 안에 답이 없으면 알아서 진행(`askWait`) |
1144
1145
  | `confirm` | 되돌릴 수 없는 명령만 |
1145
- | `strict` | 파일 변경·명령 전부 |
1146
+ | `strict` | 파일 변경·명령 전부. **바뀔 내용(diff)을 먼저 보여 주고** 묻습니다 |
1147
+
1148
+ 회사에서 「바꾸기 전에 늘 사람이 본다」 를 정해야 하면 관리 정책에 `"approval": "strict"` 한 줄을
1149
+ 적습니다. 그러면 `/mode` · Shift+Tab · `--yes` · 「앞으로 묻지 않기」 어느 것으로도 그 아래로 못
1150
+ 내립니다 — [사내 배포 안내](docs/ko/company.md).
1151
+
1152
+ **「다 됐다」 는 검사가 정합니다.** 설정에 `"check": "npm test"` 를 적거나 `deel run --check` 로 주면,
1153
+ 모델이 무엇을 바꾸고 끝내려 할 때 deel 이 그 검사를 **직접** 돌립니다. 실패하면 출력을 돌려주고 이어서
1154
+ 고치게 하고(기본 3판), 끝내 실패면 실패로 끝냅니다 — `deel run` 은 종료코드 8. 검사 명령도 모델의
1155
+ 명령과 같은 관문(규칙·훅·승인·위험 명령 차단)을 지납니다 — [완료 검사](docs/ko/company.md#완료-검사).
1156
+
1157
+ **얼마나 해내나는 골든셋으로 잽니다.** `deel eval --init` 이 예제 과제 다섯을 만들고, `deel eval` 이
1158
+ 과제마다 임시 폴더에서 deel 을 돌린 뒤 **모델이 못 본** 정답 검사로 채점합니다. 결과를 남겨 두고
1159
+ 지난번보다 나빠진 과제를 짚습니다 — [골든셋](docs/ko/company.md#골든셋).
1160
+
1161
+ 위험 명령 차단은 **금지 목록 방식**입니다. 목록에 없는 꼴은 새어 나갈 수 있고, 실제로 그렇게
1162
+ 찾아 막아 온 기록이 릴리스 노트에 있습니다. 그래서 하나에 기대지 않고 겹쳐 씁니다 — 작업 범위,
1163
+ 정책 금지 규칙, 승인, 되돌리기, 감사 로그.
1146
1164
 
1147
1165
  되돌리기 이력은 파일 내용을 통째로 담기 때문에 큰 파일을 여러 번 고치면 금방 커집니다.
1148
1166
  32MB 를 넘으면 **최근 50턴만 남기고** 오래된 것을 버립니다. 방금 한 일은 언제나
@@ -1246,7 +1264,7 @@ deel stats # 이 폴더에서 실제로 무엇을 했나 (.de
1246
1264
  ## 개발
1247
1265
 
1248
1266
  ```bash
1249
- npm test 전체 검증 (14,151항목 — 몇몇은 터미널에 따라 갈립니다)
1267
+ npm test 전체 검증 (14,322항목 — 몇몇은 터미널에 따라 갈립니다)
1250
1268
  npm run coverage 검사가 소스의 어디를 밟았는지
1251
1269
  npm run verify 반입·통신 검증만
1252
1270
  npm run bench 편집 성공률 측정
@@ -1303,7 +1321,7 @@ zip 은 진짜 `unzip` 으로, tar 는 진짜 `tar` 가 만든 것을 읽혀 교
1303
1321
  | `exitcode` · `doorparity` | 7 · 19 | 화면에 적은 종료코드 표가 진짜인가 · 문 **네 개**가 같은 것을 주는가 |
1304
1322
  | `no-bundle` | 28 | 배포 묶음에 남의 것이 안 섞였는가 · 검사 파일 위생 |
1305
1323
  | `edit-bench` | 15건 | 편집 성공률 |
1306
- | `mutate` | 어긋 1,733개 | **검사가 정말 지키는가** — 지켜야 할 줄을 일부러 어긋내고 빨개지는지 본다 |
1324
+ | `mutate` | 어긋 1,785개 | **검사가 정말 지키는가** — 지켜야 할 줄을 일부러 어긋내고 빨개지는지 본다 |
1307
1325
 
1308
1326
  > **자세히** — 어디를 밟았는지 · 폴더 구조
1309
1327
  >
@@ -1315,7 +1333,9 @@ zip 은 진짜 `unzip` 으로, tar 는 진짜 `tar` 가 만든 것을 읽혀 교
1315
1333
 
1316
1334
  | 판 | 무엇이 바뀌었나 |
1317
1335
  |---|---|
1318
- | **[2.0.3](docs/ko/releases/2.0.md#203)** | 2.0.2 는 npm 에 못 나갔습니다 — 리눅스에서만 틀리는 검사 둘을 고쳐 2.0.2 를 그대로 냅니다 |
1336
+ | **[2.1.1](docs/ko/releases/2.1.md#211)** | 전체 검수에서 나온 것 — 한 칸 위를 물으면 `config explain` 이 열쇠를 보였고, 웹훅 주소·Sentry DSN 이 셸 환경 거르기와 가리기를 둘 다 지났고, `kill` 로 strict 를 지나 Jobs 서버를 껐고, Def·Refs 가 EUC-KR 을 못 읽었습니다 · `deel eval` 고침 |
1337
+ | [2.1.0](docs/ko/releases/2.1.md#210) | 맡기면 멈추지 않고, 묻게 하면 diff 를 보여 줍니다 — 「다 됐다」 는 검사가 정합니다(`check`) · 얼마나 해내나는 골든셋으로 잽니다(`deel eval`) · 관리 정책 `"approval"` 로 회사가 승인 바닥을 겁니다 |
1338
+ | [2.0.3](docs/ko/releases/2.0.md#203) | 2.0.2 는 npm 에 못 나갔습니다 — 이제 GitHub 릴리스는 npm 에 올라간 뒤에만 생깁니다 |
1319
1339
  | [2.0.2](docs/ko/releases/2.0.md#202) | 2.0.1 까지 넘겨 둔 것을 다 잡았습니다 — `deel run` 에만 없던 MCP 도구, 끝난 명령의 번호로 가던 `taskkill`, 한 번 넘어지면 세션 내내 못 쓰던 MCP 서버 |
1320
1340
  | [2.0.1](docs/ko/releases/2.0.md#201) | 울타리 하나가 안 걸렸습니다 — `sudo -u root bash` 가 `curl … \| bash` 막이를 지나갔습니다 — 그리고 관문 넷이 빨개질 수 없었습니다 |
1321
1341
  | [2.0.0](docs/ko/releases/2.0.md#200) | 있다고 적힌 울타리마다 실제로 걸리는지 다시 봤습니다 — 바뀐 것 전부·어디를 어떻게 고쳤나·어떻게 찾았나(CHA) |
package/README.md CHANGED
@@ -20,7 +20,7 @@ Vendor APIs connect too — **only when you say so**
20
20
 
21
21
  [![Node.js CI](https://img.shields.io/github/actions/workflow/status/jysvai/deel-local-cli/test.yml?branch=main&logo=github&logoColor=white&label=Node.js%20CI)](https://github.com/jysvai/deel-local-cli/actions/workflows/test.yml)
22
22
  [![CodeQL](https://img.shields.io/github/actions/workflow/status/jysvai/deel-local-cli/codeql.yml?branch=main&logo=github&logoColor=white&label=CodeQL)](https://github.com/jysvai/deel-local-cli/actions/workflows/codeql.yml)
23
- [![tests](https://img.shields.io/badge/tests-14%2C151%20passing-1a7f37?logo=checkmarx&logoColor=white)](docs/en/develop.md)
23
+ [![tests](https://img.shields.io/badge/tests-14%2C322%20passing-1a7f37?logo=checkmarx&logoColor=white)](docs/en/develop.md)
24
24
 
25
25
  [![dependencies](https://img.shields.io/badge/dependencies-0-1a7f37)](https://www.npmjs.com/package/deel-local-cli?activeTab=dependencies)
26
26
  [![ESM](https://img.shields.io/badge/ESM-Node%2020%2B-5FA04E?logo=javascript&logoColor=white)](package.json)
@@ -162,6 +162,7 @@ This page is the **summary**. Each section links to the detail behind it.
162
162
  | [Extending](docs/en/extend.md) | Skills · plugins · MCP · subagents · hooks · ACP |
163
163
  | [Speed and spend](docs/en/tuning.md) | Per-stage effort · the prefix cache · context length |
164
164
  | [Safety and corporate review](docs/en/safety.md) | Undo · working scope · audit log · the review package |
165
+ | [Rolling it out at work](docs/en/company.md) | First-time users · one policy file · where requests go · what the guards cannot do |
165
166
  | [Configuration](docs/en/config.md) · [Development](docs/en/develop.md) | Env vars · run flags · running the tests · folder layout |
166
167
  | [Release notes](docs/en/releases.md) | [1.16.x](docs/en/releases/1.x.md#116x) · [1.15.x](docs/en/releases/1.x.md#115x) · [1.14.x](docs/en/releases/1.x.md#114x) · [1.13.x](docs/en/releases/1.x.md#113x) · [1.12.x](docs/en/releases/1.x.md#112x) · [1.10.x](docs/en/releases/1.x.md#110x) · [1.9.x](docs/en/releases/1.x.md#19x) · [older](docs/en/releases.md) |
167
168
 
@@ -395,7 +396,7 @@ deel --offline
395
396
  The destination is printed at the top of every session:
396
397
 
397
398
  ```
398
- deel 2.0.3 ⌂ inside
399
+ deel 2.1.1 ⌂ inside
399
400
  Sends to this machine 127.0.0.1:11434 ← nowhere else
400
401
  ```
401
402
 
@@ -659,7 +660,7 @@ Korean IME composition, paste, `Ctrl+A/E` and backspace all keep working.
659
660
  </picture>
660
661
 
661
662
  What you are working on changes **which tools the model is given and how hard it thinks.**
662
- Cycle with `Shift+Tab`, or type the name.
663
+ Cycle with `Ctrl+O`, or type the name. (`Shift+Tab` cycles the approval policy.)
663
664
 
664
665
  | Mode | For | Can edit files | Reasoning |
665
666
  |---|---|---|---|
@@ -1242,9 +1243,27 @@ does not ask.
1242
1243
 
1243
1244
  | Mode | Asks when |
1244
1245
  |---|---|
1245
- | `auto` (default) | Never — undo is the safety net |
1246
+ | `auto` (default) | Never — **the hand-off mode**; undo is the safety net. If the model asks a question or offers a plan first, it carries on by itself after 60 seconds with no answer (`askWait`) |
1246
1247
  | `confirm` | Irreversible commands only |
1247
- | `strict` | All file changes and commands |
1248
+ | `strict` | All file changes and commands. **Shows the change (a diff) first**, then asks |
1249
+
1250
+ If your organisation needs "a person sees every change before it lands", put `"approval": "strict"`
1251
+ in the managed policy. Then `/mode`, Shift+Tab, `--yes` and "don't ask again" all stop at that
1252
+ floor — see [Rolling it out at work](docs/en/company.md).
1253
+
1254
+ **A check, not the model, says when it is done.** Put `"check": "npm test"` in the config or pass
1255
+ `deel run --check`, and when the model has changed something and tries to finish, deel runs that check
1256
+ **itself**. A failure goes back to the model to keep fixing (3 rounds by default); still failing ends as
1257
+ failed — `deel run` exits with 8. The check goes through the same gates as the model's own commands
1258
+ (rules, hooks, approval, the dangerous-command guard) — see [Completion check](docs/en/company.md#completion-check).
1259
+
1260
+ **How much it gets done is measured with a golden set.** `deel eval --init` creates five example tasks,
1261
+ and `deel eval` runs deel on each one in a temp folder, then grades it with checks **the model never saw**.
1262
+ Results are kept, and tasks that got worse since last time are pointed out — see [Golden set](docs/en/company.md#golden-set).
1263
+
1264
+ Blocked commands are a **deny list**. Shapes the list does not know can get through, and the
1265
+ release notes record the ones found and closed so far. So no single layer is relied on: working
1266
+ scope, policy deny rules, approval, undo and the audit log are stacked.
1248
1267
 
1249
1268
  Undo history stores whole file contents, so repeated edits to large files add up. Past 32MB
1250
1269
  it keeps the **most recent 50 turns** and drops the rest. What you just did is always
@@ -1340,7 +1359,7 @@ Stored in `~/.deel/config.json`. A `.deel/config.json` in the project folder tak
1340
1359
  ## Development
1341
1360
 
1342
1361
  ```bash
1343
- npm test Full suite (14,151 checks; a few are TTY-dependent)
1362
+ npm test Full suite (14,322 checks; a few are TTY-dependent)
1344
1363
  npm run coverage Which lines the tests actually execute
1345
1364
  npm run verify Import + network checks only
1346
1365
  npm run bench Edit success rate
@@ -1384,7 +1403,7 @@ so one run tells you everything.
1384
1403
  | `exitcode` · `doorparity` | 7 · 19 | The printed exit-code table is real · all **four** doors hand out the same thing |
1385
1404
  | `no-bundle` | 28 | Nothing foreign in the published package; test-file hygiene |
1386
1405
  | `edit-bench` | 15 cases | Edit success rate |
1387
- | `mutate` | 1,733 mutants | **Whether the tests actually guard** — break the line on purpose, check it turns red |
1406
+ | `mutate` | 1,785 mutants | **Whether the tests actually guard** — break the line on purpose, check it turns red |
1388
1407
 
1389
1408
  > **More** — Coverage · Layout
1390
1409
  >
@@ -1396,7 +1415,9 @@ so one run tells you everything.
1396
1415
 
1397
1416
  | Version | What changed |
1398
1417
  |---|---|
1399
- | **[2.0.3](docs/en/releases/2.0.md#203)** | 2.0.2 never reached npm — two checks that were wrong only on Linux, fixed; otherwise 2.0.2 as is |
1418
+ | **[2.1.1](docs/en/releases/2.1.md#211)** | From a full review — `config explain` one level up showed keys, webhook URLs and Sentry DSNs passed both the shell-env filter and masking, `kill` stopped a Jobs server past strict, Def/Refs could not read EUC-KR, and `deel eval` fixes |
1419
+ | [2.1.0](docs/en/releases/2.1.md#210) | Hand it off and it keeps going; make it ask and it shows the diff — a check decides when it is done (`check`), a golden set measures how much gets done (`deel eval`), and `"approval"` in the managed policy lets a company set an approval floor |
1420
+ | [2.0.3](docs/en/releases/2.0.md#203) | 2.0.2 never reached npm — GitHub releases now appear only after npm has the version |
1400
1421
  | [2.0.2](docs/en/releases/2.0.md#202) | Everything 2.0.1 left open, closed — MCP tools missing only from `deel run`, `taskkill` sent to an exited command's pid, MCP servers that stayed down for the session after one crash |
1401
1422
  | [2.0.1](docs/en/releases/2.0.md#201) | One fence in 2.0.0 did not hold — `sudo -u root bash` walked through the `curl … \| bash` guard — and four gates could not go red |
1402
1423
  | [2.0.0](docs/en/releases/2.0.md#200) | Every fence we said was there, checked for whether it actually holds — everything that changed, where and how it was fixed, and how it was found (CHA) |
package/bin/deel.js CHANGED
@@ -23,6 +23,7 @@ import { 규칙모으기, 어떻게할까, 확인목록, 확인인자, 확인돌
23
23
  import { 기록자리, 세기, 도구차례, 막힘차례, 셈JSON } from '../src/stats.js';
24
24
  import { 진찰 } from '../src/doctor.js';
25
25
  import { 설명, 설명줄들 } from '../src/configexplain.js';
26
+ import { runEval } from '../src/eval/run.js';
26
27
 
27
28
  const MIN_NODE = 20;
28
29
 
@@ -625,6 +626,8 @@ const BOOL = new Set([
625
626
  'hard',
626
627
  // trust --off · --list, scan --save · --pick. 여태 목록에 없어 뒤의 낱말을 삼킬 수 있었다.
627
628
  'off', 'list', 'save', 'pick',
629
+ // eval --init · --keep (src/eval/run.js).
630
+ 'init', 'keep',
628
631
  ]);
629
632
 
630
633
  /*
@@ -646,6 +649,8 @@ const BOOL = new Set([
646
649
  const 값깃발 = new Set([
647
650
  'root', 'mode', 'work', 'level', 'ctx', 'max-tokens', 'think', 'effort', 'output-schema',
648
651
  'out', 'only', 'days', 'tool', 'url', 'key', 'model', 'host', 'ports', 'timeout', 'rm', 'delete',
652
+ // run --check <명령> (agent/donecheck.js) · eval --repeat <수> (src/eval/run.js).
653
+ 'check', 'repeat',
649
654
  ]);
650
655
  // 값을 줘도 되고 안 줘도 되는 깃발. `--resume` 만 치면 이어 할 대화를 고른다.
651
656
  const 값골라깃발 = new Set(['resume']);
@@ -902,6 +907,8 @@ function help() {
902
907
  say(` ${c.gray('--json')} 결과를 JSON 한 덩이로 (답·도구 횟수·토큰·끝난 까닭)`);
903
908
  say(` ${c.gray('--quiet')} 도구가 무엇을 했는지 안 적음 (오류는 그래도 적음)`);
904
909
  say(` ${c.gray('--yes')} 승인이 필요한 것도 그냥 실행. ${c.yellow('기본은 거부입니다')}`);
910
+ // 새로 넣는 도움말 줄은 말() 로 적는다 — 영어로 켠 사람에게 한국어 줄을 더 새게 하지 않는다(test/langleak.test.js 의 래칫).
911
+ say(` ${c.gray('--check <cmd>')} ${말('cli.checkFlag')}`);
905
912
  /*
906
913
  * 답의 모양을 못 박는 자리 (src/agent/outschema.js).
907
914
  *
@@ -914,7 +921,13 @@ function help() {
914
921
  // 이 줄은 src/oneshot.js 의 EXIT 와 짝이다. test/exitcode.test.js 가 둘이
915
922
  // 어긋나면 빨개진다 — 한 판 동안 refusal(6) 이 여기서 빠져 있었고, 그
916
923
  // 사실을 말해 주는 자리가 아무 데도 없었다.
917
- say(` ${c.gray('끝난 까닭이 종료코드에 담깁니다:')} ${c.gray('0 끝냄 · 1 오류 · 2 걸음수상한 · 3 헛돎 · 4 중단 · 5 말없이끊김 · 6 거절 · 7 모양안맞음 · 64 사용법틀림')}`);
924
+ say(` ${c.gray('끝난 까닭이 종료코드에 담깁니다:')} ${c.gray('0 끝냄 · 1 오류 · 2 걸음수상한 · 3 헛돎 · 4 중단 · 5 말없이끊김 · 6 거절 · 7 모양안맞음 · 8 검사실패 · 64 사용법틀림')}`);
925
+ say('');
926
+ say(` ${c.bold(말('cli.evalTitle'))} ${c.gray(말('cli.evalWhy'))}`);
927
+ say('');
928
+ say(` ${c.cyan('deel eval --init')} ${말('cli.evalInit')}`);
929
+ say(` ${c.cyan('deel eval [dir]')} ${말('cli.evalRun')}`);
930
+ say(` ${c.gray('--repeat <n> · --only <name> · --keep · --json')}`);
918
931
  say('');
919
932
  say(` ${c.bold('진단 직접 지정')} ${c.gray('— 설정을 남기지 않고 확인만 할 때')}`);
920
933
  say('');
@@ -1113,6 +1126,8 @@ async function main() {
1113
1126
  quiet: flags.quiet === true || flags.quiet === 'true',
1114
1127
  // 답의 모양을 못 박는다. 이게 있으면 표준출력은 그 JSON 하나다.
1115
1128
  outputSchema: flags['output-schema'] ? String(flags['output-schema']) : undefined,
1129
+ // 끝내려는 자리에서 돌릴 검사 (agent/donecheck.js). 설정의 check 를 이긴다.
1130
+ check: flags.check !== undefined ? String(flags.check) : undefined,
1116
1131
  });
1117
1132
  case '':
1118
1133
  case 'chat':
@@ -1186,6 +1201,24 @@ async function main() {
1186
1201
  return runRules(args, flags);
1187
1202
  case 'doc2md':
1188
1203
  return runDoc2md(args, flags);
1204
+ // 과제 모음(골든셋)을 실제 모델로 돌려 성공률을 잰다 (src/eval/run.js).
1205
+ case 'eval':
1206
+ return runEval({
1207
+ 폴더: args[0] ?? null,
1208
+ init: flags.init === true || flags.init === 'true',
1209
+ repeat: flags.repeat ?? 1,
1210
+ keep: flags.keep === true || flags.keep === 'true',
1211
+ json: flags.json === true || flags.json === 'true',
1212
+ only: flags.only !== undefined ? String(flags.only) : null,
1213
+ 시간초: flags.timeout !== undefined ? Number(flags.timeout) : null,
1214
+ // 과제마다 띄우는 `deel run` 에 그대로 넘길 것 — 바깥 게이트웨이면 --online 이 있어야 돈다.
1215
+ 깃발들: [
1216
+ ...(flags.online === true || flags.online === 'true' ? ['--online'] : []),
1217
+ ...(flags.offline === true || flags.offline === 'true' ? ['--offline'] : []),
1218
+ ...(flags.think ? ['--think', String(flags.think)] : []),
1219
+ ...(flags.effort ? ['--effort', String(flags.effort)] : []),
1220
+ ],
1221
+ });
1189
1222
  case 'sbom':
1190
1223
  return runSbom(flags);
1191
1224
  case 'scan':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deel-local-cli",
3
- "version": "2.0.3",
3
+ "version": "2.1.1",
4
4
  "description": "A coding-agent CLI that runs on local models and private gateways. Zero dependencies, Node 20+.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/acp/serve.js CHANGED
@@ -30,7 +30,8 @@
30
30
  import { existsSync, statSync, realpathSync } from 'node:fs';
31
31
  import { isAbsolute, join, resolve } from 'node:path';
32
32
  import { VERSION } from '../version.js';
33
- import { 규칙모으기, 늘허락, 정책읽기 } from '../safety/policy.js';
33
+ import { 규칙모으기, 늘허락, 정책읽기, 승인바닥 } from '../safety/policy.js';
34
+ import { 검사설정 } from '../agent/donecheck.js';
34
35
  import { 훅읽기 } from '../safety/hooks.js';
35
36
  import { 에이전트읽기 } from '../agent/agents.js';
36
37
  import { 남길것읽기 } from '../safety/shellenv.js';
@@ -546,6 +547,8 @@ export async function acp(opts = {}) {
546
547
  get 눈있나() { return !!conn.vision; },
547
548
  // 적어 둔 허락·금지 규칙 (safety/policy.js). 승인 모드보다 먼저 본다.
548
549
  규칙들: 규칙모으기(cfg),
550
+ // 끝내려는 자리에서 돌릴 검사 (agent/donecheck.js). 터미널과 같은 설정을 읽는다.
551
+ 완료검사: 검사설정(cfg),
549
552
  // 사람이 적어 둔 훅 (safety/hooks.js). 에디터 안에서도 같은 것이 돌아야 한다 —
550
553
  // 여기만 빠지면 「터미널에서는 막히고 에디터에서는 안 막힌다」 가 된다.
551
554
  훅들: 훅정보.훅들,
@@ -569,7 +572,7 @@ export async function acp(opts = {}) {
569
572
  */
570
573
  ask: async (_라벨, o = {}) => o?.def ?? '',
571
574
  askPassword: async () => null,
572
- confirm: (이름, 인자) => 승인묻기(방, 이름, 인자),
575
+ confirm: (이름, 인자, 곁것 = {}) => 승인묻기(방, 이름, 인자, 곁것?.미리보기),
573
576
  };
574
577
 
575
578
  방.ctx.배움 = new 배움(root, homeDir());
@@ -616,7 +619,7 @@ export async function acp(opts = {}) {
616
619
  // deel 의 안전장치를 에디터의 승인 창으로 그대로 내보낸다. 이게 붙는 것과
617
620
  // 안 붙는 것의 차이가 크다 — 안 붙으면 위험한 명령을 물어볼 데가 없어서
618
621
  // 무조건 거부하게 되고, 그러면 에디터 안에서는 아무 일도 못 하는 도구가 된다.
619
- async function 승인묻기(방, 이름, 인자) {
622
+ async function 승인묻기(방, 이름, 인자, 미리보기 = []) {
620
623
  /*
621
624
  * ── 끊긴 턴의 승인은 없다 (사냥5 H5-3) ─────────────────────────────────
622
625
  *
@@ -631,7 +634,13 @@ export async function acp(opts = {}) {
631
634
  */
632
635
  const 신호 = 방.도는신호 ?? null;
633
636
  if (신호?.aborted) return false;
634
- if (방.늘허락.has(이름)) return true;
637
+ /*
638
+ * 관리 정책이 승인 바닥을 걸었으면 「앞으로 묻지 않기」 는 없다 (safety/policy.js 의 승인바닥).
639
+ * 바닥은 바꾸기 전에 사람이 본다는 약속이라, 버튼 한 번으로 그 사람을 빼는 길을 주면
640
+ * 안 된다. 전에 눌러 둔 늘 허락도 이 판에서는 안 먹는다.
641
+ */
642
+ const 바닥걸림 = 승인바닥().바닥 !== 'auto';
643
+ if (방.늘허락.has(이름) && !바닥걸림) return true;
635
644
 
636
645
  const 아이디 = `t${++방.도구번호}`;
637
646
  try {
@@ -644,10 +653,18 @@ export async function acp(opts = {}) {
644
653
  status: 'pending',
645
654
  locations: 도구자리(이름, 인자, null, { 뿌리: 방.root }),
646
655
  rawInput: 인자 ?? {},
656
+ /*
657
+ * 무엇이 바뀌는지를 같이 보낸다 (tools/index.js 의 바뀔내용). 편집기는 이걸 승인 창에
658
+ * diff 로 그린다. 여태는 제목 한 줄과 날것 인자만 보내서, 사람은 old_string·new_string
659
+ * JSON 을 읽고 허락해야 했다. 새 파일이면 oldText 가 null 이다 (ACP 의 Diff 꼴).
660
+ */
661
+ ...(Array.isArray(미리보기) && 미리보기.length
662
+ ? { content: 미리보기.map((것) => ({ type: 'diff', path: 것.경로, oldText: 것.전, newText: 것.후 })) }
663
+ : {}),
647
664
  },
648
665
  options: [
649
666
  { optionId: 'allow_once', name: '이번만 실행', kind: 'allow_once' },
650
- { optionId: 'allow_always', name: `${이름} 은 앞으로 묻지 않기`, kind: 'allow_always' },
667
+ ...(바닥걸림 ? [] : [{ optionId: 'allow_always', name: `${이름} 은 앞으로 묻지 않기`, kind: 'allow_always' }]),
651
668
  { optionId: 'reject_once', name: '하지 않기', kind: 'reject_once' },
652
669
  ],
653
670
  }, { signal: 신호 });
@@ -656,6 +673,7 @@ export async function acp(opts = {}) {
656
673
  if (신호?.aborted) return false;
657
674
  const 결과 = 답?.outcome ?? {};
658
675
  if (결과.outcome !== 'selected') return false; // cancelled 도 여기로 온다
676
+ if (결과.optionId === 'allow_always' && 바닥걸림) return true;
659
677
  if (결과.optionId === 'allow_always') {
660
678
  방.늘허락.add(이름);
661
679
  /*
@@ -882,6 +900,18 @@ export async function acp(opts = {}) {
882
900
  말하기(`\n\n_(${옮긴말('ev.hookBlock')})_\n\n`);
883
901
  break;
884
902
 
903
+ // 완료 검사 (agent/donecheck.js). 에디터에도 무엇을 돌렸고 어떻게 됐는지 적는다.
904
+ case 'check_start':
905
+ 말하기(`\n\n_(${옮긴말('check.start', { 판: ev.판, 최대: ev.최대, 명령: ev.명령 })})_\n\n`);
906
+ for (const 줄 of ev.줄임 ?? []) 말하기(`_(${옮긴말('check.clamped', 줄)})_\n\n`);
907
+ break;
908
+
909
+ case 'check':
910
+ 말하기(`\n\n_(${ev.ok === true ? 옮긴말('check.pass', { 명령: ev.명령 })
911
+ : ev.ok === false ? `${옮긴말('check.fail', { 판: ev.판, 최대: ev.최대, 요약: ev.요약 })} — ${ev.판 < ev.최대 ? 옮긴말('check.retry') : 옮긴말('check.gaveUp', { 판: ev.판 })}`
912
+ : 옮긴말('check.skipped', { 까닭: ev.까닭 })})_\n\n`);
913
+ break;
914
+
885
915
  case 'nudge':
886
916
  말하기(`\n\n_(${ev.why === '요청누락'
887
917
  ? 옮긴말('ev.nudgeMissed', { n: ev.빠진?.length ?? 0 })
@@ -0,0 +1,105 @@
1
+ // 완료 검사 — 「다 됐습니다」 를 모델의 말이 아니라 **검사 결과**로 가른다 (2.1.0).
2
+ //
3
+ // ── 왜 만드나 ───────────────────────────────────────────────────────────
4
+ //
5
+ // 사내 검토에서 나온 말이 둘이었다. 「검증을 정적 검사에 기대면 한계가 있다」 와
6
+ // 「루프를 더 다듬어야 한다」. 재 보니 맞았다. 턴이 끝나는 자리는 **모델이 도구를
7
+ // 안 부르고 말만 한 걸음** 하나였다. 모델이 「고쳤습니다」 라고 하면 그걸로 끝이었다.
8
+ //
9
+ // 확인할 길은 있었다. Verify 는 문법·짝을 읽어서 보고(tools/verify.js), checkmethods 는
10
+ // 이 프로젝트의 검사 명령을 찾아 알려 주고(tools/checkmethods.js), 증거는 「고친 뒤에
11
+ // 돌려서 통과한 검사가 있나」 를 사람에게 보여 준다(agent/evidence.js). 셋 다 **권하거나
12
+ // 보여 주기만** 한다. 돌릴지는 모델이 정했고, 안 돌리고 끝내도 막는 자리가 없었다.
13
+ //
14
+ // 그래서 사람이 검사 명령 하나를 정해 두면(`"check": "npm test"`), 모델이 끝내려는
15
+ // 자리에서 **deel 이 직접** 그 명령을 돌린다.
16
+ //
17
+ // 통과 그대로 끝낸다. 끝맺음에 「완료 검사 통과」 가 붙는다
18
+ // 실패 실패 출력을 모델에게 돌려주고 이어서 고치게 한다 — 정해 둔 판수까지
19
+ // 다 써도 실패 끝내되 **실패로** 끝낸다. `deel run` 은 종료코드 8 을 낸다
20
+ //
21
+ // ── 안 하는 것 ──────────────────────────────────────────────────────────
22
+ //
23
+ // 명령을 **지어내지 않는다.** 정해 둔 것이 없으면 이 고리는 아예 안 돈다. 있을 법한
24
+ // 검사(npm test)를 짐작해 돌리면, 그 프로젝트에서 그게 20분짜리이거나 DB 를 비우는
25
+ // 스크립트일 때 사람이 모르는 채로 돈다.
26
+ //
27
+ // 따로 도는 길도 **안 만든다.** 이 명령은 모델이 부른 Bash 와 똑같은 관문을 지난다 —
28
+ // 적어 둔 금지 규칙, 사람이 건 훅, 승인 모드(strict 면 묻는다), 위험 명령 막이, 열쇠 뺀
29
+ // 환경. 설정에 적힌 명령이라고 관문을 건너뛰면 저장소 설정 한 줄이 strict 를 뚫는다.
30
+ //
31
+ // 아무것도 안 바꾼 턴에는 안 돈다. 물어본 것에 답만 한 턴에 테스트가 도는 것은 소음이다.
32
+ // 고친 뒤 실패를 받고 **다시 아무것도 안 바꾼 채** 끝내려 하면 다시 돌리지 않는다 —
33
+ // 같은 결과가 나올 것을 또 기다리게 하지 않고, 실패로 끝낸다.
34
+
35
+ /** 한 턴에 몇 번까지 「고치고 → 다시 검사」 를 돌까. 기본과 상한. */
36
+ export const 기본판수 = 3;
37
+ export const 최대판수 = 10;
38
+ /** 검사 한 번의 제한 시간(ms). Bash 도구의 상한(10분)과 같다 — 넘겨 봐야 거기서 잘린다. */
39
+ export const 기본시간 = 600_000;
40
+ const 최소시간 = 1_000;
41
+
42
+ /**
43
+ * 설정과 실행 깃발에서 완료 검사를 읽는다. 정해 둔 명령이 없으면 null — 고리를 안 돈다.
44
+ *
45
+ * @param {object} cfg 설정 (`check` · `checkRounds` · `checkTimeout`)
46
+ * @param {string} [덮을명령] `deel run --check` 로 준 명령. 설정보다 이긴다
47
+ * @returns {{명령:string, 판수:number, 시간:number} | null}
48
+ */
49
+ export function 검사설정(cfg = {}, 덮을명령 = undefined) {
50
+ const 날것 = 덮을명령 !== undefined && 덮을명령 !== null ? 덮을명령 : cfg?.check;
51
+ if (typeof 날것 !== 'string') return null;
52
+ const 명령 = 날것.trim();
53
+ if (!명령) return null;
54
+ /*
55
+ * 판수는 1 ~ 10 에서만 받는다. 0 이나 음수를 「끈다」 로 읽으면 명령을 적어 둔 사람이
56
+ * 왜 안 도는지 모른다. 끄려면 check 를 지운다. 숫자가 아니면 기본값이다.
57
+ */
58
+ const n = Number(cfg?.checkRounds);
59
+ const 판수 = Number.isInteger(n) && n >= 1 ? Math.min(n, 최대판수) : 기본판수;
60
+ const 초 = Number(cfg?.checkTimeout);
61
+ const 시간 = Number.isFinite(초) && 초 > 0 ? Math.min(기본시간, Math.max(최소시간, Math.round(초 * 1000))) : 기본시간;
62
+ /*
63
+ * 상한을 넘겨 **줄인 것**은 적어 둔다 — 첫 검사 시작 사건에 실려 화면이 말한다.
64
+ * 느린 통합 검사라 1200초를 적었는데 600초에서 잘려 「시간 초과」 로 판수를 다 쓰면, 사람은
65
+ * 검사가 진짜로 안 되는 줄 안다 (2.1.1 검수).
66
+ */
67
+ const 줄임 = [];
68
+ if (Number.isInteger(n) && n > 최대판수) 줄임.push({ 칸: 'checkRounds', 준값: n, 쓴값: 최대판수 });
69
+ if (Number.isFinite(초) && 초 * 1000 > 기본시간) 줄임.push({ 칸: 'checkTimeout', 준값: 초, 쓴값: 기본시간 / 1000 });
70
+ return { 명령, 판수, 시간, 줄임 };
71
+ }
72
+
73
+ /**
74
+ * 긴 출력에서 **뒤를** 남긴다. 테스트가 무엇이 틀렸는지 말하는 자리는 대개 끝이다 —
75
+ * 앞을 남기면 모델은 「테스트 시작」 줄만 받고 무엇이 틀렸는지 모른다.
76
+ */
77
+ export function 출력꼬리(글, 한도 = 6000) {
78
+ const s = String(글 ?? '');
79
+ if (s.length <= 한도) return s;
80
+ return `…(앞 ${s.length - 한도}자 줄임)\n${s.slice(s.length - 한도)}`;
81
+ }
82
+
83
+ /**
84
+ * 도구 결과 하나를 통과·실패로 가른다. 종료코드 0 으로 끝난 것만 통과다 —
85
+ * 시그널로 죽은 것·시간 초과·못 돌린 것은 다 실패다 (tools/index.js 의 Bash 결과 머리말).
86
+ */
87
+ export function 통과했나(result) {
88
+ return !!result && !result.error && result.failed !== true;
89
+ }
90
+
91
+ /** 실패한 검사를 모델에게 돌려줄 말. 무엇을 돌렸고, 몇 번째이고, 무엇이 나왔는지. */
92
+ export function 실패말({ 명령, 출력, 판, 최대 }, { 영어 = false } = {}) {
93
+ const 남은 = Math.max(0, 최대 - 판);
94
+ const 꼬리 = 출력꼬리(출력);
95
+ if (영어) {
96
+ return `The completion check \`${명령}\` failed (${판}/${최대}). You said the work was done, but the check does not pass.\n\n`
97
+ + `${꼬리}\n\n`
98
+ + 'Read the failure, fix the cause in the code, and finish again. Do not edit or delete the check itself to make it pass.'
99
+ + (남은 ? ` The check runs again when you finish (${남은} more ${남은 === 1 ? 'try' : 'tries'}).` : '');
100
+ }
101
+ return `완료 검사 \`${명령}\` 가 실패했습니다 (${판}/${최대}). 다 됐다고 했는데 검사가 통과하지 않습니다.\n\n`
102
+ + `${꼬리}\n\n`
103
+ + '실패 내용을 읽고 코드에서 원인을 고친 뒤 다시 끝내세요. 검사를 통과시키려고 검사 자체를 고치거나 지우지 마세요.'
104
+ + (남은 ? ` 끝내면 검사가 다시 돕니다 (${남은}번 남음).` : '');
105
+ }