leerness 1.36.69 → 1.36.71
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 +35 -20
- package/lib/diagnostics.js +6 -1
- package/lib/mcp-tools.js +1 -1
- package/package.json +1 -1
- package/scripts/e2e.js +63 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.36.71 — 2026-07-24 — 성장 한계 클래스 3: retro/insights --json rows 상한 + 워크스페이스 --days 기존 버그 — R-0001 검수 14회전
|
|
4
|
+
|
|
5
|
+
- **retro/insights --json 의 data.rows 전량 임베드**(무한 성장, 72 task 에 22KB)를 기본 최신 30행으로 상한 — `rowsTotal/rowsShown/rowsTruncated` 정직 표기, `--all/--limit N` 옵트인, **집계 수치(totalTasks 등)는 전량 기준 그대로**(수치 왜곡 없음). 4개 JSON 배출 지점(단일/워크스페이스 × retro/insights) 일괄. 실측 33KB → 14KB.
|
|
6
|
+
- (검수 High) e2e 가 init 기본 T-0001 을 몰라 하드코딩 35 로 확정 실패할 케이스 — 기준선 유도 방식 + init/add exit 단언으로 교정(--force 가 init 실패를 가리던 문제도 제거).
|
|
7
|
+
- (검수 Medium) MCP `leerness_retro` 에 `limit`/`all` 노출+전달 (whats-new 1.36.69 와 동일 계약 규율). insights 는 MCP 도구 자체가 없음(현행 유지).
|
|
8
|
+
- (검수 Medium, **기존 버그**) 워크스페이스 retro(`--include/--all-apps`)가 `--days` 를 조용히 무시 — cutoff 를 `_retroWorkspace` 로 전달해 단일 모드와 일관(실측: days 0→fix 0 / 36500→fix 3 분리 확인).
|
|
9
|
+
- (검수 Low) help 에 `[--limit N|--all]` + rowsTotal 의미(수명 전체, --days 창 아님) 명시.
|
|
10
|
+
- 검증: selftest 334, e2e +1. codex 확인: 텍스트 모드 바이트 동일, data.rows 하위 소비자 없음, focusNext/집계 무변형.
|
|
11
|
+
|
|
12
|
+
## 1.36.70 — 2026-07-24 — 성장 한계 클래스 2: migrate 보고 상한 (88KB → 13.5KB) — R-0001 검수 13회전
|
|
13
|
+
|
|
14
|
+
1.36.69 whats-new 상한과 동일 규율을 migrate 보고서의 "AI must re-read" 섹션에 적용. 큰 버전 점프(602 릴리스)에서 보고서가 ~88KB 로 폭주해 AI 컨텍스트를 태우던 것.
|
|
15
|
+
|
|
16
|
+
- **헤드라인 최신 30 + 명령/플래그/파일 목록 40 캡**, 각 헤더에 실제 총수 표기 + 정직한 "… 외 N개" 오버플로 라인 + `whats-new --from X --all/--limit N` 탈출구. 작은 점프에선 상한 비가시(전량 출력).
|
|
17
|
+
- (검수 #1 Medium) **광고된 `--limit N` 이 텍스트 모드에서 조용히 무시되던 것 수정** — 명시적 `--limit` 존중 + 오버플로 라인(기본 텍스트 출력은 전체 유지, 계약 무변경). 종전엔 보고서가 권하는 명령이 602줄을 그대로 뱉었다.
|
|
18
|
+
- (검수 #2) e2e 픽스처 `--no-stale-check`(빈 캐시 npm 조회 8초 지연 차단). (검수 #3) e2e 단언을 헤딩 파싱 기반 정확 일치(shown===30·행수·오버플로 수치)로 강화.
|
|
19
|
+
- 검수 실측(codex): 30/602 헤드라인 + 외 572 · 명령 40/170+130 · 플래그 40/133+93 · 파일 40/43+3 — 전 수치 독립 파싱과 일치. 소점프 무영향, 마크다운 정합, fromV 엣지(동일/쓰레기/미래) 전부 통과.
|
|
20
|
+
- 검증: selftest 334, e2e +1.
|
|
21
|
+
|
|
3
22
|
## 1.36.69 — 2026-07-22 — 성장 한계 클래스: whats-new --json 응답 상한 (804KB → 30KB) — R-0001 검수 12회전
|
|
4
23
|
|
|
5
24
|
직전 게이트가 우연히 드러낸 누적 성장 문제를 픽스처 우회가 아니라 제품에서 해결. CHANGELOG 1MB 돌파로 `--json` 이 804KB 를 뱉어 AI 소비자 컨텍스트를 폭파시키고 기본 stdout 버퍼에서 절단됐다.
|
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.71 하네스를 사용합니다. 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.71는 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.69는 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.71 릴리스 태그 이력** (수백 라운드) · _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.71: 2026-07-24
|
|
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.71';
|
|
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') 시 호스트 프로세스 오염.
|
|
@@ -929,17 +929,22 @@ function writeMigrationReport(root, backup, actions, opts = {}) {
|
|
|
929
929
|
v.newFiles.forEach(f => allFiles.add(f));
|
|
930
930
|
}
|
|
931
931
|
if (diff.length) {
|
|
932
|
+
// 1.36.70 (성장 한계 클래스, whats-new 1.36.69 와 동일 규율): 큰 버전 점프(601+ 릴리스)에서
|
|
933
|
+
// 보고서가 ~88KB 로 폭주해 AI 컨텍스트를 태우던 것 — 목록별 상한 + 정직한 "외 N개" 표기.
|
|
934
|
+
const _capList = (set, cap, fmt) => { const a = [...set]; return a.slice(0, cap).map(fmt).join('\n') + (a.length > cap ? `\n- … 외 ${a.length - cap}개 (전체: \`leerness whats-new --from ${fromV} --all\`)` : ''); };
|
|
932
935
|
aiReadBlock = `\n## 🤖 AI must re-read (1.9.41 차분 안내)\n\n`;
|
|
933
|
-
aiReadBlock += `이 migrate는 ${fromV} → ${VERSION}
|
|
934
|
-
if (allCommands.size) aiReadBlock += `**📌 신규 명령** (이전엔 없던
|
|
935
|
-
if (allFlags.size) aiReadBlock += `**🚩 신규
|
|
936
|
-
if (allFiles.size) aiReadBlock += `**📄 신규/변경 파일** (반드시
|
|
937
|
-
|
|
938
|
-
|
|
936
|
+
aiReadBlock += `이 migrate는 ${fromV} → ${VERSION} 점프입니다 (${diff.length}개 릴리스). 메인 AI 에이전트는 다음을 인지하고 우선 활용:\n\n`;
|
|
937
|
+
if (allCommands.size) aiReadBlock += `**📌 신규 명령** (이전엔 없던 것, ${allCommands.size}개):\n${_capList(allCommands, 40, c => `- \`leerness ${c}\``)}\n\n`;
|
|
938
|
+
if (allFlags.size) aiReadBlock += `**🚩 신규 플래그** (${allFlags.size}개):\n${_capList(allFlags, 40, f => `- \`${f}\``)}\n\n`;
|
|
939
|
+
if (allFiles.size) aiReadBlock += `**📄 신규/변경 파일** (반드시 재독, ${allFiles.size}개):\n${_capList(allFiles, 40, f => `- \`${f}\``)}\n\n`;
|
|
940
|
+
const _hlCap = 30;
|
|
941
|
+
aiReadBlock += `**버전별 헤드라인** (최신 ${Math.min(_hlCap, diff.length)}/${diff.length}):\n`;
|
|
942
|
+
for (const v of diff.slice(0, _hlCap)) {
|
|
939
943
|
const firstLine = (v.body.match(/^\*\*([^*]+)\*\*/) || [])[1]
|
|
940
944
|
|| (v.body.split('\n').find(l => l.trim() && !l.startsWith('##')) || '').trim().slice(0, 120);
|
|
941
945
|
aiReadBlock += `- ${v.version} — ${firstLine || '(no headline)'}\n`;
|
|
942
946
|
}
|
|
947
|
+
if (diff.length > _hlCap) aiReadBlock += `- … 외 ${diff.length - _hlCap}개 버전 — \`leerness whats-new --from ${fromV} --limit N\` 으로 조회\n`;
|
|
943
948
|
aiReadBlock += `\n**권장 행동**:\n1. 위 신규 명령을 \`--help\`로 확인\n2. \`AGENTS.md\` / \`CLAUDE.md\` / \`.harness/session-workflow.md\` 재독 (다음 \`leerness handoff\` 호출 시 자동 안내)\n3. 이전 청크의 기억 무효 — 새 도구 우선 시도\n4. 상세: \`leerness whats-new --from ${fromV}\`\n`;
|
|
944
949
|
}
|
|
945
950
|
}
|
|
@@ -15297,21 +15302,31 @@ function _collectWorkspacePaths(rootBase) {
|
|
|
15297
15302
|
return Array.from(set);
|
|
15298
15303
|
}
|
|
15299
15304
|
|
|
15305
|
+
// 1.36.71 (성장 한계 클래스 3, whats-new 1.36.69 규율): retro/insights --json 이 data.rows 에 전체 task row 를
|
|
15306
|
+
// 임베드해 프로젝트와 함께 무한 성장(72 task 에 이미 22KB — AI 소비자 컨텍스트 낭비). 기본 최신 30행 +
|
|
15307
|
+
// rowsTotal/rowsShown/rowsTruncated 정직 표기, 전체는 --all, 개수는 --limit N. 집계 수치는 전량 기준 그대로.
|
|
15308
|
+
function _retroJsonData(agg) {
|
|
15309
|
+
const rawLim = parseInt(arg('--limit', '30'), 10);
|
|
15310
|
+
const lim = has('--all') ? agg.rows.length : (Number.isFinite(rawLim) && rawLim > 0 ? rawLim : 30);
|
|
15311
|
+
const rows = agg.rows.slice(-lim); // progress rows 는 오래된 것부터 — 최신 lim 개
|
|
15312
|
+
return Object.assign({}, agg, { rows, rowsTotal: agg.rows.length, rowsShown: rows.length, rowsTruncated: rows.length < agg.rows.length });
|
|
15313
|
+
}
|
|
15314
|
+
|
|
15300
15315
|
function retroCmd(root) {
|
|
15301
15316
|
root = absRoot(root);
|
|
15302
|
-
// 1.9.15: --all-apps / --include 통합 모드
|
|
15303
|
-
if (has('--all-apps') || arg('--include', null)) {
|
|
15304
|
-
return _retroWorkspace(root);
|
|
15305
|
-
}
|
|
15306
15317
|
// 1.26.1 (13번째 외부리뷰 P2): 비숫자 --days → NaN → new Date(Invalid) throw 로 --json 소비자에 plain text 누출. 숫자 가드 + 음수/오버플로 클램프(insights/round-history 와 일관).
|
|
15307
15318
|
let days = parseInt(arg('--days', '7'), 10);
|
|
15308
15319
|
if (!Number.isFinite(days)) { failJson(has('--json'), 'invalid_arg', _uiLang(root) === 'en' ? '--days must be a number' : '--days 는 숫자여야 합니다'); return; }
|
|
15309
15320
|
days = Math.max(0, Math.min(days, 36500));
|
|
15310
15321
|
const cutoff = new Date(Date.now() - days * 86400 * 1000).toISOString().slice(0, 10);
|
|
15322
|
+
// 1.9.15: --all-apps / --include 통합 모드 — 1.36.71 (검수, 기존 버그): 워크스페이스 모드가 --days 를 조용히 무시하던 것, cutoff 전달
|
|
15323
|
+
if (has('--all-apps') || arg('--include', null)) {
|
|
15324
|
+
return _retroWorkspace(root, cutoff);
|
|
15325
|
+
}
|
|
15311
15326
|
const agg = _retroAggregate(root, cutoff); // 1.36.38 (#2): 기간 필터 적용
|
|
15312
15327
|
// 1.9.16: --json
|
|
15313
15328
|
if (has('--json')) {
|
|
15314
|
-
log(JSON.stringify({ project: path.basename(root), days, cutoff, summary: _retroOneLine(agg), data: agg }, null, 2));
|
|
15329
|
+
log(JSON.stringify({ project: path.basename(root), days, cutoff, summary: _retroOneLine(agg), data: _retroJsonData(agg) }, null, 2));
|
|
15315
15330
|
return;
|
|
15316
15331
|
}
|
|
15317
15332
|
log(`# 회고 (retro) — 최근 ${days}일 (since ${cutoff})`);
|
|
@@ -15360,14 +15375,14 @@ function retroCmd(root) {
|
|
|
15360
15375
|
}
|
|
15361
15376
|
|
|
15362
15377
|
// 1.9.15: 워크스페이스 통합 retro (다수 프로젝트 묶음 회고)
|
|
15363
|
-
function _retroWorkspace(rootBase) {
|
|
15378
|
+
function _retroWorkspace(rootBase, cutoff) {
|
|
15364
15379
|
const paths = _collectWorkspacePaths(rootBase);
|
|
15365
15380
|
if (!paths.length) return fail('대상 프로젝트 없음. --include <path1,path2> 또는 --all-apps 사용 필요.');
|
|
15366
15381
|
// 1.9.16: --json
|
|
15367
15382
|
if (has('--json')) {
|
|
15368
15383
|
const projects = paths.map(p => {
|
|
15369
|
-
const a = _retroAggregate(p);
|
|
15370
|
-
return { project: path.basename(p), path: p, summary: _retroOneLine(a), data: a };
|
|
15384
|
+
const a = _retroAggregate(p, cutoff);
|
|
15385
|
+
return { project: path.basename(p), path: p, summary: _retroOneLine(a), data: _retroJsonData(a) };
|
|
15371
15386
|
});
|
|
15372
15387
|
const totals = projects.reduce((t, p) => ({
|
|
15373
15388
|
tasks: t.tasks + p.data.totalTasks, done: t.done + p.data.doneCount,
|
|
@@ -15381,7 +15396,7 @@ function _retroWorkspace(rootBase) {
|
|
|
15381
15396
|
log(`# Cross-project retro — ${paths.length}개 프로젝트`);
|
|
15382
15397
|
const totals = { tasks: 0, done: 0, decisions: 0, skills: 0, totalSkillUsage: 0, totalOpts: 0, activeRules: 0, fixSig: 0, passSig: 0 };
|
|
15383
15398
|
for (const p of paths) {
|
|
15384
|
-
const agg = _retroAggregate(p);
|
|
15399
|
+
const agg = _retroAggregate(p, cutoff);
|
|
15385
15400
|
const name = path.basename(p);
|
|
15386
15401
|
log(`\n## ${name}`);
|
|
15387
15402
|
log(` 📈 ${_retroOneLine(agg)}`);
|
|
@@ -15416,7 +15431,7 @@ function insightsCmd(root) {
|
|
|
15416
15431
|
// 1.9.16: --json
|
|
15417
15432
|
if (has('--json')) {
|
|
15418
15433
|
const sc = readSessionCounter(root);
|
|
15419
|
-
log(JSON.stringify({ project: path.basename(root), sessionCount: sc.count, lastCloseAt: sc.lastCloseAt, data: agg }, null, 2));
|
|
15434
|
+
log(JSON.stringify({ project: path.basename(root), sessionCount: sc.count, lastCloseAt: sc.lastCloseAt, data: _retroJsonData(agg) }, null, 2));
|
|
15420
15435
|
return;
|
|
15421
15436
|
}
|
|
15422
15437
|
const sc = readSessionCounter(root);
|
|
@@ -15458,7 +15473,7 @@ function _insightsWorkspace(rootBase) {
|
|
|
15458
15473
|
if (!paths.length) return fail('대상 프로젝트 없음. --include 또는 --all-apps 사용.');
|
|
15459
15474
|
// 1.9.16: --json
|
|
15460
15475
|
if (has('--json')) {
|
|
15461
|
-
const projects = paths.map(p => ({ project: path.basename(p), path: p, data: _retroAggregate(p) }));
|
|
15476
|
+
const projects = paths.map(p => ({ project: path.basename(p), path: p, data: _retroJsonData(_retroAggregate(p)) }));
|
|
15462
15477
|
log(JSON.stringify({ projects, projectCount: paths.length }, null, 2));
|
|
15463
15478
|
return;
|
|
15464
15479
|
}
|
|
@@ -18681,7 +18696,7 @@ function _mcpToCliArgs(name, args, targetPath) {
|
|
|
18681
18696
|
case 'leerness_skill_info': cliArgs = ['skill', 'info', args.id || '', '--path', targetPath, '--json']; break;
|
|
18682
18697
|
case 'leerness_benchmark': cliArgs = ['benchmark', targetPath, '--json', ...(args.scenario ? ['--scenario', args.scenario] : [])]; break;
|
|
18683
18698
|
case 'leerness_lazy_detect': cliArgs = ['lazy', 'detect', targetPath, '--json']; break;
|
|
18684
|
-
case 'leerness_retro': cliArgs = ['retro', targetPath, '--json', ...(args.days ? ['--days', String(args.days)] : []), ...(args.allApps ? ['--all-apps'] : [])]; break;
|
|
18699
|
+
case 'leerness_retro': cliArgs = ['retro', targetPath, '--json', ...(args.days ? ['--days', String(args.days)] : []), ...(args.allApps ? ['--all-apps'] : []), ...(args.all ? ['--all'] : []), ...(args.limit ? ['--limit', String(args.limit)] : [])]; break; // 1.36.71 (검수): rows 상한 조절 인자 전달
|
|
18685
18700
|
case 'leerness_task_add': cliArgs = ['task', 'add', String(args.text || ''), '--path', targetPath, ...(args.status ? ['--status', args.status] : []), ...(args.evidence ? ['--evidence', args.evidence] : []), ...(args.nextAction ? ['--next', args.nextAction] : [])]; break;
|
|
18686
18701
|
case 'leerness_task_update': cliArgs = ['task', 'update', String(args.id || ''), '--path', targetPath, ...(args.status ? ['--status', args.status] : []), ...(args.evidence ? ['--evidence', args.evidence] : []), ...(args.nextAction ? ['--next', args.nextAction] : []), ...(args.note ? ['--note', args.note] : [])]; break;
|
|
18687
18702
|
case 'leerness_task_drop': cliArgs = ['task', 'drop', String(args.id || ''), '--path', targetPath, ...(args.reason ? ['--reason', args.reason] : [])]; break;
|
|
@@ -22824,7 +22839,7 @@ HANDOFF & SESSION
|
|
|
22824
22839
|
handoff [path] [--compact] [--all-apps] [--json] Session-start context in one call
|
|
22825
22840
|
session close [path] Closing report + auto handoff
|
|
22826
22841
|
context [path] [--json] Agent onboarding context
|
|
22827
|
-
retro | insights [path] [--json]
|
|
22842
|
+
retro | insights [path] [--json] [--limit N|--all] Retrospective / cumulative stats (JSON rows: newest 30 by default; rowsTotal = lifetime count, not the --days window)
|
|
22828
22843
|
|
|
22829
22844
|
MEMORY (canonical JSON + markdown projections)
|
|
22830
22845
|
task list|add|update|drop|fix-evidence|relink [args]
|
package/lib/diagnostics.js
CHANGED
|
@@ -204,12 +204,17 @@ function whatsNewCmd(root, deps = {}) {
|
|
|
204
204
|
if (allFiles.size) log(t(` 📄 신규 파일: ${[...allFiles].join(', ')}`, ` 📄 new files: ${[...allFiles].join(', ')}`));
|
|
205
205
|
log('');
|
|
206
206
|
log(t(`## 📜 버전별 헤드라인`, `## 📜 per-version headlines`));
|
|
207
|
-
|
|
207
|
+
// 1.36.70 (검수 #1): migrate 보고서가 광고하는 `--limit N` 이 텍스트 모드에서 조용히 무시되던 것 —
|
|
208
|
+
// 명시적 --limit 만 존중(기본 텍스트 출력은 전체 유지, 계약 무변경).
|
|
209
|
+
const _txtRawLim = parseInt(arg('--limit', ''), 10);
|
|
210
|
+
const _txtLim = (has('--limit') && Number.isFinite(_txtRawLim) && _txtRawLim > 0) ? _txtRawLim : diff.length;
|
|
211
|
+
for (const v of diff.slice(0, _txtLim)) {
|
|
208
212
|
// body 첫 줄(또는 strong header) 추출
|
|
209
213
|
const firstLine = (v.body.match(/^\*\*([^*]+)\*\*/) || [])[1]
|
|
210
214
|
|| (v.body.split('\n').find(l => l.trim() && !l.startsWith('##')) || '').trim().slice(0, 120);
|
|
211
215
|
log(` • ${v.version}${v.date ? ` (${v.date})` : ''} — ${firstLine || '(no headline)'}`);
|
|
212
216
|
}
|
|
217
|
+
if (_txtLim < diff.length) log(t(` … 외 ${diff.length - _txtLim}개 버전 (전체: --all 또는 --limit 생략)`, ` … ${diff.length - _txtLim} more version(s) (drop --limit or use --all for everything)`));
|
|
213
218
|
log('');
|
|
214
219
|
log(t(`## 💡 권장 행동`, `## 💡 recommended actions`));
|
|
215
220
|
log(t(` 1. 위 신규 명령들을 시도해 보세요 (예: \`leerness <명령> --help\`)`, ` 1. Try the new commands above (e.g. \`leerness <command> --help\`)`));
|
package/lib/mcp-tools.js
CHANGED
|
@@ -27,7 +27,7 @@ module.exports = [
|
|
|
27
27
|
{ name: 'leerness_skill_info', requiredTier: 'read-only', description: '1.9.92 — 개별 skill 상세 조회 (version/capabilities/sources/patterns/usage/optimizations). 외부 AI가 skill 능력 정확히 파악', inputSchema: { type: 'object', properties: { id: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
|
|
28
28
|
{ name: 'leerness_benchmark', requiredTier: 'read-only', description: '1.9.46/51/94 — 6 차원 점수 + 검수 시나리오 (--scenario) 결과 JSON. 외부 AI가 워크스페이스 leerness 활용 점수 확인', inputSchema: { type: 'object', properties: { path: { type: 'string' }, scenario: { type: 'string' } } } },
|
|
29
29
|
{ name: 'leerness_lazy_detect', requiredTier: 'read-only', description: '1.9.101 — 게으른 작업 자동 감지 결과 JSON (evidence 없는 done / empty handoff / no test run / TODO 미추적 / blocker no-next-action 등). 외부 AI가 거짓 완료 신호 사전 점검', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
30
|
-
{ name: 'leerness_retro', requiredTier: 'read-only', description: '1.9.104 — 4세션 누적 회고 보고서 JSON (statusCounts/focusNext/skillUsage/recentDecisions/durations/activeRules/fixSignals/passSignals/totalOptimizations). 외부 AI가 누적 패턴 자동
|
|
30
|
+
{ name: 'leerness_retro', requiredTier: 'read-only', description: '1.9.104 — 4세션 누적 회고 보고서 JSON (statusCounts/focusNext/skillUsage/recentDecisions/durations/activeRules/fixSignals/passSignals/totalOptimizations). 외부 AI가 누적 패턴 자동 학습. 1.36.71: data.rows 는 기본 최신 30행(rowsTotal/rowsShown/rowsTruncated 로 절단 확인, rowsTotal 은 --days 창이 아니라 전체 task 수), 더 필요하면 limit(개수)/all=true(전체) 지정', inputSchema: { type: 'object', properties: { path: { type: 'string' }, days: { type: 'number' }, allApps: { type: 'boolean' }, limit: { type: 'number' }, all: { type: 'boolean' } } } },
|
|
31
31
|
{ name: 'leerness_task_add', requiredTier: 'safe-write', description: '1.9.105 — progress-tracker.md 에 새 task 추가 (양방향 제어 완성). 외부 AI가 사용자 요청을 task로 즉시 등록. 인자: { text (required), status?, evidence?, nextAction?, path? }', inputSchema: { type: 'object', properties: { text: { type: 'string' }, status: { type: 'string', enum: ['requested', 'planned', 'in-progress', 'waiting', 'on-hold', 'blocked', 'incomplete', 'done', 'dropped'] }, evidence: { type: 'string' }, nextAction: { type: 'string' }, path: { type: 'string' } }, required: ['text'] } },
|
|
32
32
|
{ name: 'leerness_task_update', requiredTier: 'safe-write', description: '1.9.106 — 기존 task 상태/evidence/nextAction 갱신. 외부 AI가 작업 진행에 따라 task를 단계적으로 업데이트 (read+add+update 3종 surface 완성). 인자: { id (required), status?, evidence?, nextAction?, note?, path? }', inputSchema: { type: 'object', properties: { id: { type: 'string' }, status: { type: 'string', enum: ['requested', 'planned', 'in-progress', 'waiting', 'on-hold', 'blocked', 'incomplete', 'done', 'dropped'] }, evidence: { type: 'string' }, nextAction: { type: 'string' }, note: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
|
|
33
33
|
{ name: 'leerness_task_drop', requiredTier: 'safe-write', description: '1.9.107 — task를 dropped 상태로 폐기 (CRUD 완성: read/add/update/drop). 외부 AI가 사용자 요청으로 task 취소. 인자: { id (required), reason?, path? }', inputSchema: { type: 'object', properties: { id: { type: 'string' }, reason: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "leerness",
|
|
3
|
-
"version": "1.36.
|
|
3
|
+
"version": "1.36.71",
|
|
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
|
@@ -7207,5 +7207,68 @@ total++;
|
|
|
7207
7207
|
if (!ok) failed++;
|
|
7208
7208
|
}
|
|
7209
7209
|
|
|
7210
|
+
// 1.36.70 (성장 한계 클래스): migrate 보고서 상한 — 큰 버전 점프에서 ~88KB 폭주하던 AI must re-read 를 목록별 캡 + "외 N개" 정직 표기
|
|
7211
|
+
total++;
|
|
7212
|
+
{
|
|
7213
|
+
let ok = false;
|
|
7214
|
+
const _d = [];
|
|
7215
|
+
try {
|
|
7216
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-migcap-')); _d.push(d);
|
|
7217
|
+
// (검수 #2) --no-stale-check: 빈 캐시에서 npm 조회로 최대 8초 지연되던 것 차단
|
|
7218
|
+
cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--no-env', '--no-stale-check'], { encoding: 'utf8', timeout: 40000 });
|
|
7219
|
+
fs.writeFileSync(path.join(d, '.harness', 'HARNESS_VERSION'), '1.9.33');
|
|
7220
|
+
cp.spawnSync(process.execPath, [CLI, 'migrate', d], { encoding: 'utf8', timeout: 60000, maxBuffer: 32 * 1024 * 1024 });
|
|
7221
|
+
const rep = fs.readFileSync(path.join(d, '.harness', 'migration-report.md'), 'utf8');
|
|
7222
|
+
const sizeOk = rep.length < 30 * 1024;
|
|
7223
|
+
// (검수 #3) 헤딩을 파싱해 shown/total 정합 + 헤드라인 행수 + 오버플로 수치 정확 일치로 단언 강화
|
|
7224
|
+
const hm = rep.match(/최신 (\d+)\/(\d+)/);
|
|
7225
|
+
const shown = hm ? parseInt(hm[1], 10) : 0, totalV = hm ? parseInt(hm[2], 10) : 0;
|
|
7226
|
+
const hlRows = (rep.match(/^- 1\.\d+\.\d+ — /gm) || []).length;
|
|
7227
|
+
const om = rep.match(/외 (\d+)개 버전/);
|
|
7228
|
+
const capOk = shown === 30 && totalV > 30 && hlRows === 30 && !!om && parseInt(om[1], 10) === totalV - 30;
|
|
7229
|
+
const escapeOk = rep.includes('whats-new --from 1.9.33'); // 전체가 필요한 소비자를 위한 탈출구
|
|
7230
|
+
// (검수 #1) 광고된 --limit 이 텍스트 모드에서도 실제 동작
|
|
7231
|
+
const limTxt = cp.spawnSync(process.execPath, [CLI, 'whats-new', '--from', '1.9.33', '--limit', '5'], { encoding: 'utf8', timeout: 20000, maxBuffer: 32 * 1024 * 1024 });
|
|
7232
|
+
const limRows = (limTxt.stdout.match(/^ {2}• 1\.\d+\.\d+/gm) || []).length;
|
|
7233
|
+
const limOk = limRows === 5 && /외 \d+개 버전|more version/.test(limTxt.stdout);
|
|
7234
|
+
ok = sizeOk && capOk && escapeOk && limOk;
|
|
7235
|
+
if (!ok) console.log(` [migcap 디버그] size=${sizeOk}(${Math.round(rep.length / 1024)}KB) cap=${capOk}(${shown}/${totalV},rows=${hlRows}) escape=${escapeOk} lim=${limOk}(${limRows})`);
|
|
7236
|
+
} catch (e) {} finally { _d.forEach(x => { try { fs.rmSync(x, { recursive: true, force: true }); } catch {} }); }
|
|
7237
|
+
console.log(ok ? '✓ B(1.36.70) migrate 보고 상한: 88KB→<30KB + 외N개 정직 표기 + whats-new 탈출구' : '✗ migrate 보고 상한 실패');
|
|
7238
|
+
if (!ok) failed++;
|
|
7239
|
+
}
|
|
7240
|
+
|
|
7241
|
+
// 1.36.71 (성장 한계 클래스 3): retro/insights --json data.rows 상한 — 전 task row 임베드로 무한 성장하던 것 (집계는 전량 기준 유지)
|
|
7242
|
+
total++;
|
|
7243
|
+
{
|
|
7244
|
+
let ok = false;
|
|
7245
|
+
const _d = [];
|
|
7246
|
+
try {
|
|
7247
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-retrocap-')); _d.push(d);
|
|
7248
|
+
const initR = cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--no-env', '--no-stale-check'], { encoding: 'utf8', timeout: 40000 });
|
|
7249
|
+
const R = (extra) => JSON.parse(cp.spawnSync(process.execPath, [CLI, ...extra, '--path', d, '--json'], { encoding: 'utf8', timeout: 20000, maxBuffer: 32 * 1024 * 1024 }).stdout);
|
|
7250
|
+
// (검수 High) init 이 기본 T-0001 을 만들 수 있음 — 하드코딩 35 대신 기준선에서 유도 + exit 단언
|
|
7251
|
+
const baseline = R(['retro', '--all']).data.rowsTotal;
|
|
7252
|
+
let addFails = 0;
|
|
7253
|
+
for (let i = 1; i <= 35; i++) { const a = cp.spawnSync(process.execPath, [CLI, 'task', 'add', `t${i}`, '--path', d], { encoding: 'utf8', timeout: 15000 }); if (a.status !== 0) addFails++; }
|
|
7254
|
+
const expTotal = baseline + 35;
|
|
7255
|
+
const r = R(['retro']);
|
|
7256
|
+
const retroOk = initR.status === 0 && addFails === 0
|
|
7257
|
+
&& r.data.rowsShown === 30 && r.data.rowsTotal === expTotal && r.data.rowsTruncated === true
|
|
7258
|
+
&& r.data.rows.length === 30 && r.data.rows[29].request === 't35' // 최신 유지 (오래된 것부터 잘림)
|
|
7259
|
+
&& r.data.totalTasks === expTotal; // 집계는 전량 기준
|
|
7260
|
+
const rAll = R(['retro', '--all']);
|
|
7261
|
+
const allOk = rAll.data.rowsShown === expTotal && rAll.data.rowsTruncated === false;
|
|
7262
|
+
const rLim = R(['retro', '--limit', '5']);
|
|
7263
|
+
const limOk = rLim.data.rowsShown === 5 && rLim.data.rows[4].request === 't35';
|
|
7264
|
+
const ins = R(['insights']);
|
|
7265
|
+
const insOk = ins.data.rowsShown === 30 && ins.data.rowsTotal === expTotal;
|
|
7266
|
+
ok = retroOk && allOk && limOk && insOk;
|
|
7267
|
+
if (!ok) console.log(` [retrocap 디버그] retro=${retroOk}(exp=${expTotal},got=${r.data.rowsTotal},addFail=${addFails}) all=${allOk} lim=${limOk} ins=${insOk}`);
|
|
7268
|
+
} catch (e) {} finally { _d.forEach(x => { try { fs.rmSync(x, { recursive: true, force: true }); } catch {} }); }
|
|
7269
|
+
console.log(ok ? '✓ B(1.36.71) retro/insights --json rows 상한 30 + 최신 유지 + 집계 전량 기준 + --all/--limit' : '✗ retro/insights rows 상한 실패');
|
|
7270
|
+
if (!ok) failed++;
|
|
7271
|
+
}
|
|
7272
|
+
|
|
7210
7273
|
console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
|
|
7211
7274
|
if (failed > 0) process.exit(1);
|