leerness 1.9.314 → 1.9.316
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 +38 -0
- package/README.md +5 -5
- package/bin/harness.js +59 -7
- package/package.json +1 -1
- package/scripts/e2e.js +41 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.316 — 2026-06-04 — 🐛 drift 'session close 누락' 영구 오발화 버그 (자가 발견)
|
|
4
|
+
|
|
5
|
+
**🔧 session close 직후에도 drift 가 `session close 누락`(13일)을 보고하던 버그 수정 — 4라운드 연속 관찰된 leerness 자체 정확성 결함.**
|
|
6
|
+
|
|
7
|
+
### 근본 원인 (자가 발견)
|
|
8
|
+
- drift 의 `session close 누락` 신호는 `session-handoff.md` 의 `Last generated:` 타임스탬프로 나이를 계산.
|
|
9
|
+
- `sessionClose` 가 파일 재작성 시 프론트매터 보존 로직 `cur.indexOf('\n---\n', 4)` 가 **본문의 `---`(수평선/구분자)** 를 프론트매터 종료로 오인 → **구 블록 전체(구 `Last generated`)를 보존**하고 그 뒤에 새 블록을 append.
|
|
10
|
+
- 결과: `session-handoff.md` 에 `Last generated:` 가 **2개 누적**(구 + 신). drift 의 `.match()` 는 **첫(=구) 매치**를 읽어 → 매 session close 후에도 13일 stale → 영구 오발화.
|
|
11
|
+
|
|
12
|
+
### 구현 (2중 수정)
|
|
13
|
+
1. **근본 (write)**: 프론트매터는 파일이 **`^---` 로 시작할 때만** 추출 — 본문 `---` 오인 차단. 프론트매터 없으면 깨끗이 덮어써 단일 블록 유지(기존 손상 파일도 다음 close 에 self-heal).
|
|
14
|
+
2. **방어 (read)**: drift 가 `matchAll` 로 **최신(마지막) `Last generated`** 사용 — 혹시 모를 중복에도 freshest 반영.
|
|
15
|
+
3. selftest 63→64 · e2e 260→261.
|
|
16
|
+
|
|
17
|
+
### 검증
|
|
18
|
+
- **selftest 64/64 PASS** · **E2E 261/261 PASS** (회귀 0).
|
|
19
|
+
- 실측: 손상 파일(구 timestamp + 본문 `---`) → session close → `Last generated` **1개**(신선), drift `session close 누락` **age 0.00d 클리어** · 프론트매터 파일 3회 연속 close → 누적 0(프론트매터 보존).
|
|
20
|
+
|
|
21
|
+
## 1.9.315 — 2026-06-04 — UR-0054(부분): doc/surface 정합 + doctor 명령 (설치리뷰)
|
|
22
|
+
|
|
23
|
+
**📋 stale 현재상태 표시 수정 + `leerness doctor` 설치 진단 명령 추가 (Codex#2·Sonnet#2·Opus#2 공통 지적).**
|
|
24
|
+
|
|
25
|
+
### 배경 (설치리뷰)
|
|
26
|
+
"문서/표면 drift — init 'MCP 46'(실제83), .harness/.leerness 혼재, --help 중복, --compact 미압축, doctor no-op, init후 gate 실패". 6개 항목 중 **검증 후** 실제/현행 상태:
|
|
27
|
+
- ✅ **stale MCP 카운트**: `commands` 요약(하드코딩 65) + `_banner`(하드코딩 46) — 실제 83. 현재상태 주장이 stale.
|
|
28
|
+
- ✅ **doctor 부재**: `leerness doctor` 명령 없음(unknown command).
|
|
29
|
+
- ⏸ `--help 중복`/`--compact`: 현재 재현 안 됨(중복 0, --compact 200→185줄 압축 동작) — 이미 정상.
|
|
30
|
+
- ⏸ `init후 gate 실패`/`.harness·.leerness 혼재`: 의미적 결정/구조 변경 필요 → 후속(별도 라운드).
|
|
31
|
+
|
|
32
|
+
### 구현 (UR-0054 부분)
|
|
33
|
+
1. **stale MCP 카운트 → 동적 `_mcpToolCount()`**: `commands` 요약 + `_banner` 외부 AI 섹션. (역사적 changelog/What's-new 박스의 숫자는 그 시점 기록이라 보존.)
|
|
34
|
+
2. **`leerness doctor` 신규** — 설치/환경 1콜 진단: version·node·경로·MCP 수·**selftest 코어 무결성(N/N)**·셸/PowerShell. `--json` + 실패 시 exit 1(CI 친화). health(프로젝트)와 구분되는 "도구 자체" 진단.
|
|
35
|
+
3. selftest 62→63 · e2e 259→260.
|
|
36
|
+
|
|
37
|
+
### 검증
|
|
38
|
+
- **selftest 63/63 PASS** · **E2E 260/260 PASS** (회귀 0).
|
|
39
|
+
- 실측: `commands`/`_banner` → "MCP 83" · `doctor` → `version 1.9.315 · MCP 83 · selftest 63/63 · 셸` exit 0 · `doctor --json` 구조화 출력 · `about`/`pulse` mcpTools 이미 동적(확인).
|
|
40
|
+
|
|
3
41
|
## 1.9.314 — 2026-06-04 — UR-0052: PowerShell 감지 정확화 (설치리뷰)
|
|
4
42
|
|
|
5
43
|
**🪟 shell-guard 가 PowerShell 7(pwsh)을 cmd/ps5.1 로 오판해 `&&` 에 잘못된 ps5-chain 경고를 내던 결함 수정 (Opus#2 지적, Windows/한국어 타깃 핵심).**
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
> **A CLI harness that stops AI coding agents from faking completion, duplicating work, forgetting context, and colliding.**
|
|
5
5
|
|
|
6
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []()
|
|
7
7
|
|
|
8
8
|
```
|
|
9
9
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -471,7 +471,7 @@ MIT — © leerness contributors
|
|
|
471
471
|
<!-- leerness:project-readme:start -->
|
|
472
472
|
## Leerness Project Harness
|
|
473
473
|
|
|
474
|
-
이 프로젝트는 Leerness v1.9.
|
|
474
|
+
이 프로젝트는 Leerness v1.9.316 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
475
475
|
|
|
476
476
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
477
477
|
|
|
@@ -525,7 +525,7 @@ leerness memory restore decision <date|title>
|
|
|
525
525
|
|
|
526
526
|
### MCP server (외부 AI 통합)
|
|
527
527
|
|
|
528
|
-
Leerness v1.9.
|
|
528
|
+
Leerness v1.9.316는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **83개 도구**를 노출:
|
|
529
529
|
|
|
530
530
|
```jsonc
|
|
531
531
|
// 카테고리별
|
|
@@ -546,7 +546,7 @@ Leerness v1.9.314는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
546
546
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
547
547
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
|
|
548
548
|
|
|
549
|
-
현재 누적: **70 라운드 (1.9.40 → 1.9.
|
|
549
|
+
현재 누적: **70 라운드 (1.9.40 → 1.9.316)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
|
|
550
550
|
|
|
551
551
|
### 성능 가이드 (1.9.140 측정)
|
|
552
552
|
|
|
@@ -584,6 +584,6 @@ leerness release pack --close --auto-main-push
|
|
|
584
584
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
585
585
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
586
586
|
|
|
587
|
-
Last synced by Leerness v1.9.
|
|
587
|
+
Last synced by Leerness v1.9.316: 2026-06-04
|
|
588
588
|
<!-- leerness:project-readme:end -->
|
|
589
589
|
|
package/bin/harness.js
CHANGED
|
@@ -14,7 +14,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
|
|
|
14
14
|
// 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
|
|
15
15
|
const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST } = require('../lib/catalogs');
|
|
16
16
|
|
|
17
|
-
const VERSION = '1.9.
|
|
17
|
+
const VERSION = '1.9.316';
|
|
18
18
|
|
|
19
19
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
20
20
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -3113,6 +3113,8 @@ function _selfTestCases() {
|
|
|
3113
3113
|
{ name: 'secret 스캐너 현대 키: OpenAI proj/svcacct·Anthropic api03(_)·GitHub 변종·Stripe·npm 검출 + 오탐 가드 (UR-0050 설치리뷰 1.9.312)', run: () => { const hit = (s) => SECRET_PATTERNS.some(p => { p.re.lastIndex = 0; return p.re.test(s); }); const named = (s, nm) => SECRET_PATTERNS.some(p => { p.re.lastIndex = 0; return p.re.test(s) && p.name === nm; }); const A = 'A'.repeat(40); const projKey = 'sk-' + 'proj-' + A + '_' + A; const svcKey = 'sk-' + 'svcacct-' + A; const antKey = 'sk-' + 'ant-api03-' + A + '_' + A; const ghoKey = 'gho_' + 'a1B2'.repeat(9); const stripeKey = 'sk_' + 'live_' + A; const npmKey = 'npm_' + 'a1B2'.repeat(9); const asiaKey = 'ASIA' + 'ABCD1234EFGH5678'; const legacy = 'sk-' + A; const hits = hit(projKey) && hit(svcKey) && hit(antKey) && hit(ghoKey) && hit(stripeKey) && hit(npmKey) && hit(asiaKey) && hit(legacy); const names = named(projKey, 'OpenAI project/service key') && named(antKey, 'Anthropic API key') && named(stripeKey, 'Stripe secret key') && named(npmKey, 'npm token'); const clean = !hit('const userName = "john' + '_doe_2024";') && !hit('https://example.com/path/to/page'); return hits && names && clean; } },
|
|
3114
3114
|
{ name: 'MCP notification 준수: id없는 요청 무응답 가드 + ping {} (UR-0049 설치리뷰 1.9.313)', run: () => { const src = read(__filename); const guard = src.includes("const isNotification = !('id' in req)") && src.includes("req.method.startsWith('notifications/')") && src.includes('if (isNotification) return;'); const ping = src.includes("req.method === 'ping'") && /ping[\s\S]{0,140}result: \{\} \}/.test(src); return guard && ping; } },
|
|
3115
3115
|
{ name: 'PowerShell 감지: pwsh7(channel/Documents\\PowerShell/install) + ps5.1 영구경로 과경고 안함 (UR-0052 설치리뷰 1.9.314)', run: () => { const f = _detectPwshFromEnv; const pwsh7a = f({ POWERSHELL_DISTRIBUTION_CHANNEL: 'MSI:Windows 10' }).version === '7'; const pwsh7b = f({ PSModulePath: 'C:\\Users\\me\\Documents\\PowerShell\\Modules' }).version === '7'; const pwsh7c = f({ PSModulePath: 'C:\\Program Files\\PowerShell\\7\\Modules' }).version === '7'; const noFalsePs5 = f({ PSModulePath: 'C:\\Users\\me\\Documents\\WindowsPowerShell\\Modules' }).isPowerShell === false; const cmdSys = f({ PSModulePath: 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\Modules' }).isPowerShell === false; const empty = f({}).isPowerShell === false; const src = read(__filename); const wired = src.includes('const fromEnv = _detectPwshFromEnv()') && src.includes('const pwshEnv = _detectPwshFromEnv()'); return pwsh7a && pwsh7b && pwsh7c && noFalsePs5 && cmdSys && empty && wired; } },
|
|
3116
|
+
{ name: 'doc/surface 정합: doctor 명령 + stale MCP 카운트 동적화(commands/banner) (UR-0054 설치리뷰 1.9.315)', run: () => { const src = read(__filename); const doctorOk = typeof doctorCmd === 'function' && /cmd === 'doctor'/.test(src) && /# leerness doctor/.test(src); const dynCount = /MCP 도구: \$\{_mcpToolCount\(\)\}/.test(src) && /외부 AI 통합 \(MCP \$\{_mcpToolCount\(\)\} 도구\)/.test(src); return doctorOk && dynCount; } },
|
|
3117
|
+
{ name: 'drift 마커 버그: session-handoff 프론트매터는 ^--- 일 때만 + drift 최신 Last generated (1.9.316)', run: () => { const src = read(__filename); const writeFix = src.includes('if (/^---\\r?\\n/.test(cur))') && src.includes('writeUtf8(handoffPath(root), frontmatter + block)'); const readFix = src.includes('matchAll(/Last generated') && src.includes('allGen[allGen.length - 1]'); return writeFix && readFix; } },
|
|
3116
3118
|
{ name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
|
|
3117
3119
|
];
|
|
3118
3120
|
}
|
|
@@ -3791,7 +3793,7 @@ function commandsCmd(root) {
|
|
|
3791
3793
|
log('');
|
|
3792
3794
|
}
|
|
3793
3795
|
log(dm(` → 총 ${Object.values(cats).reduce((s, a) => s + a.length, 0)} 명령 (9 카테고리)`));
|
|
3794
|
-
log(dm(` → MCP 도구:
|
|
3796
|
+
log(dm(` → MCP 도구: ${_mcpToolCount()} (외부 AI 노출)`)); // 1.9.315 (UR-0054): 하드코딩 65 → 동적
|
|
3795
3797
|
log(dm(` → 빠른 시작: leerness pulse | handoff | health`));
|
|
3796
3798
|
}
|
|
3797
3799
|
|
|
@@ -11285,7 +11287,7 @@ function _banner(opts = {}) {
|
|
|
11285
11287
|
cmd('leerness env sync .', '.env ↔ .env.example 동기화');
|
|
11286
11288
|
cmd('leerness health . --json', '종합 헬스 (drift+보안+skill+feature)');
|
|
11287
11289
|
|
|
11288
|
-
section(
|
|
11290
|
+
section(`🤖 외부 AI 통합 (MCP ${_mcpToolCount()} 도구)`); // 1.9.315 (UR-0054): 하드코딩 46 → 동적
|
|
11289
11291
|
cmd('npx leerness mcp serve', 'stdio JSON-RPC server');
|
|
11290
11292
|
cmd('leerness memory status . --json', '5 surface + featureGraph 한 호출');
|
|
11291
11293
|
cmd('leerness memory archive list --query "kw"', 'DELETE 5종 archive 검색');
|
|
@@ -12245,9 +12247,15 @@ function sessionClose(root, opts = {}) {
|
|
|
12245
12247
|
``
|
|
12246
12248
|
].join('\n');
|
|
12247
12249
|
const cur = exists(handoffPath(root)) ? read(handoffPath(root)) : '';
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12250
|
+
// 1.9.316 (drift 마커 버그): 프론트매터는 파일이 '---' 로 시작할 때만 추출.
|
|
12251
|
+
// 이전: 본문의 '---'(수평선/구분자)을 프론트매터 종료로 오인 → 구 블록(구 'Last generated')을 보존 →
|
|
12252
|
+
// session-handoff.md 에 'Last generated' 중복 누적 → drift 가 첫(=구) 매치를 읽어 'session close 누락' 영구 오발화.
|
|
12253
|
+
let frontmatter = '';
|
|
12254
|
+
if (/^---\r?\n/.test(cur)) {
|
|
12255
|
+
const fmEnd = cur.indexOf('\n---\n', 4);
|
|
12256
|
+
if (fmEnd > 0) frontmatter = cur.slice(0, fmEnd + 5) + MARK + '\n';
|
|
12257
|
+
}
|
|
12258
|
+
writeUtf8(handoffPath(root), frontmatter + block);
|
|
12251
12259
|
|
|
12252
12260
|
if (exists(currentStatePath(root))) {
|
|
12253
12261
|
let cs = read(currentStatePath(root));
|
|
@@ -15587,7 +15595,9 @@ function driftCheckCmd(root, opts = {}) {
|
|
|
15587
15595
|
const shPath = handoffPath(root);
|
|
15588
15596
|
if (exists(shPath)) {
|
|
15589
15597
|
const txt = read(shPath);
|
|
15590
|
-
|
|
15598
|
+
// 1.9.316 (drift 마커 버그): 최신(마지막) 'Last generated' 사용 — 구 블록 중복 시 첫(구) 매치를 읽던 오발화 방어.
|
|
15599
|
+
const allGen = [...txt.matchAll(/Last generated:\s*([\d\-T:.Z]+)/g)];
|
|
15600
|
+
const m = allGen.length ? allGen[allGen.length - 1] : null;
|
|
15591
15601
|
let ageDays;
|
|
15592
15602
|
if (m) {
|
|
15593
15603
|
ageDays = (now - new Date(m[1]).getTime()) / 86400000;
|
|
@@ -21386,6 +21396,46 @@ function reviewRequestCmd(root, request) {
|
|
|
21386
21396
|
|
|
21387
21397
|
// 1.9.164: leerness which — 진단 도구 (구버전 충돌 / npx 캐시 / PATH 충돌 해결)
|
|
21388
21398
|
// 사용자가 "최신 버전 작동 안 함" 의심 시: 실제 실행 중인 leerness 의 경로 / 버전 / npm 캐시 / PATH 후보 표시.
|
|
21399
|
+
// 1.9.315 (UR-0054, 설치리뷰): leerness doctor — 설치/환경 1콜 진단 (selftest 코어 무결성 + 버전/경로 + 셸/PowerShell).
|
|
21400
|
+
// 배경: 리뷰에서 doctor 명령 부재 지적("doctor no-op"). health(프로젝트 상태)와 달리 doctor 는 "도구 자체"의 정상성 진단.
|
|
21401
|
+
function doctorCmd(opts = {}) {
|
|
21402
|
+
const json = opts.json || has('--json');
|
|
21403
|
+
// 1) 코어 무결성: selftest 케이스 인라인 실행
|
|
21404
|
+
let pass = 0; const failNames = [];
|
|
21405
|
+
try {
|
|
21406
|
+
for (const c of _selfTestCases()) {
|
|
21407
|
+
let okc = false;
|
|
21408
|
+
try { okc = !!c.run(); } catch { okc = false; }
|
|
21409
|
+
if (okc) pass++; else failNames.push(c.name);
|
|
21410
|
+
}
|
|
21411
|
+
} catch {}
|
|
21412
|
+
const total = pass + failNames.length;
|
|
21413
|
+
// 2) 셸/PowerShell 컨텍스트 (UR-0052)
|
|
21414
|
+
let shell = null, psVersion = null;
|
|
21415
|
+
try { const ctx = _detectShellCtx(); shell = ctx.shell; psVersion = ctx.psVersion; } catch {}
|
|
21416
|
+
const mcpCount = (() => { try { return _mcpToolCount(); } catch { return null; } })();
|
|
21417
|
+
const report = {
|
|
21418
|
+
version: VERSION, node: process.version, platform: process.platform + '/' + process.arch,
|
|
21419
|
+
runningFrom: __filename, mcpTools: mcpCount,
|
|
21420
|
+
selftest: { pass, total, ok: failNames.length === 0, failed: failNames },
|
|
21421
|
+
shell, psVersion, healthy: failNames.length === 0
|
|
21422
|
+
};
|
|
21423
|
+
if (json) { process.stdout.write(JSON.stringify(report, null, 2) + '\n'); if (!report.healthy) process.exitCode = 1; return report; }
|
|
21424
|
+
const isTty = process.stdout && process.stdout.isTTY;
|
|
21425
|
+
const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s, rd = s => isTty ? `\x1b[31m${s}\x1b[0m` : s, cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s, dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
|
|
21426
|
+
log(cy(`# leerness doctor — 설치/환경 진단`));
|
|
21427
|
+
log('');
|
|
21428
|
+
log(` ${gr('✓')} version ${VERSION} · node ${process.version} · ${process.platform}/${process.arch}`);
|
|
21429
|
+
log(` ${gr('✓')} 설치 경로: ${dm(__filename)}`);
|
|
21430
|
+
log(` ${gr('✓')} MCP 도구: ${mcpCount}`);
|
|
21431
|
+
log(` ${report.selftest.ok ? gr('✓') : rd('✗')} selftest: ${pass}/${total} ${report.selftest.ok ? '통과' : '실패'}`);
|
|
21432
|
+
if (!report.selftest.ok) report.selftest.failed.slice(0, 5).forEach(n => log(rd(` ✗ ${n}`)));
|
|
21433
|
+
log(` ${gr('✓')} 셸: ${shell || 'unknown'}${psVersion && shell === 'powershell' ? ` (PowerShell ${psVersion})` : ''}`);
|
|
21434
|
+
log('');
|
|
21435
|
+
log(report.healthy ? gr(' ✓ leerness 설치 정상') : rd(' ✗ 문제 감지 — 재설치: npm i -g leerness@latest · 진단: leerness which'));
|
|
21436
|
+
if (!report.healthy) process.exitCode = 1;
|
|
21437
|
+
return report;
|
|
21438
|
+
}
|
|
21389
21439
|
function whichCmd() {
|
|
21390
21440
|
const out = {
|
|
21391
21441
|
version: VERSION,
|
|
@@ -21559,6 +21609,8 @@ async function main() {
|
|
|
21559
21609
|
if (cmd === 'provider') return providerCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
21560
21610
|
// 1.9.164: leerness which — 진단 도구 (구버전 충돌 / npx 캐시 / PATH 후보)
|
|
21561
21611
|
if (cmd === 'which') return whichCmd();
|
|
21612
|
+
// 1.9.315 (UR-0054): leerness doctor — 설치/환경 1콜 진단 (selftest + 버전/경로 + 셸)
|
|
21613
|
+
if (cmd === 'doctor') return doctorCmd({ json: has('--json') });
|
|
21562
21614
|
// 1.9.165: leerness web — playwright bridge (opt-in 의존성)
|
|
21563
21615
|
if (cmd === 'web') return webCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
|
|
21564
21616
|
// 1.9.166: leerness pc — robotjs/nut-tree bridge (opt-in 의존성)
|
package/package.json
CHANGED
package/scripts/e2e.js
CHANGED
|
@@ -3711,5 +3711,46 @@ total++;
|
|
|
3711
3711
|
if (!ok) failed++;
|
|
3712
3712
|
}
|
|
3713
3713
|
|
|
3714
|
+
// 1.9.315 회귀 (UR-0054 설치리뷰): doc/surface 정합 — doctor 진단 명령 + stale MCP 카운트 동적화(commands/banner)
|
|
3715
|
+
total++;
|
|
3716
|
+
{
|
|
3717
|
+
let ok = false;
|
|
3718
|
+
try {
|
|
3719
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'doctor', '--json'], { encoding: 'utf8', timeout: 30000 });
|
|
3720
|
+
let j = null; try { j = JSON.parse(r.stdout); } catch {}
|
|
3721
|
+
const doctorOk = j && j.version && typeof j.mcpTools === 'number' && j.mcpTools >= 80 && j.selftest && j.selftest.total > 0 && j.healthy === true && r.status === 0;
|
|
3722
|
+
// commands 요약 + banner 가 실제 MCP 수 노출 (하드코딩 65/46 아님)
|
|
3723
|
+
const rc = cp.spawnSync(process.execPath, [CLI, 'commands'], { encoding: 'utf8', timeout: 15000 });
|
|
3724
|
+
const dynOk = j && new RegExp('MCP 도구: ' + j.mcpTools).test(rc.stdout || '') && !/MCP 도구: 65\b/.test(rc.stdout || '');
|
|
3725
|
+
ok = doctorOk && dynOk;
|
|
3726
|
+
} catch {}
|
|
3727
|
+
console.log(ok ? '✓ B(1.9.315) doc/surface: doctor 진단(selftest+버전+셸) + commands MCP 카운트 동적 (UR-0054)' : '✗ doc/surface 실패');
|
|
3728
|
+
if (!ok) failed++;
|
|
3729
|
+
}
|
|
3730
|
+
|
|
3731
|
+
// 1.9.316 회귀 (drift 마커 버그): session-handoff 'Last generated' 중복 누적 방지 + drift 'session close 누락' 클리어
|
|
3732
|
+
// 근본: sessionClose 프론트매터 추출이 본문 '---' 를 오인 → 구 블록 보존 → 첫(구) Last generated 를 drift 가 읽어 영구 오발화
|
|
3733
|
+
total++;
|
|
3734
|
+
{
|
|
3735
|
+
let ok = false;
|
|
3736
|
+
try {
|
|
3737
|
+
const dd = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-driftmark-'));
|
|
3738
|
+
cp.spawnSync(process.execPath, [CLI, 'init', dd, '--yes', '--language', 'ko', '--skills', 'recommended'], { encoding: 'utf8', timeout: 30000 });
|
|
3739
|
+
const shp = path.join(dd, '.harness', 'session-handoff.md');
|
|
3740
|
+
// 손상 시뮬: 구 timestamp + 본문 '---' (프론트매터 없는 파일)
|
|
3741
|
+
fs.writeFileSync(shp, '# Session Handoff\n\nLast generated: 2026-01-01T00:00:00.000Z\n\n## Completed\n- old\n\n---\n## x\n');
|
|
3742
|
+
cp.spawnSync(process.execPath, [CLI, 'session', 'close', dd], { encoding: 'utf8', timeout: 30000 });
|
|
3743
|
+
const after = fs.readFileSync(shp, 'utf8');
|
|
3744
|
+
const genCount = (after.match(/Last generated:/g) || []).length; // 단일화
|
|
3745
|
+
const noOld = !after.includes('2026-01-01'); // 구 timestamp 제거
|
|
3746
|
+
const dr = cp.spawnSync(process.execPath, [CLI, 'drift', 'check', dd, '--json'], { encoding: 'utf8', timeout: 20000 });
|
|
3747
|
+
let driftOk = false; try { const j = JSON.parse(dr.stdout); const sig = (j.signals || []).find(x => x.label && x.label.includes('session close')); driftOk = sig && sig.ageDays <= sig.threshold; } catch {}
|
|
3748
|
+
ok = genCount === 1 && noOld && driftOk;
|
|
3749
|
+
fs.rmSync(dd, { recursive: true, force: true });
|
|
3750
|
+
} catch {}
|
|
3751
|
+
console.log(ok ? '✓ B(1.9.316) drift 마커: session-handoff Last generated 단일화 + session close 누락 신호 클리어' : '✗ drift 마커 실패');
|
|
3752
|
+
if (!ok) failed++;
|
|
3753
|
+
}
|
|
3754
|
+
|
|
3714
3755
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
3715
3756
|
if (failed > 0) process.exit(1);
|