leerness 1.36.68 → 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 +10 -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/package.json +1 -1
- package/scripts/e2e.js +35 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
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
|
+
|
|
3
13
|
## 1.36.68 — 2026-07-22 — F14 재구현: 마감 보고의 미승인 미리보기 (8차 헌트 이연분 종결) — R-0001 검수 11회전
|
|
4
14
|
|
|
5
15
|
1.36.67 에서 이연했던 F14 를 원인 규명 후 재구현.
|
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.68는 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/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,8 +1444,8 @@ total++;
|
|
|
1444
1444
|
total++;
|
|
1445
1445
|
{
|
|
1446
1446
|
// whats-new --from 큰 점프 → 신규 명령 추출
|
|
1447
|
-
// 1.36.
|
|
1448
|
-
const r = cp.spawnSync(process.execPath, [CLI, 'whats-new', '--from', '1.9.33', '--json'], { encoding: 'utf8', timeout: 20000, maxBuffer: 32 * 1024 * 1024 });
|
|
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 });
|
|
1449
1449
|
let parsed = null;
|
|
1450
1450
|
try { parsed = JSON.parse(r.stdout); } catch {}
|
|
1451
1451
|
const ok = parsed
|
|
@@ -7174,5 +7174,38 @@ total++;
|
|
|
7174
7174
|
if (!ok) failed++;
|
|
7175
7175
|
}
|
|
7176
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
|
+
|
|
7177
7210
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
7178
7211
|
if (failed > 0) process.exit(1);
|