leerness 1.36.300 → 1.36.301
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/CHANGELOG.md +37 -0
- package/CONTRIBUTING.md +2 -0
- package/README.md +4 -4
- package/bin/leerness.js +5 -2
- package/docs/runtime-compatibility-api.md +8 -4
- package/lib/runtime-layout.js +7 -3
- package/lib/workspace-dir.js +134 -35
- package/package.json +6 -4
- package/scripts/ci-shard-probe.js +343 -0
- package/scripts/ci-shard-weights.json +590 -0
- package/scripts/ci-shard.js +394 -0
- package/scripts/e2e-child-diagnostics-probe.js +5 -3
- package/scripts/e2e.js +843 -822
- package/scripts/release-runtime-probe.js +9 -6
- package/scripts/workspace-migration-heartbeat-probe.js +334 -0
package/scripts/e2e.js
CHANGED
|
@@ -11,6 +11,14 @@ const { spawnNpmSync } = require('../lib/npm-process');
|
|
|
11
11
|
const { gitSpawn } = require('../lib/git');
|
|
12
12
|
const { EXTERNAL_AGENTS } = require('../lib/agent-registry');
|
|
13
13
|
const { withRedirectWarnings } = require('./runtime-warning-gate');
|
|
14
|
+
// T-0359: CI runs this file in parallel shards (LEERNESS_E2E_SHARD=i/k, scripts/ci-shard.js). Each top-level case is
|
|
15
|
+
// `if (e2eShard.take('<unique key>')) {` with its `total++;` inside; a shard runs the cases assigned to it and an
|
|
16
|
+
// unsharded run runs all of them. The check next to the E2E result fails the run when a top-level block has no
|
|
17
|
+
// guard, a key repeats, or a guard was not reached exactly once. Case weights: scripts/ci-shard-weights.json.
|
|
18
|
+
// The names below are top-level state that one case leaves for the next: the shared fixture project `tmp`, `run()`
|
|
19
|
+
// (which works in it), and files in it. Every case that mentions one runs in one shard, in this file's order.
|
|
20
|
+
// A new top-level variable that cases change or read across cases belongs in this list.
|
|
21
|
+
const e2eShard = require('./ci-shard').e2eShard(__filename, ['tmp', 'run', 'dsPath', 'dsText', 'secretFile']);
|
|
14
22
|
|
|
15
23
|
// 1.9.12: e2e 안정성을 위해 자식 프로세스의 npm 호출 차단 (hang 방지)
|
|
16
24
|
// 1.36.87: 일부 e2e 타임아웃을 상향했다 — 격리 실측(agents list 5.2s · init --language 8.5s) 대비
|
|
@@ -178,8 +186,11 @@ run('lazy detect', ['lazy', 'detect', tmp]);
|
|
|
178
186
|
run('memory search', ['memory', 'search', '마일스톤', '--path', tmp]);
|
|
179
187
|
|
|
180
188
|
const offline = Object.assign({}, process.env, { LEERNESS_OFFLINE: '1' });
|
|
181
|
-
|
|
182
|
-
|
|
189
|
+
// e2e-shard: cases start here. Below this line, top-level code outside a case only prepares later cases: a case
|
|
190
|
+
// counter (total++), a block or a control-flow block there fails the run (scripts/ci-shard.js). Smoke checks that
|
|
191
|
+
// every shard runs belong above it.
|
|
192
|
+
if (e2eShard.take('update --check (offline): 조회 실패를')) {
|
|
193
|
+
total++;
|
|
183
194
|
// 1.36.102: 이 단언이 결함을 고정하고 있었다 — 오프라인(레지스트리 조회 불가)에서 "up to date" 가 나오는 것을
|
|
184
195
|
// 정답으로 못박고 있었다. 모르는 것을 안다고 말하는 게 정답일 수 없다. 계약을 바꾼다:
|
|
185
196
|
// 조회에 실패하면 '확인 불가' 여야 하고, 마이그레이션이 필요하면 그건 조회와 무관하니 그대로 인정한다.
|
|
@@ -191,8 +202,8 @@ total++;
|
|
|
191
202
|
}
|
|
192
203
|
|
|
193
204
|
run('auto-update install', ['auto-update', 'install', tmp]);
|
|
194
|
-
|
|
195
|
-
|
|
205
|
+
if (e2eShard.take('SessionStart hook for update --check ins')) {
|
|
206
|
+
total++;
|
|
196
207
|
const settingsFile = path.join(tmp, '.claude/settings.local.json');
|
|
197
208
|
if (fs.existsSync(settingsFile)) {
|
|
198
209
|
const s = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
@@ -203,8 +214,8 @@ total++;
|
|
|
203
214
|
}
|
|
204
215
|
|
|
205
216
|
// 1.9.1 P6 회귀: evidence가 plan:M-XXXX + 검증 키워드면 lazy detect가 통과해야 한다.
|
|
206
|
-
|
|
207
|
-
|
|
217
|
+
if (e2eShard.take('B(P6)')) {
|
|
218
|
+
total++;
|
|
208
219
|
const trackerPath = path.join(tmp, '.leerness/progress-tracker.md');
|
|
209
220
|
let cur = fs.readFileSync(trackerPath, 'utf8');
|
|
210
221
|
cur = cur.replace(/^\| T-0001 \|.*$/m, '| T-0001 | done | mile A | tests:32/32 (plan:M-0002) | next | 2026-05-08 |');
|
|
@@ -215,8 +226,8 @@ total++;
|
|
|
215
226
|
if (!ok) failed++;
|
|
216
227
|
}
|
|
217
228
|
// 1.9.1 P6 negative: plan:M-XXXX 단독은 여전히 경고
|
|
218
|
-
|
|
219
|
-
|
|
229
|
+
if (e2eShard.take('B(P6 neg)')) {
|
|
230
|
+
total++;
|
|
220
231
|
const trackerPath = path.join(tmp, '.leerness/progress-tracker.md');
|
|
221
232
|
let cur = fs.readFileSync(trackerPath, 'utf8');
|
|
222
233
|
cur = cur.replace(/^\| T-0001 \|.*$/m, '| T-0001 | done | mile A | plan:M-0002 | next | 2026-05-08 |');
|
|
@@ -231,8 +242,8 @@ total++;
|
|
|
231
242
|
}
|
|
232
243
|
|
|
233
244
|
// 1.9.1 P1 회귀: legacy leerness-plus hook이 있으면 auto-update install이 정리해야 한다.
|
|
234
|
-
|
|
235
|
-
|
|
245
|
+
if (e2eShard.take('B(P1)')) {
|
|
246
|
+
total++;
|
|
236
247
|
const settingsFile = path.join(tmp, '.claude/settings.local.json');
|
|
237
248
|
const s = JSON.parse(fs.readFileSync(settingsFile, 'utf8'));
|
|
238
249
|
s.hooks.SessionStart = [{ matcher: '*', command: 'leerness-plus update --check' }];
|
|
@@ -247,8 +258,8 @@ total++;
|
|
|
247
258
|
}
|
|
248
259
|
|
|
249
260
|
// 1.9.1 P4 회귀: NA 마커가 있으면 audit이 placeholder 경고를 스킵.
|
|
250
|
-
|
|
251
|
-
|
|
261
|
+
if (e2eShard.take('B(P4)')) {
|
|
262
|
+
total++;
|
|
252
263
|
const ds = path.join(tmp, '.leerness/design-system.md');
|
|
253
264
|
fs.appendFileSync(ds, '\n<!-- leerness:na CLI 프로젝트라 디자인 토큰 없음 -->\n');
|
|
254
265
|
const r = cp.spawnSync(process.execPath, [CLI, 'audit', tmp], { encoding: 'utf8' });
|
|
@@ -258,8 +269,8 @@ total++;
|
|
|
258
269
|
}
|
|
259
270
|
|
|
260
271
|
// 1.9.1 P7 회귀: init 직후 progress에 M-0001 연결 row가 자동으로 있다.
|
|
261
|
-
|
|
262
|
-
|
|
272
|
+
if (e2eShard.take('B(P7)')) {
|
|
273
|
+
total++;
|
|
263
274
|
const tmp2 = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-e2e-p7-'));
|
|
264
275
|
cp.spawnSync(process.execPath, [CLI, 'init', tmp2, '--yes', '--language', 'ko', '--skills', 'recommended'], { encoding: 'utf8' });
|
|
265
276
|
const tracker = fs.readFileSync(path.join(tmp2, '.leerness/progress-tracker.md'), 'utf8');
|
|
@@ -280,15 +291,15 @@ run('skill use (new)', ['skill', 'use', 'open-meteo', '--note', 'first call'
|
|
|
280
291
|
run('skill use (catalog)', ['skill', 'use', 'feature-implementation', '--note', 'catalog skill materialize', '--path', tmp]);
|
|
281
292
|
run('skill optimize', ['skill', 'optimize', 'open-meteo', '--before', 'no cache', '--after', 'If-Modified-Since', '--note', 'e2e opt', '--path', tmp]);
|
|
282
293
|
|
|
283
|
-
|
|
284
|
-
|
|
294
|
+
if (e2eShard.take('skill.json optimizations 누적')) {
|
|
295
|
+
total++;
|
|
285
296
|
const f = path.join(tmp, '.leerness/skills/open-meteo/skill.json');
|
|
286
297
|
const ok = fs.existsSync(f) && JSON.parse(fs.readFileSync(f, 'utf8')).optimizations.length === 1;
|
|
287
298
|
console.log(ok ? '✓ skill.json optimizations 누적' : '✗ skill.json optimizations 누적 실패');
|
|
288
299
|
if (!ok) failed++;
|
|
289
300
|
}
|
|
290
|
-
|
|
291
|
-
|
|
301
|
+
if (e2eShard.take('skill usage/sources/patterns 누적')) {
|
|
302
|
+
total++;
|
|
292
303
|
const data = JSON.parse(fs.readFileSync(path.join(tmp, '.leerness/skills/open-meteo/skill.json'), 'utf8'));
|
|
293
304
|
const ok = data.usage.count === 1 && data.sources.length >= 1 && data.patterns.length >= 1;
|
|
294
305
|
console.log(ok ? '✓ skill usage/sources/patterns 누적' : '✗ skill usage/sources/patterns 누적 실패');
|
|
@@ -297,8 +308,8 @@ total++;
|
|
|
297
308
|
|
|
298
309
|
run('skill consolidate', ['skill', 'consolidate', '--threshold', '0.1', '--path', tmp]);
|
|
299
310
|
run('skill remove (user)', ['skill', 'remove', 'open-meteo', '--path', tmp]);
|
|
300
|
-
|
|
301
|
-
|
|
311
|
+
if (e2eShard.take('skill remove: 디렉토리 삭제')) {
|
|
312
|
+
total++;
|
|
302
313
|
const ok = !fs.existsSync(path.join(tmp, '.leerness/skills/open-meteo'));
|
|
303
314
|
console.log(ok ? '✓ skill remove: 디렉토리 삭제' : '✗ skill remove: 디렉토리 잔존');
|
|
304
315
|
if (!ok) failed++;
|
|
@@ -324,8 +335,8 @@ run('impact: Card.html → home/about', ['impact', 'src/components/Card.html',
|
|
|
324
335
|
run('reuse find: Card 후보', ['reuse', 'find', 'Card', '--path', tmp]);
|
|
325
336
|
run('reuse register: Card', ['reuse', 'register', 'Card', '--where', 'src/components/Card.html', '--kind', 'component', '--note', 'e2e card', '--path', tmp]);
|
|
326
337
|
|
|
327
|
-
|
|
328
|
-
|
|
338
|
+
if (e2eShard.take('reuse-map.md에 Card row 자동 추가')) {
|
|
339
|
+
total++;
|
|
329
340
|
const reuse = fs.readFileSync(path.join(tmp, '.leerness/reuse-map.md'), 'utf8');
|
|
330
341
|
const ok = /\| Card \| src\/components\/Card\.html \| component \|/.test(reuse);
|
|
331
342
|
console.log(ok ? '✓ reuse-map.md에 Card row 자동 추가' : '✗ Card row 미등록');
|
|
@@ -336,8 +347,8 @@ run('ui consistency: pass (토큰 외 색상 없음)', ['ui', 'consistency', tmp
|
|
|
336
347
|
|
|
337
348
|
// 의도적 일탈: 토큰 외 색상 추가 → ui consistency 경고
|
|
338
349
|
fs.writeFileSync(path.join(tmp, 'src/pages/contact.html'), `<style>.box { color: #abcdef; background: #123456; }</style>\n`);
|
|
339
|
-
|
|
340
|
-
|
|
350
|
+
if (e2eShard.take('ui consistency: 일탈 색상 #abcdef 검출 + 비0 종료')) {
|
|
351
|
+
total++;
|
|
341
352
|
const r = cp.spawnSync(process.execPath, [CLI, 'ui', 'consistency', tmp, '--fail-on-violation'], { encoding: 'utf8' });
|
|
342
353
|
const ok = r.status === 1 && /토큰 외 값/.test(r.stdout) && /#abcdef/i.test(r.stdout);
|
|
343
354
|
console.log(ok ? '✓ ui consistency: 일탈 색상 #abcdef 검출 + 비0 종료' : '✗ ui consistency 일탈 미검출');
|
|
@@ -348,8 +359,8 @@ run('graph: mermaid 출력', ['graph', tmp]);
|
|
|
348
359
|
run('guide: 통합 가이드', ['guide', 'src/components/Card.html', '--path', tmp]);
|
|
349
360
|
|
|
350
361
|
// 1.9.4 회귀: impact strong/weak 구분
|
|
351
|
-
|
|
352
|
-
|
|
362
|
+
if (e2eShard.take('B(A)')) {
|
|
363
|
+
total++;
|
|
353
364
|
// home.html에 "Card"라는 식별자가 plain text로 들어가도록 (false positive 시드)
|
|
354
365
|
fs.appendFileSync(path.join(tmp, 'src/pages/home.html'), '\n<!-- 카드 콘텐츠는 Cards 배열로 -->\n');
|
|
355
366
|
const r = cp.spawnSync(process.execPath, [CLI, 'impact', 'src/components/Card.html', '--path', tmp], { encoding: 'utf8' });
|
|
@@ -360,8 +371,8 @@ total++;
|
|
|
360
371
|
}
|
|
361
372
|
|
|
362
373
|
// 1.9.16 회귀: brainstorm --all-apps / --json / session close 워크스페이스 안내
|
|
363
|
-
|
|
364
|
-
|
|
374
|
+
if (e2eShard.take('B(1.9.16)')) {
|
|
375
|
+
total++;
|
|
365
376
|
// brainstorm --all-apps
|
|
366
377
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-bsa-'));
|
|
367
378
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-bsb-'));
|
|
@@ -375,8 +386,8 @@ total++;
|
|
|
375
386
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 600)); }
|
|
376
387
|
}
|
|
377
388
|
|
|
378
|
-
|
|
379
|
-
|
|
389
|
+
if (e2eShard.take('B(1.9.16) #2')) {
|
|
390
|
+
total++;
|
|
380
391
|
// --json 단일 brainstorm
|
|
381
392
|
const tmpJ = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-json-'));
|
|
382
393
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpJ, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -389,8 +400,8 @@ total++;
|
|
|
389
400
|
if (!ok) failed++;
|
|
390
401
|
}
|
|
391
402
|
|
|
392
|
-
|
|
393
|
-
|
|
403
|
+
if (e2eShard.take('B(1.9.16) #3')) {
|
|
404
|
+
total++;
|
|
394
405
|
// retro --json
|
|
395
406
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rj-'));
|
|
396
407
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -402,8 +413,8 @@ total++;
|
|
|
402
413
|
if (!ok) failed++;
|
|
403
414
|
}
|
|
404
415
|
|
|
405
|
-
|
|
406
|
-
|
|
416
|
+
if (e2eShard.take('B(1.9.16) #4')) {
|
|
417
|
+
total++;
|
|
407
418
|
// insights --json (workspace)
|
|
408
419
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-iwsa-'));
|
|
409
420
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-iwsb-'));
|
|
@@ -417,8 +428,8 @@ total++;
|
|
|
417
428
|
if (!ok) failed++;
|
|
418
429
|
}
|
|
419
430
|
|
|
420
|
-
|
|
421
|
-
|
|
431
|
+
if (e2eShard.take('B(1.9.16) #5')) {
|
|
432
|
+
total++;
|
|
422
433
|
// session close 끝에 워크스페이스 안내 (다른 leerness 프로젝트 시뮬)
|
|
423
434
|
const wsRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ws-'));
|
|
424
435
|
fs.mkdirSync(path.join(wsRoot, '_apps'), { recursive: true });
|
|
@@ -435,8 +446,8 @@ total++;
|
|
|
435
446
|
}
|
|
436
447
|
|
|
437
448
|
// 1.9.17 회귀: handoff --all-apps / reuse-map --all-apps (워크스페이스 오케스트레이션)
|
|
438
|
-
|
|
439
|
-
|
|
449
|
+
if (e2eShard.take('B(1.9.17)')) {
|
|
450
|
+
total++;
|
|
440
451
|
// handoff --include
|
|
441
452
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ha-'));
|
|
442
453
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-hb-'));
|
|
@@ -448,8 +459,8 @@ total++;
|
|
|
448
459
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 600)); }
|
|
449
460
|
}
|
|
450
461
|
|
|
451
|
-
|
|
452
|
-
|
|
462
|
+
if (e2eShard.take('B(1.9.17) #2')) {
|
|
463
|
+
total++;
|
|
453
464
|
// handoff --include --json
|
|
454
465
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-haj-'));
|
|
455
466
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-hbj-'));
|
|
@@ -463,8 +474,8 @@ total++;
|
|
|
463
474
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
464
475
|
}
|
|
465
476
|
|
|
466
|
-
|
|
467
|
-
|
|
477
|
+
if (e2eShard.take('B(1.9.17) #3')) {
|
|
478
|
+
total++;
|
|
468
479
|
// reuse-map 단일 + 워크스페이스 모드 + 중복 감지
|
|
469
480
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rma-'));
|
|
470
481
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rmb-'));
|
|
@@ -492,8 +503,8 @@ total++;
|
|
|
492
503
|
}
|
|
493
504
|
|
|
494
505
|
// 1.9.18 회귀: handoff --since / reuse-map --strict-elements + depends-on / verify-claim
|
|
495
|
-
|
|
496
|
-
|
|
506
|
+
if (e2eShard.take('B(1.9.18)')) {
|
|
507
|
+
total++;
|
|
497
508
|
// handoff --since: 최근 변경 강조
|
|
498
509
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sincea-'));
|
|
499
510
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sinceb-'));
|
|
@@ -508,8 +519,8 @@ total++;
|
|
|
508
519
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
509
520
|
}
|
|
510
521
|
|
|
511
|
-
|
|
512
|
-
|
|
522
|
+
if (e2eShard.take('B(1.9.18) #2')) {
|
|
523
|
+
total++;
|
|
513
524
|
// handoff --since 형식 오류 → fail
|
|
514
525
|
const tmpE = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sincee-'));
|
|
515
526
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpE, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -519,8 +530,8 @@ total++;
|
|
|
519
530
|
if (!ok) failed++;
|
|
520
531
|
}
|
|
521
532
|
|
|
522
|
-
|
|
523
|
-
|
|
533
|
+
if (e2eShard.take('B(1.9.18) #3')) {
|
|
534
|
+
total++;
|
|
524
535
|
// reuse-map --strict-elements: 같은 함수명 다른 capability 감지
|
|
525
536
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-strict-a-'));
|
|
526
537
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-strict-b-'));
|
|
@@ -545,8 +556,8 @@ total++;
|
|
|
545
556
|
if (!ok) { failed++; console.log(r2.stdout.slice(0, 500)); }
|
|
546
557
|
}
|
|
547
558
|
|
|
548
|
-
|
|
549
|
-
|
|
559
|
+
if (e2eShard.take('B(1.9.18) #4')) {
|
|
560
|
+
total++;
|
|
550
561
|
// reuse-map depends-on: notes 컬럼에서 의존 추출
|
|
551
562
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-deps-'));
|
|
552
563
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpA, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -559,8 +570,8 @@ total++;
|
|
|
559
570
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
560
571
|
}
|
|
561
572
|
|
|
562
|
-
|
|
563
|
-
|
|
573
|
+
if (e2eShard.take('pass (실측 ≥ 주장)')) {
|
|
574
|
+
total++;
|
|
564
575
|
// verify-claim: 파일 존재 + 테스트 카운트 검증
|
|
565
576
|
const tmpV = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-vc-'));
|
|
566
577
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpV, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -592,8 +603,8 @@ total++;
|
|
|
592
603
|
}
|
|
593
604
|
|
|
594
605
|
// 1.9.19 회귀: verify-claim --run-tests + --strict-elements same-file 구분
|
|
595
|
-
|
|
596
|
-
|
|
606
|
+
if (e2eShard.take('B(1.9.19)')) {
|
|
607
|
+
total++;
|
|
597
608
|
// verify-claim --run-tests: npm test 자동 실행 + 주장 vs 실행 결과 대조
|
|
598
609
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rt-'));
|
|
599
610
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -619,8 +630,8 @@ total++;
|
|
|
619
630
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 600)); }
|
|
620
631
|
}
|
|
621
632
|
|
|
622
|
-
|
|
623
|
-
|
|
633
|
+
if (e2eShard.take('B(1.9.19) #2')) {
|
|
634
|
+
total++;
|
|
624
635
|
// --run-tests 실패 케이스: 5/5 주장 vs 실제 3/5 실행 → 불일치 + exit 1
|
|
625
636
|
const tmpF = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rtf-'));
|
|
626
637
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpF, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -640,8 +651,8 @@ total++;
|
|
|
640
651
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 600)); }
|
|
641
652
|
}
|
|
642
653
|
|
|
643
|
-
|
|
644
|
-
|
|
654
|
+
if (e2eShard.take('B(1.9.19) #3')) {
|
|
655
|
+
total++;
|
|
645
656
|
// --strict-elements: same-file ⚠ vs diff-file ℹ 구분
|
|
646
657
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-strict2-a-'));
|
|
647
658
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-strict2-b-'));
|
|
@@ -668,8 +679,8 @@ total++;
|
|
|
668
679
|
}
|
|
669
680
|
|
|
670
681
|
// 1.9.20 회귀: verify-claim file regex 확장 + jest/mocha 파싱 + --bench
|
|
671
|
-
|
|
672
|
-
|
|
682
|
+
if (e2eShard.take('B(1.9.20)')) {
|
|
683
|
+
total++;
|
|
673
684
|
// verify-claim regex: 도메인 폴더 (scenes/scripts) + 루트 메타 파일 (project.godot)
|
|
674
685
|
const tmpG = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-godot-'));
|
|
675
686
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpG, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -691,8 +702,8 @@ total++;
|
|
|
691
702
|
}
|
|
692
703
|
|
|
693
704
|
// 1.9.21 회귀: 설정 파일 확장자 (.cfg/.ini/.env/.toml/.lock) 추가
|
|
694
|
-
|
|
695
|
-
|
|
705
|
+
if (e2eShard.take('B(1.9.21)')) {
|
|
706
|
+
total++;
|
|
696
707
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cfg-'));
|
|
697
708
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
698
709
|
fs.writeFileSync(path.join(tmpC, 'export_presets.cfg'), '[preset.0]\n');
|
|
@@ -710,8 +721,8 @@ total++;
|
|
|
710
721
|
}
|
|
711
722
|
|
|
712
723
|
// 1.9.25 회귀: memory search --include-code + brainstorm --include-code + register-pending
|
|
713
|
-
|
|
714
|
-
|
|
724
|
+
if (e2eShard.take('B(1.9.25)')) {
|
|
725
|
+
total++;
|
|
715
726
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-incode-'));
|
|
716
727
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
717
728
|
fs.mkdirSync(path.join(tmpC, 'src'), { recursive: true });
|
|
@@ -725,8 +736,8 @@ total++;
|
|
|
725
736
|
if (!ok) { failed++; console.log(r2.stdout.slice(0, 400)); }
|
|
726
737
|
}
|
|
727
738
|
|
|
728
|
-
|
|
729
|
-
|
|
739
|
+
if (e2eShard.take('B(1.9.25) #2')) {
|
|
740
|
+
total++;
|
|
730
741
|
// register-pending
|
|
731
742
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-regp-'));
|
|
732
743
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -741,8 +752,8 @@ total++;
|
|
|
741
752
|
}
|
|
742
753
|
|
|
743
754
|
// 1.9.26 회귀: optimism-check (낙관적 표시 자동 감지) + verify-claim --strict-claims
|
|
744
|
-
|
|
745
|
-
|
|
755
|
+
if (e2eShard.take('B(1.9.26)')) {
|
|
756
|
+
total++;
|
|
746
757
|
const tmpO = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-optm-'));
|
|
747
758
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpO, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
748
759
|
fs.mkdirSync(path.join(tmpO, 'src'), { recursive: true });
|
|
@@ -765,8 +776,8 @@ total++;
|
|
|
765
776
|
if (!ok) { failed++; console.log('--- 거짓 case ---\n' + r1.stdout.slice(0, 300) + '\n--- 정상 case ---\n' + r2.stdout.slice(0, 300)); }
|
|
766
777
|
}
|
|
767
778
|
|
|
768
|
-
|
|
769
|
-
|
|
779
|
+
if (e2eShard.take('B(1.9.26) #2')) {
|
|
780
|
+
total++;
|
|
770
781
|
// verify-claim --strict-claims 통합
|
|
771
782
|
const tmpS = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-stct-'));
|
|
772
783
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpS, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -781,8 +792,8 @@ total++;
|
|
|
781
792
|
}
|
|
782
793
|
|
|
783
794
|
// 1.9.27 회귀: URL/메서드 매핑 + 신뢰도 점수 + 신규 카테고리 (FileIO/Queue/Cache/Notify/Storage)
|
|
784
|
-
|
|
785
|
-
|
|
795
|
+
if (e2eShard.take('B(1.9.27)')) {
|
|
796
|
+
total++;
|
|
786
797
|
// T-9001 false negative 해결 검증: 코드에 다른 목적의 http.request 있어도 URL 미매치로 잡아냄
|
|
787
798
|
const tmpU = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-url-'));
|
|
788
799
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpU, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -801,8 +812,8 @@ total++;
|
|
|
801
812
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
802
813
|
}
|
|
803
814
|
|
|
804
|
-
|
|
805
|
-
|
|
815
|
+
if (e2eShard.take('B(1.9.27) #2')) {
|
|
816
|
+
total++;
|
|
806
817
|
// 신규 카테고리 (Slack/Notify)
|
|
807
818
|
const tmpN = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-notify-'));
|
|
808
819
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpN, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -816,8 +827,8 @@ total++;
|
|
|
816
827
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
817
828
|
}
|
|
818
829
|
|
|
819
|
-
|
|
820
|
-
|
|
830
|
+
if (e2eShard.take('B(1.9.27) #3')) {
|
|
831
|
+
total++;
|
|
821
832
|
// 신뢰도 점수 — 정상 케이스는 1.0
|
|
822
833
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-conf-'));
|
|
823
834
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -834,8 +845,8 @@ total++;
|
|
|
834
845
|
}
|
|
835
846
|
|
|
836
847
|
// 1.9.28 회귀: 한국형 PG (카카오페이) 패턴 + confidence floor 0.15
|
|
837
|
-
|
|
838
|
-
|
|
848
|
+
if (e2eShard.take('B(1.9.28)')) {
|
|
849
|
+
total++;
|
|
839
850
|
const tmpK = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-kpay-'));
|
|
840
851
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpK, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
841
852
|
fs.mkdirSync(path.join(tmpK, 'src'), { recursive: true });
|
|
@@ -853,8 +864,8 @@ total++;
|
|
|
853
864
|
}
|
|
854
865
|
|
|
855
866
|
// 1.9.29 회귀: 페르소나 시스템 + review 명령
|
|
856
|
-
|
|
857
|
-
|
|
867
|
+
if (e2eShard.take('B(1.9.29)')) {
|
|
868
|
+
total++;
|
|
858
869
|
// persona list — 5 내장
|
|
859
870
|
const r1 = cp.spawnSync(process.execPath, [CLI, 'persona', 'list'], { encoding: 'utf8', timeout: 10000 });
|
|
860
871
|
const okList = r1.status === 0
|
|
@@ -874,8 +885,8 @@ total++;
|
|
|
874
885
|
if (!ok) { failed++; console.log(r1.stdout.slice(0, 400)); }
|
|
875
886
|
}
|
|
876
887
|
|
|
877
|
-
|
|
878
|
-
|
|
888
|
+
if (e2eShard.take('B(1.9.29) #2')) {
|
|
889
|
+
total++;
|
|
879
890
|
// review <file> --persona X
|
|
880
891
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rev-'));
|
|
881
892
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -901,8 +912,8 @@ total++;
|
|
|
901
912
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
902
913
|
}
|
|
903
914
|
|
|
904
|
-
|
|
905
|
-
|
|
915
|
+
if (e2eShard.take('B(1.9.29) #3')) {
|
|
916
|
+
total++;
|
|
906
917
|
// 사용자 정의 페르소나 add
|
|
907
918
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-padd-'));
|
|
908
919
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -919,8 +930,8 @@ total++;
|
|
|
919
930
|
}
|
|
920
931
|
|
|
921
932
|
// 1.9.30 회귀: 외부 CLI 오케스트레이션 (agents list/check/dispatch)
|
|
922
|
-
|
|
923
|
-
|
|
933
|
+
if (e2eShard.take('B(1.9.30+1.9.277)')) {
|
|
934
|
+
total++;
|
|
924
935
|
// agents list — claude가 환경변수 + PATH 둘 다 충족 시 ready
|
|
925
936
|
const env1 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_GROK: '0', LEERNESS_ENABLE_COPILOT: '0' };
|
|
926
937
|
const r1 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list'], { encoding: 'utf8', timeout: 90000, env: env1 });
|
|
@@ -945,8 +956,8 @@ total++;
|
|
|
945
956
|
if (!ok) { failed++; console.log(r1.stdout.slice(0, 500)); }
|
|
946
957
|
}
|
|
947
958
|
|
|
948
|
-
|
|
949
|
-
|
|
959
|
+
if (e2eShard.take('B(1.9.30)')) {
|
|
960
|
+
total++;
|
|
950
961
|
// agents dispatch — 활성 미충족 시 거부
|
|
951
962
|
const env = { ...process.env, LEERNESS_ENABLE_CODEX: '0' };
|
|
952
963
|
// 1.30.2: timeout 10s→30s flake 하드닝(1.9.375 계열) — 전체 e2e 부하(수백 spawn) 하에서 짧은 타임아웃이 간헐 빈-stdout→오판.
|
|
@@ -964,8 +975,8 @@ total++;
|
|
|
964
975
|
}
|
|
965
976
|
|
|
966
977
|
// 1.36.185 회귀: agents quota는 설치·활성·인증·로컬 라우팅·모델 호출·잔여량을 분리하고 추정하지 않는다
|
|
967
|
-
|
|
968
|
-
|
|
978
|
+
if (e2eShard.take('B(1.36.185)')) {
|
|
979
|
+
total++;
|
|
969
980
|
// agents quota — env=0 시 인증 확인을 실행하지 않고 disabled/not-installed를 정직하게 표시
|
|
970
981
|
const env = { ...process.env };
|
|
971
982
|
for (const key of Object.keys(env)) if (/^LEERNESS_ENABLE_/i.test(key)) delete env[key];
|
|
@@ -1020,8 +1031,8 @@ total++;
|
|
|
1020
1031
|
console.log(ok ? '✓ B(1.36.185) agents quota: 관측 축 분리·비밀 비출력·비활성 인증 무실행·추정 금지' : `✗ quota 실패 (text=${okText} json=${okJson})`);
|
|
1021
1032
|
if (!ok) { failed++; console.log((r.stdout + '\n' + r2.stdout + '\n' + r2.stderr).slice(0, 1400)); }
|
|
1022
1033
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1034
|
+
if (e2eShard.take('B(1.9.31)')) {
|
|
1035
|
+
total++;
|
|
1025
1036
|
// 사용법 메시지에 quota 포함
|
|
1026
1037
|
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'foo'], { encoding: 'utf8', timeout: 10000 });
|
|
1027
1038
|
const usage = r.stdout + r.stderr;
|
|
@@ -1033,8 +1044,8 @@ total++;
|
|
|
1033
1044
|
}
|
|
1034
1045
|
|
|
1035
1046
|
// Historical banner checks and the removed CLI-agent activation surface.
|
|
1036
|
-
|
|
1037
|
-
|
|
1047
|
+
if (e2eShard.take('B(1.9.32)')) {
|
|
1048
|
+
total++;
|
|
1038
1049
|
// --version --banner: LEERNESS ASCII + 신규 슬로건 (1.9.144+ "AI 에이전트 검수·기억·드리프트 방지 하네스")
|
|
1039
1050
|
const r = cp.spawnSync(process.execPath, [CLI, '--version', '--banner'], { encoding: 'utf8', timeout: 30000, env: { ...process.env, TERM: 'dumb' } });
|
|
1040
1051
|
const ok = r.status === 0
|
|
@@ -1046,8 +1057,8 @@ total++;
|
|
|
1046
1057
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
|
|
1047
1058
|
}
|
|
1048
1059
|
|
|
1049
|
-
|
|
1050
|
-
|
|
1060
|
+
if (e2eShard.take('B(1.9.32) #2')) {
|
|
1061
|
+
total++;
|
|
1051
1062
|
// LEERNESS_NO_BANNER=1: 배너 스킵
|
|
1052
1063
|
const r = cp.spawnSync(process.execPath, [CLI, '--version', '--banner'], { encoding: 'utf8', timeout: 10000, env: { ...process.env, LEERNESS_NO_BANNER: '1' } });
|
|
1053
1064
|
const ok = r.status === 0 && !/███████╗/.test(r.stdout) && /^1\./m.test(r.stdout.trim());
|
|
@@ -1055,8 +1066,8 @@ total++;
|
|
|
1055
1066
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 300)); }
|
|
1056
1067
|
}
|
|
1057
1068
|
|
|
1058
|
-
|
|
1059
|
-
|
|
1069
|
+
if (e2eShard.take('init: 에이전트 활성화 문항/기본값 없이 지침 설치')) {
|
|
1070
|
+
total++;
|
|
1060
1071
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-setup-removed-'));
|
|
1061
1072
|
const r = cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended', '--minimal', '--no-banner'], { encoding: 'utf8', timeout: 30000 });
|
|
1062
1073
|
const configPath = path.join(tmpC, '.leerness', 'leerness-config.json');
|
|
@@ -1068,8 +1079,8 @@ total++;
|
|
|
1068
1079
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1069
1080
|
}
|
|
1070
1081
|
|
|
1071
|
-
|
|
1072
|
-
|
|
1082
|
+
if (e2eShard.take('--help: 제거된 설정 명령 없음')) {
|
|
1083
|
+
total++;
|
|
1073
1084
|
// Removed command is not advertised.
|
|
1074
1085
|
const r = cp.spawnSync(process.execPath, [CLI, '--help'], { encoding: 'utf8', timeout: 10000 });
|
|
1075
1086
|
const ok = r.status === 0 && !/setup-agents|CLI 에이전트 활성화/.test(r.stdout);
|
|
@@ -1077,8 +1088,8 @@ total++;
|
|
|
1077
1088
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1078
1089
|
}
|
|
1079
1090
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1091
|
+
if (e2eShard.take('제거된 setup-agents 명령은 설정을 변경하지 않음')) {
|
|
1092
|
+
total++;
|
|
1082
1093
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-setup-disabled-'));
|
|
1083
1094
|
const tmpEnv = path.join(tmpC, '.env');
|
|
1084
1095
|
fs.writeFileSync(tmpEnv, 'EXISTING=1\nLEERNESS_ENABLE_CLAUDE=1\n', 'utf8');
|
|
@@ -1090,8 +1101,8 @@ total++;
|
|
|
1090
1101
|
}
|
|
1091
1102
|
|
|
1092
1103
|
// 1.9.33 회귀: npx 캐시 함정 — stale 버전 실행 시 경고
|
|
1093
|
-
|
|
1094
|
-
|
|
1104
|
+
if (e2eShard.take('B(1.9.33)')) {
|
|
1105
|
+
total++;
|
|
1095
1106
|
// 캐시에 미래 버전을 심어 stale 시뮬레이션 → 경고 출력 + init은 계속 진행
|
|
1096
1107
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-stale-'));
|
|
1097
1108
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended', '--no-stale-check'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1109,8 +1120,8 @@ total++;
|
|
|
1109
1120
|
}
|
|
1110
1121
|
|
|
1111
1122
|
// 1.9.63/64 회귀
|
|
1112
|
-
|
|
1113
|
-
|
|
1123
|
+
if (e2eShard.take('B(1.9.63)')) {
|
|
1124
|
+
total++;
|
|
1114
1125
|
// audit --strict
|
|
1115
1126
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-st-'));
|
|
1116
1127
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1120,8 +1131,8 @@ total++;
|
|
|
1120
1131
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1121
1132
|
}
|
|
1122
1133
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1134
|
+
if (e2eShard.take('B(1.9.63) #2')) {
|
|
1135
|
+
total++;
|
|
1125
1136
|
// audit --strict --threshold 10 → exit 0
|
|
1126
1137
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-st2-'));
|
|
1127
1138
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1131,8 +1142,8 @@ total++;
|
|
|
1131
1142
|
if (!ok) { failed++; console.log(r.stdout.slice(-300)); }
|
|
1132
1143
|
}
|
|
1133
1144
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1145
|
+
if (e2eShard.take('B(1.9.64)')) {
|
|
1146
|
+
total++;
|
|
1136
1147
|
// install 별칭
|
|
1137
1148
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-iv-'));
|
|
1138
1149
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1146,8 +1157,8 @@ total++;
|
|
|
1146
1157
|
}
|
|
1147
1158
|
|
|
1148
1159
|
// 1.9.60/61/62 회귀
|
|
1149
|
-
|
|
1150
|
-
|
|
1160
|
+
if (e2eShard.take('B(1.9.60)')) {
|
|
1161
|
+
total++;
|
|
1151
1162
|
// task export → TodoWrite JSON 호환
|
|
1152
1163
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tex-'));
|
|
1153
1164
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1160,8 +1171,8 @@ total++;
|
|
|
1160
1171
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1161
1172
|
}
|
|
1162
1173
|
|
|
1163
|
-
|
|
1164
|
-
|
|
1174
|
+
if (e2eShard.take('B(1.9.61)')) {
|
|
1175
|
+
total++;
|
|
1165
1176
|
// MCP cursor 페이지네이션 (chunkSize override)
|
|
1166
1177
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mcc-'));
|
|
1167
1178
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1173,8 +1184,8 @@ total++;
|
|
|
1173
1184
|
if (!ok) { failed++; console.log(JSON.stringify(resp).slice(0, 300)); }
|
|
1174
1185
|
}
|
|
1175
1186
|
|
|
1176
|
-
|
|
1177
|
-
|
|
1187
|
+
if (e2eShard.take('B(1.9.62)')) {
|
|
1188
|
+
total++;
|
|
1178
1189
|
// audit npm CVE — OFFLINE 또는 package.json 없을 때 graceful
|
|
1179
1190
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ac-'));
|
|
1180
1191
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1186,8 +1197,8 @@ total++;
|
|
|
1186
1197
|
}
|
|
1187
1198
|
|
|
1188
1199
|
// 1.9.58/59 회귀
|
|
1189
|
-
|
|
1190
|
-
|
|
1200
|
+
if (e2eShard.take('B(1.9.58)')) {
|
|
1201
|
+
total++;
|
|
1191
1202
|
// fuzzy 매칭 — 어간 변형 (webhook ↔ webhooks)
|
|
1192
1203
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-fz-'));
|
|
1193
1204
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1200,8 +1211,8 @@ total++;
|
|
|
1200
1211
|
if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
|
|
1201
1212
|
}
|
|
1202
1213
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1214
|
+
if (e2eShard.take('B(1.9.59)')) {
|
|
1215
|
+
total++;
|
|
1205
1216
|
// session close default suggest
|
|
1206
1217
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sd-'));
|
|
1207
1218
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1212,8 +1223,8 @@ total++;
|
|
|
1212
1223
|
if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
|
|
1213
1224
|
}
|
|
1214
1225
|
|
|
1215
|
-
|
|
1216
|
-
|
|
1226
|
+
if (e2eShard.take('B(1.9.59) #2')) {
|
|
1227
|
+
total++;
|
|
1217
1228
|
// --no-suggest 비활성 (이전 동작 보존)
|
|
1218
1229
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ns-'));
|
|
1219
1230
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1224,8 +1235,8 @@ total++;
|
|
|
1224
1235
|
}
|
|
1225
1236
|
|
|
1226
1237
|
// 1.9.56/57 회귀
|
|
1227
|
-
|
|
1228
|
-
|
|
1238
|
+
if (e2eShard.take('B(1.9.56)')) {
|
|
1239
|
+
total++;
|
|
1229
1240
|
// handoff 자동 lessons 재상기
|
|
1230
1241
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ha-'));
|
|
1231
1242
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1238,8 +1249,8 @@ total++;
|
|
|
1238
1249
|
if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
|
|
1239
1250
|
}
|
|
1240
1251
|
|
|
1241
|
-
|
|
1242
|
-
|
|
1252
|
+
if (e2eShard.take('B(1.9.57)')) {
|
|
1253
|
+
total++;
|
|
1243
1254
|
// session close --suggest
|
|
1244
1255
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sc-'));
|
|
1245
1256
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1250,8 +1261,8 @@ total++;
|
|
|
1250
1261
|
}
|
|
1251
1262
|
|
|
1252
1263
|
// 1.9.54/55 회귀
|
|
1253
|
-
|
|
1254
|
-
|
|
1264
|
+
if (e2eShard.take('B(1.9.54)')) {
|
|
1265
|
+
total++;
|
|
1255
1266
|
// lessons --auto 키워드 자동 추출
|
|
1256
1267
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-la-'));
|
|
1257
1268
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1264,8 +1275,8 @@ total++;
|
|
|
1264
1275
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1265
1276
|
}
|
|
1266
1277
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1278
|
+
if (e2eShard.take('B(1.9.55)')) {
|
|
1279
|
+
total++;
|
|
1269
1280
|
// MCP server tools/list 12 도구 (1.9.55)
|
|
1270
1281
|
const r = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], {
|
|
1271
1282
|
encoding: 'utf8', timeout: 8000,
|
|
@@ -1281,8 +1292,8 @@ total++;
|
|
|
1281
1292
|
}
|
|
1282
1293
|
|
|
1283
1294
|
// 1.9.53 회귀: skill suggest 자동 학습
|
|
1284
|
-
|
|
1285
|
-
|
|
1295
|
+
if (e2eShard.take('B(1.9.53)')) {
|
|
1296
|
+
total++;
|
|
1286
1297
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sg-'));
|
|
1287
1298
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
1288
1299
|
// 반복 키워드 6회
|
|
@@ -1298,8 +1309,8 @@ total++;
|
|
|
1298
1309
|
}
|
|
1299
1310
|
|
|
1300
1311
|
// 1.9.51/52 회귀
|
|
1301
|
-
|
|
1302
|
-
|
|
1312
|
+
if (e2eShard.take('B(1.9.51)')) {
|
|
1313
|
+
total++;
|
|
1303
1314
|
// benchmark --scenario all → 4개 시나리오 모두 감지
|
|
1304
1315
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sc-'));
|
|
1305
1316
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1311,8 +1322,8 @@ total++;
|
|
|
1311
1322
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1312
1323
|
}
|
|
1313
1324
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1325
|
+
if (e2eShard.take('B(1.9.51) #2')) {
|
|
1326
|
+
total++;
|
|
1316
1327
|
// benchmark --scenario 알 수 없는 ID → 친절 안내
|
|
1317
1328
|
const r = cp.spawnSync(process.execPath, [CLI, 'benchmark', '--scenario', 'unknown-x'], { encoding: 'utf8', timeout: 15000 });
|
|
1318
1329
|
const ok = r.status !== 0 && /알 수 없는 scenario/.test(r.stdout + r.stderr);
|
|
@@ -1320,8 +1331,8 @@ total++;
|
|
|
1320
1331
|
if (!ok) { failed++; console.log((r.stdout + r.stderr).slice(0, 300)); }
|
|
1321
1332
|
}
|
|
1322
1333
|
|
|
1323
|
-
|
|
1324
|
-
|
|
1334
|
+
if (e2eShard.take('B(1.9.52)')) {
|
|
1335
|
+
total++;
|
|
1325
1336
|
// _parseSkillCatalog 4 형식 인식 — 1.9.370 (UR-0025): lib/pure-utils 직접 require (이전: harness 소스 regex+eval, 모듈 이동으로 전환)
|
|
1326
1337
|
const fn = require(path.resolve(__dirname, '..', 'lib', 'pure-utils'))._parseSkillCatalog;
|
|
1327
1338
|
if (typeof fn !== 'function') {
|
|
@@ -1340,8 +1351,8 @@ total++;
|
|
|
1340
1351
|
}
|
|
1341
1352
|
|
|
1342
1353
|
// 1.9.48~50 회귀
|
|
1343
|
-
|
|
1344
|
-
|
|
1354
|
+
if (e2eShard.take('B(1.9.48)')) {
|
|
1355
|
+
total++;
|
|
1345
1356
|
// 1.9.48 cross-platform archive — PowerShell ZIP or tar
|
|
1346
1357
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-arc-'));
|
|
1347
1358
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'all'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1354,8 +1365,8 @@ total++;
|
|
|
1354
1365
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1355
1366
|
}
|
|
1356
1367
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1368
|
+
if (e2eShard.take('B(1.9.49)')) {
|
|
1369
|
+
total++;
|
|
1359
1370
|
// 1.9.49 benchmark --measure 인자 검증
|
|
1360
1371
|
const r = cp.spawnSync(process.execPath, [CLI, 'benchmark', '--measure'], { encoding: 'utf8', timeout: 15000 });
|
|
1361
1372
|
const ok = r.status !== 0 && /사용법|task/.test(r.stdout + r.stderr);
|
|
@@ -1363,8 +1374,8 @@ total++;
|
|
|
1363
1374
|
if (!ok) { failed++; console.log((r.stdout + r.stderr).slice(0, 300)); }
|
|
1364
1375
|
}
|
|
1365
1376
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1377
|
+
if (e2eShard.take('B(1.9.50)')) {
|
|
1378
|
+
total++;
|
|
1368
1379
|
// 1.9.50 skill match --embedding (Ollama URL 없을 때 거부)
|
|
1369
1380
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-emb-'));
|
|
1370
1381
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1377,8 +1388,8 @@ total++;
|
|
|
1377
1388
|
}
|
|
1378
1389
|
|
|
1379
1390
|
// 1.9.45 회귀: skill match — 키워드 매칭 추천 (jaccard)
|
|
1380
|
-
|
|
1381
|
-
|
|
1391
|
+
if (e2eShard.take('B(1.9.45)')) {
|
|
1392
|
+
total++;
|
|
1382
1393
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-match-'));
|
|
1383
1394
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'all'], { stdio: 'ignore', timeout: 30000 });
|
|
1384
1395
|
const r = cp.spawnSync(process.execPath, [CLI, 'skill', 'match', '기능 구현 재사용 검사', '--path', tmpC, '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
@@ -1392,8 +1403,8 @@ total++;
|
|
|
1392
1403
|
}
|
|
1393
1404
|
|
|
1394
1405
|
// 1.9.46 회귀: benchmark — 자체 6차원 점수 + 타도구 비교
|
|
1395
|
-
|
|
1396
|
-
|
|
1406
|
+
if (e2eShard.take('B(1.9.46)')) {
|
|
1407
|
+
total++;
|
|
1397
1408
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-bench-'));
|
|
1398
1409
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
1399
1410
|
const r = cp.spawnSync(process.execPath, [CLI, 'benchmark', tmpC, '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
@@ -1410,8 +1421,8 @@ total++;
|
|
|
1410
1421
|
}
|
|
1411
1422
|
|
|
1412
1423
|
// 1.9.47 회귀: skill publish — 9개 SKILL.md export + manifest.json
|
|
1413
|
-
|
|
1414
|
-
|
|
1424
|
+
if (e2eShard.take('B(1.9.47)')) {
|
|
1425
|
+
total++;
|
|
1415
1426
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-pub-'));
|
|
1416
1427
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'all'], { stdio: 'ignore', timeout: 30000 });
|
|
1417
1428
|
const r = cp.spawnSync(process.execPath, [CLI, 'skill', 'publish', '--path', tmpC, '--bundle-only'], { encoding: 'utf8', timeout: 30000 });
|
|
@@ -1429,8 +1440,8 @@ total++;
|
|
|
1429
1440
|
}
|
|
1430
1441
|
|
|
1431
1442
|
// 1.9.44 회귀: BOM SKILL.md 처리 (stress-v2 G2 발견)
|
|
1432
|
-
|
|
1433
|
-
|
|
1443
|
+
if (e2eShard.take('B(1.9.44)')) {
|
|
1444
|
+
total++;
|
|
1434
1445
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-bom-'));
|
|
1435
1446
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
1436
1447
|
const src = path.join(tmpC, 'bom.md');
|
|
@@ -1445,8 +1456,8 @@ total++;
|
|
|
1445
1456
|
}
|
|
1446
1457
|
|
|
1447
1458
|
// 1.9.43 회귀: MCP server + skill export-all + _reports 비공개
|
|
1448
|
-
|
|
1449
|
-
|
|
1459
|
+
if (e2eShard.take('B(1.9.43)')) {
|
|
1460
|
+
total++;
|
|
1450
1461
|
// skill export-all: 모든 내장 skill을 SKILL.md로 일괄 export
|
|
1451
1462
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-exall-'));
|
|
1452
1463
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'all'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1459,8 +1470,8 @@ total++;
|
|
|
1459
1470
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1460
1471
|
}
|
|
1461
1472
|
|
|
1462
|
-
|
|
1463
|
-
|
|
1473
|
+
if (e2eShard.take('B(1.9.43) #2')) {
|
|
1474
|
+
total++;
|
|
1464
1475
|
// MCP server initialize: stdio JSON-RPC 정상 응답
|
|
1465
1476
|
const r = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], {
|
|
1466
1477
|
encoding: 'utf8', timeout: 10000,
|
|
@@ -1478,8 +1489,8 @@ total++;
|
|
|
1478
1489
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1479
1490
|
}
|
|
1480
1491
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1492
|
+
if (e2eShard.take('B(1.9.43) #3')) {
|
|
1493
|
+
total++;
|
|
1483
1494
|
// MCP server tools/list: 10개 도구 노출
|
|
1484
1495
|
const r = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], {
|
|
1485
1496
|
encoding: 'utf8', timeout: 10000,
|
|
@@ -1498,7 +1509,7 @@ total++;
|
|
|
1498
1509
|
|
|
1499
1510
|
// 1.36.55 (외부감사 이식성): .gitignore/.npmignore 는 npm files 화이트리스트에서 의도적으로 제외되는 저장소-전용 파일 —
|
|
1500
1511
|
// 게시 아티팩트에서는 검사 자체가 성립 안 하므로 명시 SKIP (조용한 누락이 아니라 사유 로그).
|
|
1501
|
-
{
|
|
1512
|
+
if (e2eShard.take('B(1.9.43) #4')) {
|
|
1502
1513
|
const giPath = path.join(__dirname, '..', '.gitignore');
|
|
1503
1514
|
if (!fs.existsSync(giPath)) console.log('⊘ SKIP(아티팩트 프로필): leerness-pkg/.gitignore 검사 — 저장소 체크아웃 전용');
|
|
1504
1515
|
else {
|
|
@@ -1510,7 +1521,7 @@ total++;
|
|
|
1510
1521
|
}
|
|
1511
1522
|
}
|
|
1512
1523
|
|
|
1513
|
-
{
|
|
1524
|
+
if (e2eShard.take('B(1.9.43) #5')) {
|
|
1514
1525
|
const niPath = path.join(__dirname, '..', '.npmignore');
|
|
1515
1526
|
if (!fs.existsSync(niPath)) console.log('⊘ SKIP(아티팩트 프로필): leerness-pkg/.npmignore 검사 — 저장소 체크아웃 전용');
|
|
1516
1527
|
else {
|
|
@@ -1523,8 +1534,8 @@ total++;
|
|
|
1523
1534
|
}
|
|
1524
1535
|
|
|
1525
1536
|
// 1.9.42 회귀: agentskills.io 표준 호환 (skill install/discover/export + .env opt-in)
|
|
1526
|
-
|
|
1527
|
-
|
|
1537
|
+
if (e2eShard.take('B(1.9.42)')) {
|
|
1538
|
+
total++;
|
|
1528
1539
|
// skill discover: env 없으면 opt-in 안내로 거부
|
|
1529
1540
|
const env = { ...process.env };
|
|
1530
1541
|
delete env.LEERNESS_SKILL_DISCOVER_URL;
|
|
@@ -1534,8 +1545,8 @@ total++;
|
|
|
1534
1545
|
if (!ok) { failed++; console.log((r.stdout + r.stderr).slice(0, 400)); }
|
|
1535
1546
|
}
|
|
1536
1547
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1548
|
+
if (e2eShard.take('B(1.9.42) #2')) {
|
|
1549
|
+
total++;
|
|
1539
1550
|
// skill export → SKILL.md frontmatter 정확 생성
|
|
1540
1551
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-skex-'));
|
|
1541
1552
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1551,8 +1562,8 @@ total++;
|
|
|
1551
1562
|
if (!ok) { failed++; console.log(body.slice(0, 300) || r.stdout.slice(0, 300)); }
|
|
1552
1563
|
}
|
|
1553
1564
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1565
|
+
if (e2eShard.take('B(1.9.42) #3')) {
|
|
1566
|
+
total++;
|
|
1556
1567
|
// skill install: 로컬 SKILL.md import → .leerness/skills/<id>/SKILL.md 자동 배치
|
|
1557
1568
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-skin-'));
|
|
1558
1569
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1568,8 +1579,8 @@ total++;
|
|
|
1568
1579
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1569
1580
|
}
|
|
1570
1581
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1582
|
+
if (e2eShard.take('B(1.9.42) #4')) {
|
|
1583
|
+
total++;
|
|
1573
1584
|
// skill install이 skill.json도 자동 작성 (자체 catalog 호환)
|
|
1574
1585
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-skin2-'));
|
|
1575
1586
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1587,8 +1598,8 @@ total++;
|
|
|
1587
1598
|
}
|
|
1588
1599
|
|
|
1589
1600
|
// 1.9.41 회귀: whats-new 명령 + migrate 차분 AI must re-read + handoff fresh 알림
|
|
1590
|
-
|
|
1591
|
-
|
|
1601
|
+
if (e2eShard.take('B(1.9.41)')) {
|
|
1602
|
+
total++;
|
|
1592
1603
|
// whats-new --from 큰 점프 → 신규 명령 추출
|
|
1593
1604
|
// 1.36.69: 기본 응답은 최신 30개로 제한됨 — 전체 차분 검증은 --all (대용량이라 maxBuffer 명시)
|
|
1594
1605
|
const r = cp.spawnSync(process.execPath, [CLI, 'whats-new', '--from', '1.9.33', '--json', '--all'], { encoding: 'utf8', timeout: 20000, maxBuffer: 32 * 1024 * 1024 });
|
|
@@ -1603,8 +1614,8 @@ total++;
|
|
|
1603
1614
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1604
1615
|
}
|
|
1605
1616
|
|
|
1606
|
-
|
|
1607
|
-
|
|
1617
|
+
if (e2eShard.take('B(1.9.41) #2')) {
|
|
1618
|
+
total++;
|
|
1608
1619
|
// migrate가 fromV가 있는 경우 AI must re-read 블록을 stdout에 출력
|
|
1609
1620
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mig-'));
|
|
1610
1621
|
// 1.9.30 표시로 init한 척 (HARNESS_VERSION 직접 작성)
|
|
@@ -1619,8 +1630,8 @@ total++;
|
|
|
1619
1630
|
if (!ok) { failed++; console.log(r.stdout.slice(-800)); }
|
|
1620
1631
|
}
|
|
1621
1632
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1633
|
+
if (e2eShard.take('B(1.9.41) #3')) {
|
|
1634
|
+
total++;
|
|
1624
1635
|
// migration-report.md에 AI must re-read 섹션 영구 기록
|
|
1625
1636
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mig2-'));
|
|
1626
1637
|
fs.mkdirSync(path.join(tmpC, '.leerness'), { recursive: true });
|
|
@@ -1633,8 +1644,8 @@ total++;
|
|
|
1633
1644
|
if (!ok) { failed++; console.log(body.slice(0, 600)); }
|
|
1634
1645
|
}
|
|
1635
1646
|
|
|
1636
|
-
|
|
1637
|
-
|
|
1647
|
+
if (e2eShard.take('B(1.9.41) #4')) {
|
|
1648
|
+
total++;
|
|
1638
1649
|
// handoff가 fresh migration-report (24h 내) 시 자동 알림
|
|
1639
1650
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-fresh-'));
|
|
1640
1651
|
fs.mkdirSync(path.join(tmpC, '.leerness'), { recursive: true });
|
|
@@ -1652,8 +1663,8 @@ total++;
|
|
|
1652
1663
|
}
|
|
1653
1664
|
|
|
1654
1665
|
// 1.9.40 회귀: release pack 통합 명령 + audit README mismatch 감지
|
|
1655
|
-
|
|
1656
|
-
|
|
1666
|
+
if (e2eShard.take('B(1.9.40/1.36.144)')) {
|
|
1667
|
+
total++;
|
|
1657
1668
|
// release pack --dry-run --task-add: 자동 task 등록
|
|
1658
1669
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rp-'));
|
|
1659
1670
|
// init 후 가벼운 package.json 흉내 (release pack은 npm pack 시도하므로 dry-run으로 우회)
|
|
@@ -1681,8 +1692,8 @@ total++;
|
|
|
1681
1692
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); console.log('--- real ---'); console.log(r2.stdout.slice(0, 400)); }
|
|
1682
1693
|
}
|
|
1683
1694
|
|
|
1684
|
-
|
|
1685
|
-
|
|
1695
|
+
if (e2eShard.take('B(1.9.40)')) {
|
|
1696
|
+
total++;
|
|
1686
1697
|
// release pack --parent-migrate (인공 parent .leerness 생성)
|
|
1687
1698
|
const tmpParent = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rp-parent-'));
|
|
1688
1699
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpParent, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1696,8 +1707,8 @@ total++;
|
|
|
1696
1707
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1697
1708
|
}
|
|
1698
1709
|
|
|
1699
|
-
|
|
1700
|
-
|
|
1710
|
+
if (e2eShard.take('B(1.9.40) #2')) {
|
|
1711
|
+
total++;
|
|
1701
1712
|
// audit README version mismatch 감지
|
|
1702
1713
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mm-'));
|
|
1703
1714
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1710,8 +1721,8 @@ total++;
|
|
|
1710
1721
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1711
1722
|
}
|
|
1712
1723
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1724
|
+
if (e2eShard.take('B(1.9.40) #3')) {
|
|
1725
|
+
total++;
|
|
1715
1726
|
// audit --fix가 README 자동 갱신
|
|
1716
1727
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mm2-'));
|
|
1717
1728
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1725,8 +1736,8 @@ total++;
|
|
|
1725
1736
|
}
|
|
1726
1737
|
|
|
1727
1738
|
// 1.9.39 회귀: session workflow 가이드 + auto-fix + auto-recover
|
|
1728
|
-
|
|
1729
|
-
|
|
1739
|
+
if (e2eShard.take('B(1.9.39)')) {
|
|
1740
|
+
total++;
|
|
1730
1741
|
// handoff 끝에 워크플로 6단계 가이드 자동 표시
|
|
1731
1742
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-wf-'));
|
|
1732
1743
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1739,8 +1750,8 @@ total++;
|
|
|
1739
1750
|
if (!ok) { failed++; console.log(r.stdout.slice(-500)); }
|
|
1740
1751
|
}
|
|
1741
1752
|
|
|
1742
|
-
|
|
1743
|
-
|
|
1753
|
+
if (e2eShard.take('B(1.9.39) #2')) {
|
|
1754
|
+
total++;
|
|
1744
1755
|
// session-workflow.md 파일이 init 시 생성
|
|
1745
1756
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-wf2-'));
|
|
1746
1757
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1752,8 +1763,8 @@ total++;
|
|
|
1752
1763
|
if (!ok) { failed++; if (fs.existsSync(wfFile)) console.log(fs.readFileSync(wfFile, 'utf8').slice(0, 300)); else console.log('파일 없음'); }
|
|
1753
1764
|
}
|
|
1754
1765
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1766
|
+
if (e2eShard.take('B(1.9.39) #3')) {
|
|
1767
|
+
total++;
|
|
1757
1768
|
// AGENTS.md / CLAUDE.md에 session-workflow.md 참조 포함
|
|
1758
1769
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-wf3-'));
|
|
1759
1770
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1764,8 +1775,8 @@ total++;
|
|
|
1764
1775
|
if (!ok) { failed++; }
|
|
1765
1776
|
}
|
|
1766
1777
|
|
|
1767
|
-
|
|
1768
|
-
|
|
1778
|
+
if (e2eShard.take('B(1.9.39) #4')) {
|
|
1779
|
+
total++;
|
|
1769
1780
|
// drift check --auto-fix: critical 시 session close 자동 실행 (시뮬은 어려우니 옵션 인식만)
|
|
1770
1781
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-af-'));
|
|
1771
1782
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1779,8 +1790,8 @@ total++;
|
|
|
1779
1790
|
}
|
|
1780
1791
|
|
|
1781
1792
|
// 1.9.38 회귀: usage stats, task sync, drift reminder, drift skip learning
|
|
1782
|
-
|
|
1783
|
-
|
|
1793
|
+
if (e2eShard.take('B(1.9.38)')) {
|
|
1794
|
+
total++;
|
|
1784
1795
|
// B. usage stats: 빈 상태 + 호출 후 카운터 증가
|
|
1785
1796
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-usage-'));
|
|
1786
1797
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1799,8 +1810,8 @@ total++;
|
|
|
1799
1810
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1800
1811
|
}
|
|
1801
1812
|
|
|
1802
|
-
|
|
1803
|
-
|
|
1813
|
+
if (e2eShard.take('B(1.9.38) #2')) {
|
|
1814
|
+
total++;
|
|
1804
1815
|
// C. task sync — TodoWrite JSON 임포트
|
|
1805
1816
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tasksync-'));
|
|
1806
1817
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1818,8 +1829,8 @@ total++;
|
|
|
1818
1829
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
1819
1830
|
}
|
|
1820
1831
|
|
|
1821
|
-
|
|
1822
|
-
|
|
1832
|
+
if (e2eShard.take('B(1.9.38) #3')) {
|
|
1833
|
+
total++;
|
|
1823
1834
|
// A. drift reminder 파일 자동 생성 (인공 stale 시뮬)
|
|
1824
1835
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rem-'));
|
|
1825
1836
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1840,8 +1851,8 @@ total++;
|
|
|
1840
1851
|
if (!ok) { failed++; if (fs.existsSync(remPath)) console.log(fs.readFileSync(remPath, 'utf8').slice(0, 400)); else console.log('(reminder 파일 없음)'); }
|
|
1841
1852
|
}
|
|
1842
1853
|
|
|
1843
|
-
|
|
1844
|
-
|
|
1854
|
+
if (e2eShard.take('B(1.9.38) #4')) {
|
|
1855
|
+
total++;
|
|
1845
1856
|
// D. drift 학습 — --no-drift-check 5회 호출 후 임계 완화
|
|
1846
1857
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-learn-'));
|
|
1847
1858
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1856,8 +1867,8 @@ total++;
|
|
|
1856
1867
|
}
|
|
1857
1868
|
|
|
1858
1869
|
// 1.9.37 회귀: drift detection
|
|
1859
|
-
|
|
1860
|
-
|
|
1870
|
+
if (e2eShard.take('B(1.9.37)')) {
|
|
1871
|
+
total++;
|
|
1861
1872
|
// drift check: 신규 init 직후 (메타파일은 fresh) → healthy 또는 attention
|
|
1862
1873
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-drift-'));
|
|
1863
1874
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1869,8 +1880,8 @@ total++;
|
|
|
1869
1880
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1870
1881
|
}
|
|
1871
1882
|
|
|
1872
|
-
|
|
1873
|
-
|
|
1883
|
+
if (e2eShard.take('B(1.9.37) #2')) {
|
|
1884
|
+
total++;
|
|
1874
1885
|
// drift check --json: 점수/신호 구조 검증
|
|
1875
1886
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-drift2-'));
|
|
1876
1887
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1887,8 +1898,8 @@ total++;
|
|
|
1887
1898
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1888
1899
|
}
|
|
1889
1900
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1901
|
+
if (e2eShard.take('B(1.9.37) #3')) {
|
|
1902
|
+
total++;
|
|
1892
1903
|
// handoff 자동 drift 경고 — 인공 stale 시뮬 (session-handoff.md의 Last generated를 옛 날짜로)
|
|
1893
1904
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-drift3-'));
|
|
1894
1905
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1907,8 +1918,8 @@ total++;
|
|
|
1907
1918
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1908
1919
|
}
|
|
1909
1920
|
|
|
1910
|
-
|
|
1911
|
-
|
|
1921
|
+
if (e2eShard.take('B(1.9.37) #4')) {
|
|
1922
|
+
total++;
|
|
1912
1923
|
// LEERNESS_NO_DRIFT_CHECK=1: 자동 경고 스킵
|
|
1913
1924
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-drift4-'));
|
|
1914
1925
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -1928,8 +1939,8 @@ total++;
|
|
|
1928
1939
|
}
|
|
1929
1940
|
|
|
1930
1941
|
// 1.9.36 회귀: dispatch 권장 플래그 + bench + 작업 유형 추천
|
|
1931
|
-
|
|
1932
|
-
|
|
1942
|
+
if (e2eShard.take('B(1.9.36)')) {
|
|
1943
|
+
total++;
|
|
1933
1944
|
// dispatch --write 시 agy --yolo 자동 추가
|
|
1934
1945
|
const env = { ...process.env, LEERNESS_ENABLE_AGY: '1' };
|
|
1935
1946
|
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '코드 분석해서 요약', '--to', 'agy', '--write'], { encoding: 'utf8', timeout: 15000, env });
|
|
@@ -1940,8 +1951,8 @@ total++;
|
|
|
1940
1951
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1941
1952
|
}
|
|
1942
1953
|
|
|
1943
|
-
|
|
1944
|
-
|
|
1954
|
+
if (e2eShard.take('B(1.9.36) #2')) {
|
|
1955
|
+
total++;
|
|
1945
1956
|
// dispatch read-only (기본) — --yolo/--dangerously 같은 위험 플래그 없음
|
|
1946
1957
|
const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1' };
|
|
1947
1958
|
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '번역해줘', '--to', 'claude'], { encoding: 'utf8', timeout: 15000, env });
|
|
@@ -1952,8 +1963,8 @@ total++;
|
|
|
1952
1963
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1953
1964
|
}
|
|
1954
1965
|
|
|
1955
|
-
|
|
1956
|
-
|
|
1966
|
+
if (e2eShard.take('B(1.9.36) #3')) {
|
|
1967
|
+
total++;
|
|
1957
1968
|
// 작업 유형 추천 — 비활성 CLI에도 추천 메시지 우선 출력
|
|
1958
1969
|
const env = { ...process.env, LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_CLAUDE: '0' };
|
|
1959
1970
|
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '번역해줘 한국어를 영어로', '--to', 'agy'], { encoding: 'utf8', timeout: 15000, env });
|
|
@@ -1963,8 +1974,8 @@ total++;
|
|
|
1963
1974
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
1964
1975
|
}
|
|
1965
1976
|
|
|
1966
|
-
|
|
1967
|
-
|
|
1977
|
+
if (e2eShard.take('B(1.9.36) #4')) {
|
|
1978
|
+
total++;
|
|
1968
1979
|
// bench 명령: ready CLI 없을 때 거부
|
|
1969
1980
|
// 빌트인 provider가 늘어나도 상속 환경에서 실제 agent가 실행되지 않도록 레지스트리 전체를 끈다.
|
|
1970
1981
|
// PATH 선두의 실행 마커는 disabled provider가 version probe조차 받지 않는지를 검증한다.
|
|
@@ -1983,8 +1994,8 @@ total++;
|
|
|
1983
1994
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 300)); }
|
|
1984
1995
|
}
|
|
1985
1996
|
|
|
1986
|
-
|
|
1987
|
-
|
|
1997
|
+
if (e2eShard.take('B(1.9.36) #5')) {
|
|
1998
|
+
total++;
|
|
1988
1999
|
// 사용법 메시지에 bench 포함
|
|
1989
2000
|
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'unknown'], { encoding: 'utf8', timeout: 10000 });
|
|
1990
2001
|
const ok = r.status !== 0 && /bench/.test(r.stdout + r.stderr);
|
|
@@ -1993,8 +2004,8 @@ total++;
|
|
|
1993
2004
|
}
|
|
1994
2005
|
|
|
1995
2006
|
// 1.9.35 회귀: 5개 신규 기능
|
|
1996
|
-
|
|
1997
|
-
|
|
2007
|
+
if (e2eShard.take('B(1.9.35)')) {
|
|
2008
|
+
total++;
|
|
1998
2009
|
// 개선#1: handoff 시 .leerness 부재 자동 경고
|
|
1999
2010
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-noinit-'));
|
|
2000
2011
|
const r = cp.spawnSync(process.execPath, [CLI, 'handoff', tmpC], { encoding: 'utf8', timeout: 10000 });
|
|
@@ -2003,8 +2014,8 @@ total++;
|
|
|
2003
2014
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
2004
2015
|
}
|
|
2005
2016
|
|
|
2006
|
-
|
|
2007
|
-
|
|
2017
|
+
if (e2eShard.take('B(1.9.35) #2')) {
|
|
2018
|
+
total++;
|
|
2008
2019
|
// 개선#5: audit --fix 옵션 (플래그 인식만 검증, 실 fix는 통합 환경 필요)
|
|
2009
2020
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-fix-'));
|
|
2010
2021
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--no-banner', '--no-stale-check', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2014,8 +2025,8 @@ total++;
|
|
|
2014
2025
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
2015
2026
|
}
|
|
2016
2027
|
|
|
2017
|
-
|
|
2018
|
-
|
|
2028
|
+
if (e2eShard.take('B(1.9.35) #3')) {
|
|
2029
|
+
total++;
|
|
2019
2030
|
// 개선#3: contract verify
|
|
2020
2031
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-contract-'));
|
|
2021
2032
|
const specFile = path.join(tmpDir, 'spec.md');
|
|
@@ -2033,8 +2044,8 @@ total++;
|
|
|
2033
2044
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
2034
2045
|
}
|
|
2035
2046
|
|
|
2036
|
-
|
|
2037
|
-
|
|
2047
|
+
if (e2eShard.take('B(1.9.35) #4')) {
|
|
2048
|
+
total++;
|
|
2038
2049
|
// 개선#2: reuse autodetect — src/*.js의 module.exports 스캔
|
|
2039
2050
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-autodetect-'));
|
|
2040
2051
|
fs.mkdirSync(path.join(tmpDir, 'src'), { recursive: true });
|
|
@@ -2048,8 +2059,8 @@ total++;
|
|
|
2048
2059
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
|
|
2049
2060
|
}
|
|
2050
2061
|
|
|
2051
|
-
|
|
2052
|
-
|
|
2062
|
+
if (e2eShard.take('B(1.9.35) #5')) {
|
|
2063
|
+
total++;
|
|
2053
2064
|
// 개선#4: agents dispatch 안내문에 mtime/contract 안전 규칙 추가
|
|
2054
2065
|
const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1' };
|
|
2055
2066
|
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', 'test task', '--to', 'claude'], { encoding: 'utf8', timeout: 15000, env });
|
|
@@ -2063,8 +2074,8 @@ total++;
|
|
|
2063
2074
|
}
|
|
2064
2075
|
|
|
2065
2076
|
// 1.9.34 회귀: 인터랙티브 multi-select (방향키/스페이스) — 비-TTY 폴백
|
|
2066
|
-
|
|
2067
|
-
|
|
2077
|
+
if (e2eShard.take('B(1.9.34)')) {
|
|
2078
|
+
total++;
|
|
2068
2079
|
// 비-TTY에서는 _selectOne/_selectMany가 defaults 또는 첫 옵션 반환
|
|
2069
2080
|
// → init이 --yes 없이도 비대화형이면 기본 스킬셋(recommended)로 진행
|
|
2070
2081
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-multi-'));
|
|
@@ -2077,8 +2088,8 @@ total++;
|
|
|
2077
2088
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
2078
2089
|
}
|
|
2079
2090
|
|
|
2080
|
-
|
|
2081
|
-
|
|
2091
|
+
if (e2eShard.take('B(1.9.34) #2')) {
|
|
2092
|
+
total++;
|
|
2082
2093
|
// 1.9.34 배너 256색 그라데이션 — TTY 강제 + --banner (1.9.144+ 신규 슬로건)
|
|
2083
2094
|
const r = cp.spawnSync(process.execPath, [CLI, '--version', '--banner'], { encoding: 'utf8', timeout: 10000 });
|
|
2084
2095
|
const ok = r.status === 0
|
|
@@ -2090,8 +2101,8 @@ total++;
|
|
|
2090
2101
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
2091
2102
|
}
|
|
2092
2103
|
|
|
2093
|
-
|
|
2094
|
-
|
|
2104
|
+
if (e2eShard.take('B(1.9.34) #3')) {
|
|
2105
|
+
total++;
|
|
2095
2106
|
// 1.9.34 LEERNESS_NO_INTERACTIVE=1: 구식 숫자 prompt 사용 (TTY 일 때만 의미 있음; 비-TTY는 어차피 --yes 폴백)
|
|
2096
2107
|
// 검증: --no-interactive-select 플래그가 인식되고 에러 안 남
|
|
2097
2108
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-noint-'));
|
|
@@ -2101,8 +2112,8 @@ total++;
|
|
|
2101
2112
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 300)); }
|
|
2102
2113
|
}
|
|
2103
2114
|
|
|
2104
|
-
|
|
2105
|
-
|
|
2115
|
+
if (e2eShard.take('B(1.9.33) #2')) {
|
|
2116
|
+
total++;
|
|
2106
2117
|
// --no-stale-check / LEERNESS_NO_STALE_CHECK=1: 경고 스킵
|
|
2107
2118
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-stale2-'));
|
|
2108
2119
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended', '--no-stale-check'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2121,8 +2132,8 @@ total++;
|
|
|
2121
2132
|
}
|
|
2122
2133
|
|
|
2123
2134
|
// 1.9.22 회귀: handoff --compact + orchestrate opt-in 정책 + llm-bench record
|
|
2124
|
-
|
|
2125
|
-
|
|
2135
|
+
if (e2eShard.take('B(1.9.22/1.36.255)')) {
|
|
2136
|
+
total++;
|
|
2126
2137
|
// handoff --compact: 1줄 요약 출력
|
|
2127
2138
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-compact-'));
|
|
2128
2139
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2139,8 +2150,8 @@ total++;
|
|
|
2139
2150
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
2140
2151
|
}
|
|
2141
2152
|
|
|
2142
|
-
|
|
2143
|
-
|
|
2153
|
+
if (e2eShard.take('B(1.9.22)')) {
|
|
2154
|
+
total++;
|
|
2144
2155
|
// orchestrate: LEERNESS_OLLAMA_BASE_URL 없으면 거부 (자동 적용 금지 정책)
|
|
2145
2156
|
const tmpO = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-orch-'));
|
|
2146
2157
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpO, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2156,8 +2167,8 @@ total++;
|
|
|
2156
2167
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
2157
2168
|
}
|
|
2158
2169
|
|
|
2159
|
-
|
|
2160
|
-
|
|
2170
|
+
if (e2eShard.take('B(1.9.22) #2')) {
|
|
2171
|
+
total++;
|
|
2161
2172
|
// orchestrate: .env 파일 자동 로드 (단, fake URL이라 실제 호출은 실패)
|
|
2162
2173
|
const tmpE = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-orch-env-'));
|
|
2163
2174
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpE, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2171,8 +2182,8 @@ total++;
|
|
|
2171
2182
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
2172
2183
|
}
|
|
2173
2184
|
|
|
2174
|
-
|
|
2175
|
-
|
|
2185
|
+
if (e2eShard.take('B(1.9.22) #3')) {
|
|
2186
|
+
total++;
|
|
2176
2187
|
// llm-bench record: 히스토리 누적
|
|
2177
2188
|
const tmpL = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-llmb-'));
|
|
2178
2189
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpL, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2184,8 +2195,8 @@ total++;
|
|
|
2184
2195
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
2185
2196
|
}
|
|
2186
2197
|
|
|
2187
|
-
|
|
2188
|
-
|
|
2198
|
+
if (e2eShard.take('B(1.9.20) #2')) {
|
|
2199
|
+
total++;
|
|
2189
2200
|
// jest 출력 파싱
|
|
2190
2201
|
const tmpJ = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tparse-'));
|
|
2191
2202
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpJ, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2204,8 +2215,8 @@ total++;
|
|
|
2204
2215
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
2205
2216
|
}
|
|
2206
2217
|
|
|
2207
|
-
|
|
2208
|
-
|
|
2218
|
+
if (e2eShard.take('B(1.9.20) #3')) {
|
|
2219
|
+
total++;
|
|
2209
2220
|
// mocha "N passing"
|
|
2210
2221
|
const tmpM = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mocha-'));
|
|
2211
2222
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpM, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2222,8 +2233,8 @@ total++;
|
|
|
2222
2233
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
|
|
2223
2234
|
}
|
|
2224
2235
|
|
|
2225
|
-
|
|
2226
|
-
|
|
2236
|
+
if (e2eShard.take('B(1.9.20) #4')) {
|
|
2237
|
+
total++;
|
|
2227
2238
|
// verify-code --bench: scripts.bench 자동 실행 + evidence 누적
|
|
2228
2239
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-bench-'));
|
|
2229
2240
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpB, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2246,8 +2257,8 @@ total++;
|
|
|
2246
2257
|
}
|
|
2247
2258
|
|
|
2248
2259
|
// 1.9.15 회귀: brainstorm 라인번호 / --all-apps / --include
|
|
2249
|
-
|
|
2250
|
-
|
|
2260
|
+
if (e2eShard.take('B(1.9.15)')) {
|
|
2261
|
+
total++;
|
|
2251
2262
|
const tmpL = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-line-'));
|
|
2252
2263
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpL, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
2253
2264
|
fs.appendFileSync(path.join(tmpL, '.leerness/decisions.md'), '\n### 2026-05-13 — 캐시 정책 결정\n- Reason: rate limit 회피\n');
|
|
@@ -2257,8 +2268,8 @@ total++;
|
|
|
2257
2268
|
console.log(ok ? '✓ B(1.9.15) brainstorm: 파일:라인 + 매치 필드 표시' : `✗ 1.9.15 brainstorm 위치 실패\n${r.stdout.slice(0,500)}`);
|
|
2258
2269
|
if (!ok) failed++;
|
|
2259
2270
|
}
|
|
2260
|
-
|
|
2261
|
-
|
|
2271
|
+
if (e2eShard.take('B(1.9.15) #2')) {
|
|
2272
|
+
total++;
|
|
2262
2273
|
// --include 다중 경로 통합
|
|
2263
2274
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-wsA-'));
|
|
2264
2275
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-wsB-'));
|
|
@@ -2271,8 +2282,8 @@ total++;
|
|
|
2271
2282
|
console.log(ok ? '✓ B(1.9.15) retro --include: 2개 통합' : '✗ 1.9.15 retro --include 실패');
|
|
2272
2283
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 600)); }
|
|
2273
2284
|
}
|
|
2274
|
-
|
|
2275
|
-
|
|
2285
|
+
if (e2eShard.take('B(1.9.15) #3')) {
|
|
2286
|
+
total++;
|
|
2276
2287
|
// insights --include
|
|
2277
2288
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-iA-'));
|
|
2278
2289
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-iB-'));
|
|
@@ -2283,8 +2294,8 @@ total++;
|
|
|
2283
2294
|
console.log(ok ? '✓ B(1.9.15) insights --include: 표 형식 통합' : '✗ 1.9.15 insights --include 실패');
|
|
2284
2295
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 600)); }
|
|
2285
2296
|
}
|
|
2286
|
-
|
|
2287
|
-
|
|
2297
|
+
if (e2eShard.take('B(1.9.15) #4')) {
|
|
2298
|
+
total++;
|
|
2288
2299
|
// 잘못된 --include 경로 — warn 출력 + .leerness 있는 것만 처리
|
|
2289
2300
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-bad-'));
|
|
2290
2301
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpA, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2296,8 +2307,8 @@ total++;
|
|
|
2296
2307
|
}
|
|
2297
2308
|
|
|
2298
2309
|
// 1.9.14 회귀: A(Template 제외) / B(word boundary) / C(planned 포함) / D(코드블록 템플릿)
|
|
2299
|
-
|
|
2300
|
-
|
|
2310
|
+
if (e2eShard.take('B(1.9.14-A)')) {
|
|
2311
|
+
total++;
|
|
2301
2312
|
// A: init 직후 decisions.md의 Template이 결정으로 카운트되지 않아야 함
|
|
2302
2313
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-A-'));
|
|
2303
2314
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpA, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2306,8 +2317,8 @@ total++;
|
|
|
2306
2317
|
console.log(ok ? '✓ B(1.9.14-A) Template 제외: 누적 결정 0건' : `✗ A 실패\n${r.stdout.slice(0, 500)}`);
|
|
2307
2318
|
if (!ok) failed++;
|
|
2308
2319
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2320
|
+
if (e2eShard.take('B(1.9.14-B)')) {
|
|
2321
|
+
total++;
|
|
2311
2322
|
// B: brainstorm 토큰 매칭 — "API"는 매치, "AP"는 부분 매치라 안 잡힘
|
|
2312
2323
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-B-'));
|
|
2313
2324
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpB, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2320,8 +2331,8 @@ total++;
|
|
|
2320
2331
|
console.log(ok ? '✓ B(1.9.14-B) brainstorm word boundary: limit 매치 / lim 부분매치 안 잡힘' : `✗ B 실패\n${r1.stdout.slice(0, 200)}\n${r2.stdout.slice(0, 200)}`);
|
|
2321
2332
|
if (!ok) failed++;
|
|
2322
2333
|
}
|
|
2323
|
-
|
|
2324
|
-
|
|
2334
|
+
if (e2eShard.take('B(1.9.14-C)')) {
|
|
2335
|
+
total++;
|
|
2325
2336
|
// C: retro 다음 우선 작업에 planned 포함
|
|
2326
2337
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-C-'));
|
|
2327
2338
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2332,8 +2343,8 @@ total++;
|
|
|
2332
2343
|
console.log(ok ? '✓ B(1.9.14-C) retro 다음 우선 작업에 planned 포함' : '✗ C 실패');
|
|
2333
2344
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 600)); }
|
|
2334
2345
|
}
|
|
2335
|
-
|
|
2336
|
-
|
|
2346
|
+
if (e2eShard.take('B(1.9.14-D)')) {
|
|
2347
|
+
total++;
|
|
2337
2348
|
// D: init decisions.md가 ```md 코드블록으로 감싸짐
|
|
2338
2349
|
const tmpD = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-D-'));
|
|
2339
2350
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpD, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2344,8 +2355,8 @@ total++;
|
|
|
2344
2355
|
}
|
|
2345
2356
|
|
|
2346
2357
|
// 1.9.13: retro / insights / brainstorm
|
|
2347
|
-
|
|
2348
|
-
|
|
2358
|
+
if (e2eShard.take('B(1.9.13)')) {
|
|
2359
|
+
total++;
|
|
2349
2360
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-retro-'));
|
|
2350
2361
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
2351
2362
|
cp.spawnSync(process.execPath, [CLI, 'plan', 'add', '캐시 helper', '--status', 'done', '--path', tmpR], { stdio: 'ignore', timeout: 10000 });
|
|
@@ -2360,8 +2371,8 @@ total++;
|
|
|
2360
2371
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
|
|
2361
2372
|
}
|
|
2362
2373
|
|
|
2363
|
-
|
|
2364
|
-
|
|
2374
|
+
if (e2eShard.take('B(1.9.13) #2')) {
|
|
2375
|
+
total++;
|
|
2365
2376
|
const tmpI = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ins-'));
|
|
2366
2377
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpI, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
2367
2378
|
const r = cp.spawnSync(process.execPath, [CLI, 'insights', tmpI], { encoding: 'utf8', timeout: 15000 });
|
|
@@ -2370,8 +2381,8 @@ total++;
|
|
|
2370
2381
|
if (!ok) failed++;
|
|
2371
2382
|
}
|
|
2372
2383
|
|
|
2373
|
-
|
|
2374
|
-
|
|
2384
|
+
if (e2eShard.take('B(1.9.13) #3')) {
|
|
2385
|
+
total++;
|
|
2375
2386
|
const tmpB = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-brain-'));
|
|
2376
2387
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpB, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
2377
2388
|
fs.appendFileSync(path.join(tmpB, '.leerness/decisions.md'), `\n### 2026-05-13 — 캐시 차등 TTL 결정\n- Reason: open-meteo 응답 최적화\n`);
|
|
@@ -2382,8 +2393,8 @@ total++;
|
|
|
2382
2393
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 600)); }
|
|
2383
2394
|
}
|
|
2384
2395
|
|
|
2385
|
-
|
|
2386
|
-
|
|
2396
|
+
if (e2eShard.take('B(1.9.13) #4')) {
|
|
2397
|
+
total++;
|
|
2387
2398
|
// session close 한 줄 요약 자동 출력
|
|
2388
2399
|
const tmpS = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-summary-'));
|
|
2389
2400
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpS, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2393,8 +2404,8 @@ total++;
|
|
|
2393
2404
|
if (!ok) { failed++; console.log(r.stdout.slice(-600)); }
|
|
2394
2405
|
}
|
|
2395
2406
|
|
|
2396
|
-
|
|
2397
|
-
|
|
2407
|
+
if (e2eShard.take('B(1.9.13) #5')) {
|
|
2408
|
+
total++;
|
|
2398
2409
|
// 5세션 마일스톤 — 자동 깊은 회고
|
|
2399
2410
|
const tmpD = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-deep-'));
|
|
2400
2411
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpD, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
|
|
@@ -2410,8 +2421,8 @@ total++;
|
|
|
2410
2421
|
// 1.9.284: roadmap 전용 테스트 블록 — roadmap 생성 일시 ON (이후 다시 OFF)
|
|
2411
2422
|
process.env.LEERNESS_NO_AUTO_ROADMAP = '0';
|
|
2412
2423
|
// 1.9.12: auto roadmap — install 직후 자동 생성
|
|
2413
|
-
|
|
2414
|
-
|
|
2424
|
+
if (e2eShard.take('B(1.9.12)')) {
|
|
2425
|
+
total++;
|
|
2415
2426
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-auto1-'));
|
|
2416
2427
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpA, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2417
2428
|
const ok = fs.existsSync(path.join(tmpA, 'leerness.html'));
|
|
@@ -2420,8 +2431,8 @@ total++;
|
|
|
2420
2431
|
}
|
|
2421
2432
|
|
|
2422
2433
|
// 1.9.12: session close 후 자동 갱신
|
|
2423
|
-
|
|
2424
|
-
|
|
2434
|
+
if (e2eShard.take('B(1.9.12) #2')) {
|
|
2435
|
+
total++;
|
|
2425
2436
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-auto2-'));
|
|
2426
2437
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpA, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2427
2438
|
// 첫 mtime 캡처
|
|
@@ -2439,8 +2450,8 @@ total++;
|
|
|
2439
2450
|
}
|
|
2440
2451
|
|
|
2441
2452
|
// 1.9.12: roadmap auto off → 더 이상 자동 갱신 안 함
|
|
2442
|
-
|
|
2443
|
-
|
|
2453
|
+
if (e2eShard.take('B(1.9.12) #3')) {
|
|
2454
|
+
total++;
|
|
2444
2455
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-auto3-'));
|
|
2445
2456
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpA, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2446
2457
|
cp.spawnSync(process.execPath, [CLI, 'roadmap', 'auto', 'off', '--path', tmpA], { stdio: 'ignore' });
|
|
@@ -2456,8 +2467,8 @@ total++;
|
|
|
2456
2467
|
}
|
|
2457
2468
|
|
|
2458
2469
|
// 1.9.12: --on-every-change 옵트인 시 task add만으로 갱신
|
|
2459
|
-
|
|
2460
|
-
|
|
2470
|
+
if (e2eShard.take('B(1.9.12) #4')) {
|
|
2471
|
+
total++;
|
|
2461
2472
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-auto4-'));
|
|
2462
2473
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpA, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2463
2474
|
cp.spawnSync(process.execPath, [CLI, 'roadmap', 'auto', 'on', '--on-every-change', '--path', tmpA], { stdio: 'ignore' });
|
|
@@ -2474,8 +2485,8 @@ total++;
|
|
|
2474
2485
|
process.env.LEERNESS_NO_AUTO_ROADMAP = '1';
|
|
2475
2486
|
|
|
2476
2487
|
// 1.9.12: status 출력
|
|
2477
|
-
|
|
2478
|
-
|
|
2488
|
+
if (e2eShard.take('B(1.9.12) #5')) {
|
|
2489
|
+
total++;
|
|
2479
2490
|
const tmpA = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-auto5-'));
|
|
2480
2491
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpA, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2481
2492
|
const r = cp.spawnSync(process.execPath, [CLI, 'roadmap', 'auto', 'status', '--path', tmpA], { encoding: 'utf8' });
|
|
@@ -2485,8 +2496,8 @@ total++;
|
|
|
2485
2496
|
}
|
|
2486
2497
|
|
|
2487
2498
|
// 1.9.11: roadmap 명령 통합 + 화이트보드/토큰/중앙정렬 회귀
|
|
2488
|
-
|
|
2489
|
-
|
|
2499
|
+
if (e2eShard.take('B(1.9.11)')) {
|
|
2500
|
+
total++;
|
|
2490
2501
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rm-'));
|
|
2491
2502
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2492
2503
|
const r = cp.spawnSync(process.execPath, [CLI, 'roadmap', tmpR], { encoding: 'utf8' });
|
|
@@ -2495,8 +2506,8 @@ total++;
|
|
|
2495
2506
|
console.log(ok ? '✓ B(1.9.11) roadmap: 명령 + 파일 생성' : `✗ roadmap 실패\n${r.stdout}\n${r.stderr}`);
|
|
2496
2507
|
if (!ok) failed++;
|
|
2497
2508
|
}
|
|
2498
|
-
|
|
2499
|
-
|
|
2509
|
+
if (e2eShard.take('B(1.9.11) #2')) {
|
|
2510
|
+
total++;
|
|
2500
2511
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rm2-'));
|
|
2501
2512
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2502
2513
|
cp.spawnSync(process.execPath, [CLI, 'roadmap', tmpR], { stdio: 'ignore' });
|
|
@@ -2505,8 +2516,8 @@ total++;
|
|
|
2505
2516
|
console.log(ok ? '✓ B(1.9.11) roadmap: 화이트보드 (panning/zoom JS)' : '✗ 화이트보드 부재');
|
|
2506
2517
|
if (!ok) failed++;
|
|
2507
2518
|
}
|
|
2508
|
-
|
|
2509
|
-
|
|
2519
|
+
if (e2eShard.take('B(1.9.11) #3')) {
|
|
2520
|
+
total++;
|
|
2510
2521
|
// 사용자 design-system 토큰 주입
|
|
2511
2522
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rm3-'));
|
|
2512
2523
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
@@ -2519,8 +2530,8 @@ total++;
|
|
|
2519
2530
|
console.log(ok ? '✓ B(1.9.11) roadmap: design-system 토큰 자동 주입' : '✗ 토큰 주입 실패');
|
|
2520
2531
|
if (!ok) failed++;
|
|
2521
2532
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2533
|
+
if (e2eShard.take('B(1.9.11) #4')) {
|
|
2534
|
+
total++;
|
|
2524
2535
|
// recommended에 project-roadmap-generator 자동 포함
|
|
2525
2536
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rm4-'));
|
|
2526
2537
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
@@ -2532,8 +2543,8 @@ total++;
|
|
|
2532
2543
|
|
|
2533
2544
|
// 1.9.10 A: skillpack 동적 로드 (LEERNESS_SKILLPACK_PATH로 시뮬)
|
|
2534
2545
|
// 1.36.55 (외부감사 이식성): 형제 디렉토리 가정 제거 — 없으면 최소 catalog.json 픽스처를 테스트가 직접 생성 (자기완결)
|
|
2535
|
-
|
|
2536
|
-
|
|
2546
|
+
if (e2eShard.take('B(1.9.10)')) {
|
|
2547
|
+
total++;
|
|
2537
2548
|
let skillpackDir = path.resolve(__dirname, '..', '..', 'leerness-skillpack');
|
|
2538
2549
|
if (!fs.existsSync(path.join(skillpackDir, 'catalog.json'))) {
|
|
2539
2550
|
skillpackDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-spfix-'));
|
|
@@ -2551,8 +2562,8 @@ total++;
|
|
|
2551
2562
|
if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
|
|
2552
2563
|
}
|
|
2553
2564
|
// 1.9.10 A: skillpack 없을 때 builtin fallback
|
|
2554
|
-
|
|
2555
|
-
|
|
2565
|
+
if (e2eShard.take('B(1.9.10) #2')) {
|
|
2566
|
+
total++;
|
|
2556
2567
|
const r = cp.spawnSync(process.execPath, [CLI, 'skill', 'list', '--path', tmp], {
|
|
2557
2568
|
encoding: 'utf8',
|
|
2558
2569
|
env: Object.assign({}, process.env, { LEERNESS_SKILLPACK_PATH: '' })
|
|
@@ -2563,8 +2574,8 @@ total++;
|
|
|
2563
2574
|
}
|
|
2564
2575
|
|
|
2565
2576
|
// 1.9.10 B: detectGitRemote (가짜 git remote 시뮬은 어려움 — 실제 git 명령으로 확인)
|
|
2566
|
-
|
|
2567
|
-
|
|
2577
|
+
if (e2eShard.take('B(1.9.10) #3')) {
|
|
2578
|
+
total++;
|
|
2568
2579
|
// tmp는 git init이 없음 → detectGitRemote는 null → publish 호출 시 'Git remote: 없음' 출력
|
|
2569
2580
|
// 시뮬: tmp에 git init + remote add
|
|
2570
2581
|
gitSpawn(['init'], { cwd: tmp, encoding: 'utf8' });
|
|
@@ -2580,8 +2591,8 @@ total++;
|
|
|
2580
2591
|
}
|
|
2581
2592
|
|
|
2582
2593
|
// 1.9.8: rule add/list/pause/resume/remove
|
|
2583
|
-
|
|
2584
|
-
|
|
2594
|
+
if (e2eShard.take('B(1.9.8)')) {
|
|
2595
|
+
total++;
|
|
2585
2596
|
const r1 = cp.spawnSync(process.execPath, [CLI, 'rule', 'add', '매 업데이트마다 버전 patch bump', '--trigger', 'every-update', '--path', tmp], { encoding: 'utf8' });
|
|
2586
2597
|
const r2 = cp.spawnSync(process.execPath, [CLI, 'rule', 'add', '매 업데이트마다 패치노트 추가', '--trigger', 'every-update', '--path', tmp], { encoding: 'utf8' });
|
|
2587
2598
|
const r3 = cp.spawnSync(process.execPath, [CLI, 'rule', 'add', '세션 종료마다 배포', '--trigger', 'session-close', '--path', tmp], { encoding: 'utf8' });
|
|
@@ -2590,8 +2601,8 @@ total++;
|
|
|
2590
2601
|
console.log(ok ? '✓ B(1.9.8) rule add/list: 3개 등록' : '✗ B(1.9.8) rule add/list 실패');
|
|
2591
2602
|
if (!ok) failed++;
|
|
2592
2603
|
}
|
|
2593
|
-
|
|
2594
|
-
|
|
2604
|
+
if (e2eShard.take('B(1.9.8) #2')) {
|
|
2605
|
+
total++;
|
|
2595
2606
|
// pause + handoff에서 paused는 안 보여야 함
|
|
2596
2607
|
cp.spawnSync(process.execPath, [CLI, 'rule', 'pause', 'R-0001', '--path', tmp], { encoding: 'utf8' });
|
|
2597
2608
|
const hr = cp.spawnSync(process.execPath, [CLI, 'handoff', tmp], { encoding: 'utf8' });
|
|
@@ -2600,8 +2611,8 @@ total++;
|
|
|
2600
2611
|
if (!ok) { failed++; console.log(hr.stdout); }
|
|
2601
2612
|
cp.spawnSync(process.execPath, [CLI, 'rule', 'resume', 'R-0001', '--path', tmp], { encoding: 'utf8' });
|
|
2602
2613
|
}
|
|
2603
|
-
|
|
2604
|
-
|
|
2614
|
+
if (e2eShard.take('B(1.9.8) #3')) {
|
|
2615
|
+
total++;
|
|
2605
2616
|
// rule stop / resume-all
|
|
2606
2617
|
cp.spawnSync(process.execPath, [CLI, 'rule', 'stop', '--path', tmp], { encoding: 'utf8' });
|
|
2607
2618
|
const rl = cp.spawnSync(process.execPath, [CLI, 'rule', 'list', '--path', tmp], { encoding: 'utf8' });
|
|
@@ -2614,8 +2625,8 @@ total++;
|
|
|
2614
2625
|
}
|
|
2615
2626
|
|
|
2616
2627
|
// 1.9.8: release bump
|
|
2617
|
-
|
|
2618
|
-
|
|
2628
|
+
if (e2eShard.take('B(1.9.8) #4')) {
|
|
2629
|
+
total++;
|
|
2619
2630
|
const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rel-'));
|
|
2620
2631
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2621
2632
|
fs.writeFileSync(path.join(tmpR, 'package.json'), JSON.stringify({ name: 't', version: '1.0.0' }));
|
|
@@ -2633,8 +2644,8 @@ total++;
|
|
|
2633
2644
|
}
|
|
2634
2645
|
|
|
2635
2646
|
// T-0122: --patch는 경고 없이 수용하고, leerness 패키지의 package/bin/README 버전 표면을 함께 갱신한다.
|
|
2636
|
-
|
|
2637
|
-
|
|
2647
|
+
if (e2eShard.take('B(T-0122)')) {
|
|
2648
|
+
total++;
|
|
2638
2649
|
const probe = cp.spawnSync(process.execPath, [path.join(__dirname, 'release-bump-probe.js')], {
|
|
2639
2650
|
cwd: path.resolve(__dirname, '..'), encoding: 'utf8', timeout: 30000
|
|
2640
2651
|
});
|
|
@@ -2644,8 +2655,8 @@ total++;
|
|
|
2644
2655
|
}
|
|
2645
2656
|
|
|
2646
2657
|
// 1.9.8: release note → CHANGELOG.md 자동 갱신
|
|
2647
|
-
|
|
2648
|
-
|
|
2658
|
+
if (e2eShard.take('B(1.9.8) #5')) {
|
|
2659
|
+
total++;
|
|
2649
2660
|
const tmpN = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-note-'));
|
|
2650
2661
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpN, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2651
2662
|
fs.writeFileSync(path.join(tmpN, 'package.json'), JSON.stringify({ name: 't', version: '0.1.0' }));
|
|
@@ -2657,8 +2668,8 @@ total++;
|
|
|
2657
2668
|
}
|
|
2658
2669
|
|
|
2659
2670
|
// 1.9.8: session close가 rule verification 보고
|
|
2660
|
-
|
|
2661
|
-
|
|
2671
|
+
if (e2eShard.take('pass')) {
|
|
2672
|
+
total++;
|
|
2662
2673
|
// tmp는 위에서 rule 3개 등록됨
|
|
2663
2674
|
// package.json 만들기 + 버전 변경 시뮬 (rule R-0001은 every-update 버전 룰)
|
|
2664
2675
|
fs.writeFileSync(path.join(tmp, 'package.json'), JSON.stringify({ name: 'tmp-e2e', version: '0.1.0' }));
|
|
@@ -2680,8 +2691,8 @@ total++;
|
|
|
2680
2691
|
}
|
|
2681
2692
|
|
|
2682
2693
|
// 1.9.7 A: verify-code — 가짜 package.json + 통과 시나리오
|
|
2683
|
-
|
|
2684
|
-
|
|
2694
|
+
if (e2eShard.take('B(1.9.7-A)')) {
|
|
2695
|
+
total++;
|
|
2685
2696
|
const tmpV = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-vc-'));
|
|
2686
2697
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpV, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2687
2698
|
fs.writeFileSync(path.join(tmpV, 'package.json'), JSON.stringify({ name: 't', version: '0.0.1', scripts: { test: 'node -e "console.log(\\"OK\\");process.exit(0)"' } }));
|
|
@@ -2693,8 +2704,8 @@ total++;
|
|
|
2693
2704
|
}
|
|
2694
2705
|
|
|
2695
2706
|
// 1.9.7 A: verify-code — 실패 시나리오
|
|
2696
|
-
|
|
2697
|
-
|
|
2707
|
+
if (e2eShard.take('B(1.9.7-A) #2')) {
|
|
2708
|
+
total++;
|
|
2698
2709
|
const tmpV2 = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-vc2-'));
|
|
2699
2710
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpV2, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2700
2711
|
fs.writeFileSync(path.join(tmpV2, 'package.json'), JSON.stringify({ name: 't', version: '0.0.1', scripts: { test: 'node -e "process.exit(2)"' } }));
|
|
@@ -2705,8 +2716,8 @@ total++;
|
|
|
2705
2716
|
}
|
|
2706
2717
|
|
|
2707
2718
|
// 1.9.7 B: lessons — decisions/evidence에 시드 후 query로 회수
|
|
2708
|
-
|
|
2709
|
-
|
|
2719
|
+
if (e2eShard.take('B(1.9.7-B)')) {
|
|
2720
|
+
total++;
|
|
2710
2721
|
fs.appendFileSync(path.join(tmp, '.leerness/decisions.md'), `\n### 2026-05-08 — Decision: 캐시 차등 TTL 도입\n- Reason: 단일 5분 TTL이 daily 데이터에 비효율\n- Impact: open-meteo 응답 캐시 적중률 ↑\n`);
|
|
2711
2722
|
fs.appendFileSync(path.join(tmp, '.leerness/review-evidence.md'), `\n## 2026-05-08 e2e\n✗ 캐시 키 불안정 — 좌표 정규화 부재 (롤백 후 fix)\n`);
|
|
2712
2723
|
const r = cp.spawnSync(process.execPath, [CLI, 'lessons', '--query', '캐시', '--path', tmp], { encoding: 'utf8' });
|
|
@@ -2716,8 +2727,8 @@ total++;
|
|
|
2716
2727
|
}
|
|
2717
2728
|
|
|
2718
2729
|
// 1.9.7 B: guide가 lessons를 자동 통합
|
|
2719
|
-
|
|
2720
|
-
|
|
2730
|
+
if (e2eShard.take('B(1.9.7-B) #2')) {
|
|
2731
|
+
total++;
|
|
2721
2732
|
const r = cp.spawnSync(process.execPath, [CLI, 'guide', 'src/components/Card.html', '--path', tmp], { encoding: 'utf8' });
|
|
2722
2733
|
const ok = r.status === 0 && /## 4\. Lessons/.test(r.stdout);
|
|
2723
2734
|
console.log(ok ? '✓ B(1.9.7-B) guide: lessons 섹션 자동 추가' : '✗ B guide 통합 실패');
|
|
@@ -2725,8 +2736,8 @@ total++;
|
|
|
2725
2736
|
}
|
|
2726
2737
|
|
|
2727
2738
|
// 1.9.7 C: lazy detect --auto-track
|
|
2728
|
-
|
|
2729
|
-
|
|
2739
|
+
if (e2eShard.take('B(1.9.7-C)')) {
|
|
2740
|
+
total++;
|
|
2730
2741
|
const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-c-'));
|
|
2731
2742
|
cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore' });
|
|
2732
2743
|
// 의도된 진짜 TODO (주석)
|
|
@@ -2746,8 +2757,8 @@ total++;
|
|
|
2746
2757
|
}
|
|
2747
2758
|
|
|
2748
2759
|
// 1.9.7 C: 같은 TODO 재실행 시 known-todos가 적용되어 newTodos 0
|
|
2749
|
-
|
|
2750
|
-
|
|
2760
|
+
if (e2eShard.take('B(1.9.7-C) #2')) {
|
|
2761
|
+
total++;
|
|
2751
2762
|
// 위 시나리오 이어서 — known-todos가 있으므로 새 TODO=0이어야 함
|
|
2752
2763
|
// 별도 새 tmp로 재현 (tmpC는 위에서 자동 등록됐으니 same dir에서 다시 호출)
|
|
2753
2764
|
// 위 tmpC는 already auto-tracked
|
|
@@ -2768,8 +2779,8 @@ total++;
|
|
|
2768
2779
|
}
|
|
2769
2780
|
|
|
2770
2781
|
// 1.9.6 회귀: task relink — 인위적 link 손실 → 자동 매칭 제안 + --apply 자동 복구
|
|
2771
|
-
|
|
2772
|
-
|
|
2782
|
+
if (e2eShard.take('B(1.9.6)')) {
|
|
2783
|
+
total++;
|
|
2773
2784
|
// plan.md에 새 milestone 추가, progress엔 link 없는 비슷한 row 추가
|
|
2774
2785
|
const planPath_ = path.join(tmp, '.leerness/plan.md');
|
|
2775
2786
|
fs.appendFileSync(planPath_,
|
|
@@ -2790,8 +2801,8 @@ total++;
|
|
|
2790
2801
|
console.log(okSuggest ? '✓ B(1.9.6) task relink 제안: 2개 매칭 발견 (--dry-run 도 같은 제안)' : `✗ B(1.9.6) 제안 실패\n${r1.stdout}\n--dry-run: exit=${r1d.status} ${r1d.stdout}${r1d.stderr}`);
|
|
2791
2802
|
if (!okSuggest) failed++;
|
|
2792
2803
|
}
|
|
2793
|
-
|
|
2794
|
-
|
|
2804
|
+
if (e2eShard.take('B(1.9.6) #2')) {
|
|
2805
|
+
total++;
|
|
2795
2806
|
// --apply
|
|
2796
2807
|
const r2 = cp.spawnSync(process.execPath, [CLI, 'task', 'relink', '--apply', '--path', tmp], { encoding: 'utf8' });
|
|
2797
2808
|
const tracker = fs.readFileSync(path.join(tmp, '.leerness/progress-tracker.md'), 'utf8');
|
|
@@ -2799,8 +2810,8 @@ total++;
|
|
|
2799
2810
|
console.log(okApply ? '✓ B(1.9.6) task relink --apply: 자동 복구' : '✗ B(1.9.6) --apply 실패');
|
|
2800
2811
|
if (!okApply) failed++;
|
|
2801
2812
|
}
|
|
2802
|
-
|
|
2803
|
-
|
|
2813
|
+
if (e2eShard.take('B(1.9.6) #3')) {
|
|
2814
|
+
total++;
|
|
2804
2815
|
// audit이 task relink 안내를 출력하는지 (이번엔 link 복구 후라 미연결 milestone 없을 것)
|
|
2805
2816
|
// 일부러 새 milestone 추가 후 audit
|
|
2806
2817
|
fs.appendFileSync(path.join(tmp, '.leerness/plan.md'),
|
|
@@ -2812,8 +2823,8 @@ total++;
|
|
|
2812
2823
|
}
|
|
2813
2824
|
|
|
2814
2825
|
// 1.9.5 G 회귀: impact medium 카테고리 — 동적 path 패턴
|
|
2815
|
-
|
|
2816
|
-
|
|
2826
|
+
if (e2eShard.take('B(G)')) {
|
|
2827
|
+
total++;
|
|
2817
2828
|
// builder.js에서 path.join + readFileSync + Card.html을 동적 사용
|
|
2818
2829
|
fs.writeFileSync(path.join(tmp, 'src/builder.js'),
|
|
2819
2830
|
`const fs = require('fs'); const path = require('path');\n` +
|
|
@@ -2826,8 +2837,8 @@ total++;
|
|
|
2826
2837
|
}
|
|
2827
2838
|
|
|
2828
2839
|
// 1.9.4 회귀: --fail-on-violation cross-platform 종료
|
|
2829
|
-
|
|
2830
|
-
|
|
2840
|
+
if (e2eShard.take('B(B)')) {
|
|
2841
|
+
total++;
|
|
2831
2842
|
fs.appendFileSync(path.join(tmp, 'src/pages/home.html'), '\n<style>.x{color:#cafe00;}</style>\n');
|
|
2832
2843
|
const r = cp.spawnSync(process.execPath, [CLI, 'ui', 'consistency', tmp, '--fail-on-violation'], { encoding: 'utf8' });
|
|
2833
2844
|
const ok = r.status === 1;
|
|
@@ -2836,8 +2847,8 @@ total++;
|
|
|
2836
2847
|
}
|
|
2837
2848
|
|
|
2838
2849
|
// 1.9.4 회귀: lazy detect string literal 무시
|
|
2839
|
-
|
|
2840
|
-
|
|
2850
|
+
if (e2eShard.take('B(C)')) {
|
|
2851
|
+
total++;
|
|
2841
2852
|
// false positive 시드: TODO 단어가 string literal 안에 있는 코드
|
|
2842
2853
|
fs.writeFileSync(path.join(tmp, 'src/regex-helper.js'), `module.exports = { TODO_RE: /\\bTODO\\b/g, label: 'TODO list' };\n`);
|
|
2843
2854
|
// 다른 한편 진짜 TODO 주석
|
|
@@ -2853,8 +2864,8 @@ total++;
|
|
|
2853
2864
|
}
|
|
2854
2865
|
|
|
2855
2866
|
// 1.9.4 회귀: task fix-evidence 표시
|
|
2856
|
-
|
|
2857
|
-
|
|
2867
|
+
if (e2eShard.take('B(D)')) {
|
|
2868
|
+
total++;
|
|
2858
2869
|
// T-0001 evidence를 placeholder로 (test before가 'review-evidence:e2e' 였음 → 'user-request'로 바꿈)
|
|
2859
2870
|
const trackerPath = path.join(tmp, '.leerness/progress-tracker.md');
|
|
2860
2871
|
let cur = fs.readFileSync(trackerPath, 'utf8');
|
|
@@ -2867,8 +2878,8 @@ total++;
|
|
|
2867
2878
|
}
|
|
2868
2879
|
|
|
2869
2880
|
// 1.9.4 회귀: --set 일괄 갱신
|
|
2870
|
-
|
|
2871
|
-
|
|
2881
|
+
if (e2eShard.take('B(D2)')) {
|
|
2882
|
+
total++;
|
|
2872
2883
|
// T-0001 evidence를 plan:M-0002 링크 포함한 placeholder로 (1.9.5 link 보존 검증용 시드)
|
|
2873
2884
|
const trackerPath2 = path.join(tmp, '.leerness/progress-tracker.md');
|
|
2874
2885
|
let cur = fs.readFileSync(trackerPath2, 'utf8');
|
|
@@ -2882,8 +2893,8 @@ total++;
|
|
|
2882
2893
|
}
|
|
2883
2894
|
|
|
2884
2895
|
// 1.9.5 F 회귀: --set 시 plan:M-XXXX 링크 자동 보존
|
|
2885
|
-
|
|
2886
|
-
|
|
2896
|
+
if (e2eShard.take('B(F)')) {
|
|
2897
|
+
total++;
|
|
2887
2898
|
const tracker = fs.readFileSync(path.join(tmp, '.leerness/progress-tracker.md'), 'utf8');
|
|
2888
2899
|
const ok = /npm test 통과 \(e2e\) \(plan:M-0002\)/.test(tracker);
|
|
2889
2900
|
console.log(ok ? '✓ B(F) fix-evidence --set: link 자동 보존' : `✗ B(F) link 손실\n${tracker}`);
|
|
@@ -2891,8 +2902,8 @@ total++;
|
|
|
2891
2902
|
}
|
|
2892
2903
|
|
|
2893
2904
|
// 1.9.5 F neg: --no-preserve-link
|
|
2894
|
-
|
|
2895
|
-
|
|
2905
|
+
if (e2eShard.take('B(F neg)')) {
|
|
2906
|
+
total++;
|
|
2896
2907
|
// T-0002 같은 row를 placeholder로 시드
|
|
2897
2908
|
const trackerPath3 = path.join(tmp, '.leerness/progress-tracker.md');
|
|
2898
2909
|
let cur = fs.readFileSync(trackerPath3, 'utf8');
|
|
@@ -2907,8 +2918,8 @@ total++;
|
|
|
2907
2918
|
}
|
|
2908
2919
|
|
|
2909
2920
|
// 1.9.4 회귀: .leerness-skip-dirs 적용
|
|
2910
|
-
|
|
2911
|
-
|
|
2921
|
+
if (e2eShard.take('B(E)')) {
|
|
2922
|
+
total++;
|
|
2912
2923
|
fs.mkdirSync(path.join(tmp, '_devspace'), { recursive: true });
|
|
2913
2924
|
fs.writeFileSync(path.join(tmp, '_devspace/secret-config.js'), `const k = "ghp_${'a1B2'.repeat(9)}";\n`);
|
|
2914
2925
|
fs.writeFileSync(path.join(tmp, '.leerness-skip-dirs'), '_devspace/\n# 주석은 무시\n');
|
|
@@ -2927,8 +2938,8 @@ run('--version', ['--version']);
|
|
|
2927
2938
|
run('--help', ['--help']);
|
|
2928
2939
|
|
|
2929
2940
|
// 1.9.269 회귀 (UR-0022): 빈 디렉토리 신규 init + auto → OS 시스템 언어로 .leerness/LANGUAGE 결정
|
|
2930
|
-
|
|
2931
|
-
|
|
2941
|
+
if (e2eShard.take('B(1.9.269)')) {
|
|
2942
|
+
total++;
|
|
2932
2943
|
const tmpEn = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-lang-en-'));
|
|
2933
2944
|
const tmpKo = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-lang-ko-'));
|
|
2934
2945
|
// LANG 명시 (POSIX) — Windows 에서도 _detectSystemLang 이 LANG 우선 읽음
|
|
@@ -2945,8 +2956,8 @@ total++;
|
|
|
2945
2956
|
}
|
|
2946
2957
|
|
|
2947
2958
|
// 1.9.270 회귀: agent roles — 모델별 역할 부여 (사용자 명시)
|
|
2948
|
-
|
|
2949
|
-
|
|
2959
|
+
if (e2eShard.take('B(1.9.270)')) {
|
|
2960
|
+
total++;
|
|
2950
2961
|
const tmpRole = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-roles-'));
|
|
2951
2962
|
// set (한국어 별칭) + model
|
|
2952
2963
|
const rSet = cp.spawnSync(process.execPath, [CLI, 'roles', 'set', '코딩', '--provider', 'codex', '--model', 'gpt-5.5', '--path', tmpRole, '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
@@ -2971,8 +2982,8 @@ total++;
|
|
|
2971
2982
|
}
|
|
2972
2983
|
|
|
2973
2984
|
// 1.9.272 회귀: capabilities — 권한/보안 표면 공개 (GPT-5.5 리뷰 반영)
|
|
2974
|
-
|
|
2975
|
-
|
|
2985
|
+
if (e2eShard.take('B(1.9.272)')) {
|
|
2986
|
+
total++;
|
|
2976
2987
|
const rJson = cp.spawnSync(process.execPath, [CLI, 'capabilities', '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
2977
2988
|
let jsonOk = false;
|
|
2978
2989
|
try {
|
|
@@ -2989,8 +3000,8 @@ total++;
|
|
|
2989
3000
|
}
|
|
2990
3001
|
|
|
2991
3002
|
// 1.9.273 회귀 (UR-0027): 빠른 테스트 인프라 존재 검증 (smoke.js + test:fast script). 실제 smoke 실행은 별도(npm run test:fast).
|
|
2992
|
-
|
|
2993
|
-
|
|
3003
|
+
if (e2eShard.take('B(1.9.273)')) {
|
|
3004
|
+
total++;
|
|
2994
3005
|
const smokePath = path.resolve(__dirname, 'smoke.js');
|
|
2995
3006
|
const smokeExists = fs.existsSync(smokePath);
|
|
2996
3007
|
// 구문 유효성 (node --check)
|
|
@@ -3006,8 +3017,8 @@ total++;
|
|
|
3006
3017
|
}
|
|
3007
3018
|
|
|
3008
3019
|
// 1.9.274 회귀 (UR-0025 1단계): lib/ 모듈 분리 — pure-utils.js 존재 + require + 7종 export + 패키지 files 포함.
|
|
3009
|
-
|
|
3010
|
-
|
|
3020
|
+
if (e2eShard.take('B(1.9.274/283)')) {
|
|
3021
|
+
total++;
|
|
3011
3022
|
const libPath = path.resolve(__dirname, '..', 'lib', 'pure-utils.js');
|
|
3012
3023
|
const libExists = fs.existsSync(libPath);
|
|
3013
3024
|
let exportsOk = false;
|
|
@@ -3030,8 +3041,8 @@ total++;
|
|
|
3030
3041
|
}
|
|
3031
3042
|
|
|
3032
3043
|
// 1.9.275 회귀 (UR-0026): release channel — 안정/실험 채널 정책 (npm dist-tag)
|
|
3033
|
-
|
|
3034
|
-
|
|
3044
|
+
if (e2eShard.take('B(1.9.275)')) {
|
|
3045
|
+
total++;
|
|
3035
3046
|
const r = cp.spawnSync(process.execPath, [CLI, 'release', 'channel', tmp, '--json', '--offline'], { encoding: 'utf8', timeout: 15000 });
|
|
3036
3047
|
let jsonOk = false;
|
|
3037
3048
|
try { const j = JSON.parse(r.stdout); jsonOk = j.defaultPublishTag === 'latest' && j.policy && j.policy.stable === 'latest' && j.policy.experimental === 'next'; } catch {}
|
|
@@ -3044,8 +3055,8 @@ total++;
|
|
|
3044
3055
|
}
|
|
3045
3056
|
|
|
3046
3057
|
// 1.9.276 회귀 (GPT-5.5 2차 리뷰): init --dry-run / --minimal / --no-env
|
|
3047
|
-
|
|
3048
|
-
|
|
3058
|
+
if (e2eShard.take('B(1.9.276)')) {
|
|
3059
|
+
total++;
|
|
3049
3060
|
// --dry-run: 파일 0개 생성 + 요약 출력
|
|
3050
3061
|
const dDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-dry-'));
|
|
3051
3062
|
const rDry = cp.spawnSync(process.execPath, [CLI, 'init', dDir, '--dry-run', '--yes'], { encoding: 'utf8', timeout: 30000 });
|
|
@@ -3070,8 +3081,8 @@ total++;
|
|
|
3070
3081
|
}
|
|
3071
3082
|
|
|
3072
3083
|
// 1.9.278 회귀 (UR-0032): .leerness/ 상태 스키마 라이프사이클 (start→record→verify→handoff→show)
|
|
3073
|
-
|
|
3074
|
-
|
|
3084
|
+
if (e2eShard.take('B(1.9.278)')) {
|
|
3085
|
+
total++;
|
|
3075
3086
|
const sDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-state-'));
|
|
3076
3087
|
const run = (a) => cp.spawnSync(process.execPath, [CLI, 'state', ...a, '--path', sDir], { encoding: 'utf8', timeout: 15000 });
|
|
3077
3088
|
run(['start', 'API 구현', '--agent', 'claude', '--model', 'claude-opus-4-7', '--task', 'T-0001']);
|
|
@@ -3102,8 +3113,8 @@ total++;
|
|
|
3102
3113
|
}
|
|
3103
3114
|
|
|
3104
3115
|
// 1.9.279 회귀 (UR-0031): 상태 substrate MCP 시맨틱 verb (state_start → state_show 라운드트립)
|
|
3105
|
-
|
|
3106
|
-
|
|
3116
|
+
if (e2eShard.take('B(1.9.279)')) {
|
|
3117
|
+
total++;
|
|
3107
3118
|
const mDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mcpstate-'));
|
|
3108
3119
|
const mcpCall = (req) => {
|
|
3109
3120
|
const r = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], { encoding: 'utf8', timeout: 10000, input: JSON.stringify(req) + '\n' });
|
|
@@ -3124,8 +3135,8 @@ total++;
|
|
|
3124
3135
|
}
|
|
3125
3136
|
|
|
3126
3137
|
// 1.9.280 회귀 (UR-0033): leerness adapter — 도구별 선택 생성 + .mcp.json
|
|
3127
|
-
|
|
3128
|
-
|
|
3138
|
+
if (e2eShard.take('B(1.9.280)')) {
|
|
3139
|
+
total++;
|
|
3129
3140
|
const aDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-adapter-'));
|
|
3130
3141
|
cp.spawnSync(process.execPath, [CLI, 'init', aDir, '--minimal', '--no-env', '--yes'], { encoding: 'utf8', timeout: 30000 });
|
|
3131
3142
|
// adapter cursor: .cursor + .mcp.json 생성, .claude 미생성(minimal이라 commands는 있으나 cursor adapter는 안 건드림)
|
|
@@ -3150,8 +3161,8 @@ total++;
|
|
|
3150
3161
|
}
|
|
3151
3162
|
|
|
3152
3163
|
// 1.9.281 회귀 (UR-0034): 권한 등급 policy (show/set/check + enforce)
|
|
3153
|
-
|
|
3154
|
-
|
|
3164
|
+
if (e2eShard.take('B(1.9.281)')) {
|
|
3165
|
+
total++;
|
|
3155
3166
|
const pDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-policy-'));
|
|
3156
3167
|
const run = (a) => cp.spawnSync(process.execPath, [CLI, 'policy', ...a, '--path', pDir], { encoding: 'utf8', timeout: 15000 });
|
|
3157
3168
|
// 기본: enforce OFF + 8 등급
|
|
@@ -3172,8 +3183,8 @@ total++;
|
|
|
3172
3183
|
}
|
|
3173
3184
|
|
|
3174
3185
|
// 1.9.282 회귀 (UR-0035): AGENTS.md 정적 vs 동적(leerness) 역할 경계 포지셔닝
|
|
3175
|
-
|
|
3176
|
-
|
|
3186
|
+
if (e2eShard.take('B(1.9.282)')) {
|
|
3187
|
+
total++;
|
|
3177
3188
|
const gDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-pos-'));
|
|
3178
3189
|
cp.spawnSync(process.execPath, [CLI, 'init', gDir, '--minimal', '--no-env', '--yes'], { encoding: 'utf8', timeout: 30000 });
|
|
3179
3190
|
let ok = false;
|
|
@@ -3186,8 +3197,8 @@ total++;
|
|
|
3186
3197
|
}
|
|
3187
3198
|
|
|
3188
3199
|
// 1.9.285 회귀 (UR-0023): reuse-check — 외부 OSS 빌드 vs 재사용 게이트
|
|
3189
|
-
|
|
3190
|
-
|
|
3200
|
+
if (e2eShard.take('B(1.9.285)')) {
|
|
3201
|
+
total++;
|
|
3191
3202
|
const r1 = cp.spawnSync(process.execPath, [CLI, 'reuse-check', 'JWT 인증 로그인', '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
3192
3203
|
let jsonOk = false;
|
|
3193
3204
|
try { const j = JSON.parse(r1.stdout); jsonOk = j.feature && j.categories.some(c => c.key === 'auth') && Array.isArray(j.checklist) && j.checklist.length >= 5 && j.network === false; } catch {}
|
|
@@ -3202,8 +3213,8 @@ total++;
|
|
|
3202
3213
|
}
|
|
3203
3214
|
|
|
3204
3215
|
// 1.9.286 회귀 (UR-0024): skill impact — 영향 경량 상관추적 + 정직한 advisory
|
|
3205
|
-
|
|
3206
|
-
|
|
3216
|
+
if (e2eShard.take('B(1.9.286)')) {
|
|
3217
|
+
total++;
|
|
3207
3218
|
const iDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-impact-'));
|
|
3208
3219
|
cp.spawnSync(process.execPath, [CLI, 'init', iDir, '--minimal', '--no-env', '--yes'], { encoding: 'utf8', timeout: 30000, env: { ...process.env, LEERNESS_OFFLINE: '1' } });
|
|
3209
3220
|
// 표본 부족 (검증 0건) → 판단 보류 advisory
|
|
@@ -3223,8 +3234,8 @@ total++;
|
|
|
3223
3234
|
}
|
|
3224
3235
|
|
|
3225
3236
|
// 1.9.287 회귀 (Codex 리뷰 수렴): verify-claim --require-evidence + handoff 펜스 sanitize + status minimal
|
|
3226
|
-
|
|
3227
|
-
|
|
3237
|
+
if (e2eShard.take('B(1.9.287)')) {
|
|
3238
|
+
total++;
|
|
3228
3239
|
const cDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-codex-'));
|
|
3229
3240
|
const env = { ...process.env, LEERNESS_OFFLINE: '1' };
|
|
3230
3241
|
cp.spawnSync(process.execPath, [CLI, 'init', cDir, '--minimal', '--no-env', '--yes', '--language', 'ko'], { encoding: 'utf8', timeout: 30000, env }); // 1.36.55: 로케일 고정 — auto(en 환경)에서 ko 정규식 단언이 깨지던 이식성 결함
|
|
@@ -3258,8 +3269,8 @@ total++;
|
|
|
3258
3269
|
}
|
|
3259
3270
|
|
|
3260
3271
|
// 1.9.288 회귀 (Codex gpt-5.5 리뷰 수렴): MCP policy enforce + release dry-run + 도구수 정합
|
|
3261
|
-
|
|
3262
|
-
|
|
3272
|
+
if (e2eShard.take('B(1.9.288)')) {
|
|
3273
|
+
total++;
|
|
3263
3274
|
const env = { ...process.env, LEERNESS_OFFLINE: '1' };
|
|
3264
3275
|
// #1 MCP policy: read-only enforce → state_start(write) 차단 + state.json 미생성
|
|
3265
3276
|
const pDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mcppol-'));
|
|
@@ -3295,8 +3306,8 @@ total++;
|
|
|
3295
3306
|
}
|
|
3296
3307
|
|
|
3297
3308
|
// 1.9.289 회귀 (Codex #3): _shellQuoteArg — REPL agy/copilot 프롬프트 셸 주입 중립화
|
|
3298
|
-
|
|
3299
|
-
|
|
3309
|
+
if (e2eShard.take('B(1.9.289)')) {
|
|
3310
|
+
total++;
|
|
3300
3311
|
let ok = false;
|
|
3301
3312
|
try {
|
|
3302
3313
|
const h = require(path.resolve(__dirname, '..', 'bin', 'leerness.js'));
|
|
@@ -3312,8 +3323,8 @@ total++;
|
|
|
3312
3323
|
}
|
|
3313
3324
|
|
|
3314
3325
|
// 1.9.290 회귀 (Codex #4 UR-0037): require('harness') 가 호스트 프로세스 오염 X (top-level side effect 격리)
|
|
3315
|
-
|
|
3316
|
-
|
|
3326
|
+
if (e2eShard.take('B(1.9.290)')) {
|
|
3327
|
+
total++;
|
|
3317
3328
|
let ok = false;
|
|
3318
3329
|
try {
|
|
3319
3330
|
// 깨끗한 자식에서: 내 warning listener 등록 → require → 보존 확인 + NODE_OPTIONS 미변경
|
|
@@ -3331,8 +3342,8 @@ total++;
|
|
|
3331
3342
|
}
|
|
3332
3343
|
|
|
3333
3344
|
// 1.9.291 회귀 (UR-0025 2단계): lib/agent-registry.js 모듈 분리 — 단일출처 + 부작용 0
|
|
3334
|
-
|
|
3335
|
-
|
|
3345
|
+
if (e2eShard.take('B(1.9.291)')) {
|
|
3346
|
+
total++;
|
|
3336
3347
|
let ok = false;
|
|
3337
3348
|
try {
|
|
3338
3349
|
const reg = require(path.resolve(__dirname, '..', 'lib', 'agent-registry.js'));
|
|
@@ -3352,8 +3363,8 @@ total++;
|
|
|
3352
3363
|
}
|
|
3353
3364
|
|
|
3354
3365
|
// 1.9.292 회귀 (UR-0031): leerness context — get_project_context 집약 시맨틱 verb
|
|
3355
|
-
|
|
3356
|
-
|
|
3366
|
+
if (e2eShard.take('B(1.9.292)')) {
|
|
3367
|
+
total++;
|
|
3357
3368
|
let ok = false;
|
|
3358
3369
|
try {
|
|
3359
3370
|
const r = cp.spawnSync(process.execPath, [CLI, 'context', tmp, '--json'], { cwd: tmp, encoding: 'utf8', timeout: 30000 });
|
|
@@ -3370,8 +3381,8 @@ total++;
|
|
|
3370
3381
|
}
|
|
3371
3382
|
|
|
3372
3383
|
// 1.9.293 회귀: idempotency audit --auto-fix + progressHeader 복제버그 fix
|
|
3373
|
-
|
|
3374
|
-
|
|
3384
|
+
if (e2eShard.take('B(1.9.293)')) {
|
|
3385
|
+
total++;
|
|
3375
3386
|
let ok = false;
|
|
3376
3387
|
try {
|
|
3377
3388
|
const idemDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-idem-'));
|
|
@@ -3397,8 +3408,8 @@ total++;
|
|
|
3397
3408
|
}
|
|
3398
3409
|
|
|
3399
3410
|
// 1.9.294 회귀 (UR-0025 3단계): lib/role-catalog.js 모듈 분리 — 단일출처 + 부작용 0
|
|
3400
|
-
|
|
3401
|
-
|
|
3411
|
+
if (e2eShard.take('B(1.9.294)')) {
|
|
3412
|
+
total++;
|
|
3402
3413
|
let ok = false;
|
|
3403
3414
|
try {
|
|
3404
3415
|
const reg = require(path.resolve(__dirname, '..', 'lib', 'role-catalog.js'));
|
|
@@ -3418,8 +3429,8 @@ total++;
|
|
|
3418
3429
|
}
|
|
3419
3430
|
|
|
3420
3431
|
// 1.9.295 회귀 (UR-0025 4단계): lib/catalogs.js 모듈 분리 — 단일출처 + 부작용 0 + 소비 명령 회귀
|
|
3421
|
-
|
|
3422
|
-
|
|
3432
|
+
if (e2eShard.take('B(1.9.295)')) {
|
|
3433
|
+
total++;
|
|
3423
3434
|
let ok = false;
|
|
3424
3435
|
try {
|
|
3425
3436
|
const reg = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -3443,8 +3454,8 @@ total++;
|
|
|
3443
3454
|
}
|
|
3444
3455
|
|
|
3445
3456
|
// 1.9.296 회귀 (UR-0030): leerness about — 정체성(AI 운영 레이어) verb + MCP + README 섹션
|
|
3446
|
-
|
|
3447
|
-
|
|
3457
|
+
if (e2eShard.take('B(1.9.296)')) {
|
|
3458
|
+
total++;
|
|
3448
3459
|
let ok = false;
|
|
3449
3460
|
try {
|
|
3450
3461
|
const r = cp.spawnSync(process.execPath, [CLI, 'about', '--json'], { cwd: tmp, encoding: 'utf8', timeout: 20000 });
|
|
@@ -3467,8 +3478,8 @@ total++;
|
|
|
3467
3478
|
}
|
|
3468
3479
|
|
|
3469
3480
|
// 1.9.297 회귀 (UR-0025 5단계): lib/mcp-tools.js 단일출처 — tools/list == 모듈 == _mcpToolCount (Codex #5 영구해소)
|
|
3470
|
-
|
|
3471
|
-
|
|
3481
|
+
if (e2eShard.take('B(1.9.297)')) {
|
|
3482
|
+
total++;
|
|
3472
3483
|
let ok = false;
|
|
3473
3484
|
try {
|
|
3474
3485
|
const T = require(path.resolve(__dirname, '..', 'lib', 'mcp-tools.js'));
|
|
@@ -3487,8 +3498,8 @@ total++;
|
|
|
3487
3498
|
}
|
|
3488
3499
|
|
|
3489
3500
|
// 1.9.298 회귀 (UR-0038 외부리뷰): writeUtf8 원자적 쓰기 — 반복 쓰기 후 무손상 + temp 잔여 0
|
|
3490
|
-
|
|
3491
|
-
|
|
3501
|
+
if (e2eShard.take('B(1.9.298)')) {
|
|
3502
|
+
total++;
|
|
3492
3503
|
let ok = false;
|
|
3493
3504
|
try {
|
|
3494
3505
|
const aDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-atomic-'));
|
|
@@ -3518,8 +3529,8 @@ total++;
|
|
|
3518
3529
|
}
|
|
3519
3530
|
|
|
3520
3531
|
// 1.9.299 회귀 (UR-0039 외부리뷰): npm test 시크릿 차단 — 토큰이 보이면 exit 1 인 test 스크립트가 verify-code 에서 통과해야(스크럽됨)
|
|
3521
|
-
|
|
3522
|
-
|
|
3532
|
+
if (e2eShard.take('B(1.9.299)')) {
|
|
3533
|
+
total++;
|
|
3523
3534
|
let ok = false;
|
|
3524
3535
|
try {
|
|
3525
3536
|
const secDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sec-'));
|
|
@@ -3544,8 +3555,8 @@ total++;
|
|
|
3544
3555
|
}
|
|
3545
3556
|
|
|
3546
3557
|
// 1.9.300 회귀 (UR-0040 외부리뷰): 셸 주입 표면 제거 — fetchNpmLatest execFile + argList 인용 + update --check 회귀
|
|
3547
|
-
|
|
3548
|
-
|
|
3558
|
+
if (e2eShard.take('B(1.9.300)')) {
|
|
3559
|
+
total++;
|
|
3549
3560
|
let ok = false;
|
|
3550
3561
|
try {
|
|
3551
3562
|
const harnessSrc = fs.readFileSync(path.resolve(__dirname, '..', 'bin', 'leerness.js'), 'utf8');
|
|
@@ -3565,8 +3576,8 @@ total++;
|
|
|
3565
3576
|
}
|
|
3566
3577
|
|
|
3567
3578
|
// 1.9.301 회귀 (UR-0041 외부리뷰): MCP 정책 게이트가 도구 requiredTier 메타데이터로 under-classify 갭 차단
|
|
3568
|
-
|
|
3569
|
-
|
|
3579
|
+
if (e2eShard.take('B(1.9.301)')) {
|
|
3580
|
+
total++;
|
|
3570
3581
|
let ok = false;
|
|
3571
3582
|
try {
|
|
3572
3583
|
const pDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tier-'));
|
|
@@ -3598,8 +3609,8 @@ total++;
|
|
|
3598
3609
|
}
|
|
3599
3610
|
|
|
3600
3611
|
// 1.9.302 회귀 (UR-0042 외부리뷰): verify-claim git diff 시맨틱 교차검증 (변경 파일 주장 ✓ / 미변경 주장 ⚠ + strict FAIL)
|
|
3601
|
-
|
|
3602
|
-
|
|
3612
|
+
if (e2eShard.take('B(1.9.302)')) {
|
|
3613
|
+
total++;
|
|
3603
3614
|
let ok = false;
|
|
3604
3615
|
try {
|
|
3605
3616
|
const vDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-vc-'));
|
|
@@ -3626,8 +3637,8 @@ total++;
|
|
|
3626
3637
|
}
|
|
3627
3638
|
|
|
3628
3639
|
// 1.9.303 회귀 (UR-0043 외부리뷰): 동시 task add lost-update 락 — 6개 병렬 추가 시 모두 보존 + ID 충돌 0
|
|
3629
|
-
|
|
3630
|
-
|
|
3640
|
+
if (e2eShard.take('B(1.9.303)')) {
|
|
3641
|
+
total++;
|
|
3631
3642
|
let ok = false;
|
|
3632
3643
|
try {
|
|
3633
3644
|
const lDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-lock-'));
|
|
@@ -3663,8 +3674,8 @@ total++;
|
|
|
3663
3674
|
}
|
|
3664
3675
|
|
|
3665
3676
|
// 1.9.304 회귀 (UR-0025): lib/analyzers.js 모듈 분리 — 단일출처 + 부작용 0 + 소비 명령 회귀
|
|
3666
|
-
|
|
3667
|
-
|
|
3677
|
+
if (e2eShard.take('B(1.9.304)')) {
|
|
3678
|
+
total++;
|
|
3668
3679
|
let ok = false;
|
|
3669
3680
|
try {
|
|
3670
3681
|
const a = require(path.resolve(__dirname, '..', 'lib', 'analyzers.js'));
|
|
@@ -3685,8 +3696,8 @@ total++;
|
|
|
3685
3696
|
}
|
|
3686
3697
|
|
|
3687
3698
|
// 1.9.305 회귀 (사용자 명시): honesty-check — AI 인식론적 정직성 3차원 + exit code + MCP 노출
|
|
3688
|
-
|
|
3689
|
-
|
|
3699
|
+
if (e2eShard.take('B(1.9.305)')) {
|
|
3700
|
+
total++;
|
|
3690
3701
|
let ok = false;
|
|
3691
3702
|
try {
|
|
3692
3703
|
// 양호(근거 있음) → exit 0 + ✓
|
|
@@ -3708,8 +3719,8 @@ total++;
|
|
|
3708
3719
|
}
|
|
3709
3720
|
|
|
3710
3721
|
// 1.9.306 회귀 (UR-0045 설치리뷰): exit code 일관성 — 오류 경로 exit 1, 정상/help exit 0
|
|
3711
|
-
|
|
3712
|
-
|
|
3722
|
+
if (e2eShard.take('B(1.9.306)')) {
|
|
3723
|
+
total++;
|
|
3713
3724
|
let ok = false;
|
|
3714
3725
|
try {
|
|
3715
3726
|
const eDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-exit-'));
|
|
@@ -3732,8 +3743,8 @@ total++;
|
|
|
3732
3743
|
}
|
|
3733
3744
|
|
|
3734
3745
|
// 1.9.307 회귀 (UR-0055 사용자명시): brief — 프로젝트 청사진 set/show/export + README 섹션 + 멱등 업데이트
|
|
3735
|
-
|
|
3736
|
-
|
|
3746
|
+
if (e2eShard.take('B(1.9.307)')) {
|
|
3747
|
+
total++;
|
|
3737
3748
|
let ok = false;
|
|
3738
3749
|
try {
|
|
3739
3750
|
const bDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-brief-'));
|
|
@@ -3761,8 +3772,8 @@ total++;
|
|
|
3761
3772
|
}
|
|
3762
3773
|
|
|
3763
3774
|
// 1.9.308 회귀 (UR-0055 2단계): brief update --direction 이력 + context 통합 + MCP leerness_brief
|
|
3764
|
-
|
|
3765
|
-
|
|
3775
|
+
if (e2eShard.take('B(1.9.308)')) {
|
|
3776
|
+
total++;
|
|
3766
3777
|
let ok = false;
|
|
3767
3778
|
try {
|
|
3768
3779
|
const b2 = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-brief2-'));
|
|
@@ -3793,8 +3804,8 @@ total++;
|
|
|
3793
3804
|
}
|
|
3794
3805
|
|
|
3795
3806
|
// 1.9.309 회귀 (UR-0048 설치리뷰 critical): verify-claim done 주장 evidence 기본강제 + --lenient + MCP 도달
|
|
3796
|
-
|
|
3797
|
-
|
|
3807
|
+
if (e2eShard.take('B(1.9.309)')) {
|
|
3808
|
+
total++;
|
|
3798
3809
|
let ok = false;
|
|
3799
3810
|
try {
|
|
3800
3811
|
const vc = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-vc48-'));
|
|
@@ -3819,8 +3830,8 @@ total++;
|
|
|
3819
3830
|
}
|
|
3820
3831
|
|
|
3821
3832
|
// 1.9.310 회귀 (UR-0046 설치리뷰): 입력 스키마 검증 — 무효 status/trigger 거부, 유효/--force 통과, every-round 보존
|
|
3822
|
-
|
|
3823
|
-
|
|
3833
|
+
if (e2eShard.take('B(1.9.310)')) {
|
|
3834
|
+
total++;
|
|
3824
3835
|
let ok = false;
|
|
3825
3836
|
try {
|
|
3826
3837
|
const vd = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-val-'));
|
|
@@ -3840,8 +3851,8 @@ total++;
|
|
|
3840
3851
|
}
|
|
3841
3852
|
|
|
3842
3853
|
// 1.9.311 회귀 (UR-0047 설치리뷰): init 가드 — 미초기화 디렉토리 write 차단, .leerness 미생성, --force 우회, init 후 정상
|
|
3843
|
-
|
|
3844
|
-
|
|
3854
|
+
if (e2eShard.take('B(1.9.311)')) {
|
|
3855
|
+
total++;
|
|
3845
3856
|
let ok = false;
|
|
3846
3857
|
try {
|
|
3847
3858
|
// dir1: 미초기화 7개 write 차단(exit 1) + .leerness 미생성 + --force 우회
|
|
@@ -3879,8 +3890,8 @@ total++;
|
|
|
3879
3890
|
|
|
3880
3891
|
// 1.9.312 회귀 (UR-0050 설치리뷰): secret 스캐너 현대 키 — sk-proj-/sk-ant-api03-(_)/gho_/Stripe/npm 검출 + exit 1
|
|
3881
3892
|
// 주의: e2e.js 는 scan 대상이라 키는 문자열 연결로 구성(리터럴 금지 — 자기 repo scan 오탐 방지)
|
|
3882
|
-
|
|
3883
|
-
|
|
3893
|
+
if (e2eShard.take('B(1.9.312)')) {
|
|
3894
|
+
total++;
|
|
3884
3895
|
let ok = false;
|
|
3885
3896
|
try {
|
|
3886
3897
|
const sd = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-secmod-'));
|
|
@@ -3908,8 +3919,8 @@ total++;
|
|
|
3908
3919
|
}
|
|
3909
3920
|
|
|
3910
3921
|
// 1.9.313 회귀 (UR-0049 설치리뷰): MCP notification 준수 — id 없는 요청 무응답 + ping {} + notifications/* 가드
|
|
3911
|
-
|
|
3912
|
-
|
|
3922
|
+
if (e2eShard.take('B(1.9.313)')) {
|
|
3923
|
+
total++;
|
|
3913
3924
|
let ok = false;
|
|
3914
3925
|
try {
|
|
3915
3926
|
const input = [
|
|
@@ -3934,8 +3945,8 @@ total++;
|
|
|
3934
3945
|
|
|
3935
3946
|
// 1.9.314 회귀 (UR-0049→UR-0052 설치리뷰): PowerShell 감지 — pwsh7(channel) ps5-chain 오탐 제거 + ps5.1 정상 발화
|
|
3936
3947
|
// env 마커로 셸/버전 판별(cross-platform) — POWERSHELL_DISTRIBUTION_CHANNEL=pwsh7, Documents\WindowsPowerShell=ps5.1
|
|
3937
|
-
|
|
3938
|
-
|
|
3948
|
+
if (e2eShard.take('B(1.9.314)')) {
|
|
3949
|
+
total++;
|
|
3939
3950
|
let ok = false;
|
|
3940
3951
|
try {
|
|
3941
3952
|
const sg = (extra) => { const r = cp.spawnSync(process.execPath, [CLI, 'shell-guard', 'a && b', '--json'], { encoding: 'utf8', timeout: 15000, env: { ...process.env, ...extra } }); try { return JSON.parse(r.stdout); } catch { return null; } };
|
|
@@ -3950,8 +3961,8 @@ total++;
|
|
|
3950
3961
|
}
|
|
3951
3962
|
|
|
3952
3963
|
// 1.9.315 회귀 (UR-0054 설치리뷰): doc/surface 정합 — doctor 진단 명령 + stale MCP 카운트 동적화(commands/banner)
|
|
3953
|
-
|
|
3954
|
-
|
|
3964
|
+
if (e2eShard.take('B(1.9.315)')) {
|
|
3965
|
+
total++;
|
|
3955
3966
|
let ok = false; const dbg = {};
|
|
3956
3967
|
try {
|
|
3957
3968
|
// 1.36.100: 이 호출만 30초로 남아 있었다 — doctor 는 selftest 를 내장해 격리 실측 31s(게시된 1.36.98 도 동일)라
|
|
@@ -3981,8 +3992,8 @@ total++;
|
|
|
3981
3992
|
|
|
3982
3993
|
// 1.9.316 회귀 (drift 마커 버그): session-handoff 'Last generated' 중복 누적 방지 + drift 'session close 누락' 클리어
|
|
3983
3994
|
// 근본: sessionClose 프론트매터 추출이 본문 '---' 를 오인 → 구 블록 보존 → 첫(구) Last generated 를 drift 가 읽어 영구 오발화
|
|
3984
|
-
|
|
3985
|
-
|
|
3995
|
+
if (e2eShard.take('B(1.9.316/1.36.262)')) {
|
|
3996
|
+
total++;
|
|
3986
3997
|
let ok = false;
|
|
3987
3998
|
try {
|
|
3988
3999
|
const dd = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-driftmark-'));
|
|
@@ -4013,8 +4024,8 @@ total++;
|
|
|
4013
4024
|
}
|
|
4014
4025
|
|
|
4015
4026
|
// 1.9.317 회귀 (UR-0051 설치리뷰): 텔레메트리 분리 — 내부 auto-call(LEERNESS_INTERNAL)이 usage 집계 오염 안 함
|
|
4016
|
-
|
|
4017
|
-
|
|
4027
|
+
if (e2eShard.take('B(1.9.317)')) {
|
|
4028
|
+
total++;
|
|
4018
4029
|
let ok = false;
|
|
4019
4030
|
try {
|
|
4020
4031
|
const td = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-telem-'));
|
|
@@ -4039,8 +4050,8 @@ total++;
|
|
|
4039
4050
|
}
|
|
4040
4051
|
|
|
4041
4052
|
// 1.9.318 회귀 (UR-0025 모듈화): HTML 파싱 유틸 3종 lib/pure-utils 분리 + harness 인라인 제거 + 동작 보존 + 소비명령 로드
|
|
4042
|
-
|
|
4043
|
-
|
|
4053
|
+
if (e2eShard.take('B(1.9.318)')) {
|
|
4054
|
+
total++;
|
|
4044
4055
|
let ok = false;
|
|
4045
4056
|
try {
|
|
4046
4057
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4059,8 +4070,8 @@ total++;
|
|
|
4059
4070
|
}
|
|
4060
4071
|
|
|
4061
4072
|
// 1.9.319 회귀 (UR-0044): MCP ToolRegistry 일치성 — tools/list 수 = def 수 + 대표 도구 dispatch(Unknown tool 아님)
|
|
4062
|
-
|
|
4063
|
-
|
|
4073
|
+
if (e2eShard.take('B(1.9.319)')) {
|
|
4074
|
+
total++;
|
|
4064
4075
|
let ok = false;
|
|
4065
4076
|
try {
|
|
4066
4077
|
const toolsLen = require(path.resolve(__dirname, '..', 'lib', 'mcp-tools.js')).length;
|
|
@@ -4076,8 +4087,8 @@ total++;
|
|
|
4076
4087
|
}
|
|
4077
4088
|
|
|
4078
4089
|
// 1.9.320 회귀 (UR-0053): count drift — decisions/lessons 카운터가 코드펜스(```md 템플릿 예시) 제외 (decisions=2 실제1 버그)
|
|
4079
|
-
|
|
4080
|
-
|
|
4090
|
+
if (e2eShard.take('B(1.9.320)')) {
|
|
4091
|
+
total++;
|
|
4081
4092
|
let ok = false;
|
|
4082
4093
|
try {
|
|
4083
4094
|
const cd = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-countdrift-'));
|
|
@@ -4094,8 +4105,8 @@ total++;
|
|
|
4094
4105
|
}
|
|
4095
4106
|
|
|
4096
4107
|
// 1.9.321 회귀 (UR-0053): decision 빈 필드(alternatives)가 [ \t]* 파싱으로 다음 줄(impact)을 캡처하지 않음
|
|
4097
|
-
|
|
4098
|
-
|
|
4108
|
+
if (e2eShard.take('B(1.9.321)')) {
|
|
4109
|
+
total++;
|
|
4099
4110
|
let ok = false;
|
|
4100
4111
|
try {
|
|
4101
4112
|
const fd = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-field-'));
|
|
@@ -4113,8 +4124,8 @@ total++;
|
|
|
4113
4124
|
}
|
|
4114
4125
|
|
|
4115
4126
|
// 1.9.322 회귀 (UR-0044): _mcpToCliArgs 추출 후 인자 매핑(push) 보존 + 미지 도구 -32601
|
|
4116
|
-
|
|
4117
|
-
|
|
4127
|
+
if (e2eShard.take('B(1.9.322)')) {
|
|
4128
|
+
total++;
|
|
4118
4129
|
let ok = false;
|
|
4119
4130
|
try {
|
|
4120
4131
|
const md = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tocli-'));
|
|
@@ -4135,8 +4146,8 @@ total++;
|
|
|
4135
4146
|
}
|
|
4136
4147
|
|
|
4137
4148
|
// 1.9.323 회귀 (UR-0054 ⑥): fresh init → gate(lazy detect) 통과(부재신호 비차단) + active 거짓완료는 차단 유지
|
|
4138
|
-
|
|
4139
|
-
|
|
4149
|
+
if (e2eShard.take('B(1.9.323)')) {
|
|
4150
|
+
total++;
|
|
4140
4151
|
let ok = false;
|
|
4141
4152
|
try {
|
|
4142
4153
|
const fg = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-freshgate-'));
|
|
@@ -4153,8 +4164,8 @@ total++;
|
|
|
4153
4164
|
}
|
|
4154
4165
|
|
|
4155
4166
|
// 1.9.324 회귀 (UR-0025 모듈화): 메모리 MD 파서 2종 lib/pure-utils 분리 + harness 인라인 제거 + _compareSemver 중복제거 + 소비명령
|
|
4156
|
-
|
|
4157
|
-
|
|
4167
|
+
if (e2eShard.take('B(1.9.324)')) {
|
|
4168
|
+
total++;
|
|
4158
4169
|
let ok = false;
|
|
4159
4170
|
try {
|
|
4160
4171
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4180,8 +4191,8 @@ total++;
|
|
|
4180
4191
|
}
|
|
4181
4192
|
|
|
4182
4193
|
// 1.9.325 회귀 (UR-0025 모듈화): _classifyIntent lib/pure-utils 분리 + harness 인라인 제거 + 소비명령(intent classify)
|
|
4183
|
-
|
|
4184
|
-
|
|
4194
|
+
if (e2eShard.take('B(1.9.325)')) {
|
|
4195
|
+
total++;
|
|
4185
4196
|
let ok = false;
|
|
4186
4197
|
try {
|
|
4187
4198
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4206,8 +4217,8 @@ total++;
|
|
|
4206
4217
|
}
|
|
4207
4218
|
|
|
4208
4219
|
// 1.9.326 회귀 (UR-0025 모듈화): 순수 문자열/셸/env 유틸 3종 lib/pure-utils 분리 + harness 인라인 제거
|
|
4209
|
-
|
|
4210
|
-
|
|
4220
|
+
if (e2eShard.take('B(1.9.326)')) {
|
|
4221
|
+
total++;
|
|
4211
4222
|
let ok = false;
|
|
4212
4223
|
try {
|
|
4213
4224
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4230,8 +4241,8 @@ total++;
|
|
|
4230
4241
|
}
|
|
4231
4242
|
|
|
4232
4243
|
// 1.9.327 회귀 (UR-0025 모듈화): TZ/날짜 포맷 2종 lib/pure-utils 분리 + harness 인라인 제거
|
|
4233
|
-
|
|
4234
|
-
|
|
4244
|
+
if (e2eShard.take('B(1.9.327)')) {
|
|
4245
|
+
total++;
|
|
4235
4246
|
let ok = false;
|
|
4236
4247
|
try {
|
|
4237
4248
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4250,8 +4261,8 @@ total++;
|
|
|
4250
4261
|
}
|
|
4251
4262
|
|
|
4252
4263
|
// 1.9.328 회귀 (UR-0025 모듈화): 순수 문자열 유틸 2종(_truncate/_splitList) lib/pure-utils 분리 + harness 인라인 제거
|
|
4253
|
-
|
|
4254
|
-
|
|
4264
|
+
if (e2eShard.take('B(1.9.328)')) {
|
|
4265
|
+
total++;
|
|
4255
4266
|
let ok = false;
|
|
4256
4267
|
try {
|
|
4257
4268
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4269,8 +4280,8 @@ total++;
|
|
|
4269
4280
|
}
|
|
4270
4281
|
|
|
4271
4282
|
// 1.9.329 회귀 (UR-0025 모듈화): roadmap MD 파서 3종 lib/pure-utils 분리 + harness 인라인 제거
|
|
4272
|
-
|
|
4273
|
-
|
|
4283
|
+
if (e2eShard.take('B(1.9.329)')) {
|
|
4284
|
+
total++;
|
|
4274
4285
|
let ok = false;
|
|
4275
4286
|
try {
|
|
4276
4287
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4289,8 +4300,8 @@ total++;
|
|
|
4289
4300
|
}
|
|
4290
4301
|
|
|
4291
4302
|
// 1.9.330 회귀 (UR-0025 모듈화): project-brief config(_BRIEF_FIELDS) + _briefFilled lib/pure-utils 분리 + brief 명령 회귀
|
|
4292
|
-
|
|
4293
|
-
|
|
4303
|
+
if (e2eShard.take('B(1.9.330)')) {
|
|
4304
|
+
total++;
|
|
4294
4305
|
let ok = false;
|
|
4295
4306
|
try {
|
|
4296
4307
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4314,8 +4325,8 @@ total++;
|
|
|
4314
4325
|
}
|
|
4315
4326
|
|
|
4316
4327
|
// 1.9.331 회귀 (UR-0025 서브시스템): brief 빌더(_briefReadmeBlock/_briefBlueprint)+마커 lib/pure-utils 분리 + brief export/sync 회귀
|
|
4317
|
-
|
|
4318
|
-
|
|
4328
|
+
if (e2eShard.take('B(1.9.331)')) {
|
|
4329
|
+
total++;
|
|
4319
4330
|
let ok = false;
|
|
4320
4331
|
try {
|
|
4321
4332
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4342,8 +4353,8 @@ total++;
|
|
|
4342
4353
|
}
|
|
4343
4354
|
|
|
4344
4355
|
// 1.9.332 회귀 (UR-0025 모듈화): lessons.md 파서(_parseLessonEntries) lib/pure-utils 분리 + lesson list 회귀
|
|
4345
|
-
|
|
4346
|
-
|
|
4356
|
+
if (e2eShard.take('B(1.9.332)')) {
|
|
4357
|
+
total++;
|
|
4347
4358
|
let ok = false;
|
|
4348
4359
|
try {
|
|
4349
4360
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4366,8 +4377,8 @@ total++;
|
|
|
4366
4377
|
}
|
|
4367
4378
|
|
|
4368
4379
|
// 1.9.333 회귀 (UR-0025 심층): constraints 서브시스템 핵심 분리 — catalog→lib/catalogs + _matchConstraints→pure-utils + constraints 명령 회귀
|
|
4369
|
-
|
|
4370
|
-
|
|
4380
|
+
if (e2eShard.take('B(1.9.333)')) {
|
|
4381
|
+
total++;
|
|
4371
4382
|
let ok = false;
|
|
4372
4383
|
try {
|
|
4373
4384
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4402,8 +4413,8 @@ total++;
|
|
|
4402
4413
|
}
|
|
4403
4414
|
|
|
4404
4415
|
// 1.9.334 회귀 (UR-0025 심층, Codex 위임·검증): intent domain catalog→lib/catalogs + _matchDomain→pure-utils + intent expand 회귀
|
|
4405
|
-
|
|
4406
|
-
|
|
4416
|
+
if (e2eShard.take('B(1.9.334)')) {
|
|
4417
|
+
total++;
|
|
4407
4418
|
let ok = false;
|
|
4408
4419
|
try {
|
|
4409
4420
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4428,8 +4439,8 @@ total++;
|
|
|
4428
4439
|
}
|
|
4429
4440
|
|
|
4430
4441
|
// 1.9.335 회귀 (UR-0025 심층): LSP catalog→lib/catalogs + _detectLspLang/_matchLspSymbols→pure-utils + lsp symbols 회귀
|
|
4431
|
-
|
|
4432
|
-
|
|
4442
|
+
if (e2eShard.take('B(1.9.335)')) {
|
|
4443
|
+
total++;
|
|
4433
4444
|
let ok = false;
|
|
4434
4445
|
try {
|
|
4435
4446
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4456,8 +4467,8 @@ total++;
|
|
|
4456
4467
|
}
|
|
4457
4468
|
|
|
4458
4469
|
// 1.9.336 회귀 (UR-0025 심층, Codex 위임·검증): anti-laziness OPTIMISM_PATTERNS→lib/catalogs + optimism 순수로직→pure-utils + optimism-check 회귀
|
|
4459
|
-
|
|
4460
|
-
|
|
4470
|
+
if (e2eShard.take('B(1.9.336)')) {
|
|
4471
|
+
total++;
|
|
4461
4472
|
let ok = false;
|
|
4462
4473
|
try {
|
|
4463
4474
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4489,8 +4500,8 @@ total++;
|
|
|
4489
4500
|
}
|
|
4490
4501
|
|
|
4491
4502
|
// 1.9.337 회귀 (UR-0025 심층): persona BUILT_IN_PERSONAS→lib/catalogs + _personaSummaries→pure-utils + persona list/review 회귀
|
|
4492
|
-
|
|
4493
|
-
|
|
4503
|
+
if (e2eShard.take('B(1.9.337)')) {
|
|
4504
|
+
total++;
|
|
4494
4505
|
let ok = false;
|
|
4495
4506
|
try {
|
|
4496
4507
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4519,8 +4530,8 @@ total++;
|
|
|
4519
4530
|
}
|
|
4520
4531
|
|
|
4521
4532
|
// 1.9.338 회귀 (UR-0025 심층): i18n STRINGS→lib/catalogs + _translate→pure-utils + _t 박막 (인터랙티브 전용 getter라 구조+순수동작 검증)
|
|
4522
|
-
|
|
4523
|
-
|
|
4533
|
+
if (e2eShard.take('B(1.9.338)')) {
|
|
4534
|
+
total++;
|
|
4524
4535
|
let ok = false;
|
|
4525
4536
|
try {
|
|
4526
4537
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4544,8 +4555,8 @@ total++;
|
|
|
4544
4555
|
}
|
|
4545
4556
|
|
|
4546
4557
|
// 1.9.339 회귀 (UR-0053): decisions canonical = JSON, MD = projection. add→json+md / context count 단일소스 / drop+archive / MD-only 백필
|
|
4547
|
-
|
|
4548
|
-
|
|
4558
|
+
if (e2eShard.take('B(1.9.339)')) {
|
|
4559
|
+
total++;
|
|
4549
4560
|
let ok = false;
|
|
4550
4561
|
try {
|
|
4551
4562
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4591,8 +4602,8 @@ total++;
|
|
|
4591
4602
|
}
|
|
4592
4603
|
|
|
4593
4604
|
// 1.9.340 회귀 (UR-0058, Codex 위임·검증): lessons canonical = JSON, MD = projection. save→json+md / list+tag / drop+archive / MD-only 백필
|
|
4594
|
-
|
|
4595
|
-
|
|
4605
|
+
if (e2eShard.take('B(1.9.340)')) {
|
|
4606
|
+
total++;
|
|
4596
4607
|
let ok = false;
|
|
4597
4608
|
try {
|
|
4598
4609
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4637,8 +4648,8 @@ total++;
|
|
|
4637
4648
|
}
|
|
4638
4649
|
|
|
4639
4650
|
// 1.9.341 회귀 (UR-0025 심층): BUILTIN_CATALOG→lib/catalogs + _withBuiltinSource→pure-utils + skill list 회귀
|
|
4640
|
-
|
|
4641
|
-
|
|
4651
|
+
if (e2eShard.take('B(1.9.341)')) {
|
|
4652
|
+
total++;
|
|
4642
4653
|
let ok = false;
|
|
4643
4654
|
try {
|
|
4644
4655
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4667,8 +4678,8 @@ total++;
|
|
|
4667
4678
|
}
|
|
4668
4679
|
|
|
4669
4680
|
// 1.9.342 회귀 (UR-0025 심층): ROADMAP_STATUS_LABEL/COLOR→lib/catalogs + roadmap.html 렌더 회귀
|
|
4670
|
-
|
|
4671
|
-
|
|
4681
|
+
if (e2eShard.take('B(1.9.342)')) {
|
|
4682
|
+
total++;
|
|
4672
4683
|
let ok = false;
|
|
4673
4684
|
try {
|
|
4674
4685
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4694,8 +4705,8 @@ total++;
|
|
|
4694
4705
|
}
|
|
4695
4706
|
|
|
4696
4707
|
// 1.9.343 회귀 (UR-0025 심층, R300 마일스톤): SECRET_PATTERNS→lib/catalogs 보안 응집 + scan secrets 탐지/오탐 가드 회귀
|
|
4697
|
-
|
|
4698
|
-
|
|
4708
|
+
if (e2eShard.take('B(1.9.343)')) {
|
|
4709
|
+
total++;
|
|
4699
4710
|
let ok = false;
|
|
4700
4711
|
try {
|
|
4701
4712
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4729,8 +4740,8 @@ total++;
|
|
|
4729
4740
|
}
|
|
4730
4741
|
|
|
4731
4742
|
// 1.9.344 회귀 (UR-0025 심층): SKILL_CATALOG_PRESETS→lib/catalogs + skill discover preset 인식 회귀
|
|
4732
|
-
|
|
4733
|
-
|
|
4743
|
+
if (e2eShard.take('B(1.9.344)')) {
|
|
4744
|
+
total++;
|
|
4734
4745
|
let ok = false;
|
|
4735
4746
|
try {
|
|
4736
4747
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
|
|
@@ -4754,8 +4765,8 @@ total++;
|
|
|
4754
4765
|
}
|
|
4755
4766
|
|
|
4756
4767
|
// 1.9.345 회귀 (UR-0025 심층): _esc(HTML escape)→pure-utils + roadmap.html XSS 이스케이프 회귀
|
|
4757
|
-
|
|
4758
|
-
|
|
4768
|
+
if (e2eShard.take('B(1.9.345)')) {
|
|
4769
|
+
total++;
|
|
4759
4770
|
let ok = false;
|
|
4760
4771
|
try {
|
|
4761
4772
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4782,8 +4793,8 @@ total++;
|
|
|
4782
4793
|
}
|
|
4783
4794
|
|
|
4784
4795
|
// 1.9.346 회귀 (UR-0025 심층): _roadmapTokenStyles→pure-utils + roadmap.html CSS 변수 회귀
|
|
4785
|
-
|
|
4786
|
-
|
|
4796
|
+
if (e2eShard.take('B(1.9.346)')) {
|
|
4797
|
+
total++;
|
|
4787
4798
|
let ok = false;
|
|
4788
4799
|
try {
|
|
4789
4800
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4809,8 +4820,8 @@ total++;
|
|
|
4809
4820
|
}
|
|
4810
4821
|
|
|
4811
4822
|
// 1.9.347 회귀 (UR-0025 심층): _parseSkillMd(SKILL.md frontmatter, BOM-aware)→pure-utils + skill install(BOM) 회귀
|
|
4812
|
-
|
|
4813
|
-
|
|
4823
|
+
if (e2eShard.take('B(1.9.347)')) {
|
|
4824
|
+
total++;
|
|
4814
4825
|
let ok = false;
|
|
4815
4826
|
try {
|
|
4816
4827
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4836,8 +4847,8 @@ total++;
|
|
|
4836
4847
|
}
|
|
4837
4848
|
|
|
4838
4849
|
// 1.9.348 회귀 (외부리뷰 UR-0059 P0): --path 라우팅 일관화 — positional-dispatch 가 --path 우선(→positional→cwd)
|
|
4839
|
-
|
|
4840
|
-
|
|
4850
|
+
if (e2eShard.take('B(1.9.348)')) {
|
|
4851
|
+
total++;
|
|
4841
4852
|
let ok = false;
|
|
4842
4853
|
try {
|
|
4843
4854
|
const A = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-pa-'));
|
|
@@ -4862,8 +4873,8 @@ total++;
|
|
|
4862
4873
|
}
|
|
4863
4874
|
|
|
4864
4875
|
// 1.9.349 회귀 (외부리뷰 UR-0063, GPT5.5+Opus 교차검증): selftest/doctor 위치독립 — 비초기화 dir 에서도 통과(거짓 "설치 손상" 없음)
|
|
4865
|
-
|
|
4866
|
-
|
|
4876
|
+
if (e2eShard.take('B(1.9.349)')) {
|
|
4877
|
+
total++;
|
|
4867
4878
|
let ok = false; let _liDbg = null;
|
|
4868
4879
|
try {
|
|
4869
4880
|
const ni = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-noinit-')); // .leerness 없는 비초기화 dir
|
|
@@ -4892,8 +4903,8 @@ total++;
|
|
|
4892
4903
|
}
|
|
4893
4904
|
|
|
4894
4905
|
// 1.9.350 회귀 (외부리뷰 P1 보안 하드닝): UR-0061 CSS breakout 차단 + UR-0062 skill traversal 차단 + UR-0060 scan false-neg/패턴 보강
|
|
4895
|
-
|
|
4896
|
-
|
|
4906
|
+
if (e2eShard.take('B(1.9.350)')) {
|
|
4907
|
+
total++;
|
|
4897
4908
|
let ok = false;
|
|
4898
4909
|
try {
|
|
4899
4910
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4922,8 +4933,8 @@ total++;
|
|
|
4922
4933
|
}
|
|
4923
4934
|
|
|
4924
4935
|
// 1.9.351 회귀 (외부리뷰 UR-0064/0065): decision/lesson 제목 오염 차단 + 문서 정합(AGENTS.md .leerness / --help 누락 명령)
|
|
4925
|
-
|
|
4926
|
-
|
|
4936
|
+
if (e2eShard.take('B(1.9.351)')) {
|
|
4937
|
+
total++;
|
|
4927
4938
|
let ok = false;
|
|
4928
4939
|
try {
|
|
4929
4940
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-doc-'));
|
|
@@ -4947,8 +4958,8 @@ total++;
|
|
|
4947
4958
|
}
|
|
4948
4959
|
|
|
4949
4960
|
// 1.9.352 회귀 (외부리뷰 P2): UR-0069 usage subcommand 집계 + UR-0068 milestone 파서 블록 경계
|
|
4950
|
-
|
|
4951
|
-
|
|
4961
|
+
if (e2eShard.take('B(1.9.352)')) {
|
|
4962
|
+
total++;
|
|
4952
4963
|
let ok = false;
|
|
4953
4964
|
try {
|
|
4954
4965
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -4972,8 +4983,8 @@ total++;
|
|
|
4972
4983
|
}
|
|
4973
4984
|
|
|
4974
4985
|
// 1.9.353 회귀 (외부리뷰 P2): UR-0067 encoding(CP949 감지/.ps1 BOM 예외) + UR-0070 shell-guard --shell + UR-0071 init non-TTY ANSI
|
|
4975
|
-
|
|
4976
|
-
|
|
4986
|
+
if (e2eShard.take('B(1.9.353)')) {
|
|
4987
|
+
total++;
|
|
4977
4988
|
let ok = false;
|
|
4978
4989
|
try {
|
|
4979
4990
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-enc-'));
|
|
@@ -5002,8 +5013,8 @@ total++;
|
|
|
5002
5013
|
}
|
|
5003
5014
|
|
|
5004
5015
|
// 1.9.354 회귀 (외부리뷰 P3 UR-0072): compareVer pre-release + _classifyCJK + scan secrets 파일경로 + requests drop ✓
|
|
5005
|
-
|
|
5006
|
-
|
|
5016
|
+
if (e2eShard.take('B(1.9.354)')) {
|
|
5017
|
+
total++;
|
|
5007
5018
|
let ok = false;
|
|
5008
5019
|
try {
|
|
5009
5020
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -5030,8 +5041,8 @@ total++;
|
|
|
5030
5041
|
}
|
|
5031
5042
|
|
|
5032
5043
|
// 1.9.355 회귀 (UR-0075 Phase A): migrate --guide 가이드 출력 + update/init/migrate --path 타깃(이전 positional 전용)
|
|
5033
|
-
|
|
5034
|
-
|
|
5044
|
+
if (e2eShard.take('B(1.9.355)')) {
|
|
5045
|
+
total++;
|
|
5035
5046
|
let ok = false;
|
|
5036
5047
|
try {
|
|
5037
5048
|
const m = require(path.resolve(__dirname, '..', 'lib', 'pure-utils.js'));
|
|
@@ -5056,8 +5067,8 @@ total++;
|
|
|
5056
5067
|
}
|
|
5057
5068
|
|
|
5058
5069
|
// 1.9.356 회귀 (UR-0075 Phase B): migrate audit — clean=변경없음 · 구버전+canonical 누락=findings (비파괴 dry-run)
|
|
5059
|
-
|
|
5060
|
-
|
|
5070
|
+
if (e2eShard.take('B(1.9.356)')) {
|
|
5071
|
+
total++;
|
|
5061
5072
|
let ok = false;
|
|
5062
5073
|
try {
|
|
5063
5074
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-aud-'));
|
|
@@ -5083,8 +5094,8 @@ total++;
|
|
|
5083
5094
|
}
|
|
5084
5095
|
|
|
5085
5096
|
// 1.9.357 회귀 (UR-0075 Phase C): migrate apply — dry-run 비파괴 · --yes canonical 백필 · 멱등
|
|
5086
|
-
|
|
5087
|
-
|
|
5097
|
+
if (e2eShard.take('B(1.9.357/1.36.191)')) {
|
|
5098
|
+
total++;
|
|
5088
5099
|
let ok = false;
|
|
5089
5100
|
try {
|
|
5090
5101
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-app-'));
|
|
@@ -5123,8 +5134,8 @@ total++;
|
|
|
5123
5134
|
}
|
|
5124
5135
|
|
|
5125
5136
|
// 1.9.358 회귀 (UR-0075 Phase D): migrate plan — 임시폴더 설치 후 비교 · clean=변경없음 · 코어파일 누락=missing 감지 · 읽기전용
|
|
5126
|
-
|
|
5127
|
-
|
|
5137
|
+
if (e2eShard.take('B(1.9.358)')) {
|
|
5138
|
+
total++;
|
|
5128
5139
|
let ok = false;
|
|
5129
5140
|
try {
|
|
5130
5141
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-plan-'));
|
|
@@ -5148,8 +5159,8 @@ total++;
|
|
|
5148
5159
|
}
|
|
5149
5160
|
|
|
5150
5161
|
// 1.9.359 회귀 (UR-0074): install-safety — 0 런타임 deps / 0 install-script / safe-install 워크플로 (공급망 신뢰 가드)
|
|
5151
|
-
|
|
5152
|
-
|
|
5162
|
+
if (e2eShard.take('B(1.9.359)')) {
|
|
5163
|
+
total++;
|
|
5153
5164
|
let ok = false;
|
|
5154
5165
|
try {
|
|
5155
5166
|
const r = cp.spawnSync(process.execPath, [CLI, 'install-safety', '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
@@ -5161,8 +5172,8 @@ total++;
|
|
|
5161
5172
|
}
|
|
5162
5173
|
|
|
5163
5174
|
// 1.9.360 회귀 (외부리뷰 CV-2/UR-0077): update --check 가 신형 Node Windows 에서 spawn EINVAL 없이 동작 (cmd.exe 경유)
|
|
5164
|
-
|
|
5165
|
-
|
|
5175
|
+
if (e2eShard.take('B(1.9.360)')) {
|
|
5176
|
+
total++;
|
|
5166
5177
|
let ok = false;
|
|
5167
5178
|
try {
|
|
5168
5179
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-einval-'));
|
|
@@ -5178,8 +5189,8 @@ total++;
|
|
|
5178
5189
|
}
|
|
5179
5190
|
|
|
5180
5191
|
// 1.9.361 회귀 (외부리뷰 CV-1/UR-0076): --path 라우팅 통일 — session close --path 가 cwd 아닌 정타깃에 쓰기 + context --path= 등호형
|
|
5181
|
-
|
|
5182
|
-
|
|
5192
|
+
if (e2eShard.take('B(1.9.361)')) {
|
|
5193
|
+
total++;
|
|
5183
5194
|
let ok = false;
|
|
5184
5195
|
try {
|
|
5185
5196
|
const a = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rootA-'));
|
|
@@ -5205,8 +5216,8 @@ total++;
|
|
|
5205
5216
|
}
|
|
5206
5217
|
|
|
5207
5218
|
// 1.9.361 회귀 (외부리뷰 CV-3/UR-0078): audit 가 미초기화/존재하지않는 경로를 healthy 로 오판 안 함 (verify 와 일관)
|
|
5208
|
-
|
|
5209
|
-
|
|
5219
|
+
if (e2eShard.take('B(1.9.361) #2')) {
|
|
5220
|
+
total++;
|
|
5210
5221
|
let ok = false;
|
|
5211
5222
|
try {
|
|
5212
5223
|
const r = cp.spawnSync(process.execPath, [CLI, 'audit', path.join(os.tmpdir(), 'leerness-no-such-' + total), '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
@@ -5218,8 +5229,8 @@ total++;
|
|
|
5218
5229
|
}
|
|
5219
5230
|
|
|
5220
5231
|
// 1.9.362 회귀 (외부리뷰 CV-4/UR-0079): archive retention — init/migrate 반복해도 --keep 상한 유지 (무한 누적 차단)
|
|
5221
|
-
|
|
5222
|
-
|
|
5232
|
+
if (e2eShard.take('B(1.9.362)')) {
|
|
5233
|
+
total++;
|
|
5223
5234
|
let ok = false;
|
|
5224
5235
|
try {
|
|
5225
5236
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-arch-'));
|
|
@@ -5239,8 +5250,8 @@ total++;
|
|
|
5239
5250
|
}
|
|
5240
5251
|
|
|
5241
5252
|
// 1.9.363 회귀 (외부리뷰 CV-7/UR-0082): commands 카탈로그가 누락 명령군(8그룹+install-safety+migrate sub) 전수 등재
|
|
5242
|
-
|
|
5243
|
-
|
|
5253
|
+
if (e2eShard.take('B(1.9.363)')) {
|
|
5254
|
+
total++;
|
|
5244
5255
|
let ok = false;
|
|
5245
5256
|
try {
|
|
5246
5257
|
const r = cp.spawnSync(process.execPath, [CLI, 'commands', '--json'], { encoding: 'utf8', timeout: 15000 });
|
|
@@ -5254,8 +5265,8 @@ total++;
|
|
|
5254
5265
|
}
|
|
5255
5266
|
|
|
5256
5267
|
// 1.9.364 회귀 (4번째 외부평가 9.3/UR-0083): auto-update hook 비침투 — update --check --quiet 가 up-to-date 시 무음 + hook 명령이 --quiet 사용
|
|
5257
|
-
|
|
5258
|
-
|
|
5268
|
+
if (e2eShard.take('B(1.9.364)')) {
|
|
5269
|
+
total++;
|
|
5259
5270
|
let ok = false;
|
|
5260
5271
|
try {
|
|
5261
5272
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-quiet-'));
|
|
@@ -5274,8 +5285,8 @@ total++;
|
|
|
5274
5285
|
}
|
|
5275
5286
|
|
|
5276
5287
|
// 1.9.365 회귀 (외부리뷰 CV-6/UR-0081): 시크릿 스캐너 정밀도 — placeholder FP 무시 / unquoted FN 탐지 / gitignored 강등
|
|
5277
|
-
|
|
5278
|
-
|
|
5288
|
+
if (e2eShard.take('B(1.9.365)')) {
|
|
5289
|
+
total++;
|
|
5279
5290
|
let ok = false;
|
|
5280
5291
|
try {
|
|
5281
5292
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sec-'));
|
|
@@ -5301,8 +5312,8 @@ total++;
|
|
|
5301
5312
|
}
|
|
5302
5313
|
|
|
5303
5314
|
// 1.9.366 회귀 (외부리뷰 CV-5/UR-0080): selftest 무결성 — 설치 패키지 관점에서 --json 전부 통과 (행위 케이스 포함)
|
|
5304
|
-
|
|
5305
|
-
|
|
5315
|
+
if (e2eShard.take('B(1.9.366)')) {
|
|
5316
|
+
total++;
|
|
5306
5317
|
let ok = false;
|
|
5307
5318
|
try {
|
|
5308
5319
|
// 1.36.85: 행위검사 전환으로 selftest 약 13s — 4초 시절 기준 30s 는 전체 부하에서 터진다.
|
|
@@ -5319,8 +5330,8 @@ total++;
|
|
|
5319
5330
|
// 또한 stderr 잡음 0 을 함께 단언한다 — 잡음의 실제 원인(프로브가 fail() 의 stderr 를 안 가로챔 · graph 케이스 quiet 누락)은
|
|
5320
5331
|
// io 래치가 사람용 log 를 stderr 로 강등해 주는 **우연** 덕에 stdout 만 겨우 지켜졌다. 래치가 바뀌면 곧장 stdout 오염이 된다.
|
|
5321
5332
|
// → stdout 순수성만 단언하면 "우연히 통과"하므로, 잡음 자체를 0 으로 고정해 원인 단계에서 막는다.
|
|
5322
|
-
|
|
5323
|
-
|
|
5333
|
+
if (e2eShard.take('B(1.36.88)')) {
|
|
5334
|
+
total++;
|
|
5324
5335
|
let ok = false, why = '';
|
|
5325
5336
|
// T-0341: run in a harness made through the product (scripts/e2e-lived-in-harness.js), the same on every tree.
|
|
5326
5337
|
// T-0315 copied this repository's own .leerness, AGENTS.md and CLAUDE.md, and the public export has none of
|
|
@@ -5361,8 +5372,8 @@ total++;
|
|
|
5361
5372
|
// 프로젝트 상태에 따라 달라져 같은 코드로 게이트가 통과/실패로 갈렸다. 한 곳에서 나온 결함은 대개 클래스다 —
|
|
5362
5373
|
// 실측 스윕(조회성 79개)에서 정보성 누출은 audit 하나였고 나머지 4건은 **사용법 오류의 stderr 진단**(CLI 관례)이었다.
|
|
5363
5374
|
// 그 측정을 래칫으로 고정한다: 성공 경로의 `--json` 은 stderr 가 비어야 한다.
|
|
5364
|
-
|
|
5365
|
-
|
|
5375
|
+
if (e2eShard.take('B(1.36.121)')) {
|
|
5376
|
+
total++;
|
|
5366
5377
|
let ok = false; const bad = []; const diagnostics = {};
|
|
5367
5378
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-json121-'));
|
|
5368
5379
|
const ENV = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -5408,8 +5419,8 @@ total++;
|
|
|
5408
5419
|
// 실측으로 두 결함이 나왔다: ① `leerness_selftest` 는 실측 116초인데 MCP 기본 한도가 60초라 **항상 타임아웃**
|
|
5409
5420
|
// (광고된 89개 도구 중 설치 검증 도구가 MCP 로는 무용) ② selftest 프로브가 `process.env` 를 상속해
|
|
5410
5421
|
// MCP 래퍼가 끄는 안내를 단언 → MCP 경유에서만 1건 실패 = AI 에게 "설치가 깨졌다" 는 **거짓 보고**.
|
|
5411
|
-
|
|
5412
|
-
|
|
5422
|
+
if (e2eShard.take('B(1.36.124)')) {
|
|
5423
|
+
total++;
|
|
5413
5424
|
let ok = false, mcpSeconds = null; const bad = [];
|
|
5414
5425
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mcp124-'));
|
|
5415
5426
|
const ENVm = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -5503,8 +5514,8 @@ total++;
|
|
|
5503
5514
|
}
|
|
5504
5515
|
|
|
5505
5516
|
// 1.9.367 회귀 (UR-0025): _mergeEnvLines 모듈 분리 — migrate 가 사용자 .env 값을 key-aware 로 보존 (덮어쓰기 X)
|
|
5506
|
-
|
|
5507
|
-
|
|
5517
|
+
if (e2eShard.take('B(1.9.367)')) {
|
|
5518
|
+
total++;
|
|
5508
5519
|
let ok = false;
|
|
5509
5520
|
try {
|
|
5510
5521
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-envm-'));
|
|
@@ -5521,8 +5532,8 @@ total++;
|
|
|
5521
5532
|
}
|
|
5522
5533
|
|
|
5523
5534
|
// 1.9.368 회귀 (UR-0025): _managedMerge 모듈 분리 — migrate 가 사용자 편집(AGENTS.md)을 migration-preserved 블록으로 보존
|
|
5524
|
-
|
|
5525
|
-
|
|
5535
|
+
if (e2eShard.take('B(1.9.368)')) {
|
|
5536
|
+
total++;
|
|
5526
5537
|
let ok = false;
|
|
5527
5538
|
try {
|
|
5528
5539
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mm-'));
|
|
@@ -5542,8 +5553,8 @@ total++;
|
|
|
5542
5553
|
// `adapter` 처럼 새 백업을 만들지 않는 실행이 일반 경로(`.leerness/archive`)를 채워 넘겨서,
|
|
5543
5554
|
// 이전 마이그레이션이 적어 둔 구체 경로(`.leerness/archive/leerness-1.36.100-…`)를 덮었다.
|
|
5544
5555
|
// 그 문장은 "삭제하지 않았다" 는 약속의 근거이므로 격하는 곧 정보 손실이다. 세 방향을 모두 잰다.
|
|
5545
|
-
|
|
5546
|
-
|
|
5556
|
+
if (e2eShard.take('B(1.36.122)')) {
|
|
5557
|
+
total++;
|
|
5547
5558
|
let ok = false; const bad = [];
|
|
5548
5559
|
try {
|
|
5549
5560
|
const PUx = require(path.resolve(path.dirname(CLI), '..', 'lib', 'pure-utils'));
|
|
@@ -5600,8 +5611,8 @@ total++;
|
|
|
5600
5611
|
}
|
|
5601
5612
|
|
|
5602
5613
|
// 1.9.369 회귀 (UR-0025): MINIMAL_SKIP_KEYS/_parseSkillsValue 분리 — init --minimal 비핵심 스킵+코어 유지, --skills recommended 설치
|
|
5603
|
-
|
|
5604
|
-
|
|
5614
|
+
if (e2eShard.take('B(1.9.369)')) {
|
|
5615
|
+
total++;
|
|
5605
5616
|
let ok = false;
|
|
5606
5617
|
try {
|
|
5607
5618
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-min-'));
|
|
@@ -5620,8 +5631,8 @@ total++;
|
|
|
5620
5631
|
}
|
|
5621
5632
|
|
|
5622
5633
|
// 1.9.370 회귀 (UR-0025): _parseArchiveBlocks/_parseSkillCatalog 순수 파서 모듈 분리 — lib/pure-utils 직접 호출 행위
|
|
5623
|
-
|
|
5624
|
-
|
|
5634
|
+
if (e2eShard.take('B(1.9.370)')) {
|
|
5635
|
+
total++;
|
|
5625
5636
|
let ok = false;
|
|
5626
5637
|
try {
|
|
5627
5638
|
const pu = require(path.resolve(__dirname, '..', 'lib', 'pure-utils'));
|
|
@@ -5636,8 +5647,8 @@ total++;
|
|
|
5636
5647
|
}
|
|
5637
5648
|
|
|
5638
5649
|
// 1.9.371 회귀 (UR-0073 Phase A): team 정의 레지스트리 — add/list/remove + canonical JSON/MD (opt-in · 정의 전용)
|
|
5639
|
-
|
|
5640
|
-
|
|
5650
|
+
if (e2eShard.take('B(1.9.371)')) {
|
|
5651
|
+
total++;
|
|
5641
5652
|
let ok = false;
|
|
5642
5653
|
try {
|
|
5643
5654
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-team-'));
|
|
@@ -5660,8 +5671,8 @@ total++;
|
|
|
5660
5671
|
}
|
|
5661
5672
|
|
|
5662
5673
|
// 1.9.372 회귀 (UR-0073 Phase B): team preview — dry-run 실행 계획 미리보기 (멤버별 dispatch 명령, 실제 실행/파일변경 없음)
|
|
5663
|
-
|
|
5664
|
-
|
|
5674
|
+
if (e2eShard.take('B(1.9.372)')) {
|
|
5675
|
+
total++;
|
|
5665
5676
|
let ok = false;
|
|
5666
5677
|
try {
|
|
5667
5678
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tprev-'));
|
|
@@ -5683,8 +5694,8 @@ total++;
|
|
|
5683
5694
|
}
|
|
5684
5695
|
|
|
5685
5696
|
// 1.9.373 회귀 (UR-0073 Phase C): handoff 가 비-manual 팀 스케줄 알림 노출(미리보기 안내) · manual 미표시 · opt-out
|
|
5686
|
-
|
|
5687
|
-
|
|
5697
|
+
if (e2eShard.take('B(1.9.373)')) {
|
|
5698
|
+
total++;
|
|
5688
5699
|
let ok = false;
|
|
5689
5700
|
try {
|
|
5690
5701
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tsch-'));
|
|
@@ -5704,8 +5715,8 @@ total++;
|
|
|
5704
5715
|
}
|
|
5705
5716
|
|
|
5706
5717
|
// 1.9.374 회귀 (UR-0074): release cadence 진단 — --json level/recommendation/releasesPerDay 구조 (릴리스 빈도 가시화)
|
|
5707
|
-
|
|
5708
|
-
|
|
5718
|
+
if (e2eShard.take('B(1.9.374)')) {
|
|
5719
|
+
total++;
|
|
5709
5720
|
let ok = false;
|
|
5710
5721
|
try {
|
|
5711
5722
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cad-'));
|
|
@@ -5723,8 +5734,8 @@ total++;
|
|
|
5723
5734
|
}
|
|
5724
5735
|
|
|
5725
5736
|
// 1.9.376 회귀 (UR-0073 Phase D): team deploy 2중 게이트 — dry-run 실행안함 · --yes만이면 거부 · --yes+env 만 실행
|
|
5726
|
-
|
|
5727
|
-
|
|
5737
|
+
if (e2eShard.take('B(1.9.376)')) {
|
|
5738
|
+
total++;
|
|
5728
5739
|
let ok = false;
|
|
5729
5740
|
try {
|
|
5730
5741
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tdep-'));
|
|
@@ -5748,8 +5759,8 @@ total++;
|
|
|
5748
5759
|
}
|
|
5749
5760
|
|
|
5750
5761
|
// 1.9.377 회귀 (UR-0025): _renderWorkspaceReferenceGuide 모듈 분리 — 빌더가 dirName/version 반영 + 핵심 섹션 포함 (출력 동일성)
|
|
5751
|
-
|
|
5752
|
-
|
|
5762
|
+
if (e2eShard.take('B(1.9.377)')) {
|
|
5763
|
+
total++;
|
|
5753
5764
|
let ok = false;
|
|
5754
5765
|
try {
|
|
5755
5766
|
const pu = require(path.resolve(__dirname, '..', 'lib', 'pure-utils'));
|
|
@@ -5765,8 +5776,8 @@ total++;
|
|
|
5765
5776
|
}
|
|
5766
5777
|
|
|
5767
5778
|
// 1.9.378 회귀 (UR-0073): team MCP 도구 2종(read-only) 정의 + 매핑 CLI(team list/preview) 동작
|
|
5768
|
-
|
|
5769
|
-
|
|
5779
|
+
if (e2eShard.take('B(1.9.378)')) {
|
|
5780
|
+
total++;
|
|
5770
5781
|
let ok = false;
|
|
5771
5782
|
try {
|
|
5772
5783
|
const tools = require(path.resolve(__dirname, '..', 'lib', 'mcp-tools'));
|
|
@@ -5788,8 +5799,8 @@ total++;
|
|
|
5788
5799
|
}
|
|
5789
5800
|
|
|
5790
5801
|
// 1.9.379 회귀 (UR-0025 심화): pulse 렌더 코어(_memorySurface/_renderPulseLine) 분리 — 빌더 동작 + pulse CLI 출력 유지
|
|
5791
|
-
|
|
5792
|
-
|
|
5802
|
+
if (e2eShard.take('B(1.9.379)')) {
|
|
5803
|
+
total++;
|
|
5793
5804
|
let ok = false;
|
|
5794
5805
|
try {
|
|
5795
5806
|
const pu = require(path.resolve(__dirname, '..', 'lib', 'pure-utils'));
|
|
@@ -5809,8 +5820,8 @@ total++;
|
|
|
5809
5820
|
}
|
|
5810
5821
|
|
|
5811
5822
|
// 1.9.380 회귀 (UR-0025): REQUIRED_WORKSPACE_FILES 단일출처 — verify 가 catalog 리스트로 init통과/누락실패
|
|
5812
|
-
|
|
5813
|
-
|
|
5823
|
+
if (e2eShard.take('B(1.9.380)')) {
|
|
5824
|
+
total++;
|
|
5814
5825
|
let ok = false;
|
|
5815
5826
|
try {
|
|
5816
5827
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs'));
|
|
@@ -5830,8 +5841,8 @@ total++;
|
|
|
5830
5841
|
}
|
|
5831
5842
|
|
|
5832
5843
|
// 1.9.381 회귀 (UR-0025): KEYWORD_STOPWORDS 단일출처 — catalog Set + 키워드 필터 동작 + handoff consumer 무크래시
|
|
5833
|
-
|
|
5834
|
-
|
|
5844
|
+
if (e2eShard.take('B(1.9.381)')) {
|
|
5845
|
+
total++;
|
|
5835
5846
|
let ok = false;
|
|
5836
5847
|
try {
|
|
5837
5848
|
const c = require(path.resolve(__dirname, '..', 'lib', 'catalogs'));
|
|
@@ -5853,8 +5864,8 @@ total++;
|
|
|
5853
5864
|
}
|
|
5854
5865
|
|
|
5855
5866
|
// 1.9.382 회귀 (UR-0025 큰핸들러토대): lib/io.js 프리미티브 분리 — exports + 동작 + init(ok)/verify(fail→exit1) consumer 유지
|
|
5856
|
-
|
|
5857
|
-
|
|
5867
|
+
if (e2eShard.take('B(1.9.382)')) {
|
|
5868
|
+
total++;
|
|
5858
5869
|
let ok = false;
|
|
5859
5870
|
try {
|
|
5860
5871
|
const io = require(path.resolve(__dirname, '..', 'lib', 'io'));
|
|
@@ -5873,8 +5884,8 @@ total++;
|
|
|
5873
5884
|
}
|
|
5874
5885
|
|
|
5875
5886
|
// 1.9.383 회귀 (UR-0025 큰핸들러토대): lib/io.js fs 프리미티브 분리 — round-trip + decision write→context read consumer 유지
|
|
5876
|
-
|
|
5877
|
-
|
|
5887
|
+
if (e2eShard.take('B(1.9.383)')) {
|
|
5888
|
+
total++;
|
|
5878
5889
|
let ok = false;
|
|
5879
5890
|
try {
|
|
5880
5891
|
const io = require(path.resolve(__dirname, '..', 'lib', 'io'));
|
|
@@ -5897,8 +5908,8 @@ total++;
|
|
|
5897
5908
|
}
|
|
5898
5909
|
|
|
5899
5910
|
// 1.9.384 회귀 (5번째 외부평가/UR-0085): status/verify --json 구조화 출력 일관성 — pass JSON + fail JSON(exit1) + 사람용 exit1 유지
|
|
5900
|
-
|
|
5901
|
-
|
|
5911
|
+
if (e2eShard.take('B(1.9.384)')) {
|
|
5912
|
+
total++;
|
|
5902
5913
|
let ok = false;
|
|
5903
5914
|
try {
|
|
5904
5915
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-svjson-'));
|
|
@@ -5923,8 +5934,8 @@ total++;
|
|
|
5923
5934
|
}
|
|
5924
5935
|
|
|
5925
5936
|
// 1.9.385 회귀 (5번째 외부평가/UR-0086): contract verify spec 파서가 markdown bullet "- name(args)" 함수 선언 감지 + backtick 약언급 관대 유지
|
|
5926
|
-
|
|
5927
|
-
|
|
5937
|
+
if (e2eShard.take('B(1.9.385)')) {
|
|
5938
|
+
total++;
|
|
5928
5939
|
let ok = false;
|
|
5929
5940
|
try {
|
|
5930
5941
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-contract-bullet-'));
|
|
@@ -5952,8 +5963,8 @@ total++;
|
|
|
5952
5963
|
}
|
|
5953
5964
|
|
|
5954
5965
|
// 1.9.386 회귀 (5번째 외부평가/UR-0087): secret scan env-family 포함 + gitignore git-일치 (.env↛.env.bad 미보호=커밋대상)
|
|
5955
|
-
|
|
5956
|
-
|
|
5966
|
+
if (e2eShard.take('B(1.9.386)')) {
|
|
5967
|
+
total++;
|
|
5957
5968
|
let ok = false;
|
|
5958
5969
|
try {
|
|
5959
5970
|
const key = 'OPENAI_API_KEY=sk-proj-abc123def456ghi789jkl012mno345pqr678stuvwx\n';
|
|
@@ -5983,8 +5994,8 @@ total++;
|
|
|
5983
5994
|
}
|
|
5984
5995
|
|
|
5985
5996
|
// 1.9.387 회귀 (5번째 외부평가 일관성/UR-0088): incident/runs list 빈 케이스도 --json 구조화 {total:0,items:[]} + 사람용 보존
|
|
5986
|
-
|
|
5987
|
-
|
|
5997
|
+
if (e2eShard.take('B(1.9.387)')) {
|
|
5998
|
+
total++;
|
|
5988
5999
|
let ok = false;
|
|
5989
6000
|
try {
|
|
5990
6001
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-listjson-'));
|
|
@@ -6005,8 +6016,8 @@ total++;
|
|
|
6005
6016
|
}
|
|
6006
6017
|
|
|
6007
6018
|
// 1.9.393 회귀 (UR-0025 + whats-new BUG-fix): _parseChangelogBetween 가 "## X — DATE — title" 헤더를 파싱 (종전 0건 반환 버그)
|
|
6008
|
-
|
|
6009
|
-
|
|
6019
|
+
if (e2eShard.take('B(1.9.393)')) {
|
|
6020
|
+
total++;
|
|
6010
6021
|
let ok = false;
|
|
6011
6022
|
try {
|
|
6012
6023
|
const pkgRoot = path.resolve(__dirname, '..');
|
|
@@ -6030,8 +6041,8 @@ total++;
|
|
|
6030
6041
|
}
|
|
6031
6042
|
|
|
6032
6043
|
// 1.9.395 회귀가드 (행위검증): audit README 배지 mismatch + current-state stale 의 FP/FN — 종전 전용 e2e 가드 부재
|
|
6033
|
-
|
|
6034
|
-
|
|
6044
|
+
if (e2eShard.take('B(1.9.395)')) {
|
|
6045
|
+
total++;
|
|
6035
6046
|
let ok = false;
|
|
6036
6047
|
try {
|
|
6037
6048
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-auditchk-'));
|
|
@@ -6060,8 +6071,8 @@ total++;
|
|
|
6060
6071
|
}
|
|
6061
6072
|
|
|
6062
6073
|
// 1.9.396 회귀 (6번째 외부평가/codex P1-B): task drop 없는 ID → fail + 가짜 row 무생성(데이터 손상 차단). 실제 task drop 은 정상.
|
|
6063
|
-
|
|
6064
|
-
|
|
6074
|
+
if (e2eShard.take('B(1.9.396)')) {
|
|
6075
|
+
total++;
|
|
6065
6076
|
let ok = false;
|
|
6066
6077
|
try {
|
|
6067
6078
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-taskdrop-'));
|
|
@@ -6083,8 +6094,8 @@ total++;
|
|
|
6083
6094
|
}
|
|
6084
6095
|
|
|
6085
6096
|
// 1.9.397 회귀 (6번째 외부평가/codex P1-A, UR-0098): install-safety 레시피 셸-무관(POSIX env-prefix 제거) + hardeningNote
|
|
6086
|
-
|
|
6087
|
-
|
|
6097
|
+
if (e2eShard.take('B(1.9.397)')) {
|
|
6098
|
+
total++;
|
|
6088
6099
|
let ok = false;
|
|
6089
6100
|
try {
|
|
6090
6101
|
const r = cp.spawnSync(process.execPath, [CLI, 'install-safety', '--json'], { encoding: 'utf8', timeout: 10000 });
|
|
@@ -6099,8 +6110,8 @@ total++;
|
|
|
6099
6110
|
}
|
|
6100
6111
|
|
|
6101
6112
|
// 1.9.398 회귀 (6번째 외부평가/codex P1-C, UR-0099): --json 에러 경로가 구조화 JSON(텍스트 아님) + 사람용 보존
|
|
6102
|
-
|
|
6103
|
-
|
|
6113
|
+
if (e2eShard.take('B(1.9.398)')) {
|
|
6114
|
+
total++;
|
|
6104
6115
|
let ok = false;
|
|
6105
6116
|
try {
|
|
6106
6117
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-jsonerr-'));
|
|
@@ -6120,8 +6131,8 @@ total++;
|
|
|
6120
6131
|
}
|
|
6121
6132
|
|
|
6122
6133
|
// 1.9.399 회귀 (7번째 버그헌트 P1-A, UR-0104): 테이블셀 injection 차단 — task/rule 텍스트의 파이프(|) 보존 + 개행 가짜행 주입 차단 + rule 멱등성
|
|
6123
|
-
|
|
6124
|
-
|
|
6134
|
+
if (e2eShard.take('B(1.9.399)')) {
|
|
6135
|
+
total++;
|
|
6125
6136
|
let ok = false;
|
|
6126
6137
|
try {
|
|
6127
6138
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cellinj-'));
|
|
@@ -6146,8 +6157,8 @@ total++;
|
|
|
6146
6157
|
}
|
|
6147
6158
|
|
|
6148
6159
|
// 1.9.400 회귀 (7번째 버그헌트 P1-B, UR-0105): verify-claim/optimism-check/honesty-check --json 에러가 구조화 JSON + 사람용 보존
|
|
6149
|
-
|
|
6150
|
-
|
|
6160
|
+
if (e2eShard.take('B(1.9.400)')) {
|
|
6161
|
+
total++;
|
|
6151
6162
|
let ok = false;
|
|
6152
6163
|
try {
|
|
6153
6164
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-antilazyjson-'));
|
|
@@ -6164,8 +6175,8 @@ total++;
|
|
|
6164
6175
|
}
|
|
6165
6176
|
|
|
6166
6177
|
// 1.9.401 회귀 (7번째 버그헌트 P1-C, UR-0106): 시크릿 FN — gitignore 부정(!) 처리 + placeholder substring 정밀화
|
|
6167
|
-
|
|
6168
|
-
|
|
6178
|
+
if (e2eShard.take('B(1.9.401)')) {
|
|
6179
|
+
total++;
|
|
6169
6180
|
let ok = false;
|
|
6170
6181
|
try {
|
|
6171
6182
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-secfn-'));
|
|
@@ -6187,8 +6198,8 @@ total++;
|
|
|
6187
6198
|
}
|
|
6188
6199
|
|
|
6189
6200
|
// 1.9.402 회귀 (7번째 버그헌트 P1-A 잔여, UR-0108): decision/lesson 텍스트 개행이 MD projection 위조 블록 주입 차단
|
|
6190
|
-
|
|
6191
|
-
|
|
6201
|
+
if (e2eShard.take('B(1.9.402)')) {
|
|
6202
|
+
total++;
|
|
6192
6203
|
let ok = false;
|
|
6193
6204
|
try {
|
|
6194
6205
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mdinj-'));
|
|
@@ -6209,8 +6220,8 @@ total++;
|
|
|
6209
6220
|
}
|
|
6210
6221
|
|
|
6211
6222
|
// 1.9.403 회귀 (7번째 버그헌트 P2, UR-0107): api-skill show/drop 의 missing-id/not-found 에러가 exit 1(성공 오판 방지)
|
|
6212
|
-
|
|
6213
|
-
|
|
6223
|
+
if (e2eShard.take('B(1.9.403)')) {
|
|
6224
|
+
total++;
|
|
6214
6225
|
let ok = false;
|
|
6215
6226
|
try {
|
|
6216
6227
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-apiskillexit-'));
|
|
@@ -6229,8 +6240,8 @@ total++;
|
|
|
6229
6240
|
}
|
|
6230
6241
|
|
|
6231
6242
|
// 1.9.404 회귀 (7번째 버그헌트 P2, UR-0105 잔여): reuse autodetect / creds check 의 --json 에러도 구조화 JSON
|
|
6232
|
-
|
|
6233
|
-
|
|
6243
|
+
if (e2eShard.take('B(1.9.404)')) {
|
|
6244
|
+
total++;
|
|
6234
6245
|
let ok = false;
|
|
6235
6246
|
try {
|
|
6236
6247
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-jsonleak2-'));
|
|
@@ -6252,8 +6263,8 @@ total++;
|
|
|
6252
6263
|
}
|
|
6253
6264
|
|
|
6254
6265
|
// 1.9.405 회귀수정 (8번째 버그헌트, UR-0109): 1.9.401 looksReal 가드 FP — 긴 서술형 placeholder 가 실키로 오탐되던 것 차단
|
|
6255
|
-
|
|
6256
|
-
|
|
6266
|
+
if (e2eShard.take('B(1.9.405)')) {
|
|
6267
|
+
total++;
|
|
6257
6268
|
let ok = false;
|
|
6258
6269
|
try {
|
|
6259
6270
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-phfp-'));
|
|
@@ -6274,8 +6285,8 @@ total++;
|
|
|
6274
6285
|
}
|
|
6275
6286
|
|
|
6276
6287
|
// 1.9.406 회귀 (8번째 버그헌트, UR-0110): rule/decision/lesson add 락 리팩터가 멱등성/정확성 보존 (skip 플래그 회귀가드)
|
|
6277
|
-
|
|
6278
|
-
|
|
6288
|
+
if (e2eShard.take('B(1.9.406)')) {
|
|
6289
|
+
total++;
|
|
6279
6290
|
let ok = false;
|
|
6280
6291
|
try {
|
|
6281
6292
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-lockrefactor-'));
|
|
@@ -6305,8 +6316,8 @@ total++;
|
|
|
6305
6316
|
}
|
|
6306
6317
|
|
|
6307
6318
|
// 1.9.407 회귀 (8번째 버그헌트, UR-0111): MCP feature_link safe-write tier + NaN --limit 가 결과 은폐 안 함
|
|
6308
|
-
|
|
6309
|
-
|
|
6319
|
+
if (e2eShard.take('B(1.9.407)')) {
|
|
6320
|
+
total++;
|
|
6310
6321
|
let ok = false;
|
|
6311
6322
|
try {
|
|
6312
6323
|
// ① feature_link tier safe-write (권한경계)
|
|
@@ -6329,8 +6340,8 @@ total++;
|
|
|
6329
6340
|
}
|
|
6330
6341
|
|
|
6331
6342
|
// 1.9.408 회귀 (8번째 버그헌트, UR-0112): CRLF/CR SKILL.md frontmatter 파싱 — Windows/Notepad 줄바꿈으로 meta 소실되던 skill install 복구
|
|
6332
|
-
|
|
6333
|
-
|
|
6343
|
+
if (e2eShard.take('B(1.9.408)')) {
|
|
6344
|
+
total++;
|
|
6334
6345
|
let ok = false;
|
|
6335
6346
|
try {
|
|
6336
6347
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-crlfskill-'));
|
|
@@ -6354,8 +6365,8 @@ total++;
|
|
|
6354
6365
|
// 1.9.409 회귀 (8번째 버그헌트, UR-0113): env encoding-check --apply 는 .sh/shebang 을 byte-exact 보존한다.
|
|
6355
6366
|
// 1.36.177 데이터 무결성 계약: .ps1 BOM 교체는 Windows 에서만 메타데이터 보존 writer 로 수행하고,
|
|
6356
6367
|
// POSIX 에서는 inode 교체가 ACL/xattr/ownership 을 잃을 수 있어 byte-exact no-op 한다.
|
|
6357
|
-
|
|
6358
|
-
|
|
6368
|
+
if (e2eShard.take('B(1.9.409/1.36.177)')) {
|
|
6369
|
+
total++;
|
|
6359
6370
|
let ok = false;
|
|
6360
6371
|
try {
|
|
6361
6372
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-bomsh-'));
|
|
@@ -6383,8 +6394,8 @@ total++;
|
|
|
6383
6394
|
// 그 폴백이 바로 이번에 고친 결함이다 — `decision add SNEAKY --path=` 가 cwd 에 쓰고 "✓ 기록됨" 을 냈다(실측).
|
|
6384
6395
|
// 플래그를 적었는데 값이 없으면 그건 오타다. 원래 목적(**raw TypeError 를 흘리지 않는다**)은 그대로 두고,
|
|
6385
6396
|
// "조용한 폴백" 대신 "사유를 말하는 거절" 을 단언한다. 그리고 값을 준 경우가 여전히 도는지 대조군으로 잰다.
|
|
6386
|
-
|
|
6387
|
-
|
|
6397
|
+
if (e2eShard.take('B(1.9.410/1.36.144)')) {
|
|
6398
|
+
total++;
|
|
6388
6399
|
let ok = false;
|
|
6389
6400
|
try {
|
|
6390
6401
|
const r = cp.spawnSync(process.execPath, [CLI, 'status', '--path'], { encoding: 'utf8', timeout: 15000 });
|
|
@@ -6402,8 +6413,8 @@ total++;
|
|
|
6402
6413
|
}
|
|
6403
6414
|
|
|
6404
6415
|
// 1.9.411 회귀 (8번째 버그헌트, UR-0115): lazy detect --auto-track 배치화가 다수 TODO 를 순차 ID 로 정확히 등록(동작 보존)
|
|
6405
|
-
|
|
6406
|
-
|
|
6416
|
+
if (e2eShard.take('B(1.9.411)')) {
|
|
6417
|
+
total++;
|
|
6407
6418
|
let ok = false;
|
|
6408
6419
|
try {
|
|
6409
6420
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-autotrack-'));
|
|
@@ -6426,8 +6437,8 @@ total++;
|
|
|
6426
6437
|
}
|
|
6427
6438
|
|
|
6428
6439
|
// 1.9.412 회귀 (6th외부평가 Opus P1, UR-0100): list-family 가 positional path 를 인식(조용한 cwd 오독 차단) + add/show 회귀 없음
|
|
6429
|
-
|
|
6430
|
-
|
|
6440
|
+
if (e2eShard.take('B(1.9.412)')) {
|
|
6441
|
+
total++;
|
|
6431
6442
|
let ok = false;
|
|
6432
6443
|
try {
|
|
6433
6444
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-pospath-'));
|
|
@@ -6451,8 +6462,8 @@ total++;
|
|
|
6451
6462
|
// 1.36.2 회귀 (클린룸 리뷰, UR-0184): feature add/show/link/impact 도 trailing positional path 인식 —
|
|
6452
6463
|
// 기존엔 add 가 모든 non-flag positional 을 NAME 으로 join → 경로가 이름에 흡수 + 비-프로젝트 cwd 에 stray .leerness scaffold(조용한 오독).
|
|
6453
6464
|
// fix: --path > path-like positional > cwd; add 의 NAME 은 _parseAddTitle 로 절단; 미초기화 dir 은 _requireInit 게이트(scaffold 대신 에러).
|
|
6454
|
-
|
|
6455
|
-
|
|
6465
|
+
if (e2eShard.take('B(1.36.2)')) {
|
|
6466
|
+
total++;
|
|
6456
6467
|
let ok = false;
|
|
6457
6468
|
try {
|
|
6458
6469
|
const target = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-featpos-t-'));
|
|
@@ -6488,8 +6499,8 @@ total++;
|
|
|
6488
6499
|
}
|
|
6489
6500
|
|
|
6490
6501
|
// 1.9.413 회귀 (6th외부평가 codex P2, UR-0101): action 명령 --json 구조화 출력 + 데이터 영속 + 사람용 보존
|
|
6491
|
-
|
|
6492
|
-
|
|
6502
|
+
if (e2eShard.take('B(1.9.413)')) {
|
|
6503
|
+
total++;
|
|
6493
6504
|
let ok = false;
|
|
6494
6505
|
try {
|
|
6495
6506
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-actionjson-'));
|
|
@@ -6513,8 +6524,8 @@ total++;
|
|
|
6513
6524
|
}
|
|
6514
6525
|
|
|
6515
6526
|
// 1.9.414 회귀 (UR-0119/0120): team review(메인 검수) — preview 가 분배 후 메인 검수 단계 표시, --no-review 시 생략
|
|
6516
|
-
|
|
6517
|
-
|
|
6527
|
+
if (e2eShard.take('B(1.9.414)')) {
|
|
6528
|
+
total++;
|
|
6518
6529
|
let ok = false;
|
|
6519
6530
|
try {
|
|
6520
6531
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-teamreview-'));
|
|
@@ -6537,8 +6548,8 @@ total++;
|
|
|
6537
6548
|
}
|
|
6538
6549
|
|
|
6539
6550
|
// 1.9.415 회귀 (9th 외부평가 Codex P1/Opus P2, UR-0121): handoff 보안 헤드라인 정직화 + scan/encoding --json + contract --json exit
|
|
6540
|
-
|
|
6541
|
-
|
|
6551
|
+
if (e2eShard.take('B(1.9.415)')) {
|
|
6552
|
+
total++;
|
|
6542
6553
|
let ok = false;
|
|
6543
6554
|
try {
|
|
6544
6555
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-honesty-'));
|
|
@@ -6572,8 +6583,8 @@ total++;
|
|
|
6572
6583
|
}
|
|
6573
6584
|
|
|
6574
6585
|
// 1.9.416 회귀 (9th 외부평가 Sonnet/Codex, UR-0122): add류 제목 경로흡수 차단 + 빈 입력 거부
|
|
6575
|
-
|
|
6576
|
-
|
|
6586
|
+
if (e2eShard.take('B(1.9.416)')) {
|
|
6587
|
+
total++;
|
|
6577
6588
|
let ok = false;
|
|
6578
6589
|
try {
|
|
6579
6590
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-addtitle-'));
|
|
@@ -6601,8 +6612,8 @@ total++;
|
|
|
6601
6612
|
}
|
|
6602
6613
|
|
|
6603
6614
|
// 1.9.417 회귀 (9th 외부평가 Opus, UR-0123): contract verify field 범용화 — ## Fields 섹션 불릿 누락 감지
|
|
6604
|
-
|
|
6605
|
-
|
|
6615
|
+
if (e2eShard.take('B(1.9.417)')) {
|
|
6616
|
+
total++;
|
|
6606
6617
|
let ok = false;
|
|
6607
6618
|
try {
|
|
6608
6619
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cfield-'));
|
|
@@ -6623,8 +6634,8 @@ total++;
|
|
|
6623
6634
|
}
|
|
6624
6635
|
|
|
6625
6636
|
// 1.35.11 (자체 contract 적대적 헌트 + codex 교차): 필드명 $ 정규식-앵커 FP — $scope/foo$bar 실존 필드를 항상 missing 오탐하던 버그. 이스케이프+식별자 룩어라운드로 수정. 정규식 안전 + no-FN 회귀 가드.
|
|
6626
|
-
|
|
6627
|
-
|
|
6637
|
+
if (e2eShard.take('B(1.35.11)')) {
|
|
6638
|
+
total++;
|
|
6628
6639
|
let ok = false;
|
|
6629
6640
|
try {
|
|
6630
6641
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cvdollar-'));
|
|
@@ -6661,8 +6672,8 @@ total++;
|
|
|
6661
6672
|
}
|
|
6662
6673
|
|
|
6663
6674
|
// 1.9.418 회귀 (9th 외부평가 Codex P2, UR-0121 잔여): health 보안 정직화 + status scope
|
|
6664
|
-
|
|
6665
|
-
|
|
6675
|
+
if (e2eShard.take('B(1.9.418)')) {
|
|
6676
|
+
total++;
|
|
6666
6677
|
let ok = false;
|
|
6667
6678
|
try {
|
|
6668
6679
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-hlabel-'));
|
|
@@ -6689,8 +6700,8 @@ total++;
|
|
|
6689
6700
|
// 1.30.1 회귀 (14th 외부리뷰 F1+F2): audit/handoff 보안요약이 커밋된 시크릿을 정직하게 노출.
|
|
6690
6701
|
// F1: audit 가 _collectSecretFindings 콘텐츠 스캔을 돌려 committed 시크릿을 failure 로 승격(scan secrets 와 일관) — gitignored 는 무영향(FP 0).
|
|
6691
6702
|
// F2: handoff 🔒 보안요약 섹션이 .env 없어도 committed 시크릿을 노출(envExists 단독 게이팅 제거).
|
|
6692
|
-
|
|
6693
|
-
|
|
6703
|
+
if (e2eShard.take('B(1.30.1)')) {
|
|
6704
|
+
total++;
|
|
6694
6705
|
let ok = false;
|
|
6695
6706
|
try {
|
|
6696
6707
|
const H = /[가-힣]/;
|
|
@@ -6731,8 +6742,8 @@ total++;
|
|
|
6731
6742
|
}
|
|
6732
6743
|
|
|
6733
6744
|
// 1.30.2 회귀 (#157 사용자명시, 하위 프로젝트 방향 — 외부AI+Claude 교차검토 → 방향 C): parent detect 가 상위 leerness 부모를 탐지(read-only) + handoff 헤드라인 노출 + 자동 적용 안 함.
|
|
6734
|
-
|
|
6735
|
-
|
|
6745
|
+
if (e2eShard.take('B(1.30.2)')) {
|
|
6746
|
+
total++;
|
|
6736
6747
|
let ok = false;
|
|
6737
6748
|
try {
|
|
6738
6749
|
const par = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-par-'));
|
|
@@ -6765,8 +6776,8 @@ total++;
|
|
|
6765
6776
|
}
|
|
6766
6777
|
|
|
6767
6778
|
// 1.30.3 회귀 (#158 사용자명시): parent adopt 게이트형 적용 — dry-run 기본(쓰기 0) + --apply(사용자 명시) 시에만 자식-로컬 참조 기록 + 자식 design-system.md 무변경(비파괴) + handoff 헤드라인 adopted 반영.
|
|
6768
|
-
|
|
6769
|
-
|
|
6779
|
+
if (e2eShard.take('B(1.30.3)')) {
|
|
6780
|
+
total++;
|
|
6770
6781
|
let ok = false;
|
|
6771
6782
|
try {
|
|
6772
6783
|
const par = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-adopt-'));
|
|
@@ -6800,8 +6811,8 @@ total++;
|
|
|
6800
6811
|
}
|
|
6801
6812
|
|
|
6802
6813
|
// 1.30.4 회귀 (#155 / 14th리뷰 F5+F6+F7): add류 cli-ux 일관성 — decision/lesson dedup + rule/lesson 빈입력 --json 구조화 + task/rule bogus subcommand 토큰 명시.
|
|
6803
|
-
|
|
6804
|
-
|
|
6814
|
+
if (e2eShard.take('B(1.30.4)')) {
|
|
6815
|
+
total++;
|
|
6805
6816
|
let ok = false;
|
|
6806
6817
|
try {
|
|
6807
6818
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-f567-'));
|
|
@@ -6835,8 +6846,8 @@ total++;
|
|
|
6835
6846
|
}
|
|
6836
6847
|
|
|
6837
6848
|
// 1.31.1 회귀 (UR-0010): install-safety 출력 영어 opt-in — en 한글 0 + ko 보존 + 셸-무관 가드(npx --yes/PowerShell/no npm_config prefix) 양 언어 보존.
|
|
6838
|
-
|
|
6839
|
-
|
|
6849
|
+
if (e2eShard.take('B(1.31.1)')) {
|
|
6850
|
+
total++;
|
|
6840
6851
|
let ok = false;
|
|
6841
6852
|
try {
|
|
6842
6853
|
const H = /[가-힣]/;
|
|
@@ -6853,8 +6864,8 @@ total++;
|
|
|
6853
6864
|
}
|
|
6854
6865
|
|
|
6855
6866
|
// 1.31.2 회귀 (UR-0010): constraints 영어화 — list/check 라벨 + 카탈로그 detailEn + suggestion 영어 / ko 보존 / 매칭(한국어 alias) 무회귀.
|
|
6856
|
-
|
|
6857
|
-
|
|
6867
|
+
if (e2eShard.take('B(1.31.2)')) {
|
|
6868
|
+
total++;
|
|
6858
6869
|
let ok = false;
|
|
6859
6870
|
try {
|
|
6860
6871
|
const H = /[가-힣]/;
|
|
@@ -6879,8 +6890,8 @@ total++;
|
|
|
6879
6890
|
}
|
|
6880
6891
|
|
|
6881
6892
|
// 1.31.3 회귀 (UR-0010): capabilities(권한·보안 표면) 영어화(라벨+카탈로그 descEn/optOutEn/noteEn+principles) + team reminder 본문 영어화 / ko 보존.
|
|
6882
|
-
|
|
6883
|
-
|
|
6893
|
+
if (e2eShard.take('B(1.31.3)')) {
|
|
6894
|
+
total++;
|
|
6884
6895
|
let ok = false;
|
|
6885
6896
|
try {
|
|
6886
6897
|
const H = /[가-힣]/;
|
|
@@ -6913,8 +6924,8 @@ total++;
|
|
|
6913
6924
|
}
|
|
6914
6925
|
|
|
6915
6926
|
// 1.32.1 회귀 (15th 외부리뷰 맹신X): constraints/parent --json 에러 구조화(C2) + parent adopt --json 에러경로 비공백(A1) + --select 무효 kind applied:false(A2).
|
|
6916
|
-
|
|
6917
|
-
|
|
6927
|
+
if (e2eShard.take('B(1.32.1)')) {
|
|
6928
|
+
total++;
|
|
6918
6929
|
let ok = false;
|
|
6919
6930
|
try {
|
|
6920
6931
|
const isJson = s => { try { JSON.parse(String(s).trim()); return true; } catch { return false; } };
|
|
@@ -6949,8 +6960,8 @@ total++;
|
|
|
6949
6960
|
|
|
6950
6961
|
// 1.32.3 회귀 (15th 리뷰 A3 방어심화): 적대적 부모 design-system content(가짜 ## 헤더 + 가짜 leerness 마커 + 백틱런)가
|
|
6951
6962
|
// parent adopt --apply 시 자식 inherited-from-parent.md 에 동적 코드펜스로 감싸져 marker/헤더 spoofing 차단되는지 + content 보존.
|
|
6952
|
-
|
|
6953
|
-
|
|
6963
|
+
if (e2eShard.take('B(1.32.3)')) {
|
|
6964
|
+
total++;
|
|
6954
6965
|
let ok = false;
|
|
6955
6966
|
try {
|
|
6956
6967
|
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-a3-'));
|
|
@@ -6980,8 +6991,8 @@ total++;
|
|
|
6980
6991
|
}
|
|
6981
6992
|
|
|
6982
6993
|
// 1.33.1 회귀 (verify-claim+gate 슬라이스 강화): ci init 생성 워크플로가 production-grade — leerness 버전 핀(재현성) + 최소권한 permissions + concurrency 취소.
|
|
6983
|
-
|
|
6984
|
-
|
|
6994
|
+
if (e2eShard.take('B(1.33.1)')) {
|
|
6995
|
+
total++;
|
|
6985
6996
|
let ok = false;
|
|
6986
6997
|
try {
|
|
6987
6998
|
const ver = require(path.resolve(__dirname, '..', 'package.json')).version;
|
|
@@ -7006,8 +7017,8 @@ total++;
|
|
|
7006
7017
|
}
|
|
7007
7018
|
|
|
7008
7019
|
// 1.33.2 회귀 (verify-claim+gate 슬라이스 강화): verify-claim --all — 모든 done 주장 일괄 검증. 거짓완료=exit 1(files-missing), 진실완료/빈프로젝트=exit 0, per-task 경로와 verdict 일치(맹신 X).
|
|
7009
|
-
|
|
7010
|
-
|
|
7020
|
+
if (e2eShard.take('B(1.33.2)')) {
|
|
7021
|
+
total++;
|
|
7011
7022
|
let ok = false;
|
|
7012
7023
|
try {
|
|
7013
7024
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-vca-'));
|
|
@@ -7046,8 +7057,8 @@ total++;
|
|
|
7046
7057
|
}
|
|
7047
7058
|
|
|
7048
7059
|
// 1.33.3 회귀 (verify-claim+CI gate 슬라이스 강화): gate --claims opt-in 6번째 체크(기본 5 무변경) + MCP leerness_verify_claim_all 라운드트립.
|
|
7049
|
-
|
|
7050
|
-
|
|
7060
|
+
if (e2eShard.take('B(1.33.3)')) {
|
|
7061
|
+
total++;
|
|
7051
7062
|
let ok = false;
|
|
7052
7063
|
try {
|
|
7053
7064
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-g3-'));
|
|
@@ -7086,8 +7097,8 @@ total++;
|
|
|
7086
7097
|
}
|
|
7087
7098
|
|
|
7088
7099
|
// 1.34.1 회귀 (16th리뷰 정직화 실증): gate --claims 정밀성 가치 — 워크스페이스가 깨끗(lazy detect 0 finding)한데 콘텐츠-레벨 거짓(부풀린 테스트 카운트)인 경우, 기본 5체크는 통과(exit 0)하고 --claims 만 차단(exit 1). 이 판별 케이스가 깨지면(기본도 잡거나 --claims가 못 잡으면) --claims 의 차별 가치가 사라진 것이므로 가드.
|
|
7089
|
-
|
|
7090
|
-
|
|
7100
|
+
if (e2eShard.take('B(1.34.1)')) {
|
|
7101
|
+
total++;
|
|
7091
7102
|
let ok = false;
|
|
7092
7103
|
try {
|
|
7093
7104
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-disc-'));
|
|
@@ -7124,8 +7135,8 @@ total++;
|
|
|
7124
7135
|
// stubFiles=[] → implementationSubstance=true, gitApplicable 은 files>0 요구 → skip.
|
|
7125
7136
|
// 전 판정이 공허참이라 "코드도 테스트도 없이 done" 이 ok:true/exit 0 이었고, 단조성이 뒤집혀 있었다
|
|
7126
7137
|
// (파일명을 적어 검증 가능하게 쓸수록 FAIL, 날조할수록 PASS). 판별 케이스로 고정한다.
|
|
7127
|
-
|
|
7128
|
-
|
|
7138
|
+
if (e2eShard.take('B(1.36.82)')) {
|
|
7139
|
+
total++;
|
|
7129
7140
|
let ok = false;
|
|
7130
7141
|
try {
|
|
7131
7142
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-vac-'));
|
|
@@ -7185,8 +7196,8 @@ total++;
|
|
|
7185
7196
|
|
|
7186
7197
|
// 1.36.82 회귀 (High #2): `gate --require-referee <id>` — 툴이 사용자에게 안내하면서 gate 는 읽지도 않아
|
|
7187
7198
|
// 조용히 exit 0 이던 플래그. 이제 실제 게이팅한다(미존재/미증명/무효 거부 + 검증기가 현재 상태를 거부하면 실패).
|
|
7188
|
-
|
|
7189
|
-
|
|
7199
|
+
if (e2eShard.take('B(1.36.82) #2')) {
|
|
7200
|
+
total++;
|
|
7190
7201
|
let ok = false;
|
|
7191
7202
|
try {
|
|
7192
7203
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-refgate-'));
|
|
@@ -7232,8 +7243,8 @@ total++;
|
|
|
7232
7243
|
}
|
|
7233
7244
|
|
|
7234
7245
|
// 1.35.10 (자체 gate 적대적 헌트): gate 를 "보안 가드레일"로 검증 — 8-프로브 헌트(FP 4 + FN 3 + by-design 1) 결과 제품 결함 0 확인 후, 유일 커버리지 갭(gate 레벨 보안 동작)을 회귀 가드로 고정. 커밋된 실키 → 차단(exit 1, scan secrets step), 정상 .env.example placeholder → 오탐 없이 통과(exit 0). scan-step 리팩터가 가드레일 보안을 조용히 무너뜨리는 회귀 차단.
|
|
7235
|
-
|
|
7236
|
-
|
|
7246
|
+
if (e2eShard.take('B(1.35.10)')) {
|
|
7247
|
+
total++;
|
|
7237
7248
|
let ok = false;
|
|
7238
7249
|
try {
|
|
7239
7250
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-gatesec-'));
|
|
@@ -7257,8 +7268,8 @@ total++;
|
|
|
7257
7268
|
}
|
|
7258
7269
|
|
|
7259
7270
|
// 1.9.430 (10th 외부평가 UR-0130): health 보안 CRITICAL(커밋 시크릿)은 --strict 없이도 exit 1(CI 게이트). 클린은 exit 0.
|
|
7260
|
-
|
|
7261
|
-
|
|
7271
|
+
if (e2eShard.take('B(1.9.430)')) {
|
|
7272
|
+
total++;
|
|
7262
7273
|
let ok = false;
|
|
7263
7274
|
try {
|
|
7264
7275
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-hexit-'));
|
|
@@ -7274,8 +7285,8 @@ total++;
|
|
|
7274
7285
|
}
|
|
7275
7286
|
|
|
7276
7287
|
// 1.9.435 (11th 외부평가 Codex P2, UR-0137): agents dispatch task 에 flag 값(--to 의 codex) 흡수 금지.
|
|
7277
|
-
|
|
7278
|
-
|
|
7288
|
+
if (e2eShard.take('B(1.9.435/1.35.6)')) {
|
|
7289
|
+
total++;
|
|
7279
7290
|
let ok = false;
|
|
7280
7291
|
let d = null;
|
|
7281
7292
|
let fbin = null;
|
|
@@ -7318,8 +7329,8 @@ total++;
|
|
|
7318
7329
|
}
|
|
7319
7330
|
|
|
7320
7331
|
// 1.9.439 (10th 외부평가 Codex P1, UR-0135): drift --auto-fix --json 은 dirty WS 에서도 stdout 순수 JSON.
|
|
7321
|
-
|
|
7322
|
-
|
|
7332
|
+
if (e2eShard.take('B(1.9.439)')) {
|
|
7333
|
+
total++;
|
|
7323
7334
|
let ok = false;
|
|
7324
7335
|
try {
|
|
7325
7336
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-djson-'));
|
|
@@ -7336,8 +7347,8 @@ total++;
|
|
|
7336
7347
|
}
|
|
7337
7348
|
|
|
7338
7349
|
// 1.9.440 (12th 외부평가 Opus P2): 시크릿 스캐너 prefix 패턴(AWS/GitHub)도 placeholder 가드 — .env.example 더미는 미탐, 진짜는 탐지.
|
|
7339
|
-
|
|
7340
|
-
|
|
7350
|
+
if (e2eShard.take('B(1.9.440)')) {
|
|
7351
|
+
total++;
|
|
7341
7352
|
let ok = false;
|
|
7342
7353
|
try {
|
|
7343
7354
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-scanph-'));
|
|
@@ -7356,8 +7367,8 @@ total++;
|
|
|
7356
7367
|
}
|
|
7357
7368
|
|
|
7358
7369
|
// 1.9.442 (12th 외부평가 Sonnet P1, UR-0141): task 계열 positional path — 다른 cwd 에서 실행해도 positional 경로에 저장(cwd 오염 차단).
|
|
7359
|
-
|
|
7360
|
-
|
|
7370
|
+
if (e2eShard.take('B(1.9.442)')) {
|
|
7371
|
+
total++;
|
|
7361
7372
|
let ok = false;
|
|
7362
7373
|
try {
|
|
7363
7374
|
const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-taskpos-'));
|
|
@@ -7376,8 +7387,8 @@ total++;
|
|
|
7376
7387
|
}
|
|
7377
7388
|
|
|
7378
7389
|
// 1.9.443 (GPT-5.5 전략리뷰 §6.3, UR-0153): evidence-first 완료 게이트 — state 워크플로에서 completion_claim_allowed 파생.
|
|
7379
|
-
|
|
7380
|
-
|
|
7390
|
+
if (e2eShard.take('B(1.9.443)')) {
|
|
7391
|
+
total++;
|
|
7381
7392
|
let ok = false;
|
|
7382
7393
|
try {
|
|
7383
7394
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cca-'));
|
|
@@ -7398,8 +7409,8 @@ total++;
|
|
|
7398
7409
|
}
|
|
7399
7410
|
|
|
7400
7411
|
// 1.9.444 (GPT-5.5 전략리뷰 §6.7, UR-0152): ci init — PR gate 워크플로 생성(멱등).
|
|
7401
|
-
|
|
7402
|
-
|
|
7412
|
+
if (e2eShard.take('B(1.9.444)')) {
|
|
7413
|
+
total++;
|
|
7403
7414
|
let ok = false;
|
|
7404
7415
|
try {
|
|
7405
7416
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ci-'));
|
|
@@ -7419,8 +7430,8 @@ total++;
|
|
|
7419
7430
|
}
|
|
7420
7431
|
|
|
7421
7432
|
// 1.9.445 (UR-0151): add-family(decision/lesson/rule) positional path — 다른 cwd 에서 실행해도 positional 경로에 저장(cwd 오염 차단).
|
|
7422
|
-
|
|
7423
|
-
|
|
7433
|
+
if (e2eShard.take('B(1.9.445)')) {
|
|
7434
|
+
total++;
|
|
7424
7435
|
let ok = false;
|
|
7425
7436
|
try {
|
|
7426
7437
|
const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-addpos-'));
|
|
@@ -7446,8 +7457,8 @@ total++;
|
|
|
7446
7457
|
|
|
7447
7458
|
// 1.25.1 (22nd 버그헌트 → i18n 행위 회귀 가드, UR-0010): --language en 런타임 렌더가 실제로 영어인지 + ko 기본 보존 + --language 값이 positional 로 누출 안 되는지 행위로 검증.
|
|
7448
7459
|
// 소스가드(문자열 존재)만으로는 1.23.0 "session close 완전 영어" 과장(런타임 한글 누출)을 못 잡았던 공백을 e2e 로 보강(defense-in-depth).
|
|
7449
|
-
|
|
7450
|
-
|
|
7460
|
+
if (e2eShard.take('B(1.25.1/1.25.2/1.27.2/1.28.2/1.29.1/1.29.2/1.29.3/1.29.4/1.30.5)')) {
|
|
7461
|
+
total++;
|
|
7451
7462
|
let ok = false;
|
|
7452
7463
|
try {
|
|
7453
7464
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-i18n-'));
|
|
@@ -7576,8 +7587,8 @@ total++;
|
|
|
7576
7587
|
}
|
|
7577
7588
|
|
|
7578
7589
|
// 1.26.1 (13번째 외부리뷰 P2 회귀가드): 개인키파일 스캔 FN + DB placeholder FP + retro --json NaN 행위 가드.
|
|
7579
|
-
|
|
7580
|
-
|
|
7590
|
+
if (e2eShard.take('B(1.26.1)')) {
|
|
7591
|
+
total++;
|
|
7581
7592
|
let ok = false;
|
|
7582
7593
|
try {
|
|
7583
7594
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rev13-'));
|
|
@@ -7609,8 +7620,8 @@ total++;
|
|
|
7609
7620
|
}
|
|
7610
7621
|
|
|
7611
7622
|
// 1.27.1 (13번째 외부리뷰 정직성 후속 회귀가드): audit 미초기화 모순출력 차단 + verify-claim no-parse 정직표기 (양방향 무회귀).
|
|
7612
|
-
|
|
7613
|
-
|
|
7623
|
+
if (e2eShard.take('B(1.27.1)')) {
|
|
7624
|
+
total++;
|
|
7614
7625
|
let ok = false;
|
|
7615
7626
|
try {
|
|
7616
7627
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rev13b-'));
|
|
@@ -7644,8 +7655,8 @@ total++;
|
|
|
7644
7655
|
}
|
|
7645
7656
|
|
|
7646
7657
|
// 1.35.7 (UR-0013, GPT5.5pro 평가): declared-pass 부풀림 게이팅 — 주장(3/3) vs 실행(2/2) → exit 1 (human/--json/--all 3경로) + spec 리포터 파싱(패턴 6) + 실행≥주장 무벌점.
|
|
7647
|
-
|
|
7648
|
-
|
|
7658
|
+
if (e2eShard.take('B(1.35.7)')) {
|
|
7659
|
+
total++;
|
|
7649
7660
|
let ok = false;
|
|
7650
7661
|
try {
|
|
7651
7662
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-dpm-'));
|
|
@@ -7687,8 +7698,8 @@ total++;
|
|
|
7687
7698
|
|
|
7688
7699
|
// 1.36.1 회귀 (클린룸 리뷰 FN): 상태파일 JSON 무결성 — 손상 .leerness/*.json 을 audit(warning)/health(degraded)/check(exit 1) 가 표면화.
|
|
7689
7700
|
// 배경: 그레이스풀 폴백이 깨진 상태 JSON 을 "healthy"/exit 0 으로 감추던 false-negative(health/doctor/check 전부). 클린(유효 JSON)엔 무오탐.
|
|
7690
|
-
|
|
7691
|
-
|
|
7701
|
+
if (e2eShard.take('B(1.36.1)')) {
|
|
7702
|
+
total++;
|
|
7692
7703
|
let ok = false;
|
|
7693
7704
|
try {
|
|
7694
7705
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-si-'));
|
|
@@ -7720,8 +7731,8 @@ total++;
|
|
|
7720
7731
|
}
|
|
7721
7732
|
|
|
7722
7733
|
// 1.36.51 (사용자 요청 UR-0061): clarify 모호성 질문 + preview 승인 워크플로 — CLI 라운드트립
|
|
7723
|
-
|
|
7724
|
-
|
|
7734
|
+
if (e2eShard.take('B(1.36.51)')) {
|
|
7735
|
+
total++;
|
|
7725
7736
|
let ok = false;
|
|
7726
7737
|
try {
|
|
7727
7738
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-clarify-'));
|
|
@@ -7754,8 +7765,8 @@ total++;
|
|
|
7754
7765
|
}
|
|
7755
7766
|
|
|
7756
7767
|
// 1.36.53 (사용자 요청 UR-0062): tech 프로필 — 언어·서비스 감지/이력 + 그래프 🛠 탭(스위치 제거·가이드) CLI 라운드트립
|
|
7757
|
-
|
|
7758
|
-
|
|
7768
|
+
if (e2eShard.take('B(1.36.53)')) {
|
|
7769
|
+
total++;
|
|
7759
7770
|
let ok = false;
|
|
7760
7771
|
try {
|
|
7761
7772
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tech-'));
|
|
@@ -7782,8 +7793,8 @@ total++;
|
|
|
7782
7793
|
}
|
|
7783
7794
|
|
|
7784
7795
|
// 1.36.59 (외부감사 F-05 1회차): en init 의 지시-레이어 5종은 한글 0 + ko 완전 보존
|
|
7785
|
-
|
|
7786
|
-
|
|
7796
|
+
if (e2eShard.take('B(1.36.59)')) {
|
|
7797
|
+
total++;
|
|
7787
7798
|
let ok = false;
|
|
7788
7799
|
const _dirs = [];
|
|
7789
7800
|
try {
|
|
@@ -7816,8 +7827,8 @@ total++;
|
|
|
7816
7827
|
}
|
|
7817
7828
|
|
|
7818
7829
|
// 1.36.65 (외부감사 F-08): no-op 재설치 — 변경 없으면 백업/아카이브/타임스탬프 무증식, 변경·구버전·force 는 정상 설치
|
|
7819
|
-
|
|
7820
|
-
|
|
7830
|
+
if (e2eShard.take('B(1.36.65)')) {
|
|
7831
|
+
total++;
|
|
7821
7832
|
let ok = false;
|
|
7822
7833
|
const _d = [];
|
|
7823
7834
|
try {
|
|
@@ -7848,8 +7859,8 @@ total++;
|
|
|
7848
7859
|
}
|
|
7849
7860
|
|
|
7850
7861
|
// 1.36.68 (8차 헌트 F14 재구현): 마감 보고/JSON 에 미승인 미리보기 노출 — 텍스트·JSON 양 모드 + 승인 후 소거
|
|
7851
|
-
|
|
7852
|
-
|
|
7862
|
+
if (e2eShard.take('B(1.36.68)')) {
|
|
7863
|
+
total++;
|
|
7853
7864
|
let ok = false;
|
|
7854
7865
|
const _d = [];
|
|
7855
7866
|
try {
|
|
@@ -7878,8 +7889,8 @@ total++;
|
|
|
7878
7889
|
}
|
|
7879
7890
|
|
|
7880
7891
|
// 1.36.69 (성장 한계 클래스): whats-new --json 기본 상한 — AI 컨텍스트 보호 + 정직한 절단 표기 + --all/--limit 옵트인
|
|
7881
|
-
|
|
7882
|
-
|
|
7892
|
+
if (e2eShard.take('B(1.36.69)')) {
|
|
7893
|
+
total++;
|
|
7883
7894
|
let ok = false;
|
|
7884
7895
|
try {
|
|
7885
7896
|
const R = (a) => cp.spawnSync(process.execPath, [CLI, 'whats-new', ...a], { encoding: 'utf8', timeout: 25000, maxBuffer: 32 * 1024 * 1024 });
|
|
@@ -7935,8 +7946,8 @@ total++;
|
|
|
7935
7946
|
}
|
|
7936
7947
|
|
|
7937
7948
|
// 1.36.70 (성장 한계 클래스): migrate 보고서 상한 — 큰 버전 점프에서 ~88KB 폭주하던 AI must re-read 를 목록별 캡 + "외 N개" 정직 표기
|
|
7938
|
-
|
|
7939
|
-
|
|
7949
|
+
if (e2eShard.take('B(1.36.70)')) {
|
|
7950
|
+
total++;
|
|
7940
7951
|
let ok = false;
|
|
7941
7952
|
const _d = [];
|
|
7942
7953
|
try {
|
|
@@ -7966,8 +7977,8 @@ total++;
|
|
|
7966
7977
|
}
|
|
7967
7978
|
|
|
7968
7979
|
// 1.36.71 (성장 한계 클래스 3): retro/insights --json data.rows 상한 — 전 task row 임베드로 무한 성장하던 것 (집계는 전량 기준 유지)
|
|
7969
|
-
|
|
7970
|
-
|
|
7980
|
+
if (e2eShard.take('B(1.36.71)')) {
|
|
7981
|
+
total++;
|
|
7971
7982
|
let ok = false;
|
|
7972
7983
|
const _d = [];
|
|
7973
7984
|
try {
|
|
@@ -7998,8 +8009,8 @@ total++;
|
|
|
7998
8009
|
}
|
|
7999
8010
|
|
|
8000
8011
|
// 1.36.72 (성장 한계 클래스 4): task list --json 상한 100 + lint 거짓 PASS(F16) 차단
|
|
8001
|
-
|
|
8002
|
-
|
|
8012
|
+
if (e2eShard.take('B(1.36.72)')) {
|
|
8013
|
+
total++;
|
|
8003
8014
|
let ok = false;
|
|
8004
8015
|
const _d = [];
|
|
8005
8016
|
try {
|
|
@@ -8026,8 +8037,8 @@ total++;
|
|
|
8026
8037
|
}
|
|
8027
8038
|
|
|
8028
8039
|
// 1.36.73 (8차 헌트 F12/F9 종결): skills-lock = 디스크 실재 + KO-잔존 이월 안내
|
|
8029
|
-
|
|
8030
|
-
|
|
8040
|
+
if (e2eShard.take('B(1.36.73)')) {
|
|
8041
|
+
total++;
|
|
8031
8042
|
let ok = false;
|
|
8032
8043
|
const _d = [];
|
|
8033
8044
|
try {
|
|
@@ -8065,8 +8076,8 @@ total++;
|
|
|
8065
8076
|
}
|
|
8066
8077
|
|
|
8067
8078
|
// 1.36.74 (9차 헌트): state 스키마 fail-closed · restore 중복 거부 · api-skill skeleton 충돌 · MCP 마지막 줄 · 손상 표시 · --json 계약
|
|
8068
|
-
|
|
8069
|
-
|
|
8079
|
+
if (e2eShard.take('B(1.36.74)')) {
|
|
8080
|
+
total++;
|
|
8070
8081
|
let ok = false;
|
|
8071
8082
|
const _d = [];
|
|
8072
8083
|
try {
|
|
@@ -8140,8 +8151,8 @@ total++;
|
|
|
8140
8151
|
}
|
|
8141
8152
|
|
|
8142
8153
|
// 1.36.75 (UR-0066): 디자인 시안 워크플로 — preview mockup 스캐폴드 + 덮어쓰기 보호 + XSS 이스케이프 + 디자인 감지 + --mockup 첨부
|
|
8143
|
-
|
|
8144
|
-
|
|
8154
|
+
if (e2eShard.take('B(1.36.75)')) {
|
|
8155
|
+
total++;
|
|
8145
8156
|
let ok = false;
|
|
8146
8157
|
const _d = [];
|
|
8147
8158
|
try {
|
|
@@ -8200,8 +8211,8 @@ total++;
|
|
|
8200
8211
|
// selftest 는 checkDoneTransition 을 in-process 로만 불러, `task update --status done` 배선을 한 줄도
|
|
8201
8212
|
// 실행하지 않았다 — 게이트를 통째로 죽여도 341/341 그린이었다(변이로 실측). 실제 CLI 경로로 못 박는다.
|
|
8202
8213
|
// 특히 done 을 기록하는 경로가 taskUpdate 하나가 아니다: task sync --from(TodoWrite 왕복)이 우회했다.
|
|
8203
|
-
|
|
8204
|
-
|
|
8214
|
+
if (e2eShard.take('B(1.36.88) #2')) {
|
|
8215
|
+
total++;
|
|
8205
8216
|
let ok = false;
|
|
8206
8217
|
const _d = [];
|
|
8207
8218
|
const R = (args, cwd) => { const r = cp.spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', timeout: 180000, cwd }); return { s: r.status, o: ((r.stdout || '') + (r.stderr || '')).trim() }; };
|
|
@@ -8272,8 +8283,8 @@ total++;
|
|
|
8272
8283
|
// 1.36.89 (P-0007, 사용자 승인 범위=제안+확인후실행): agents route 난이도 라우팅.
|
|
8273
8284
|
// 판별 케이스를 함께 넣는다 — 거부 사유(code)까지 단언하지 않으면 여러 거부 조건이 서로를 가려
|
|
8274
8285
|
// 하나를 죽여도 다른 하나가 막는다(1.36.88 에서 변이 6/8 로 실측한 함정).
|
|
8275
|
-
|
|
8276
|
-
|
|
8286
|
+
if (e2eShard.take('B(1.36.89)')) {
|
|
8287
|
+
total++;
|
|
8277
8288
|
let ok = false;
|
|
8278
8289
|
const _d = [];
|
|
8279
8290
|
const R = (args, cwd) => { const r = cp.spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', timeout: 120000, cwd }); return { s: r.status, o: ((r.stdout || '') + (r.stderr || '')).trim() }; };
|
|
@@ -8401,8 +8412,8 @@ total++;
|
|
|
8401
8412
|
// 1.36.90 (P-0006, 사용자 승인 범위=대시보드만): 읽기 전용 대시보드 + **넘지 않기로 한 경계**를 가드로 못 박는다.
|
|
8402
8413
|
// 경계가 문서에만 있으면 다음 라운드에 조용히 넘는다 — .leerness 실행코드 0 · 루트 실행기 미생성 ·
|
|
8403
8414
|
// 게이트 미판정 · 정적 leerness.html 존속을 테스트가 강제한다.
|
|
8404
|
-
|
|
8405
|
-
|
|
8415
|
+
if (e2eShard.take('B(1.36.90)')) {
|
|
8416
|
+
total++;
|
|
8406
8417
|
let ok = false;
|
|
8407
8418
|
const _d = [];
|
|
8408
8419
|
const R = (args, cwd) => { const r = cp.spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', timeout: 120000, cwd }); return { s: r.status, o: ((r.stdout || '') + (r.stderr || '')).trim() }; };
|
|
@@ -8483,8 +8494,8 @@ total++;
|
|
|
8483
8494
|
// 1.36.91: **설치됨 ≠ 사용가능** — 인증 축. 확인 가능한 provider 만 판정하고 나머지는 unknown 으로 남긴다.
|
|
8484
8495
|
// unknown 을 차단 사유로 쓰면 확인 수단이 없는 대부분의 CLI 에서 고위험 작업이 전부 잠긴다(false-BLOCK) —
|
|
8485
8496
|
// "확실히 아닌 것"만 막는다는 비대칭이 이 축의 핵심이고, 그걸 테스트가 못 박는다.
|
|
8486
|
-
|
|
8487
|
-
|
|
8497
|
+
if (e2eShard.take('B(1.36.91)')) {
|
|
8498
|
+
total++;
|
|
8488
8499
|
let ok = false;
|
|
8489
8500
|
const _d = [];
|
|
8490
8501
|
const R = (args) => { const r = cp.spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', timeout: 120000 }); return { s: r.status, o: ((r.stdout || '') + (r.stderr || '')).trim() }; };
|
|
@@ -8600,8 +8611,8 @@ total++;
|
|
|
8600
8611
|
// 1.36.92 (신규표면 헌트 확정 5건): 동의 없는 외부 CLI 실행 · 사유 오귀인 · 다국어 과소평가 ·
|
|
8601
8612
|
// 위치경로 무시 · Bearer 토큰 미마스킹 · 손상 토글 무경고.
|
|
8602
8613
|
// **PATH shim** 으로 "프로세스를 띄우지 않는다"를 증명한다 — 종전 테스트에는 그걸 증명할 수단이 아예 없었다.
|
|
8603
|
-
|
|
8604
|
-
|
|
8614
|
+
if (e2eShard.take('B(1.36.92)')) {
|
|
8615
|
+
total++;
|
|
8605
8616
|
let ok = false;
|
|
8606
8617
|
const _d = [];
|
|
8607
8618
|
let N1 = false, N2 = false, N3 = false, N4 = false, N5 = false;
|
|
@@ -8689,8 +8700,8 @@ total++;
|
|
|
8689
8700
|
// 1.36.93 (헌트 이월 2건): ① MCP 래퍼 타임아웃이 probe 예산보다 짧아 정직하게 고친 사용자가 done 을 못 하고,
|
|
8690
8701
|
// 응답이 `(no output)` 이라 진단 단서가 0 이었다 ② `plan add --status verified` 가 정규화 없이 저장돼
|
|
8691
8702
|
// lazy detect 는 done 으로 세는데 verify-claim/gate 는 그 행을 아예 보지 않았다(검증기가 못 보는 완료 행).
|
|
8692
|
-
|
|
8693
|
-
|
|
8703
|
+
if (e2eShard.take('B(1.36.93)')) {
|
|
8704
|
+
total++;
|
|
8694
8705
|
let ok = false;
|
|
8695
8706
|
const _d = [];
|
|
8696
8707
|
const R = (args, env) => { const r = cp.spawnSync(process.execPath, [CLI, ...args], { encoding: 'utf8', timeout: 300000, env: env || process.env }); return { s: r.status, o: ((r.stdout || '') + (r.stderr || '')).trim() }; };
|
|
@@ -8755,8 +8766,8 @@ total++;
|
|
|
8755
8766
|
// 검수가 9회에 걸쳐 사용자 데이터 삭제/변형 반례를 계속 찾아냈다 — 문자열로는 "우리가 내보낸 줄"과
|
|
8756
8767
|
// "사용자가 그 줄을 적은 것"을 구분할 수 없기 때문이다(바이트가 같다). 근본 해결은 생성 영역 마커
|
|
8757
8768
|
// (provenance)이고 별도 라운드로 다룬다. 여기서는 **삭제하지 않는 변경만** 출하한다.
|
|
8758
|
-
|
|
8759
|
-
|
|
8769
|
+
if (e2eShard.take('B(1.36.95)')) {
|
|
8770
|
+
total++;
|
|
8760
8771
|
let ok = false;
|
|
8761
8772
|
const _d = [];
|
|
8762
8773
|
let T1 = false, T2 = false, T3 = false;
|
|
@@ -8913,8 +8924,8 @@ total++;
|
|
|
8913
8924
|
// 이 블록이 잡아야 하는 것(전부 변이로 확인): 원시 HTML 통과 · javascript:/data: 링크 · 표/제목/링크텍스트
|
|
8914
8925
|
// 미이스케이프 · 코드펜스 안 마크다운 해석 · 코드스팬 자리표시자가 본문 숫자를 삼킴 · 시크릿 미마스킹 ·
|
|
8915
8926
|
// 누적 로그 방향 뒤집힘 · 줄경계/코드포인트 절단 붕괴 · 전체 상한에서 조용한 누락.
|
|
8916
|
-
|
|
8917
|
-
|
|
8927
|
+
if (e2eShard.take('B(1.36.96)')) {
|
|
8928
|
+
total++;
|
|
8918
8929
|
let ok = false;
|
|
8919
8930
|
const _d = [];
|
|
8920
8931
|
const dbg = {};
|
|
@@ -9369,8 +9380,8 @@ total++;
|
|
|
9369
9380
|
// 자연 경합 19.5% · 읽기 실패를 손상으로 오인 · <ID> 무시 전역 삭제 · 되돌릴 수 없는 게이트 무장해제 ·
|
|
9370
9381
|
// 안내가 --path 를 잃어 애먼 프로젝트 파괴). 손으로 고치는 회복은 그 다섯이 전부 없고 되돌리면 게이트도
|
|
9371
9382
|
// 살아난다 — 그래서 도구가 할 일은 대신 지워 주는 게 아니라 **어디를 어떻게 고치면 되는지 말하는 것**이다.
|
|
9372
|
-
|
|
9373
|
-
|
|
9383
|
+
if (e2eShard.take('B(1.36.94/1.36.185)')) {
|
|
9384
|
+
total++;
|
|
9374
9385
|
let ok = false;
|
|
9375
9386
|
const _d = [];
|
|
9376
9387
|
let E1 = false, E2 = false, E3 = false, E4 = false, E5 = false;
|
|
@@ -9632,8 +9643,8 @@ s.listen(0,'127.0.0.1',()=>fs.writeFileSync(process.argv[3],String(s.address().p
|
|
|
9632
9643
|
}
|
|
9633
9644
|
|
|
9634
9645
|
// 1.36.76 (9차 헌트 이월 3건 종결): 손상 아카이브 복원 거부 · MCP read-only 무변형 · 긴 파생 id 안전화
|
|
9635
|
-
|
|
9636
|
-
|
|
9646
|
+
if (e2eShard.take('B(1.36.76)')) {
|
|
9647
|
+
total++;
|
|
9637
9648
|
let ok = false;
|
|
9638
9649
|
const _d = [];
|
|
9639
9650
|
try {
|
|
@@ -9671,8 +9682,8 @@ total++;
|
|
|
9671
9682
|
}
|
|
9672
9683
|
|
|
9673
9684
|
// 1.36.77 (UR-0061/0066 MCP 완결): leerness_clarify + leerness_preview 도구 — MCP 전용 에이전트도 질문·시안 승인 흐름
|
|
9674
|
-
|
|
9675
|
-
|
|
9685
|
+
if (e2eShard.take('B(1.36.77)')) {
|
|
9686
|
+
total++;
|
|
9676
9687
|
let ok = false;
|
|
9677
9688
|
const _d = [];
|
|
9678
9689
|
try {
|
|
@@ -9706,8 +9717,8 @@ total++;
|
|
|
9706
9717
|
}
|
|
9707
9718
|
|
|
9708
9719
|
// 1.36.78 (10차 헌트 확정 7건): team 형상/계약 · constraints 검증/손상 · agents recommend · 빈 contract
|
|
9709
|
-
|
|
9710
|
-
|
|
9720
|
+
if (e2eShard.take('B(1.36.78)')) {
|
|
9721
|
+
total++;
|
|
9711
9722
|
let ok = false;
|
|
9712
9723
|
const _d = [];
|
|
9713
9724
|
try {
|
|
@@ -9775,8 +9786,8 @@ total++;
|
|
|
9775
9786
|
}
|
|
9776
9787
|
|
|
9777
9788
|
// 1.36.79 (실 프로젝트 60개 도그푸딩 P1 5건): progress 행 보존 · handoff --json 순수 · parent path · tech 언어 순위 · audit=integrity 정합
|
|
9778
|
-
|
|
9779
|
-
|
|
9789
|
+
if (e2eShard.take('B(1.36.79)')) {
|
|
9790
|
+
total++;
|
|
9780
9791
|
let ok = false;
|
|
9781
9792
|
const _d = [];
|
|
9782
9793
|
try {
|
|
@@ -9862,8 +9873,8 @@ total++;
|
|
|
9862
9873
|
}
|
|
9863
9874
|
|
|
9864
9875
|
// 1.36.80 (P-0001 검증기 캘리브레이션): 검증기를 신뢰하기 전에 탐지력을 실행으로 증명 — 적대적 FP 프로브 포함
|
|
9865
|
-
|
|
9866
|
-
|
|
9876
|
+
if (e2eShard.take('B(1.36.80)')) {
|
|
9877
|
+
total++;
|
|
9867
9878
|
let ok = false;
|
|
9868
9879
|
const _d = [];
|
|
9869
9880
|
try {
|
|
@@ -9950,8 +9961,8 @@ total++;
|
|
|
9950
9961
|
}
|
|
9951
9962
|
|
|
9952
9963
|
// 1.36.80 (UR-0067 라이브 미리보기 + P-0003 사다리 + 기본 스킬 정리)
|
|
9953
|
-
|
|
9954
|
-
|
|
9964
|
+
if (e2eShard.take('B(1.36.80) #2')) {
|
|
9965
|
+
total++;
|
|
9955
9966
|
let ok = false;
|
|
9956
9967
|
const _d = [];
|
|
9957
9968
|
try {
|
|
@@ -9994,7 +10005,7 @@ total++;
|
|
|
9994
10005
|
}
|
|
9995
10006
|
|
|
9996
10007
|
// ── 1.36.97 (P-0011): 렌즈 3축 심화 — CLI 표면 행위. selftest 는 함수만 보므로 실제 렌더/로케일/완료검증 인라인은 여기서 잰다.
|
|
9997
|
-
{
|
|
10008
|
+
if (e2eShard.take('B(1.36.97/P-0011)')) {
|
|
9998
10009
|
total++;
|
|
9999
10010
|
let ok = false; const dbg = {};
|
|
10000
10011
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-lens97-'));
|
|
@@ -10049,7 +10060,7 @@ total++;
|
|
|
10049
10060
|
|
|
10050
10061
|
// ── 1.36.98: session close 가 current-state.md 의 사용자 작성 내용을 지우지 않는가 (사용자 보고 재현 → 실 CLI 로 고정)
|
|
10051
10062
|
// selftest 는 순수 함수만 본다. 손실은 '명령을 실제로 돌렸을 때' 났으므로 여기서 실 CLI 로 건다.
|
|
10052
|
-
{
|
|
10063
|
+
if (e2eShard.take('B(1.36.98)')) {
|
|
10053
10064
|
total++;
|
|
10054
10065
|
let ok = false; const dbg = {};
|
|
10055
10066
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-cs98-'));
|
|
@@ -10083,7 +10094,7 @@ total++;
|
|
|
10083
10094
|
}
|
|
10084
10095
|
|
|
10085
10096
|
// ── 1.36.98 (P-0013): 재사용 인벤토리 CLI — 추출 결과가 아니라 '무엇을 싣지 않는가' 를 건다.
|
|
10086
|
-
{
|
|
10097
|
+
if (e2eShard.take('B(1.36.98/P-0013)')) {
|
|
10087
10098
|
total++;
|
|
10088
10099
|
let ok = false; const dbg = {};
|
|
10089
10100
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-lib98-'));
|
|
@@ -10121,7 +10132,7 @@ total++;
|
|
|
10121
10132
|
}
|
|
10122
10133
|
|
|
10123
10134
|
// ── 1.36.99 (P-0012): 시안 정확도 — 시안이 '빈 종이' 가 아니라 이 앱의 재료로 시작하는가.
|
|
10124
|
-
{
|
|
10135
|
+
if (e2eShard.take('B(1.36.99/P-0012)')) {
|
|
10125
10136
|
total++;
|
|
10126
10137
|
let ok = false; const dbg = {};
|
|
10127
10138
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mk99-'));
|
|
@@ -10160,7 +10171,7 @@ total++;
|
|
|
10160
10171
|
}
|
|
10161
10172
|
|
|
10162
10173
|
// ── 1.36.100 (P-0012 후반): 승인 시 시안을 계약으로 고정 → 구현 후 존재 대조. 과장하지 않는 것까지 함께 건다.
|
|
10163
|
-
{
|
|
10174
|
+
if (e2eShard.take('B(1.36.100/P-0012)')) {
|
|
10164
10175
|
total++;
|
|
10165
10176
|
let ok = false; const dbg = {};
|
|
10166
10177
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ct100-'));
|
|
@@ -10210,7 +10221,7 @@ total++;
|
|
|
10210
10221
|
// ── 1.36.103 A: 값-플래그의 값이 사용자 데이터/경로로 흡수되지 않는다.
|
|
10211
10222
|
// 허용목록이 소비자보다 45개 뒤처져 있었고, 같은 클래스의 목록이 다섯 벌로 갈라져 각자 따로 뒤처졌다.
|
|
10212
10223
|
// 실측 재현: `preview add "제목" --select X` → 저장 제목 「제목 X」 · `review --persona security f.js` → 파일이 "security".
|
|
10213
|
-
{
|
|
10224
|
+
if (e2eShard.take('A(1.36.103)')) {
|
|
10214
10225
|
total++;
|
|
10215
10226
|
let ok = false; const dbg = {};
|
|
10216
10227
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-fl103-'));
|
|
@@ -10256,7 +10267,7 @@ total++;
|
|
|
10256
10267
|
|
|
10257
10268
|
// ── 1.36.103 B: --json 은 실패해도 stdout 에 파싱 가능한 JSON 하나를 남긴다.
|
|
10258
10269
|
// 위반 5건이 서로 다른 이유였다 — 경고가 stdout 선점 / 실패 분기에 JSON 없음 / 출력을 삼키는 구간에서 표식만 서서 폴백 차단.
|
|
10259
|
-
{
|
|
10270
|
+
if (e2eShard.take('B(1.36.103)')) {
|
|
10260
10271
|
total++;
|
|
10261
10272
|
let ok = false; const dbg = {};
|
|
10262
10273
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-js103-'));
|
|
@@ -10302,7 +10313,7 @@ total++;
|
|
|
10302
10313
|
// 1.36.101 의 path-strict 가 args[1] 을 무조건 경로로 봐서 `leerness library page` 를 거부했고(게시된 1.36.102
|
|
10303
10314
|
// 에서 확인), 같은 두 명령이 root 해석은 index 2 부터 시작해 검증된 경로를 건너뛰고 cwd 에 썼다.
|
|
10304
10315
|
// 관측값은 파일시스템이다 — 문구가 아니라 '어디에 썼는지'.
|
|
10305
|
-
{
|
|
10316
|
+
if (e2eShard.take('C(1.36.103)')) {
|
|
10306
10317
|
total++;
|
|
10307
10318
|
let ok = false; const dbg = {};
|
|
10308
10319
|
const arena = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rt103-'));
|
|
@@ -10338,7 +10349,7 @@ total++;
|
|
|
10338
10349
|
// _bumpMcpUsage 는 stats.mcp.tools 에 기록하는데 usage 표시는 commands 가 비면 이른 return 이라
|
|
10339
10350
|
// 그 아래 MCP 섹션에 도달하지 못했다 → MCP 로만 일하는 에이전트의 프로젝트가 "사용 기록 없음".
|
|
10340
10351
|
// 동시에 서버 프로세스가 cwd 에 `mcp: N` 을 쌓아 남의 프로젝트 통계를 오염시켰다.
|
|
10341
|
-
{
|
|
10352
|
+
if (e2eShard.take('D(1.36.104)')) {
|
|
10342
10353
|
total++;
|
|
10343
10354
|
let ok = false; const dbg = {};
|
|
10344
10355
|
const arena = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-us104-'));
|
|
@@ -10383,7 +10394,7 @@ total++;
|
|
|
10383
10394
|
// ── 1.36.104 (P-0014): 시크릿 인정 기제 — 오경보를 없애되 눈이 멀지는 않는다.
|
|
10384
10395
|
// 동기: leerness 자기 저장소가 63건을 보고했고 전부 의도적 가짜였다(e2e.js 47 · e2e-core.js 5 · pure-utils 4 · 보고서 7).
|
|
10385
10396
|
// 매 세션 같은 숫자가 뜨면 진짜 유출이 하나 섞여도 구분되지 않는다. 그래서 대조군 3종을 함께 건다.
|
|
10386
|
-
{
|
|
10397
|
+
if (e2eShard.take('E(1.36.104/P-0014)')) {
|
|
10387
10398
|
total++;
|
|
10388
10399
|
let ok = false; const dbg = {};
|
|
10389
10400
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sb104-'));
|
|
@@ -10448,7 +10459,7 @@ total++;
|
|
|
10448
10459
|
|
|
10449
10460
|
// ── 1.36.104: handoff 가 보호 파일을 지웠다 — 표식 줄의 본문 해시로 '우리가 쓴 그대로' 일 때만 청소한다.
|
|
10450
10461
|
// 실측 귀속: 명령을 하나씩 단독 실행하니 handoff 였다(이전 라운드엔 외부 검수 탓으로 오귀속하고 있었다).
|
|
10451
|
-
{
|
|
10462
|
+
if (e2eShard.take('F(1.36.104)')) {
|
|
10452
10463
|
total++;
|
|
10453
10464
|
let ok = false; const dbg = {};
|
|
10454
10465
|
const arena = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-rem104-'));
|
|
@@ -10482,7 +10493,7 @@ total++;
|
|
|
10482
10493
|
// ── 1.36.105 (P-0015): 운영 등급 — 등급이 '이름' 이 아니라 '읽히는 양' 을 바꾸는가.
|
|
10483
10494
|
// 실측 동기: init --minimal 은 디스크 파일만 줄이고 세션 적재는 2,800→2,790 tok(-0.3%) 였다.
|
|
10484
10495
|
// 대조군 둘이 핵심이다 — 기존 사용자는 무변경이어야 하고, 최소 등급에서도 핵심 3종은 살아 있어야 한다.
|
|
10485
|
-
{
|
|
10496
|
+
if (e2eShard.take('G(1.36.105/P-0015)')) {
|
|
10486
10497
|
total++;
|
|
10487
10498
|
let ok = false; const dbg = {};
|
|
10488
10499
|
const arena = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-md105-'));
|
|
@@ -10566,7 +10577,7 @@ total++;
|
|
|
10566
10577
|
// ── 1.36.105 (P-0009 B, 사용자 승인): 검증 스캐폴딩 토글 3종.
|
|
10567
10578
|
// 승인 조건이 "경계 계약을 시험으로 고정" 이었다 — 토글이 끄는 것은 **지시문** 뿐이고,
|
|
10568
10579
|
// 도구가 실제로 실행해 남기는 증거(scan/audit/gate/verify-claim/session close)는 어떤 조합에서도 살아야 한다.
|
|
10569
|
-
{
|
|
10580
|
+
if (e2eShard.take('H(1.36.105/P-0009B)')) {
|
|
10570
10581
|
total++;
|
|
10571
10582
|
let ok = false; const dbg = {};
|
|
10572
10583
|
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sc105-'));
|
|
@@ -10631,7 +10642,7 @@ total++;
|
|
|
10631
10642
|
// ── 1.36.106 (T-0090): selftest/doctor 가 임시 디렉토리를 흘렸다 — 실행당 8개/3개, 실측 누적 83,135개.
|
|
10632
10643
|
// selftest 는 "CI 친화" 로 광고되는 명령이라 커밋마다 쌓인다. 정적으로는 안 보이고 **실행 전후 증가분**으로만 잡힌다.
|
|
10633
10644
|
// 대조군: 사용자 명령(handoff/audit)은 원래 0이어야 하고, 여기서도 0이어야 한다(회귀 감시).
|
|
10634
|
-
{
|
|
10645
|
+
if (e2eShard.take('I(1.36.106/T-0090)')) {
|
|
10635
10646
|
total++;
|
|
10636
10647
|
let ok = false; const dbg = {};
|
|
10637
10648
|
const proj = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-tl106-'));
|
|
@@ -10693,7 +10704,7 @@ total++;
|
|
|
10693
10704
|
// 손상 스토어 클래스는 이 저장소에서 3회 재발했고(토글 1.36.49 · preview 형상 1.36.28 · 토글 값 1.36.92),
|
|
10694
10705
|
// 스토어 9종 × 손상 3유형 = 27조합을 실제로 돌려보니 **manifest 만** 3/3 실패였다 —
|
|
10695
10706
|
// 다른 8종은 fail-closed 규율이 서 있는데 manifest 에는 없었고, 1.36.105 가 거기에 쓰기(mode set)를 추가했다.
|
|
10696
|
-
{
|
|
10707
|
+
if (e2eShard.take('J(1.36.107)')) {
|
|
10697
10708
|
total++;
|
|
10698
10709
|
let ok = false; const dbg = {};
|
|
10699
10710
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mf107-'));
|
|
@@ -10757,7 +10768,7 @@ total++;
|
|
|
10757
10768
|
// (실측 exit 0 · 무경고). manifest 는 managedOverwrite 목록에 있어 매번 템플릿으로 재생성되는데
|
|
10758
10769
|
// 템플릿은 사용자 선택을 모른다. installedAt 도 매 재init 마다 갱신돼 이름이 거짓말을 하고 있었다.
|
|
10759
10770
|
// 고치면서 반대 실패(등급을 바꿀 수 없게 되는 false-STICK)를 만들지 않았는지 함께 단언한다.
|
|
10760
|
-
{
|
|
10771
|
+
if (e2eShard.take('K(1.36.107)')) {
|
|
10761
10772
|
total++;
|
|
10762
10773
|
let ok = false; const dbg = {};
|
|
10763
10774
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mk107-'));
|
|
@@ -10833,7 +10844,7 @@ total++;
|
|
|
10833
10844
|
//
|
|
10834
10845
|
// 유실은 가설이 아니라 재현이다: 읽기 직후를 4초 늘린 프로세스 A 와 그 창의 프로세스 B 를 붙이면
|
|
10835
10846
|
// 락 있는 스토어는 B 가 2.4초 대기 후 둘 다 생존하고, 락 없던 user-requests.json 은 B 의 요청이 통째로 사라졌다.
|
|
10836
|
-
{
|
|
10847
|
+
if (e2eShard.take('L(1.36.108/T-0097, 1.36.169/T-0108)')) {
|
|
10837
10848
|
total++;
|
|
10838
10849
|
let ok = false; const dbg = {};
|
|
10839
10850
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-lock108-'));
|
|
@@ -10989,7 +11000,7 @@ total++;
|
|
|
10989
11000
|
// Next.js `pages/_app.tsx`, 파이썬 `__init__.py` 처럼 흔한 이름이 전부 여기 걸렸다. 이유 없이 막히면
|
|
10990
11001
|
// 사용자는 게이트를 끈다 — false-BLOCK 이 false-PASS 보다 나쁘다.
|
|
10991
11002
|
// 두 방향을 한 블록에서 함께 단언한다. 한쪽만 보면 "전부 거부" 나 "전부 통과" 로 도망갈 수 있다.
|
|
10992
|
-
{
|
|
11003
|
+
if (e2eShard.take('M(1.36.109)')) {
|
|
10993
11004
|
total++;
|
|
10994
11005
|
let ok = false; const dbg = {};
|
|
10995
11006
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-claim109-'));
|
|
@@ -11084,7 +11095,7 @@ total++;
|
|
|
11084
11095
|
// 구조적 병목은 "산문에 완료 낱말이 있는가" 를 다시 요구한 것이었다 — verify-claim 은 이미 done 행에만
|
|
11085
11096
|
// 이 검사를 돌린다. 호출부가 아는 사실(claimedDone)을 넘겨 중복 요구를 없앴다.
|
|
11086
11097
|
// FP 를 먼저 고정했다: 실제 done 이력 139건 발화 0 · 홀드아웃 정직 주장 8/8 무탐지.
|
|
11087
|
-
{
|
|
11098
|
+
if (e2eShard.take('N(1.36.110/T-0099)')) {
|
|
11088
11099
|
total++;
|
|
11089
11100
|
let ok = false; const dbg = {};
|
|
11090
11101
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-honest110-'));
|
|
@@ -11155,7 +11166,7 @@ total++;
|
|
|
11155
11166
|
// 기준선 실측: 43개 명령 중 31개가 새고 누출 234줄. 이번 라운드에 `commands` 카탈로그를 이중언어화해
|
|
11156
11167
|
// 234 → 134 로 줄였다. 나머지는 **부채**이고, 이 가드는 그 부채가 **늘지 못하게** 한다.
|
|
11157
11168
|
// ⚠ baseline 은 목표가 아니라 측정값이다. 줄이는 방향으로만 갱신한다(갚은 뒤에는 반드시 조인다).
|
|
11158
|
-
{
|
|
11169
|
+
if (e2eShard.take('O(1.36.111/T-0092)')) {
|
|
11159
11170
|
total++;
|
|
11160
11171
|
let ok = false; const dbg = {};
|
|
11161
11172
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-i18n111-'));
|
|
@@ -11286,7 +11297,7 @@ total++;
|
|
|
11286
11297
|
// 최악(이 저장소)은 지침 파일의 78.0%(6,048 tok)이고, minimal 로 낮춰도 적재 7,242 tok 중 83% 가 그것이다.
|
|
11287
11298
|
// (이 수치는 **출하되는 `_splitPreserved` 로** 다시 잰 값이다 — 분해기를 엄격하게 고친 뒤 재측정했다.)
|
|
11288
11299
|
// 이 가드는 "재서 보인다" 만 지킨다. 지우는 동작은 여전히 없고, 그게 없다는 것도 아래에서 단언한다.
|
|
11289
|
-
{
|
|
11300
|
+
if (e2eShard.take('P(1.36.112)')) {
|
|
11290
11301
|
total++;
|
|
11291
11302
|
let ok = false; const dbg = {};
|
|
11292
11303
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-carry112-'));
|
|
@@ -11516,7 +11527,7 @@ total++;
|
|
|
11516
11527
|
// 1.9.402(UR-0108)가 decisions/lessons 의 **md 투영**에 _lineSafe 를 걸었지만, 같은 클래스의
|
|
11517
11528
|
// 나머지 발화점이 남아 있었다 — plan.md 쓰기(handoff 까지 전파) + 목록 출력 12곳.
|
|
11518
11529
|
// 가드는 발견 하나가 아니라 **행렬 전체**를 고정한다. 한 표면만 막으면 다음 헌트에서 옆 표면이 나온다.
|
|
11519
|
-
{
|
|
11530
|
+
if (e2eShard.take('Q(1.36.113)')) {
|
|
11520
11531
|
total++;
|
|
11521
11532
|
let ok = false; const dbg = {};
|
|
11522
11533
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-inj113-'));
|
|
@@ -11648,7 +11659,7 @@ total++;
|
|
|
11648
11659
|
// ⚠ 이 라운드가 만든 회귀도 여기서 잡았다: 손상 시 저장은 막혔는데 명령이 `✓ 추가` 를 찍고 exit 0 이었다.
|
|
11649
11660
|
// 조용한 무동작 + 성공 표시는 조용한 유실보다 나쁘다 — 사용자는 저장됐다고 믿는다. 그래서 **세 가지를 함께** 단언한다:
|
|
11650
11661
|
// 안 쓴다 · 성공이라 말하지 않는다 · 정상 스토어에서는 오차단하지 않는다.
|
|
11651
|
-
{
|
|
11662
|
+
if (e2eShard.take('R(1.36.114)')) {
|
|
11652
11663
|
total++;
|
|
11653
11664
|
let ok = false; const dbg = {};
|
|
11654
11665
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-neg114-'));
|
|
@@ -11827,7 +11838,7 @@ total++;
|
|
|
11827
11838
|
// ③ `init` 이 `.mcp.json` 을 만들지 않았다 — `adapter list` 는 claude 에 "[+.mcp.json]" 이라 광고하는데
|
|
11828
11839
|
// 문서화된 진입점인 init 은 그 배선을 빼먹어, init 만 한 사용자의 Claude Code 는 도구를 하나도 못 봤다.
|
|
11829
11840
|
// 이 가드는 "설치하면 도구가 leerness 를 쓰게 된다" 는 **제품의 기본 약속**을 계약으로 고정한다.
|
|
11830
|
-
{
|
|
11841
|
+
if (e2eShard.take('S(1.36.115)')) {
|
|
11831
11842
|
total++;
|
|
11832
11843
|
let ok = false; const dbg = {};
|
|
11833
11844
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-wire115-'));
|
|
@@ -12205,7 +12216,7 @@ total++;
|
|
|
12205
12216
|
// ⚠ 이 기능의 성패는 **오탐**에 달렸다. 잘 만든 프로젝트는 CI 에 이름을 나열하지 않고 package.json 을
|
|
12206
12217
|
// 동적으로 열거한다 — 그걸 못 알아보면 가장 모범적인 저장소에서 수십 건이 뜨고 사용자는 경고를 꺼 버린다.
|
|
12207
12218
|
// 실제로 초안이 그 실측 프로젝트에서 **85건 오탐**을 냈고(근접 창 가정 때문), 그래서 아래 세 대조군을 모두 건다.
|
|
12208
|
-
{
|
|
12219
|
+
if (e2eShard.take('T(1.36.116)')) {
|
|
12209
12220
|
total++;
|
|
12210
12221
|
let ok = false; const dbg = {};
|
|
12211
12222
|
try {
|
|
@@ -12847,8 +12858,8 @@ total++;
|
|
|
12847
12858
|
// 메모화로 1회로 줄였다. 그런데 "안 돌렸다" 와 "한 번 돌리고 결과를 나눠 줬다" 는 겉보기가 같다 —
|
|
12848
12859
|
// 그래서 **실패하는 스위트**를 판별 케이스로 쓴다: 전파되면 N개 주장 **전부** 실패하고, 끊기면 첫 주장만 실패한다.
|
|
12849
12860
|
// 대조군(통과 스위트)도 함께 둔다 — 메모가 없는 실패를 지어내지 않는지.
|
|
12850
|
-
|
|
12851
|
-
|
|
12861
|
+
if (e2eShard.take('U(1.36.125)')) {
|
|
12862
|
+
total++;
|
|
12852
12863
|
let ok = false; const bad = []; const dbg = {};
|
|
12853
12864
|
const _dirs = [];
|
|
12854
12865
|
const N = 3;
|
|
@@ -12899,8 +12910,8 @@ total++;
|
|
|
12899
12910
|
// 1.36.126 → 1.36.162 (T-0107): `.leerness/` 가 공식 canonical 이 된 뒤의
|
|
12900
12911
|
// 호환 계약. 과거에는 대체 디렉터리를 거부했지만, 이제는 fresh init·idempotency·
|
|
12901
12912
|
// marker가 권위를 낮추지 않는 dual-live fail-closed·부모 자산 해석을 검증한다.
|
|
12902
|
-
|
|
12903
|
-
|
|
12913
|
+
if (e2eShard.take('V(1.36.126/T-0107)')) {
|
|
12914
|
+
total++;
|
|
12904
12915
|
let ok = false; const bad = []; const dbg = {};
|
|
12905
12916
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-ws126-'));
|
|
12906
12917
|
const envW = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -13038,8 +13049,8 @@ total++;
|
|
|
13038
13049
|
// CI 가 `bin/leerness.js` 를 실행하므로 그 파일이 러너로 잡히고, 그 안의 selftest 단언 문자열
|
|
13039
13050
|
// (`'test:core'`·`e2e-core.js`)이 호출로 읽혀 leerness 자신의 진짜 고아가 0건으로 보고됐다.
|
|
13040
13051
|
// ① audit 이 그 가림을 **사용자에게 말하는가** ② 대조군은 조용한가 ③ 그리고 우리 자신이 실제로 배선했는가.
|
|
13041
|
-
|
|
13042
|
-
|
|
13052
|
+
if (e2eShard.take('W(1.36.127/T-0105)')) {
|
|
13053
|
+
total++;
|
|
13043
13054
|
let ok = false; const bad = []; const dbg = {};
|
|
13044
13055
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-mask127-'));
|
|
13045
13056
|
const envM = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -13148,8 +13159,8 @@ total++;
|
|
|
13148
13159
|
// 1.36.128 (T-0104 실측): `adapter <tool> [+.mcp.json]` 은 **외부 제품이 그 파일을 읽는다**는 주장이다.
|
|
13149
13160
|
// 이 머신의 실제 CLI 로 재 보니 codex 는 읽지 않았고(`codex mcp get` → not found) opencode 는 읽었다(connected).
|
|
13150
13161
|
// 그래서 안 읽는 도구에는 파일을 **쓰지 않고** 되는 명령을 안내한다. 그 계약을 CLI 행위로 고정한다.
|
|
13151
|
-
|
|
13152
|
-
|
|
13162
|
+
if (e2eShard.take('X(1.36.128/T-0104)')) {
|
|
13163
|
+
total++;
|
|
13153
13164
|
let ok = false; const bad = []; const dbg = {};
|
|
13154
13165
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-adp128-'));
|
|
13155
13166
|
const envA = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -13229,8 +13240,8 @@ total++;
|
|
|
13229
13240
|
// 설계 경계는 전부 실측에서 나왔다: PID 라이브니스는 양방향으로 틀리고(5초에 재사용 7건, 죽은 pid 5.2%가 ALIVE),
|
|
13230
13241
|
// `leerness selftest` 1회가 leerness 프로세스 129개를 띄우므로 "모든 호출마다 등록" 은 불가능하다.
|
|
13231
13242
|
// 그래서 쓰기 지점은 handoff · session close 둘뿐이고, 판정("활성")은 아예 하지 않는다.
|
|
13232
|
-
|
|
13233
|
-
|
|
13243
|
+
if (e2eShard.take('Y(1.36.129/P-0016 P1)')) {
|
|
13244
|
+
total++;
|
|
13234
13245
|
let ok = false; const bad = []; const dbg = {};
|
|
13235
13246
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-sess129-'));
|
|
13236
13247
|
// ⚠ 이 e2e 는 **살아있는 Claude Code 세션 안에서** 돈다 — 주변 env 를 비우지 않으면
|
|
@@ -13447,8 +13458,8 @@ total++;
|
|
|
13447
13458
|
// → 이제 **별도 프로세스**가 해제 시각을 파일로 남기고, 대상 파일 **mtime 과 비교**한다.
|
|
13448
13459
|
// "잠깐 기다렸다 무락으로 쓰기" 도 mtime < 해제시각 이라 걸린다.
|
|
13449
13460
|
// · 사용자 글 보존을 `includes` 로만 봐서 **표 뒤로 재배치**해도 통과했다 → 이제 **위치**를 단언한다.
|
|
13450
|
-
|
|
13451
|
-
|
|
13461
|
+
if (e2eShard.take('Z(1.36.131/P-0016 P2)')) {
|
|
13462
|
+
total++;
|
|
13452
13463
|
let ok = false; const bad = []; const dbg = {};
|
|
13453
13464
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-p2-131-'));
|
|
13454
13465
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -13609,8 +13620,8 @@ total++;
|
|
|
13609
13620
|
// ② 세션 신호가 4표면 중 1곳에만 닿았고, 못 가는 3곳에 유일한 강제 경로(훅)·MCP 경로·기계 경로가 들어 있었다.
|
|
13610
13621
|
// ③ 억제된 세션은 "남이 N건 있다" 는 듣지만 "나는 안 보인다" 는 못 들었다(비대칭이 조용했다).
|
|
13611
13622
|
// 그리고 그 밑에서 락 자체가 새고 있었다(아래 ①).
|
|
13612
|
-
|
|
13613
|
-
|
|
13623
|
+
if (e2eShard.take('AA(1.36.132/UR-0069)')) {
|
|
13624
|
+
total++;
|
|
13614
13625
|
let ok = false; const bad = []; const dbg = {};
|
|
13615
13626
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-132-'));
|
|
13616
13627
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -13895,8 +13906,8 @@ total++;
|
|
|
13895
13906
|
// 자동유도 96.9% · 명시선언 63.1% · 혼합(채택률 1.0) 94.0% 로 전부 40% 초과였고, 유일한 생존 구성은
|
|
13896
13907
|
// **충돌이 실제로 나는 파일(bin/leerness.js, 커밋의 68.8%)을 음소거해서** 조용해진 것이었다(무출력은 통과가 아니다).
|
|
13897
13908
|
// 대신 그 정찰이 실측한 **기반 결함**을 고친다 — 전부 P3-A 없이도 이미 출하돼 있던 동작이다.
|
|
13898
|
-
|
|
13899
|
-
|
|
13909
|
+
if (e2eShard.take('AB(1.36.133/T-0109)')) {
|
|
13910
|
+
total++;
|
|
13900
13911
|
let ok = false; const bad = []; const dbg = {};
|
|
13901
13912
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-133-'));
|
|
13902
13913
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -14126,8 +14137,8 @@ total++;
|
|
|
14126
14137
|
// 그리고 `status --json` 은 git 아님/unborn/정상/HEAD손상/gitdir깨짐 **5상태에서 payload 가 동일**해
|
|
14127
14138
|
// '설치 안 됨' 과 '설치할 데가 없음' 이 구분되지 않았다.
|
|
14128
14139
|
// 원인은 근거가 셋이었다는 것이다: install=`.git` **디렉토리 존재**, audit=`git log` 실행 결과, status=아무것도 안 봄.
|
|
14129
|
-
|
|
14130
|
-
|
|
14140
|
+
if (e2eShard.take('AC(1.36.134)')) {
|
|
14141
|
+
total++;
|
|
14131
14142
|
let ok = false; const bad = []; const dbg = {};
|
|
14132
14143
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-134-'));
|
|
14133
14144
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -14578,14 +14589,14 @@ total++;
|
|
|
14578
14589
|
if (!ok) failed++;
|
|
14579
14590
|
}
|
|
14580
14591
|
|
|
14581
|
-
total++;
|
|
14582
14592
|
// AD(1.36.140) — **초크포인트는 하나여야 한다.**
|
|
14583
14593
|
// 1.36.135/139 는 `bin/leerness.js` 의 git 초크포인트에만 환경 세척을 넣었고,
|
|
14584
14594
|
// `lib/drift.js` · `lib/session-close.js` 는 같은 이름의 복제본을 갖고 있었다(세척 없음).
|
|
14585
14595
|
// 기존 가드는 "인라인 배열 형태의 직접 호출 0건" 만 셌다 — 세 복제본이 전부 변수 형태라 **모양은 통과**했다.
|
|
14586
14596
|
// 가드가 밟지 않는 경로는 가드가 지키지 못한다. 그래서 여기서는 **행위로** 잰다:
|
|
14587
14597
|
// 상속된 GIT_DIR 아래에서 남의 저장소 브랜치가 지워지는가(실측: 고치기 전 60 → 10, 50건 삭제).
|
|
14588
|
-
{
|
|
14598
|
+
if (e2eShard.take('AD(1.36.140)')) {
|
|
14599
|
+
total++;
|
|
14589
14600
|
let ok = false; const bad = []; const dbg = {};
|
|
14590
14601
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-140-'));
|
|
14591
14602
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -14693,14 +14704,14 @@ total++;
|
|
|
14693
14704
|
|
|
14694
14705
|
|
|
14695
14706
|
|
|
14696
|
-
total++;
|
|
14697
14707
|
// AE(1.36.140) — 재검수가 지적하고 **내가 재현한** 것들을 고정한다.
|
|
14698
14708
|
// 전부 "고치려고 넣은 코드가 만든 다음 결함" 이다:
|
|
14699
14709
|
// · 복합 키 예산(1.36.139) → `date` 안의 `|` 로 두 항목이 같은 키가 되어 **무관 항목 삭제 + archive 불일치**
|
|
14700
14710
|
// · 롤백(1.36.134~139) → 정의가 실패 지점보다 뒤라 EPERM 이 최상위로 튀며 **체인 백업이 덮인 채 남음**
|
|
14701
14711
|
// · archive 를 락 밖에서 씀 → 동시 drop 둘 다 성공 보고 + 이중 archive → 복원이 중복 생성
|
|
14702
14712
|
// 판정값은 모두 **실측 전/후 비교**다(문자열 존재가 아니라 무엇이 남았는지).
|
|
14703
|
-
{
|
|
14713
|
+
if (e2eShard.take('AE(1.36.140)')) {
|
|
14714
|
+
total++;
|
|
14704
14715
|
let ok = false; const bad = []; const dbg = {};
|
|
14705
14716
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-140b-'));
|
|
14706
14717
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -14887,7 +14898,6 @@ total++;
|
|
|
14887
14898
|
if (!ok) failed++;
|
|
14888
14899
|
}
|
|
14889
14900
|
|
|
14890
|
-
total++;
|
|
14891
14901
|
// AF(1.36.142/T-0116) — **증거는 일한 세션에 붙어야 한다.**
|
|
14892
14902
|
// `.leerness/state.json` 의 `currentRunId` 는 전역 슬롯 하나였다. 두 세션이 같은 프로젝트에서 일하면
|
|
14893
14903
|
// 뒤에 `state start` 한 쪽이 앞사람 슬롯을 덮어썼고, 실측으로:
|
|
@@ -14895,7 +14905,8 @@ total++;
|
|
|
14895
14905
|
// · 일하지 않은 B 가 `completion_claim_allowed: true` 를 받았고
|
|
14896
14906
|
// · A 는 `state show` 로 **자기 run 을 볼 수조차 없었다**.
|
|
14897
14907
|
// 락 문제가 아니다(직렬로 돌려도 재현된다) — 주소가 없는 것이 문제다.
|
|
14898
|
-
{
|
|
14908
|
+
if (e2eShard.take('AF(1.36.142/T-0116)')) {
|
|
14909
|
+
total++;
|
|
14899
14910
|
let ok = false; const bad = []; const dbg = {};
|
|
14900
14911
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-142-'));
|
|
14901
14912
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -15009,14 +15020,14 @@ total++;
|
|
|
15009
15020
|
if (!ok) failed++;
|
|
15010
15021
|
}
|
|
15011
15022
|
|
|
15012
|
-
total++;
|
|
15013
15023
|
// AG(1.36.143) — **없는 `--path` 는 아무것도 만들지 않는다** + 공유 판정은 추정하지 않는다.
|
|
15014
15024
|
// 검수는 `provider add` · `team add` · `creds register` 3건을 지목했다. 이름을 더 넣는 대신
|
|
15015
15025
|
// 모든 명령에 없는 `--path` 를 주고 전수로 재니 **33건**이 그 경로를 만들었다 —
|
|
15016
15026
|
// 다수는 **성공이라 말하면서**(`provider add` · `team add` · `skill learn` · `glossary` · `wakeup-interval`),
|
|
15017
15027
|
// 그중엔 직전 라운드에 내가 넣은 `state start` 도 있었다. 개별 등록은 이미 세 번째였다.
|
|
15018
15028
|
// 그래서 이름 목록이 아니라 **성질**로 막고, 여기서도 이름이 아니라 **열거**로 잰다.
|
|
15019
|
-
{
|
|
15029
|
+
if (e2eShard.take('AG(1.36.143)')) {
|
|
15030
|
+
total++;
|
|
15020
15031
|
let ok = false; const bad = []; const dbg = {};
|
|
15021
15032
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-143-'));
|
|
15022
15033
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -15176,12 +15187,12 @@ total++;
|
|
|
15176
15187
|
if (!ok) failed++;
|
|
15177
15188
|
}
|
|
15178
15189
|
|
|
15179
|
-
total++;
|
|
15180
15190
|
// AH(1.36.144/T-0117) — **`--dry-run` 은 아무것도 쓰지 않는다** + 재검수 P1/P2.
|
|
15181
15191
|
// `--dry-run` 은 등록된 전역 플래그인데 많은 명령이 그냥 무시하고 썼다. 378 호출을 전후 트리 비교로 재니
|
|
15182
15192
|
// 183건이 썼고 그중 28건이 **사용자 데이터**를 바꿨다(`session close` 가 실제 마감을 수행, `task/decision/rule/plan add`
|
|
15183
15193
|
// 가 실제로 추가). 명령마다 분기를 심으면 16개의 새 실수 자리가 생기므로 **쓰기 시점 하나**에서 막는다.
|
|
15184
|
-
{
|
|
15194
|
+
if (e2eShard.take('AH(1.36.144/T-0117)')) {
|
|
15195
|
+
total++;
|
|
15185
15196
|
let ok = false; const bad = []; const dbg = {};
|
|
15186
15197
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-144-'));
|
|
15187
15198
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -15332,12 +15343,12 @@ total++;
|
|
|
15332
15343
|
if (!ok) failed++;
|
|
15333
15344
|
}
|
|
15334
15345
|
|
|
15335
|
-
total++;
|
|
15336
15346
|
// AI(1.36.145) — **MCP 표면에도 세션 주소가 있어야 한다.**
|
|
15337
15347
|
// T-0116 을 CLI 에서만 고쳤다. MCP `state` 도구에는 주소 입력이 없어 한 서버의 모든 호출이
|
|
15338
15348
|
// 같은(=무)주소로 접혀 전역 슬롯 오귀속이 그대로 남아 있었다 — 실측: A 의 증거가 B 의 run 에 붙고
|
|
15339
15349
|
// 일하지 않은 B 가 `completion_claim_allowed: true` 를 받았다. 가드는 자기가 밟는 경로만 지킨다.
|
|
15340
|
-
{
|
|
15350
|
+
if (e2eShard.take('AI(1.36.145)')) {
|
|
15351
|
+
total++;
|
|
15341
15352
|
let ok = false; const bad = []; const dbg = {};
|
|
15342
15353
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-145-'));
|
|
15343
15354
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -15427,7 +15438,6 @@ total++;
|
|
|
15427
15438
|
if (!ok) failed++;
|
|
15428
15439
|
}
|
|
15429
15440
|
|
|
15430
|
-
total++;
|
|
15431
15441
|
// AJ(1.36.146) — dry-run 가드가 **쓰기만** 보고 있었다.
|
|
15432
15442
|
// 1.36.144 의 가드는 `writeUtf8`/`append`/`mkdirp` 세 함수만 봤다. io 를 우회하는 `fs.*` 변경 호출은
|
|
15433
15443
|
// 정적으로 **330건**(unlinkSync 15 · rmSync 85 · writeFileSync 125 …) 이었고, 실측으로:
|
|
@@ -15436,7 +15446,8 @@ total++;
|
|
|
15436
15446
|
// · 면제 정규식이 `cache` 를 경로 어디서나 매칭해 `…/cache/proj/` 프로젝트는 **가드가 통째로 꺼졌다**
|
|
15437
15447
|
// · MCP 의 빈 `path` 가 서버 root 로 대체돼 엉뚱한 저장소에 썼다
|
|
15438
15448
|
// 330곳을 배선하면 330개의 새 실수 자리가 생긴다 — 프로세스 진입점에서 한 번 막고, git 은 초크포인트에서 막는다.
|
|
15439
|
-
{
|
|
15449
|
+
if (e2eShard.take('AJ(1.36.146)')) {
|
|
15450
|
+
total++;
|
|
15440
15451
|
let ok = false; const bad = []; const dbg = {};
|
|
15441
15452
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-146-'));
|
|
15442
15453
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -15547,13 +15558,13 @@ total++;
|
|
|
15547
15558
|
if (!ok) failed++;
|
|
15548
15559
|
}
|
|
15549
15560
|
|
|
15550
|
-
total++;
|
|
15551
15561
|
// AK(1.36.147) — 소유권 코드가 만든 세 결함. 전부 **내가 T-0116 을 고치며 넣은 것**이다.
|
|
15552
15562
|
// ① 인수는 했는데 **소유를 저장하지 않아** 두 세션이 같은 legacy run 을 각자 인수했다
|
|
15553
15563
|
// (실측: 한 run 에 A.js 와 B.js 가 함께 들어갔다 — T-0116 이 잡으려던 바로 그 증거 혼합).
|
|
15554
15564
|
// ② 같은 세션 키로 두 번 `start` 하면 둘 다 성공하고 마지막만 소유해 **첫 run 이 증거 없이 고아**가 됐다.
|
|
15555
15565
|
// ③ 남의 세션 키 가림이 최상위 필드에만 걸려 **중첩 `state.runsBySession`** 으로 원본이 그대로 샜다.
|
|
15556
|
-
{
|
|
15566
|
+
if (e2eShard.take('AK(1.36.147)')) {
|
|
15567
|
+
total++;
|
|
15557
15568
|
let ok = false; const bad = []; const dbg = {};
|
|
15558
15569
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-147-'));
|
|
15559
15570
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -15629,7 +15640,6 @@ total++;
|
|
|
15629
15640
|
if (!ok) failed++;
|
|
15630
15641
|
}
|
|
15631
15642
|
|
|
15632
|
-
total++;
|
|
15633
15643
|
// AL(1.36.148) — 앞 라운드 수정들이 **다른 조건에서 무력화**되던 자리들.
|
|
15634
15644
|
// ① `keySource` 가 child 억제를 **명시 주소보다 먼저** 검사해, MCP 서버가
|
|
15635
15645
|
// `CLAUDE_CODE_CHILD_SESSION=1` 을 상속하면 1.36.145 의 주소 전달이 통째로 죽었다.
|
|
@@ -15637,7 +15647,8 @@ total++;
|
|
|
15637
15647
|
// ② run id 를 "빈 문자열만 아니면" 으로 봐서 `../../evil` 이 소유 맵에 들어가면 `.leerness` 밖에 썼다.
|
|
15638
15648
|
// ③ 중앙 `--path` 가드가 존재성만 봐서 **일반 파일**이 프로젝트 root 로 통과했다.
|
|
15639
15649
|
// ④ dry-run 가드는 파일·git 만 봤다 — `team deploy --yes --dry-run` 이 배포 subprocess 를 실제 실행했다.
|
|
15640
|
-
{
|
|
15650
|
+
if (e2eShard.take('AL(1.36.148)')) {
|
|
15651
|
+
total++;
|
|
15641
15652
|
let ok = false; const bad = []; const dbg = {};
|
|
15642
15653
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-148-'));
|
|
15643
15654
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -15755,11 +15766,11 @@ total++;
|
|
|
15755
15766
|
if (!ok) failed++;
|
|
15756
15767
|
}
|
|
15757
15768
|
|
|
15758
|
-
total++;
|
|
15759
15769
|
// AM(1.36.149) — 1.36.148 의 run-id 경로 차단은 소유 맵만 덮었다.
|
|
15760
15770
|
// legacy `currentRunId` 와 디스크 run record 의 `run_id` 도 같은 파일 경로 병목을 탄다.
|
|
15761
15771
|
// 둘 중 하나가 경로형이면 state record/handoff 가 프로젝트 밖에 쓰기 전에 손상으로 멈춰야 한다.
|
|
15762
|
-
{
|
|
15772
|
+
if (e2eShard.take('AM(1.36.149)')) {
|
|
15773
|
+
total++;
|
|
15763
15774
|
let ok = false; const bad = []; const dbg = {};
|
|
15764
15775
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-149-'));
|
|
15765
15776
|
const envP = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
|
|
@@ -15813,11 +15824,11 @@ total++;
|
|
|
15813
15824
|
if (!ok) failed++;
|
|
15814
15825
|
}
|
|
15815
15826
|
|
|
15816
|
-
total++;
|
|
15817
15827
|
// AN(1.36.150/T-0120) — `GIT_CONFIG_NOSYSTEM`은 저장소 위치 주입이 아니라 호출자가 시스템 설정을
|
|
15818
15828
|
// 숨기겠다는 의미다. 설치기만 이를 지우면 system core.hooksPath에 훅을 쓰지만, 같은 셸의 실제 git commit은
|
|
15819
15829
|
// 기본 .git/hooks를 보아 오래된 handoff도 통과한다. 설치 경로와 실제 커밋 경로가 같은 환경에서 일치해야 한다.
|
|
15820
|
-
{
|
|
15830
|
+
if (e2eShard.take('AN(1.36.150/T-0120)')) {
|
|
15831
|
+
total++;
|
|
15821
15832
|
let ok = false; const bad = []; const dbg = {};
|
|
15822
15833
|
const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-150-'));
|
|
15823
15834
|
const stripGitTargetEnv = (source) => {
|
|
@@ -15878,12 +15889,12 @@ total++;
|
|
|
15878
15889
|
if (!ok) failed++;
|
|
15879
15890
|
}
|
|
15880
15891
|
|
|
15881
|
-
total++;
|
|
15882
15892
|
// AO(1.36.152/T-0115, 1.36.169/T-0108 확장) — write 시점의 락 관측만으로는 "읽은 사본도 락 안인가"를 증명할 수 없다.
|
|
15883
15893
|
// 첫 프로세스의 최종 rename을 강제로 멈추고 두 번째 쓰기를 붙여, 서로 다른 RMW 11쌍이 모두 생존하는지 확인한다.
|
|
15884
15894
|
// T-0343: plus 19 pairs where the second command decided a write, a skip or an error from a file the first one was
|
|
15885
15895
|
// publishing (on POSIX the path is missing for that moment); 30 in all.
|
|
15886
|
-
{
|
|
15896
|
+
if (e2eShard.take('AO(1.36.152/T-0115, 1.36.169/T-0108, T-0343)')) {
|
|
15897
|
+
total++;
|
|
15887
15898
|
let ok = false; const dbg = {};
|
|
15888
15899
|
try {
|
|
15889
15900
|
const race = cp.spawnSync(process.execPath, [path.join(__dirname, 'e2e-concurrency.js'), CLI], {
|
|
@@ -15916,7 +15927,7 @@ setImmediate(() => {
|
|
|
15916
15927
|
} catch (error) {
|
|
15917
15928
|
runtimeDep0190.push(`warning redirect gate read failed: ${error.message}`);
|
|
15918
15929
|
}
|
|
15919
|
-
total++;
|
|
15930
|
+
total++; // e2e-shard: run-wide check
|
|
15920
15931
|
if (runtimeDep0190.length === 0) {
|
|
15921
15932
|
console.log('✓ AP(1.36.162/T-0139) 전체 E2E 부모+동기·ignore·async 자식 런타임 DEP0190 0건');
|
|
15922
15933
|
} else {
|
|
@@ -15925,7 +15936,7 @@ setImmediate(() => {
|
|
|
15925
15936
|
process.stderr.write(runtimeDep0190.join('\n---\n') + '\n');
|
|
15926
15937
|
}
|
|
15927
15938
|
// T-0315: 테스트는 패키지 저장소 자신의 상태를 바꾸지 않는다 — gitignore 된 캐시·원장까지 전부 대조한다.
|
|
15928
|
-
total++;
|
|
15939
|
+
total++; // e2e-shard: run-wide check
|
|
15929
15940
|
const _pkgChanged = repoGuard.diff(_pkgLeernessBefore, repoGuard.snapshot(_pkgLeerness));
|
|
15930
15941
|
if (_e2eHomeInit.status === 0 && _pkgChanged.length === 0) {
|
|
15931
15942
|
console.log(`✓ T-0315 e2e 가 패키지 저장소 .leerness 를 바꾸지 않음 (항목 ${_pkgLeernessBefore.size}개 대조 · cwd 없는 CLI 호출은 격리 홈 프로젝트에서 실행)`);
|
|
@@ -15934,6 +15945,16 @@ setImmediate(() => {
|
|
|
15934
15945
|
console.log(`✗ T-0315 e2e 격리 실패 — 격리 홈 init exit=${_e2eHomeInit.status}, 패키지 .leerness 변경 ${_pkgChanged.length}건: ${_pkgChanged.slice(0, 20).join(', ')}${_pkgChanged.length > 20 ? ' …' : ''}`
|
|
15935
15946
|
+ ' (실행 중 이 저장소에서 leerness 를 직접 실행했다면 그 쓰기도 여기에 잡힌다)');
|
|
15936
15947
|
}
|
|
15937
|
-
|
|
15948
|
+
// T-0359: every case of this file was reached exactly once, here or in its own shard, and none is unguarded.
|
|
15949
|
+
total++; // e2e-shard: run-wide check
|
|
15950
|
+
const _shard = e2eShard.finish();
|
|
15951
|
+
const _shardName = _shard.spec ? `샤드 ${_shard.spec.index}/${_shard.spec.count}` : '샤드 없음(전체)';
|
|
15952
|
+
if (_shard.ok) {
|
|
15953
|
+
console.log(`✓ T-0359 e2e ${_shardName}: 케이스 ${_shard.ran}/${_shard.total}개 실행 · 가드 ${_shard.total}개가 각각 한 번씩 도달 · 가드 없는 최상위 블록 0`);
|
|
15954
|
+
} else {
|
|
15955
|
+
failed++;
|
|
15956
|
+
console.log(`✗ T-0359 e2e 샤드 가드 이상 ${JSON.stringify({ problems: _shard.problems.slice(0, 10), missing: _shard.missing.slice(0, 10), repeated: _shard.repeated.slice(0, 10), unknown: _shard.unknown.slice(0, 10) })}`);
|
|
15957
|
+
}
|
|
15958
|
+
console.log(`\nE2E result: ${total - failed}/${total} passed${_shard.spec ? ` · shard ${_shard.spec.index}/${_shard.spec.count} (${_shard.ran}/${_shard.total} cases)` : ''} · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
15938
15959
|
if (failed > 0) process.exitCode = 1;
|
|
15939
15960
|
});
|