leerness 1.36.67 → 1.36.69
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 +19 -0
- package/README.md +4 -4
- package/bin/leerness.js +2 -2
- package/lib/diagnostics.js +19 -1
- package/lib/mcp-tools.js +1 -1
- package/lib/session-close.js +14 -1
- package/package.json +1 -1
- package/scripts/e2e.js +65 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.69 — 2026-07-22 — 성장 한계 클래스: whats-new --json 응답 상한 (804KB → 30KB) — R-0001 검수 12회전
|
|
4
|
+
|
|
5
|
+
직전 게이트가 우연히 드러낸 누적 성장 문제를 픽스처 우회가 아니라 제품에서 해결. CHANGELOG 1MB 돌파로 `--json` 이 804KB 를 뱉어 AI 소비자 컨텍스트를 폭파시키고 기본 stdout 버퍼에서 절단됐다.
|
|
6
|
+
|
|
7
|
+
- **기본 최신 30개 버전 + 정직한 절단 표기**(`totalVersions`/`shown`/`truncated`/`hint`), 전체는 `--all`, 개수는 `--limit N`. 텍스트 모드 무변경. 실측 804KB → 30KB.
|
|
8
|
+
- (검수 #1 Medium) **MCP 계약 동반 갱신**: `leerness_whats_new` 에 `limit`/`all` 인자 노출 + 전달 — 종전 그대로면 MCP 소비자는 30개로 조용히 잘리고 조절 수단이 없었다(실측 확인 후 수정).
|
|
9
|
+
- (검수 #2) 무효/비양수 `--limit`(0/-5/abc) 는 기본값 폴백(종전 -5 → 1). (검수 #3) 절단 힌트 UI 언어 준수. (검수 #4) e2e 단언을 `shown === 30` 정확 일치로 + 무효 limit·비절단 hint 부재·MCP 전달 케이스 추가.
|
|
10
|
+
- **후속 계측(검수 제공)**: migrate stdout ~88KB · memory search(--limit huge) ~54KB · retro/insights ~40KB · task list ~33KB — 다음 상한 후보로 기록(현재는 안전 범위).
|
|
11
|
+
- 검증: selftest 334, e2e +1(크기 회귀 가드 포함).
|
|
12
|
+
|
|
13
|
+
## 1.36.68 — 2026-07-22 — F14 재구현: 마감 보고의 미승인 미리보기 (8차 헌트 이연분 종결) — R-0001 검수 11회전
|
|
14
|
+
|
|
15
|
+
1.36.67 에서 이연했던 F14 를 원인 규명 후 재구현.
|
|
16
|
+
|
|
17
|
+
- **이연 원인(규명)**: 이전 삽입 지점이 `try/finally` 밖 **jsonMode 전용 후처리 구간**이라 텍스트 경로가 도달조차 못 했다(추정 아님 — 코드 구조 확인). 텍스트 렌더 구간으로 이동해 양 모드 동일 데이터 출력.
|
|
18
|
+
- **`session close` 가 승인 대기 미리보기를 보고**: 텍스트 블록(최대 5줄) + `pendingPreviews {count, items[≤10]}` JSON 필드. proposed/revision-requested 는 대기, approved 는 제외. 손상 previews.json 은 count 0 으로 안전 강등(마감 중단 없음 — 검수 확인).
|
|
19
|
+
- (검수 Medium) **`--no-suggest`/`LEERNESS_NO_SUGGEST=1` 에서도 노출**: suggest 블록 안에 있어 승인 대기 상태가 사라지던 것 — 공용 구간으로 이동 + e2e 회귀 가드.
|
|
20
|
+
- 검증: 텍스트/JSON 일치·JSON 순수성·12건 캡(텍스트 5·items 10)·en 렌더·승인 시 소거(검수 프로브), selftest 334, e2e +1.
|
|
21
|
+
|
|
3
22
|
## 1.36.67 — 2026-07-22 — tech 갱신 시 그래프 동반 정합 (8차 헌트 F15)
|
|
4
23
|
|
|
5
24
|
- **F15**: `leerness tech` 가 프로필 변경을 감지하면 이미 존재하는 `leerness.html` 의 🛠 탭도 함께 갱신 — 종전엔 프로필만 최신이고 그래프는 옛 서비스를 계속 보여줬다(다음 handoff 전까지 불일치). 그래프 산출물이 없는 프로젝트는 무동작(생성 강제 안 함).
|
package/README.md
CHANGED
|
@@ -122,7 +122,7 @@ MIT
|
|
|
122
122
|
<!-- leerness:project-readme:start -->
|
|
123
123
|
## Leerness Project Harness
|
|
124
124
|
|
|
125
|
-
이 프로젝트는 Leerness v1.36.
|
|
125
|
+
이 프로젝트는 Leerness v1.36.69 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
|
|
126
126
|
|
|
127
127
|
### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
|
|
128
128
|
|
|
@@ -176,7 +176,7 @@ leerness memory restore decision <date|title>
|
|
|
176
176
|
|
|
177
177
|
### MCP server (외부 AI 통합)
|
|
178
178
|
|
|
179
|
-
Leerness v1.36.
|
|
179
|
+
Leerness v1.36.69는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
|
|
180
180
|
|
|
181
181
|
```jsonc
|
|
182
182
|
// 카테고리별
|
|
@@ -197,7 +197,7 @@ Leerness v1.36.67는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
|
|
|
197
197
|
`<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
|
|
198
198
|
1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) 회귀 테스트 갱신 → 4) 전체 e2e 스위트 통과 → 5) npm publish + git tag → 6) main push → 7) session close → 8) 다음 라운드 예약.
|
|
199
199
|
|
|
200
|
-
현재 누적: **v1.9.x → 1.36.
|
|
200
|
+
현재 누적: **v1.9.x → 1.36.69 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
|
|
201
201
|
|
|
202
202
|
### 성능 가이드
|
|
203
203
|
|
|
@@ -235,5 +235,5 @@ leerness release pack --close --auto-main-push
|
|
|
235
235
|
- `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
|
|
236
236
|
- `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
|
|
237
237
|
|
|
238
|
-
Last synced by Leerness v1.36.
|
|
238
|
+
Last synced by Leerness v1.36.69: 2026-07-23
|
|
239
239
|
<!-- leerness:project-readme:end -->
|
package/bin/leerness.js
CHANGED
|
@@ -34,7 +34,7 @@ const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE
|
|
|
34
34
|
const { tokenizeForRank: _tokenizeForRank, expandQuery: _expandQuery, scoreHits: _scoreHits, suggestTerms: _suggestTerms } = require('../lib/search-core'); // 1.36.23: memory search 랭킹 코어(순수·0-deps)
|
|
35
35
|
const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태 무결성 (audit/health/check 공유)
|
|
36
36
|
|
|
37
|
-
const VERSION = '1.36.
|
|
37
|
+
const VERSION = '1.36.69';
|
|
38
38
|
|
|
39
39
|
// 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
|
|
40
40
|
// 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
|
|
@@ -18666,7 +18666,7 @@ function _mcpToCliArgs(name, args, targetPath) {
|
|
|
18666
18666
|
case 'leerness_contract_verify': cliArgs = ['contract', 'verify', args.spec, args.impl]; break;
|
|
18667
18667
|
case 'leerness_agents_list': cliArgs = ['agents', 'list', '--json']; break;
|
|
18668
18668
|
case 'leerness_reuse_map': cliArgs = ['reuse-map', targetPath, ...(args.allApps ? ['--all-apps'] : []), ...(args.strictElements ? ['--strict-elements'] : []), '--json']; break;
|
|
18669
|
-
case 'leerness_whats_new': cliArgs = ['whats-new', '--path', targetPath, ...(args.from ? ['--from', args.from] : []), ...(args.to ? ['--to', args.to] : []), '--json']; break;
|
|
18669
|
+
case 'leerness_whats_new': cliArgs = ['whats-new', '--path', targetPath, ...(args.from ? ['--from', args.from] : []), ...(args.to ? ['--to', args.to] : []), ...(args.all ? ['--all'] : []), ...(args.limit ? ['--limit', String(args.limit)] : []), '--json']; break; // 1.36.69 (검수 #1): limit/all 전달 — MCP 소비자도 상한 조절 가능
|
|
18670
18670
|
case 'leerness_usage_stats': cliArgs = ['usage', 'stats', targetPath, '--json']; break;
|
|
18671
18671
|
case 'leerness_session_close': cliArgs = ['session', 'close', targetPath, '--json']; break;
|
|
18672
18672
|
case 'leerness_skill_suggest': cliArgs = ['skill', 'suggest', '--path', targetPath, '--json', ...(args.min ? ['--min', String(args.min)] : []), ...(args.days ? ['--days', String(args.days)] : [])]; break;
|
package/lib/diagnostics.js
CHANGED
|
@@ -161,7 +161,25 @@ function whatsNewCmd(root, deps = {}) {
|
|
|
161
161
|
return process.exit(1);
|
|
162
162
|
}
|
|
163
163
|
const diff = _parseChangelogBetween(read(changelogPath), fromV, toV);
|
|
164
|
-
if (has('--json')) {
|
|
164
|
+
if (has('--json')) {
|
|
165
|
+
// 1.36.69 (성장 한계 클래스): CHANGELOG 누적(1MB+)으로 --json 이 800KB 를 넘겨 AI 소비자의 컨텍스트를 폭파시켰다.
|
|
166
|
+
// 기본은 최신 30개 버전으로 제한(에이전트가 실제로 읽는 범위) + 절단 사실을 정직하게 표기. 전체는 --all/--limit 로 옵트인.
|
|
167
|
+
// (검수 #2) 무효/비양수 limit 은 기본값으로 폴백 — `--limit -5` 가 1 이 되던 비일관 제거
|
|
168
|
+
const _rawLim = parseInt(arg('--limit', '30'), 10);
|
|
169
|
+
const _lim = has('--all') ? diff.length : (Number.isFinite(_rawLim) && _rawLim > 0 ? _rawLim : 30);
|
|
170
|
+
const _shown = diff.slice(0, _lim);
|
|
171
|
+
const _truncated = _shown.length < diff.length;
|
|
172
|
+
log(JSON.stringify({
|
|
173
|
+
from: fromV, to: toV,
|
|
174
|
+
versions: _shown,
|
|
175
|
+
totalVersions: diff.length,
|
|
176
|
+
shown: _shown.length,
|
|
177
|
+
truncated: _truncated,
|
|
178
|
+
// (검수 #3) 힌트도 UI 언어 준수
|
|
179
|
+
...(_truncated ? { hint: t(`최신 ${_shown.length}개만 표시 — 전체는 --all, 개수 조절은 --limit N`, `showing newest ${_shown.length} only — use --all for everything, --limit N to adjust`) } : {})
|
|
180
|
+
}, null, 2));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
165
183
|
if (!diff.length) {
|
|
166
184
|
log(`# leerness whats-new (1.9.41)`);
|
|
167
185
|
log(t(`현재 ${fromV} ↔ 대상 ${toV}: 새 항목 없음 (또는 CHANGELOG에 기록 안 됨)`, `current ${fromV} ↔ target ${toV}: no new entries (or not recorded in CHANGELOG)`));
|
package/lib/mcp-tools.js
CHANGED
|
@@ -12,7 +12,7 @@ module.exports = [
|
|
|
12
12
|
{ name: 'leerness_contract_verify', requiredTier: 'read-only', description: '명세 ↔ 구현 함수/필드 일치 자동 검사', inputSchema: { type: 'object', properties: { spec: { type: 'string' }, impl: { type: 'string' } }, required: ['spec', 'impl'] } },
|
|
13
13
|
{ name: 'leerness_agents_list', requiredTier: 'read-only', description: '외부 AI CLI 가용성 표 (claude/codex/agy/copilot 상태 + 환경변수 활성화 여부)', inputSchema: { type: 'object', properties: {} } },
|
|
14
14
|
{ name: 'leerness_reuse_map', requiredTier: 'read-only', description: '워크스페이스 중복 함수/capability 자동 감지 (--all-apps + fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, allApps: { type: 'boolean' }, strictElements: { type: 'boolean' } } } },
|
|
15
|
-
{ name: 'leerness_whats_new', requiredTier: 'read-only', description: 'CHANGELOG 차분 자동 추출 (from → to 사이 신규 명령/플래그/파일)', inputSchema: { type: 'object', properties: { from: { type: 'string' }, to: { type: 'string' } } } },
|
|
15
|
+
{ name: 'leerness_whats_new', requiredTier: 'read-only', description: 'CHANGELOG 차분 자동 추출 (from → to 사이 신규 명령/플래그/파일). 1.36.69: 기본 최신 30개 버전만 반환(응답 폭주 방지) — 응답의 totalVersions/shown/truncated 로 절단 여부 확인, 더 필요하면 limit(개수) 또는 all=true(전체, 대용량 주의) 지정.', inputSchema: { type: 'object', properties: { from: { type: 'string' }, to: { type: 'string' }, limit: { type: 'number' }, all: { type: 'boolean' } } } },
|
|
16
16
|
{ name: 'leerness_usage_stats', requiredTier: 'read-only', description: 'leerness 명령별 누적 호출 통계 + drift 통계', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
17
17
|
{ name: 'leerness_session_close', requiredTier: 'read-only', description: '1.9.103 — 세션 마감 JSON (handoff/current-state/task-log 갱신 + taskCounts + rules + skillCandidates + drift + topCommands + mcpStats). 외부 AI가 마감 통계 자동 회수', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
18
18
|
{ name: 'leerness_skill_suggest', requiredTier: 'read-only', description: '1.9.53 — 사용 패턴 자동 분석 → 새 skill 후보 제안 (Hermes-style 자동 학습)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, min: { type: 'number' }, days: { type: 'number' } } } },
|
package/lib/session-close.js
CHANGED
|
@@ -142,7 +142,19 @@ function sessionClose(root, opts = {}, deps = {}) {
|
|
|
142
142
|
ok(`session-handoff.md and current-state.md updated`);
|
|
143
143
|
// 1.9.12: session close 끝에 roadmap.html 자동 갱신
|
|
144
144
|
_autoRoadmap(root, 'session-close');
|
|
145
|
-
// 1.
|
|
145
|
+
// 1.36.68 (8차 헌트 F14 재구현, 검수: --no-suggest 에서도 노출): 미승인 미리보기(approve 대기)를 마감 보고에 노출 —
|
|
146
|
+
// 1.36.67 이연 원인: 삽입 지점이 jsonMode 전용 후처리 구간이라 텍스트 경로가 도달조차 안 했다.
|
|
147
|
+
// 이제 텍스트 렌더 구간에서 출력하고 JSON 필드는 동일 데이터로 채운다(양 모드 일치).
|
|
148
|
+
try {
|
|
149
|
+
let _pend = [];
|
|
150
|
+
try { _pend = deps.pendingPreviews ? deps.pendingPreviews(root) : require('./clarify').pendingPreviews(root); } catch {}
|
|
151
|
+
jsonResult.pendingPreviews = { count: _pend.length, items: _pend.slice(0, 10).map(p => ({ id: p.id, title: p.title, status: p.status })) };
|
|
152
|
+
if (_pend.length) {
|
|
153
|
+
log(t(` 🎨 미승인 미리보기 ${_pend.length}건 — approve 전 해당 기능 코드 작성 금지`, ` 🎨 ${_pend.length} preview(s) awaiting approval — no code before approve`));
|
|
154
|
+
for (const p of _pend.slice(0, 5)) log(` - ${p.id} ${p.title} [${p.status}]`);
|
|
155
|
+
log(t(' → 승인: leerness preview approve <P-ID> · 수정요구: leerness preview revise <P-ID> --note "..."', ' → approve: leerness preview approve <P-ID> · revise: leerness preview revise <P-ID> --note "..."'));
|
|
156
|
+
}
|
|
157
|
+
} catch {} // 1.9.57: --suggest 옵션 — 마감 시 skill suggest + drift check + lessons 통합 보고
|
|
146
158
|
// 1.9.59: default 활성 — --no-suggest로 명시 비활성 가능
|
|
147
159
|
const suggestEnabled = (has('--suggest') || (!has('--no-suggest') && process.env.LEERNESS_NO_SUGGEST !== '1'));
|
|
148
160
|
if (suggestEnabled) {
|
|
@@ -173,6 +185,7 @@ function sessionClose(root, opts = {}, deps = {}) {
|
|
|
173
185
|
jsonResult.drift = { level: j.level, score: j.score, fired: (j.fired || []).map(f => ({ label: f.label, weight: f.weight })) };
|
|
174
186
|
}
|
|
175
187
|
} catch {}
|
|
188
|
+
|
|
176
189
|
// 3) usage stats top
|
|
177
190
|
try {
|
|
178
191
|
const stats = _readUsageStats(root);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.69",
|
|
4
4
|
"description": "The AI-coding operations layer that makes \"done\" require evidence — persistent memory, evidence-gated completion checks, and clean handoffs for any AI agent (Claude Code, Codex, Cursor). State lives as plain files in your repo. CLI + MCP, 0 runtime dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"leerness",
|
package/scripts/e2e.js
CHANGED
|
@@ -1444,7 +1444,8 @@ total++;
|
|
|
1444
1444
|
total++;
|
|
1445
1445
|
{
|
|
1446
1446
|
// whats-new --from 큰 점프 → 신규 명령 추출
|
|
1447
|
-
|
|
1447
|
+
// 1.36.69: 기본 응답은 최신 30개로 제한됨 — 전체 차분 검증은 --all (대용량이라 maxBuffer 명시)
|
|
1448
|
+
const r = cp.spawnSync(process.execPath, [CLI, 'whats-new', '--from', '1.9.33', '--json', '--all'], { encoding: 'utf8', timeout: 20000, maxBuffer: 32 * 1024 * 1024 });
|
|
1448
1449
|
let parsed = null;
|
|
1449
1450
|
try { parsed = JSON.parse(r.stdout); } catch {}
|
|
1450
1451
|
const ok = parsed
|
|
@@ -7143,5 +7144,68 @@ total++;
|
|
|
7143
7144
|
if (!ok) failed++;
|
|
7144
7145
|
}
|
|
7145
7146
|
|
|
7147
|
+
// 1.36.68 (8차 헌트 F14 재구현): 마감 보고/JSON 에 미승인 미리보기 노출 — 텍스트·JSON 양 모드 + 승인 후 소거
|
|
7148
|
+
total++;
|
|
7149
|
+
{
|
|
7150
|
+
let ok = false;
|
|
7151
|
+
const _d = [];
|
|
7152
|
+
try {
|
|
7153
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-f14-')); _d.push(d);
|
|
7154
|
+
cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--no-env'], { encoding: 'utf8', timeout: 40000 });
|
|
7155
|
+
cp.spawnSync(process.execPath, [CLI, 'preview', 'add', '결제 화면', '--design', '모달', '--path', d], { encoding: 'utf8', timeout: 20000 });
|
|
7156
|
+
const txt = cp.spawnSync(process.execPath, [CLI, 'session', 'close', d], { encoding: 'utf8', timeout: 40000 });
|
|
7157
|
+
const textOk = txt.status === 0 && /미승인 미리보기 1건|1 preview\(s\) awaiting/.test(txt.stdout) && /P-0001/.test(txt.stdout);
|
|
7158
|
+
const js = cp.spawnSync(process.execPath, [CLI, 'session', 'close', d, '--json'], { encoding: 'utf8', timeout: 40000 });
|
|
7159
|
+
let jsonOk = false;
|
|
7160
|
+
try { const j = JSON.parse(js.stdout); jsonOk = j.pendingPreviews && j.pendingPreviews.count === 1 && j.pendingPreviews.items[0].id === 'P-0001'; } catch {}
|
|
7161
|
+
// (검수) --no-suggest 에서도 승인 대기 상태는 노출돼야 — suggest 게이트 밖 배치 회귀 가드
|
|
7162
|
+
const ns = cp.spawnSync(process.execPath, [CLI, 'session', 'close', d, '--no-suggest'], { encoding: 'utf8', timeout: 40000 });
|
|
7163
|
+
const nsJs = cp.spawnSync(process.execPath, [CLI, 'session', 'close', d, '--json', '--no-suggest'], { encoding: 'utf8', timeout: 40000 });
|
|
7164
|
+
let noSuggestOk = /미승인 미리보기|awaiting approval/.test(ns.stdout);
|
|
7165
|
+
try { noSuggestOk = noSuggestOk && JSON.parse(nsJs.stdout).pendingPreviews.count === 1; } catch { noSuggestOk = false; }
|
|
7166
|
+
cp.spawnSync(process.execPath, [CLI, 'preview', 'approve', 'P-0001', '--path', d], { encoding: 'utf8', timeout: 20000 });
|
|
7167
|
+
const after = cp.spawnSync(process.execPath, [CLI, 'session', 'close', d, '--json'], { encoding: 'utf8', timeout: 40000 });
|
|
7168
|
+
let clearedOk = false;
|
|
7169
|
+
try { clearedOk = JSON.parse(after.stdout).pendingPreviews.count === 0; } catch {}
|
|
7170
|
+
ok = textOk && jsonOk && noSuggestOk && clearedOk;
|
|
7171
|
+
if (!ok) console.log(` [f14 디버그] text=${textOk} json=${jsonOk} noSuggest=${noSuggestOk} cleared=${clearedOk}`);
|
|
7172
|
+
} catch (e) {} finally { _d.forEach(x => { try { fs.rmSync(x, { recursive: true, force: true }); } catch {} }); }
|
|
7173
|
+
console.log(ok ? '✓ B(1.36.68) F14: session close 미승인 미리보기 노출 (텍스트+JSON 일치 · --no-suggest 무영향 · 승인 시 소거)' : '✗ F14 미승인 미리보기 노출 실패');
|
|
7174
|
+
if (!ok) failed++;
|
|
7175
|
+
}
|
|
7176
|
+
|
|
7177
|
+
// 1.36.69 (성장 한계 클래스): whats-new --json 기본 상한 — AI 컨텍스트 보호 + 정직한 절단 표기 + --all/--limit 옵트인
|
|
7178
|
+
total++;
|
|
7179
|
+
{
|
|
7180
|
+
let ok = false;
|
|
7181
|
+
try {
|
|
7182
|
+
const R = (a) => cp.spawnSync(process.execPath, [CLI, 'whats-new', ...a], { encoding: 'utf8', timeout: 25000, maxBuffer: 32 * 1024 * 1024 });
|
|
7183
|
+
const def = JSON.parse(R(['--from', '1.9.33', '--json']).stdout);
|
|
7184
|
+
// (검수 #4) 정확 일치로 — shown<=30 은 기본값이 1로 퇴화해도 통과하던 느슨한 단언
|
|
7185
|
+
const defOk = def.shown === 30 && def.versions.length === def.shown && def.totalVersions > def.shown && def.truncated === true && typeof def.hint === 'string';
|
|
7186
|
+
// 무효 limit 은 기본값 폴백 (0/-5/abc)
|
|
7187
|
+
const badLim = ['0', '-5', 'abc'].every(v => { try { return JSON.parse(R(['--from', '1.9.33', '--json', '--limit', v]).stdout).shown === 30; } catch { return false; } });
|
|
7188
|
+
// 절단 없을 때 hint 부재
|
|
7189
|
+
const small = JSON.parse(R(['--from', '1.36.60', '--json']).stdout);
|
|
7190
|
+
const smallOk = small.truncated === false && small.hint === undefined && small.shown === small.totalVersions;
|
|
7191
|
+
// MCP 인자 전달 (limit)
|
|
7192
|
+
const mcpReq = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: 'leerness_whats_new', arguments: { from: '1.9.33', limit: 2 } } });
|
|
7193
|
+
const mcpR = cp.spawnSync(process.execPath, [CLI, 'mcp', 'serve'], { encoding: 'utf8', timeout: 25000, input: mcpReq + '\n', maxBuffer: 32 * 1024 * 1024 });
|
|
7194
|
+
let mcpOk = false;
|
|
7195
|
+
try { const txt = JSON.parse(mcpR.stdout.split('\n').filter(Boolean)[0]).result.content[0].text; mcpOk = JSON.parse(txt).shown === 2; } catch {}
|
|
7196
|
+
const all = JSON.parse(R(['--from', '1.9.33', '--json', '--all']).stdout);
|
|
7197
|
+
const allOk = all.shown === all.totalVersions && all.truncated === false && all.versions.length === def.totalVersions;
|
|
7198
|
+
const lim = JSON.parse(R(['--from', '1.9.33', '--json', '--limit', '3']).stdout);
|
|
7199
|
+
const limOk = lim.shown === 3 && lim.versions.length === 3;
|
|
7200
|
+
// 크기 회귀 가드: 기본 응답이 200KB 를 넘지 않아야(에이전트 컨텍스트 보호)
|
|
7201
|
+
const defRaw = R(['--from', '1.9.33', '--json']).stdout;
|
|
7202
|
+
const sizeOk = defRaw.length < 200 * 1024;
|
|
7203
|
+
ok = defOk && allOk && limOk && sizeOk && badLim && smallOk && mcpOk;
|
|
7204
|
+
if (!ok) console.log(` [wn 디버그] def=${defOk} all=${allOk} lim=${limOk} size=${sizeOk}(${Math.round(defRaw.length / 1024)}KB) bad=${badLim} small=${smallOk} mcp=${mcpOk}`);
|
|
7205
|
+
} catch (e) {}
|
|
7206
|
+
console.log(ok ? '✓ B(1.36.69) whats-new --json 상한 30(정확) + truncated/hint + --all/--limit + 무효limit 폴백 + MCP 전달 + 200KB 미만' : '✗ whats-new 상한 실패');
|
|
7207
|
+
if (!ok) failed++;
|
|
7208
|
+
}
|
|
7209
|
+
|
|
7146
7210
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
7147
7211
|
if (failed > 0) process.exit(1);
|