deel-local-cli 1.20.13 → 2.0.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.ko.md +96 -78
- package/README.md +119 -81
- package/bin/deel.js +398 -32
- package/package.json +2 -1
- package/src/acp/jsonrpc.js +87 -7
- package/src/acp/map.js +23 -3
- package/src/acp/serve.js +333 -25
- package/src/agent/agents.js +110 -12
- package/src/agent/askcheck.js +50 -6
- package/src/agent/asks.js +59 -5
- package/src/agent/budget.js +9 -2
- package/src/agent/card.js +8 -2
- package/src/agent/commit.js +168 -16
- package/src/agent/compact.js +93 -21
- package/src/agent/{/354/213/240/353/242/260/353/217/204.js → confidence.js} +1 -1
- package/src/agent/effort.js +85 -15
- package/src/agent/evidence.js +63 -7
- package/src/agent/evolve.js +132 -24
- package/src/agent/grade.js +12 -3
- package/src/agent/loop.js +408 -81
- package/src/agent/memory.js +176 -29
- package/src/agent/mention.js +22 -5
- package/src/agent/models.js +31 -7
- package/src/agent/modes.js +25 -10
- package/src/agent/outschema.js +381 -56
- package/src/agent/pins.js +33 -2
- package/src/agent/project.js +73 -8
- package/src/agent/recall.js +40 -9
- package/src/agent/{/354/204/261/355/225/234/352/270/260/353/241/235.js → recordshape.js} +19 -8
- package/src/agent/review.js +143 -13
- package/src/agent/route.js +253 -26
- package/src/agent/salvage.js +2 -1
- package/src/agent/session.js +457 -31
- package/src/agent/store.js +232 -22
- package/src/agent/threads.js +52 -3
- package/src/backend/adapter.js +508 -85
- package/src/backend/azure.js +12 -1
- package/src/backend/cachemark.js +31 -5
- package/src/backend/clientcert.js +23 -1
- package/src/backend/ctxsize.js +56 -9
- package/src/backend/detect.js +252 -19
- package/src/backend/http.js +383 -42
- package/src/backend/learn.js +101 -11
- package/src/backend/mcp.js +333 -24
- package/src/backend/price.js +61 -13
- package/src/backend/probe.js +188 -36
- package/src/backend/proxy.js +108 -14
- package/src/backend/quota.js +226 -30
- package/src/backend/retry.js +46 -3
- package/src/backend/scan.js +47 -10
- package/src/backend/scanui.js +57 -7
- package/src/backend/toolfit.js +204 -22
- package/src/backend/vision.js +14 -1
- package/src/backend/wire.js +58 -10
- package/src/cmdnames.js +16 -0
- package/src/commands/common.js +70 -0
- package/src/commands/extend.js +300 -0
- package/src/commands/model.js +852 -0
- package/src/commands/view.js +328 -0
- package/src/commands/work.js +823 -0
- package/src/commands.js +193 -2082
- package/src/completion.js +45 -3
- package/src/config.js +452 -39
- package/src/configexplain.js +125 -14
- package/src/doctor.js +164 -26
- package/src/i18n/en.js +22 -0
- package/src/i18n/index.js +31 -1
- package/src/i18n/ja.js +25 -1
- package/src/i18n/ko.js +25 -2
- package/src/i18n/zh.js +25 -1
- package/src/lsp/client.js +111 -9
- package/src/lsp/servers.js +86 -22
- package/src/oneshot.js +325 -65
- package/src/pack/sbom.js +28 -5
- package/src/pack/selfpack.js +54 -5
- package/src/pack/sheet.en.js +24 -3
- package/src/pack/tar.js +45 -8
- package/src/pack/zip.js +103 -8
- package/src/plugins/manage.js +165 -14
- package/src/preview/serve.js +239 -23
- package/src/providers/bedrock.js +30 -5
- package/src/providers/gemini.js +8 -0
- package/src/providers/index.js +17 -1
- package/src/repl.js +254 -61
- package/src/report.js +7 -2
- package/src/reset.js +151 -25
- package/src/safety/audit.js +108 -8
- package/src/safety/authcmd.js +75 -5
- package/src/safety/guard.js +507 -15
- package/src/safety/hooks.js +172 -20
- package/src/safety/keystore.js +178 -18
- package/src/safety/network.js +209 -6
- package/src/safety/policy.js +377 -36
- package/src/safety/runmode.js +9 -2
- package/src/safety/secrets.js +243 -17
- package/src/safety/shellenv.js +64 -3
- package/src/safety/trust.js +298 -32
- package/src/safety/undo.js +409 -17
- package/src/setup.js +175 -17
- package/src/skills/discover.js +122 -19
- package/src/stats.js +57 -16
- package/src/tools/{/355/231/225/354/235/270/353/262/225.js → checkmethods.js} +26 -1
- package/src/tools/clipboard.js +53 -20
- package/src/tools/convert.js +118 -37
- package/src/tools/desc.en.js +48 -5
- package/src/tools/doc2md.js +11 -1
- package/src/tools/docs.js +94 -18
- package/src/tools/edit-match.js +212 -13
- package/src/tools/encoding.js +525 -28
- package/src/tools/excel-com.js +63 -15
- package/src/tools/excel.js +12 -7
- package/src/tools/fastgrep.js +444 -36
- package/src/tools/fig.js +21 -4
- package/src/tools/fsutil.js +235 -21
- package/src/tools/hwpxwrite.js +80 -19
- package/src/tools/ignore.js +55 -7
- package/src/tools/index.js +1029 -145
- package/src/tools/jobs.js +325 -50
- package/src/tools/kiwi.js +50 -5
- package/src/tools/lsp.js +164 -32
- package/src/tools/outline.js +52 -12
- package/src/tools/pdf.js +366 -56
- package/src/tools/shell.js +12 -4
- package/src/tools/spawn.js +203 -18
- package/src/tools/todo.js +73 -4
- package/src/tools/verify.js +312 -43
- package/src/tools/webfetch.js +184 -25
- package/src/tools/xlsx.js +232 -31
- package/src/ui/ansi.js +209 -15
- package/src/ui/banner.js +2 -1
- package/src/ui/complete.js +46 -4
- package/src/ui/diff.js +4 -2
- package/src/ui/export.js +91 -17
- package/src/ui/inputbox.js +58 -18
- package/src/ui/intro.js +9 -2
- package/src/ui/level.js +32 -7
- package/src/ui/md.js +101 -15
- package/src/ui/motion.js +5 -2
- package/src/ui/notify.js +7 -1
- package/src/ui/office.js +37 -5
- package/src/ui/pastechip.js +3 -2
- package/src/ui/pick.js +85 -12
- package/src/ui/prompt.js +176 -13
- package/src/ui/screen.js +2 -1
- package/src/ui/spinner.js +6 -1
- package/src/ui/status.js +80 -12
- package/src/ui/wrap.js +23 -37
- /package/src/agent/{/353/213/250/352/263/204.js" → phase.js} +0 -0
- /package/src/skills/builtin/{/352/271/212/354/235/264/354/236/210/352/262/214-/353/247/214/353/223/244/352/270/260/SKILL.md" → build-deep/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/250/352/267/274/354/260/250/352/267/274-/353/224/224/353/262/204/352/271/205/SKILL.md" → debug-step-by-step/SKILL.md} +0 -0
- /package/src/skills/builtin/{/353/201/235/352/271/214/354/247/200-/355/225/230/352/270/260/SKILL.md" → finish-all/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/212/244/354/212/244/353/241/234-/352/262/200/355/206/240/SKILL.md" → self-review/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/275/224/353/223/234-/354/244/204/354/235/264/352/270/260/SKILL.md" → simplify-code/SKILL.md} +0 -0
- /package/src/skills/builtin/{/354/260/224/353/237/254/353/263/264/352/270/260/SKILL.md" → spike/SKILL.md} +0 -0
- /package/src/skills/builtin/{/352/262/200/354/202/254-/353/250/274/354/240/200/SKILL.md" → test-first/SKILL.md} +0 -0
package/src/tools/verify.js
CHANGED
|
@@ -30,23 +30,62 @@
|
|
|
30
30
|
*/
|
|
31
31
|
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
32
32
|
import { execFile } from 'node:child_process';
|
|
33
|
-
import {
|
|
33
|
+
import { tmpdir } from 'node:os';
|
|
34
|
+
import { extname, dirname, resolve, join, delimiter, isAbsolute, relative, basename, sep } from 'node:path';
|
|
34
35
|
import { walk, SKIP_DIRS, 내부살림 } from './fsutil.js';
|
|
35
36
|
import { 건너뜀말 } from './ignore.js';
|
|
36
|
-
import { 확인법들 } from '
|
|
37
|
+
import { 확인법들 } from './checkmethods.js';
|
|
37
38
|
import { decode, looksBinary } from './encoding.js';
|
|
38
|
-
import {
|
|
39
|
+
import { 셸환경 } from '../safety/shellenv.js';
|
|
39
40
|
import { 말 } from '../i18n/index.js';
|
|
40
41
|
|
|
41
42
|
/* 결과 한 줄을 잇는다 — 빈 조각은 버린다(tools/index.js 의 이어 와 같은 것). */
|
|
42
43
|
const 이어 = (...조각들) => 조각들.filter((x) => x != null && String(x) !== '').join(' · ');
|
|
43
44
|
|
|
44
|
-
/**
|
|
45
|
+
/**
|
|
46
|
+
* 짝이 맞아야 하는 HTML 태그. 안 닫아도 되는 것(void)은 뺀다.
|
|
47
|
+
*
|
|
48
|
+
* ── 닫는 태그를 **규격이 생략해도 된다고 적어 둔 것**도 같이 뺀다 ────────
|
|
49
|
+
*
|
|
50
|
+
* 여기 void 만 들어 있던 때, 아래 것들이 전부 「안 닫았습니다」 로 찍혔다 —
|
|
51
|
+
*
|
|
52
|
+
* <ul><li>하나<li>둘</ul> <p>첫째<p>둘째 <table><tr><td>a<td>b</table>
|
|
53
|
+
*
|
|
54
|
+
* 브라우저가 멀쩡히 그리는 마크업이고, HTML 이 **허락하는** 꼴이다. 같은 판의
|
|
55
|
+
* tools/webfetch.js 머리말이 이미 그렇게 적어 뒀다 — 「옛 사내 페이지에는 정말로
|
|
56
|
+
* 그렇게 적혀 있다」. 두 파일이 같은 사실에 반대로 굴고 있었다.
|
|
57
|
+
*
|
|
58
|
+
* 이 자리는 `failed: true` 를 세운다. 거짓 탈 하나면 걸음이 거기서 안 끝나고,
|
|
59
|
+
* 모델은 멀쩡한 줄을 고치러 간다 — 바로 아래 html보기 머리말이 「확인 안 하는
|
|
60
|
+
* 것보다 나쁘다」 고 적어 둔 그것이다. 여는 태그를 안 쌓으니 짝짓기에서 통째로
|
|
61
|
+
* 빠지고, `<div>` · `<span>` · `<table>` 처럼 정말 닫아야 하는 것은 그대로 잡는다.
|
|
62
|
+
*/
|
|
45
63
|
const 안닫아도되는것 = new Set([
|
|
46
64
|
'area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link',
|
|
47
65
|
'meta', 'param', 'source', 'track', 'wbr', '!doctype', '!--',
|
|
66
|
+
// 규격이 닫는 태그 생략을 허락하는 것들
|
|
67
|
+
'html', 'head', 'body', 'p', 'li', 'dt', 'dd', 'option', 'optgroup',
|
|
68
|
+
'thead', 'tbody', 'tfoot', 'tr', 'td', 'th', 'colgroup', 'rt', 'rp',
|
|
48
69
|
]);
|
|
49
70
|
|
|
71
|
+
/**
|
|
72
|
+
* 여는 태그가 **스스로 닫았나.**
|
|
73
|
+
*
|
|
74
|
+
* `뒤.endsWith('/')` 하나로 봤더니 `<a href=/>` 가 self-closing 으로 잡혔다. 거기 `/` 는
|
|
75
|
+
* 따옴표를 안 쓴 속성값이지 닫는 표시가 아니다(브라우저도 여는 `<a>` 로 읽는다). 그래서
|
|
76
|
+
* 여는 태그를 안 쌓았고, 뒤따르는 `</a>` 가 「여는 <a> 가 없습니다」 라는 거짓 탈이 됐다.
|
|
77
|
+
* 거짓 탈은 확인 안 하는 것보다 나쁘다 — 모델이 멀쩡한 줄을 고치러 간다.
|
|
78
|
+
*
|
|
79
|
+
* 따옴표 안엣것을 지운 뒤, 끝의 `/` **앞**이 빈칸이거나 따옴표이거나 아무것도 없을 때만
|
|
80
|
+
* 닫는 표시로 본다. `=` 나 글자가 붙어 있으면 속성값의 일부다.
|
|
81
|
+
*/
|
|
82
|
+
function 스스로닫았나(뒤) {
|
|
83
|
+
const 민것 = String(뒤).replace(/"[^"]*"|'[^']*'/g, '""').trimEnd();
|
|
84
|
+
if (!민것.endsWith('/')) return false;
|
|
85
|
+
const 앞 = 민것.slice(0, -1);
|
|
86
|
+
return 앞 === '' || /[\s"']$/.test(앞);
|
|
87
|
+
}
|
|
88
|
+
|
|
50
89
|
/**
|
|
51
90
|
* HTML 을 읽어 본다.
|
|
52
91
|
*
|
|
@@ -67,7 +106,7 @@ export function html보기(글, { 있는파일 = () => true } = {}) {
|
|
|
67
106
|
const 닫는가 = m[1] === '/';
|
|
68
107
|
const 이름 = m[2].toLowerCase();
|
|
69
108
|
const 뒤 = m[3] ?? '';
|
|
70
|
-
if (안닫아도되는것.has(이름) ||
|
|
109
|
+
if (안닫아도되는것.has(이름) || 스스로닫았나(뒤)) continue;
|
|
71
110
|
if (!닫는가) { 쌓임.push(이름); continue; }
|
|
72
111
|
// 닫는 태그. 짝이 맞는 자리를 찾는다.
|
|
73
112
|
const i = 쌓임.lastIndexOf(이름);
|
|
@@ -77,19 +116,112 @@ export function html보기(글, { 있는파일 = () => true } = {}) {
|
|
|
77
116
|
}
|
|
78
117
|
for (const x of 쌓임) 탈.push(`<${x}> 를 안 닫았습니다`);
|
|
79
118
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
119
|
+
/*
|
|
120
|
+
* 가리키는 파일이 실제로 있나. 이게 "열었는데 아무것도 안 보인다" 의 첫째 원인이다.
|
|
121
|
+
*
|
|
122
|
+
* ── 「없다」 와 「우리가 못 본다」 를 가른다 ────────────────────────────
|
|
123
|
+
*
|
|
124
|
+
* 여기는 있는파일() 의 답을 참/거짓으로만 봤다. 그래서 부르는 쪽이
|
|
125
|
+
* "이건 내가 못 짚는다" 고 말할 길이 없었고, 못 짚은 것이 전부 **없는
|
|
126
|
+
* 것**으로 적혔다. 실제로 이렇게 났다 —
|
|
127
|
+
*
|
|
128
|
+
* <script src="/static/app.js">
|
|
129
|
+
* ✗ /static/app.js 을 가리키는데 그 파일이 없습니다 ← 멀쩡한 마크업
|
|
130
|
+
*
|
|
131
|
+
* 웹에서 앞의 `/` 는 **문서 뿌리**지 그 파일이 있는 폴더가 아니다. 그걸
|
|
132
|
+
* 파일 폴더 기준으로 이어 붙였으니 당연히 없는 자리가 나온다. 그리고 이
|
|
133
|
+
* 거짓 경보는 조용하지도 않다 — failed 가 참으로 서고, 모델은 멀쩡한
|
|
134
|
+
* 줄을 「고치기」 시작한다. 확인 안 한 것보다 나쁜 쪽이다.
|
|
135
|
+
*
|
|
136
|
+
* 이제 null 은 '없다' 가 아니라 '우리가 못 본다' 는 뜻이다. 못 본 것은
|
|
137
|
+
* 탈로 적지 않고, 부르는 쪽이 「확인 못 한 것」 으로 따로 적는다.
|
|
138
|
+
*/
|
|
139
|
+
/*
|
|
140
|
+
* 참조는 **주석을 뺀 글**에서 찾는다 (2.0.0 6회차 검증6). 태그 짝은 주석을 뺀 뼈로 보면서 참조는
|
|
141
|
+
* 원문에서 찾아, 주석으로 막아 둔 `<script src="지운.js">` 를 없는 파일로 적었다. 뼈를 그대로 안 쓰는
|
|
142
|
+
* 까닭 — 뼈는 script·style 태그의 속성까지 비워서 `<script src>` 가 사라진다.
|
|
143
|
+
* 주소는 퍼센트를 풀어서 찾는다. 브라우저는 `my%20photo.png` 를 `my photo.png` 로 찾는데 글자
|
|
144
|
+
* 그대로 찾아 「없습니다」 를 적었다. 못 푸는 `%` 는 적힌 그대로 찾는다.
|
|
145
|
+
*/
|
|
146
|
+
/*
|
|
147
|
+
* 속성을 고르는 자리는 **놓침과 거짓 탈이 같이 사는** 곳이다 (8회차 확인). 여태 큰따옴표에
|
|
148
|
+
* 소문자에 낱말 경계도 없었다 —
|
|
149
|
+
* `src='logo.png'` · `<IMG SRC="logo.png">` 아예 안 봤다 (놓침)
|
|
150
|
+
* `data-src="avatar.png"` 참조로 세어 「없습니다」 를 적었다 (거짓 탈)
|
|
151
|
+
* 앞에 `-` 나 글자가 붙은 것은 다른 속성이므로 뒤보기로 막고, 작은따옴표도 같이 받고,
|
|
152
|
+
* 속성 이름은 대소문자를 안 가린다. `javascript:` 는 파일이 아니라 코드라 바깥 것에 넣는다.
|
|
153
|
+
*/
|
|
154
|
+
/*
|
|
155
|
+
* 대신 **script·style 의 속은 뺀다** (막판 훑기). 원문을 그대로 훑던 때,
|
|
156
|
+
* 자바스크립트 속의 대입 한 줄이 HTML 참조로 세어졌다 —
|
|
157
|
+
*
|
|
158
|
+
* <script> img.src = 'logo.svg'; el.href = "docs.html"; </script>
|
|
159
|
+
* → logo.svg 을 가리키는데 그 파일이 없습니다 (거짓 탈 · failed:true)
|
|
160
|
+
*
|
|
161
|
+
* 이번 판에 홑따옴표를 받기 시작하면서 훨씬 자주 걸린다 — JS 는 홑따옴표를 더 쓴다.
|
|
162
|
+
* 여는 태그는 **남긴다.** 위 머리말이 적어 둔 대로 `<script src="…">` 는 진짜 참조다.
|
|
163
|
+
* 뒤보기에 점을 더한 것은 닫는 태그가 없는 script 처럼 이 벗기기가 못 미치는 자리용이다
|
|
164
|
+
* (`img.src`). HTML 속성 앞에는 점이 올 수 없으니 잃는 것이 없다.
|
|
165
|
+
*/
|
|
166
|
+
const 참조볼글 = String(글)
|
|
167
|
+
.replace(/<!--[\s\S]*?-->/g, '')
|
|
168
|
+
.replace(/(<(script|style)\b[^>]*>)[\s\S]*?(<\/\2\s*>)/gi, '$1$3');
|
|
169
|
+
for (const m of 참조볼글
|
|
170
|
+
.matchAll(/(?<![-\w.])(?:src|href)\s*=\s*(?:"([^"]*)"|'([^']*)')/gi)) {
|
|
171
|
+
const 곳 = m[1] ?? m[2];
|
|
172
|
+
if (!곳) continue; // src="" 는 가리키는 데가 없다
|
|
173
|
+
if (/^(?:https?:|data:|mailto:|tel:|javascript:|#|\/\/)/i.test(곳)) continue; // 바깥 것은 못 본다
|
|
174
|
+
let 찾을곳 = 곳.split(/[?#]/)[0];
|
|
175
|
+
try { 찾을곳 = decodeURIComponent(찾을곳); } catch { /* 적힌 그대로 */ }
|
|
176
|
+
const 있나 = 있는파일(찾을곳);
|
|
177
|
+
if (있나 === null) continue;
|
|
178
|
+
if (!있나) 탈.push(`${곳} 을 가리키는데 그 파일이 없습니다`);
|
|
85
179
|
}
|
|
86
180
|
|
|
87
181
|
return 탈;
|
|
88
182
|
}
|
|
89
183
|
|
|
184
|
+
/**
|
|
185
|
+
* HTML 이 가리키는 자리를 **무엇을 기준으로** 찾나.
|
|
186
|
+
*
|
|
187
|
+
* 상대 경로(`app.js` · `../lib/x.css`)는 그 HTML 파일이 있는 폴더 기준이다.
|
|
188
|
+
* 앞에 `/` 가 붙은 것은 다르다 — 브라우저가 **문서 뿌리**부터 찾는다. 문서
|
|
189
|
+
* 뿌리가 어디인지는 이 파일을 읽어서 알 길이 없다. 서버 설정에 있고,
|
|
190
|
+
* 프로젝트 뿌리일 수도 public/ 일 수도 dist/ 일 수도 있다.
|
|
191
|
+
*
|
|
192
|
+
* 그래서 프로젝트 뿌리로 한 번 찾아보고, 거기 있으면 있다고 한다. 없으면
|
|
193
|
+
* **없다고는 안 한다** — 문서 뿌리가 딴 데라는 뜻일 뿐인데 없다고 적으면
|
|
194
|
+
* 멀쩡한 줄을 고치게 만든다.
|
|
195
|
+
*
|
|
196
|
+
* @returns {boolean|null} null 은 '우리가 못 봤다'. '없다' 가 아니다.
|
|
197
|
+
*/
|
|
198
|
+
function 참조있나(곳, 파일abs, 뿌리) {
|
|
199
|
+
if (곳.startsWith('/')) {
|
|
200
|
+
if (!뿌리) return null;
|
|
201
|
+
const 자리 = resolve(뿌리, `.${곳}`);
|
|
202
|
+
if (울타리밖(자리, 뿌리)) return null;
|
|
203
|
+
return existsSync(자리) ? true : null;
|
|
204
|
+
}
|
|
205
|
+
const 자리 = resolve(dirname(파일abs), 곳);
|
|
206
|
+
/*
|
|
207
|
+
* 작업 폴더 밖은 **안 짚는다** (2.0.0 6회차 검증6). 짚었더니 있을 때는 조용하고 없을 때는 「없습니다」
|
|
208
|
+
* 라서, `../../` 로 적은 HTML 한 장이 울타리 밖 파일이 있는지 없는지를 모델에게 알려 주는 창이 됐다.
|
|
209
|
+
* 밖은 「못 짚었다」 로 적는다 — 있다고도 없다고도 안 한다.
|
|
210
|
+
*/
|
|
211
|
+
if (뿌리 && 울타리밖(자리, 뿌리)) return null;
|
|
212
|
+
return existsSync(자리);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const 울타리밖 = (자리, 뿌리) => {
|
|
216
|
+
const rel = relative(resolve(뿌리), 자리);
|
|
217
|
+
return rel === '..' || rel.startsWith(`..${sep}`) || isAbsolute(rel);
|
|
218
|
+
};
|
|
219
|
+
|
|
90
220
|
/** CSS 중괄호 짝. 이것만으로도 '스타일이 통째로 안 먹는' 경우는 거의 다 잡힌다. */
|
|
91
221
|
export function css보기(글) {
|
|
92
|
-
|
|
222
|
+
// 주석과 **따옴표 문자열**을 한 무늬로 뺀다 (2.0.0 6회차 검증6). `content: "{"` 의 `{` 를 세어 멀쩡한
|
|
223
|
+
// CSS 에 「중괄호를 안 닫았습니다」 를 적었다. 한 무늬로 훑어야 문자열 속 `/*` 나 주석 속 따옴표에 안 속는다.
|
|
224
|
+
const 뼈 = String(글).replace(/\/\*[\s\S]*?\*\/|"(?:[^"\\\n]|\\[\s\S])*"|'(?:[^'\\\n]|\\[\s\S])*'/g, '');
|
|
93
225
|
const 연것 = (뼈.match(/\{/g) ?? []).length;
|
|
94
226
|
const 닫은것 = (뼈.match(/\}/g) ?? []).length;
|
|
95
227
|
if (연것 === 닫은것) return [];
|
|
@@ -104,26 +236,72 @@ export function json보기(글) {
|
|
|
104
236
|
catch (e) { return [String(e.message)]; }
|
|
105
237
|
}
|
|
106
238
|
|
|
107
|
-
|
|
108
|
-
|
|
239
|
+
/*
|
|
240
|
+
* ── 확인 명령은 **셸 없이**, 우리가 고른 실행 파일로 돌린다 (사냥5 H1) ──────────
|
|
241
|
+
*
|
|
242
|
+
* 여기는 `node --check "<파일>"` 을 글자로 만들어 셸에 넘겼다(윈도우 cmd.exe · 유닉스 /bin/sh).
|
|
243
|
+
* 그리고 checkCommand 를 거치니 Bash 와 같은 안전 검사라고 적어 뒀다. 실제로는 이랬다 —
|
|
244
|
+
*
|
|
245
|
+
* · cmd.exe 는 이름만 준 프로그램을 **PATH 보다 지금 폴더에서 먼저** 찾는다. cwd 가 저장소
|
|
246
|
+
* 뿌리라, 뿌리에 `node.cmd` 가 있으면 그게 돌았다. 받아 온 저장소에서 Verify({}) 한 번이면
|
|
247
|
+
* 승인 없이, strict 모드에서도, **걸러지지 않은 환경**(DEEL_API_KEY·남의 열쇠째)으로.
|
|
248
|
+
* · /bin/sh 는 큰따옴표 안의 `$(…)` 를 푼다. 파일 이름이 `x$(touch P).js` 면 그게 돈다.
|
|
249
|
+
*
|
|
250
|
+
* 이 파일 머리말이 스스로 못 박은 「임의의 명령은 여기서 안 돌린다」 가 바로 이 자리에서
|
|
251
|
+
* 깨졌다. checkCommand 는 위험한 **명령 모양**을 보는 것이라 이 둘을 못 잡는다.
|
|
252
|
+
*
|
|
253
|
+
* 그래서 셸을 안 거친다. node 는 지금 이 프로그램을 돌리는 그 node(process.execPath),
|
|
254
|
+
* 파이썬은 **우리가 PATH 에서** 찾은 절대 경로다 — 빈 칸·`.`·상대 경로 칸과 작업 폴더 안의
|
|
255
|
+
* 칸은 건너뛴다. 파일 경로는 인자 하나로 넘어가니 이름에 무엇이 들었든 글자일 뿐이다.
|
|
256
|
+
* 환경은 Bash 와 같은 거른 환경(safety/shellenv.js)을 준다. 파이썬은 `-I` 로 띄운다 —
|
|
257
|
+
* 안 그러면 `-m py_compile` 이 작업 폴더를 sys.path 앞에 넣어 저장소의 `py_compile.py` 를
|
|
258
|
+
* 가져와 돌린다(같은 구멍의 파이썬 쪽).
|
|
259
|
+
*/
|
|
260
|
+
function 명령돌리기(파일, 인자, { cwd, 제한 = 60000, env } = {}) {
|
|
109
261
|
return new Promise((끝) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
:
|
|
113
|
-
execFile(shell.file, shell.args, {
|
|
114
|
-
cwd, timeout: 제한, maxBuffer: 4 * 1024 * 1024,
|
|
115
|
-
windowsHide: true, windowsVerbatimArguments: shell.verbatim === true,
|
|
116
|
-
encoding: 'buffer',
|
|
262
|
+
execFile(파일, 인자, {
|
|
263
|
+
cwd, env, timeout: 제한, maxBuffer: 4 * 1024 * 1024,
|
|
264
|
+
windowsHide: true, encoding: 'buffer',
|
|
117
265
|
}, (err, so, se) => {
|
|
118
266
|
const 풀기 = (b) => (b && b.length ? decode(Buffer.from(b)).text : '');
|
|
119
267
|
const out = [풀기(so), 풀기(se)].filter(Boolean).join('\n').trim();
|
|
120
268
|
const 시그널 = err?.signal ?? null;
|
|
121
269
|
const code = 시그널 ? null : (err?.code ?? 0);
|
|
122
|
-
끝({ ok: !시그널 && code === 0, code, 시그널, out });
|
|
270
|
+
끝({ ok: !시그널 && code === 0, code, 시그널, out, 없음: err?.code === 'ENOENT' });
|
|
123
271
|
});
|
|
124
272
|
});
|
|
125
273
|
}
|
|
126
274
|
|
|
275
|
+
/**
|
|
276
|
+
* 이름으로 실행 파일을 PATH 에서 찾는다 — 셸이 찾게 두지 않는다 (위 머리말).
|
|
277
|
+
*
|
|
278
|
+
* 지금 폴더를 뜻하는 칸(빈 칸·`.`·상대 경로)과 작업 폴더 안의 칸은 건너뛴다. 그 자리의
|
|
279
|
+
* 파일은 저장소가 심을 수 있다. 윈도우에서는 `.exe` 만 본다 — `.cmd`·`.bat` 은 결국
|
|
280
|
+
* cmd.exe 를 부르는 것이라 셸을 안 거친다는 뜻이 없어진다.
|
|
281
|
+
*
|
|
282
|
+
* @returns {string|null} 절대 경로. 못 찾으면 null — 그러면 「도구가 없다」 로 말한다.
|
|
283
|
+
*/
|
|
284
|
+
function 경로에서찾기(이름들, { env = process.env, 뿌리 = null } = {}) {
|
|
285
|
+
const 윈 = process.platform === 'win32';
|
|
286
|
+
// 윈도우 환경 이름은 대소문자를 안 가린다(Path · PATH). 거른 환경 객체는 평범한 객체라 직접 찾는다.
|
|
287
|
+
const 칸 = Object.keys(env ?? {}).find((k) => (윈 ? k.toUpperCase() === 'PATH' : k === 'PATH'));
|
|
288
|
+
const 목록 = String((칸 && env[칸]) ?? '').split(delimiter);
|
|
289
|
+
const 뿌리abs = 뿌리 ? resolve(뿌리) : null;
|
|
290
|
+
for (const 이름 of 이름들) {
|
|
291
|
+
for (const 날것 of 목록) {
|
|
292
|
+
const 폴더 = 날것.trim().replace(/^"(.*)"$/, '$1');
|
|
293
|
+
if (!폴더 || !isAbsolute(폴더)) continue;
|
|
294
|
+
if (뿌리abs) {
|
|
295
|
+
const rel = relative(뿌리abs, resolve(폴더));
|
|
296
|
+
if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))) continue;
|
|
297
|
+
}
|
|
298
|
+
const 후보 = join(폴더, 윈 ? `${이름}.exe` : 이름);
|
|
299
|
+
try { if (statSync(후보).isFile()) return 후보; } catch { /* 여기엔 없다 */ }
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
127
305
|
/**
|
|
128
306
|
* 폴더를 통째로 볼 때 한 번에 보는 파일 수.
|
|
129
307
|
*
|
|
@@ -133,11 +311,35 @@ function 명령돌리기(cmd, cwd, 제한 = 60000) {
|
|
|
133
311
|
*/
|
|
134
312
|
const 한번에최대 = 40;
|
|
135
313
|
|
|
136
|
-
/**
|
|
137
|
-
|
|
314
|
+
/**
|
|
315
|
+
* 폴더를 훑을 때 고르는 종류 — 아래에서 돌려 보거나 읽어서 볼 줄 아는 것만.
|
|
316
|
+
* `.scss`·`.less` 는 읽는 자리(css보기)는 받는데 고르는 자리에 빠져 있어서 기본 훑기가 안 봤다 (2.0.0 6회차 검증6).
|
|
317
|
+
*/
|
|
318
|
+
const 볼만한확장 = new Set(['.html', '.htm', '.css', '.scss', '.less', '.json', '.js', '.mjs', '.cjs', '.py']);
|
|
319
|
+
const 볼만한것인가 = (p) => 볼만한확장.has(extname(p).toLowerCase());
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* 이 확장자를 돌려 볼 명령이 있나. 없으면 null — 그러면 '못 돌려 봤다' 고 말한다.
|
|
323
|
+
*
|
|
324
|
+
* 돌려주는 것은 글자 명령이 아니라 {파일, 인자} 다 — 셸을 안 거친다(명령돌리기 머리말).
|
|
325
|
+
* 파일이 null 이면 이 컴퓨터에 그 도구가 없다는 뜻이다.
|
|
326
|
+
*/
|
|
327
|
+
function 돌릴명령(확장, { 뿌리 = null, env = process.env } = {}) {
|
|
138
328
|
switch (확장) {
|
|
139
|
-
case '.js': case '.mjs': case '.cjs':
|
|
140
|
-
|
|
329
|
+
case '.js': case '.mjs': case '.cjs':
|
|
330
|
+
return { 파일: process.execPath, 인자: (p) => ['--check', p], 어떻게: 'node --check' };
|
|
331
|
+
case '.py':
|
|
332
|
+
return {
|
|
333
|
+
파일: 경로에서찾기(process.platform === 'win32' ? ['python', 'python3'] : ['python3', 'python'], { env, 뿌리 }),
|
|
334
|
+
/*
|
|
335
|
+
* `-B` 가 없으면 py_compile 이 원본 옆에 `__pycache__/*.pyc` 를 **써 놓는다.**
|
|
336
|
+
* 이 파일 머리말은 여기서 아무것도 안 돌리고 안 남긴다고 적어 두었는데, 확인
|
|
337
|
+
* 한 번에 승인도 감사도 되돌리기도 없는 파일이 작업 폴더에 생겼다 — 그러고는
|
|
338
|
+
* git status 에 뜬다. 바이트코드는 우리가 쓸 것도 아니다.
|
|
339
|
+
*/
|
|
340
|
+
인자: (p) => ['-I', '-X', `pycache_prefix=${join(tmpdir(), 'deel-pyc')}`, '-m', 'py_compile', p],
|
|
341
|
+
어떻게: 'py_compile',
|
|
342
|
+
};
|
|
141
343
|
default: return null;
|
|
142
344
|
}
|
|
143
345
|
}
|
|
@@ -180,16 +382,26 @@ export const VERIFY_TOOL = {
|
|
|
180
382
|
let 넘친수 = 0;
|
|
181
383
|
// .gitignore 로 안 본 것은 셈해 뒀다가 끝에 적는다. 조용히 빼면
|
|
182
384
|
// "빌드 산출물에 탈이 있는데 왜 확인이 통과냐" 를 사람이 못 푼다.
|
|
183
|
-
|
|
385
|
+
// 못 연 폴더·파일도 같이 이고 간다 (fsutil.js 의 walk). 여기서 안 더하면
|
|
386
|
+
// 「확인된 것 3개」 밑에 안 본 자리가 통째로 숨는다.
|
|
387
|
+
const 건너뜀 = { 폴더: 0, 파일: 0, 못연폴더: 0, 못연파일: 0 };
|
|
184
388
|
// 훑기 상한에서 멈춘 것도 같이 챙긴다 — 안 본 것을 "탈 없음" 으로 읽히게 두면 안 된다.
|
|
185
389
|
let 잘림 = false;
|
|
186
390
|
let 상한 = 0;
|
|
187
391
|
const 셈더하기 = (목록) => {
|
|
188
392
|
건너뜀.폴더 += 목록.건너뜀?.폴더 ?? 0;
|
|
189
393
|
건너뜀.파일 += 목록.건너뜀?.파일 ?? 0;
|
|
394
|
+
건너뜀.못연폴더 += 목록.건너뜀?.못연폴더 ?? 0;
|
|
395
|
+
건너뜀.못연파일 += 목록.건너뜀?.못연파일 ?? 0;
|
|
190
396
|
if (목록.잘림) { 잘림 = true; 상한 = 목록.상한 ?? 상한; }
|
|
191
397
|
};
|
|
192
398
|
if (Array.isArray(args.paths) && args.paths.length) {
|
|
399
|
+
/*
|
|
400
|
+
* 짚어 준 **폴더**는 기본 훑기와 같은 거름·상한을 탄다 (2.0.0 6회차 검증6). 여기는 폴더 안 것을
|
|
401
|
+
* 확장자도 안 가리고 상한도 없이 전부 담았다 — `paths: ['src']` 한 번에 js 가 천 개면 node 를 천 번
|
|
402
|
+
* 띄웠고, 그림·문서는 「확인 못 한 것」 으로 쏟아져 진짜 탈이 묻혔다. 이름을 대 준 파일은 그대로 다 본다.
|
|
403
|
+
*/
|
|
404
|
+
const 폴더에서 = [];
|
|
193
405
|
for (const p of args.paths) {
|
|
194
406
|
let abs;
|
|
195
407
|
try { abs = ctx.scope.resolve(p); } catch (e) { 볼것.push({ 없음: p, 왜: e.message }); continue; }
|
|
@@ -197,14 +409,15 @@ export const VERIFY_TOOL = {
|
|
|
197
409
|
if (statSync(abs).isDirectory()) {
|
|
198
410
|
const 안것 = await walk(abs, { skipDirs: SKIP_DIRS });
|
|
199
411
|
셈더하기(안것);
|
|
200
|
-
for (const f of 안것)
|
|
412
|
+
for (const f of 안것) if (볼만한것인가(f.path)) 폴더에서.push({ path: f.path });
|
|
201
413
|
} else 볼것.push({ path: abs }); // 짚어 준 파일은 규칙과 상관없이 본다
|
|
202
414
|
}
|
|
415
|
+
넘친수 = Math.max(0, 폴더에서.length - 한번에최대);
|
|
416
|
+
볼것.push(...폴더에서.slice(0, 한번에최대));
|
|
203
417
|
} else {
|
|
204
418
|
const 전부 = await walk(뿌리, { skipDirs: SKIP_DIRS });
|
|
205
419
|
셈더하기(전부);
|
|
206
|
-
const 볼만한것 =
|
|
207
|
-
.filter((f) => ['.html', '.htm', '.css', '.json', '.js', '.mjs', '.cjs', '.py'].includes(extname(f.path).toLowerCase()));
|
|
420
|
+
const 볼만한것 = 전부.filter((f) => 볼만한것인가(f.path));
|
|
208
421
|
볼것 = 볼만한것.slice(0, 한번에최대).map((f) => ({ path: f.path }));
|
|
209
422
|
/*
|
|
210
423
|
* 상한에 걸려 안 본 것은 **말한다.**
|
|
@@ -224,6 +437,9 @@ export const VERIFY_TOOL = {
|
|
|
224
437
|
const 된것 = [];
|
|
225
438
|
const 탈난것 = [];
|
|
226
439
|
const 못한것 = [];
|
|
440
|
+
// 확인 명령에 물려줄 환경 — Bash 와 같은 거른 것 (명령돌리기 머리말). 확장자마다 고른 명령은 한 번만 찾는다.
|
|
441
|
+
const 자식환경 = 셸환경(process.env, { 남길것: ctx.셸남길것 ?? [] }).env;
|
|
442
|
+
const 고른명령 = {};
|
|
227
443
|
if (넘친수 > 0) {
|
|
228
444
|
못한것.push({
|
|
229
445
|
이름: `그 밖의 ${넘친수}개`,
|
|
@@ -237,32 +453,76 @@ export const VERIFY_TOOL = {
|
|
|
237
453
|
const 이름 = ctx.scope.show(abs);
|
|
238
454
|
const 확장 = extname(abs).toLowerCase();
|
|
239
455
|
|
|
240
|
-
|
|
456
|
+
/*
|
|
457
|
+
* 안 보는 살림 파일이라도 **안 봤다고는 말한다.**
|
|
458
|
+
*
|
|
459
|
+
* 여기는 `continue` 한 줄이었다. 그래서 짚어 준 경로가 살림 자리면
|
|
460
|
+
* 된것에도 탈난것에도 못한것에도 안 들어가고 그냥 사라졌다 —
|
|
461
|
+
*
|
|
462
|
+
* Verify(paths: ['.deel/config.json'])
|
|
463
|
+
* → 확인할 수 있는 파일이 없었습니다
|
|
464
|
+
*
|
|
465
|
+
* 준 것은 하나인데 답은 「없다」 다. 사람이 이름을 대서 시킨 일에
|
|
466
|
+
* 아무 말도 안 하는 것은 이 파일 머리말이 딱 하나 못 박아 둔 것
|
|
467
|
+
* (「못 확인한 것은 못 확인했다고 말한다」)과 정면으로 어긋난다.
|
|
468
|
+
*
|
|
469
|
+
* 안 읽는 것은 그대로다 — 열쇠가 든 파일을 열지 않는 까닭은 fsutil.js
|
|
470
|
+
* 의 내부살림 에 적혀 있다. 안 읽되, 왜 안 읽었는지는 그 자리에서 말한다.
|
|
471
|
+
*/
|
|
472
|
+
const 살림이유 = 내부살림(abs);
|
|
473
|
+
if (살림이유) { 못한것.push({ 이름, 왜: 살림이유 }); continue; }
|
|
241
474
|
let buf;
|
|
242
475
|
try { buf = readFileSync(abs); } catch (e) { 탈난것.push({ 이름, 탈: [e.message] }); continue; }
|
|
243
476
|
if (looksBinary(buf)) { 못한것.push({ 이름, 왜: '바이너리 — 돌려 볼 수 없습니다' }); continue; }
|
|
244
477
|
const 글 = decode(buf).text;
|
|
245
478
|
|
|
246
479
|
// ── 돌려 볼 수 있는 것 ──────────────────────────────────────────
|
|
247
|
-
|
|
480
|
+
// 셸 없이, 고른 실행 파일로, 거른 환경으로 (명령돌리기 머리말 · 사냥5 H1).
|
|
481
|
+
if (!(확장 in 고른명령)) 고른명령[확장] = 돌릴명령(확장, { 뿌리, env: 자식환경 });
|
|
482
|
+
const 만들기 = 고른명령[확장];
|
|
248
483
|
if (만들기) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
catch { 못한것.push({ 이름, 왜: '안전 검사에 걸려 안 돌렸습니다' }); continue; }
|
|
252
|
-
const r = await 명령돌리기(cmd, 뿌리, 30000);
|
|
484
|
+
if (!만들기.파일) { 못한것.push({ 이름, 왜: `${확장} 을 확인할 도구가 이 컴퓨터에 없습니다` }); continue; }
|
|
485
|
+
const r = await 명령돌리기(만들기.파일, 만들기.인자(abs), { cwd: 뿌리, 제한: 30000, env: 자식환경 });
|
|
253
486
|
// 도구 자체가 이 컴퓨터에 없으면 '틀렸다' 가 아니라 '못 봤다' 이다.
|
|
254
487
|
// 파이썬이 안 깔린 PC 에서 py 파일을 전부 빨갛게 칠하면 아무도 안 믿는다.
|
|
255
|
-
|
|
488
|
+
// (윈도우 스토어 자리표시 python.exe 는 「was not found」 를, 옛 python2 는 -I 에 「Unknown option」 을 낸다.)
|
|
489
|
+
// 그 말들은 **이 파일 이름이 안 나오는** 글에서만 믿는다 (2.0.0 6회차 검증6). 문법 오류 글에는 파일
|
|
490
|
+
// 경로와 원문 한 줄이 따라 나오는데, 원문에 `was not found` 가 있으면 깨진 파일이 「도구가 없다」 로 넘어갔다.
|
|
491
|
+
const 도구없다는말 = !r.out.includes(basename(abs))
|
|
492
|
+
&& /not recognized|command not found|찾을 수 없|No such file|was not found|Unknown option/i.test(r.out);
|
|
493
|
+
if (!r.ok && (r.없음 || 도구없다는말)) {
|
|
256
494
|
못한것.push({ 이름, 왜: `${확장} 을 확인할 도구가 이 컴퓨터에 없습니다` });
|
|
257
|
-
} else if (r.ok) 된것.push({ 이름, 어떻게:
|
|
495
|
+
} else if (r.ok) 된것.push({ 이름, 어떻게: 만들기.어떻게 });
|
|
258
496
|
else 탈난것.push({ 이름, 탈: [짧게(r.out) || `종료코드 ${r.code}`] });
|
|
259
497
|
continue;
|
|
260
498
|
}
|
|
261
499
|
|
|
262
500
|
// ── 읽어서 보는 것 ──────────────────────────────────────────────
|
|
263
501
|
let 탈 = null;
|
|
502
|
+
/*
|
|
503
|
+
* 한 파일은 **한 칸에만** 선다 (8회차 확인).
|
|
504
|
+
*
|
|
505
|
+
* 여기서 못 짚은 참조를 바로 못한것 에 밀어 넣고는, 아래에서 탈이 없다고 된것 에 또
|
|
506
|
+
* 넣었다. 그래서 같은 파일이 「? 어느 파일인지 못 짚었습니다」 와 「✓ 읽어서 확인」 으로
|
|
507
|
+
* 나란히 찍혔고, 확인됨+탈+못확인 의 합이 실제로 본 파일 수보다 컸다. 사람은 ✓ 를 보고
|
|
508
|
+
* 넘어간다 — 못 확인한 것을 확인했다고 하는 것이 제일 나쁜 쪽이다.
|
|
509
|
+
* 그래서 말만 만들어 두고, 어느 칸에 설지는 아래 한 자리에서 정한다.
|
|
510
|
+
*/
|
|
511
|
+
let 못짚음말 = null;
|
|
264
512
|
if (확장 === '.html' || 확장 === '.htm') {
|
|
265
|
-
|
|
513
|
+
// 못 짚은 참조는 탈이 아니라 「확인 못 한 것」 이다 (참조있나 참고).
|
|
514
|
+
const 못짚은것 = [];
|
|
515
|
+
탈 = html보기(글, {
|
|
516
|
+
있는파일: (곳) => {
|
|
517
|
+
const 답 = 참조있나(곳, abs, 뿌리);
|
|
518
|
+
if (답 === null) 못짚은것.push(곳);
|
|
519
|
+
return 답;
|
|
520
|
+
},
|
|
521
|
+
});
|
|
522
|
+
if (못짚은것.length) {
|
|
523
|
+
못짚음말 = `${못짚은것.slice(0, 3).join(' · ')}${못짚은것.length > 3 ? ` 그 밖에 ${못짚은것.length - 3}개` : ''}`
|
|
524
|
+
+ ' — 앞의 / 는 문서 뿌리 기준이라, 또는 작업 폴더 밖이라 어느 파일인지 못 짚었습니다 (없다는 뜻이 아닙니다)';
|
|
525
|
+
}
|
|
266
526
|
} else if (['.css', '.scss', '.less'].includes(확장)) {
|
|
267
527
|
탈 = css보기(글);
|
|
268
528
|
} else if (확장 === '.json') {
|
|
@@ -270,8 +530,9 @@ export const VERIFY_TOOL = {
|
|
|
270
530
|
}
|
|
271
531
|
|
|
272
532
|
if (탈 === null) { 못한것.push({ 이름, 왜: `${확장 || '확장자 없음'} 은 아직 확인할 줄 모릅니다` }); continue; }
|
|
273
|
-
if (탈.length) 탈난것.push({ 이름, 탈 });
|
|
274
|
-
|
|
533
|
+
if (탈.length) { 탈난것.push({ 이름, 탈 }); continue; }
|
|
534
|
+
if (못짚음말) { 못한것.push({ 이름, 왜: 못짚음말 }); continue; }
|
|
535
|
+
된것.push({ 이름, 어떻게: 확장 === '.json' ? 'JSON 파싱' : '읽어서 확인' });
|
|
275
536
|
}
|
|
276
537
|
|
|
277
538
|
// ── 사실대로 적는다 ───────────────────────────────────────────────
|
|
@@ -339,10 +600,18 @@ export const VERIFY_TOOL = {
|
|
|
339
600
|
const 다됐나 = !탈난것.length;
|
|
340
601
|
return {
|
|
341
602
|
content: 줄.join('\n').trim() + 건너뜀말(건너뜀, 잘림, 상한),
|
|
603
|
+
/*
|
|
604
|
+
* 요약은 **본문과 같은 말**을 해야 한다 (8회차 확인).
|
|
605
|
+
*
|
|
606
|
+
* 「확인 못 한 것을 반드시 적는다」 고 위에 적어 놓고, 요약에서는 탈이 하나라도 있으면
|
|
607
|
+
* 그 줄을 통째로 뺐다. 탈난 파일을 고치고 나면 사람 눈에는 「탈 1개 · 확인 1개」 만
|
|
608
|
+
* 남았던 것이고, 못 확인한 파일은 없던 일이 됐다. 그리고 본 것이 없어도 「확인 0개」 를
|
|
609
|
+
* 적어, 아무것도 확인 못 한 판이 확인한 판처럼 보였다. 빈 조각은 이어 가 버린다.
|
|
610
|
+
*/
|
|
342
611
|
summary: 이어(
|
|
343
612
|
탈난것.length ? 말('sum.broken', { n: 탈난것.length }) : '',
|
|
344
|
-
말('sum.verified', { n: 된것.length }),
|
|
345
|
-
|
|
613
|
+
된것.length ? 말('sum.verified', { n: 된것.length }) : '',
|
|
614
|
+
못한것.length ? 말('sum.unverified', { n: 못한것.length }) : '',
|
|
346
615
|
),
|
|
347
616
|
// 루프가 '아직 안 끝났다' 고 알 수 있게. 탈이 났는데 성공으로 넘기면
|
|
348
617
|
// 다음 걸음에서 모델이 "다 됐습니다" 로 답을 맺는다.
|
|
@@ -354,4 +623,4 @@ export const VERIFY_TOOL = {
|
|
|
354
623
|
},
|
|
355
624
|
};
|
|
356
625
|
|
|
357
|
-
// 확인 방법 찾기는 tools
|
|
626
|
+
// 확인 방법 찾기는 tools/checkmethods.js 로 옮겼다 — 찾을 자리가 늘어 파일이 무거워졌다.
|